Beispiel #1
0
    def test_dicom_plan(self):
        c = DosCube()
        c.read(self.cube000)

        dp = c.create_dicom_plan()
        self.assertIsNotNone(dp)

        d = c.create_dicom()
        self.assertIsNotNone(d)
Beispiel #2
0
    def test_write_dicom(self):
        c = DosCube()
        c.read(self.cube000)

        outdir = tempfile.mkdtemp()
        c.write_dicom(outdir)
        self.assertTrue(os.path.exists(os.path.join(outdir, "rtdose.dcm")))
        self.assertTrue(os.path.exists(os.path.join(outdir, "rtplan.dcm")))
        self.assertGreater(os.path.getsize(os.path.join(outdir, "rtdose.dcm")), 0)
        self.assertGreater(os.path.getsize(os.path.join(outdir, "rtplan.dcm")), 0)
        shutil.rmtree(outdir)
Beispiel #3
0
    def analyse_cube(self):
        keys = self.projectiles.keys()

        p1 = self.projectiles[keys[0]]
        p2 = self.projectiles[keys[1]]

        cube1 = p1["target_dos"]
        cube2 = p2["target_dos"]

        if not self.cube_in_other_cube(cube1, cube2):
            temp = p1
            p1 = p2
            p2 = temp
            cube1 = p1["target_dos"]
            cube2 = p2["target_dos"]
            self.execute_order = [keys[0], keys[1]]
            self.split_proj_key = keys[1]

        else:
            self.execute_order = [keys[1], keys[0]]
            self.split_proj_key = keys[0]

        target_cube = copy.deepcopy(cube1)
        shadow_cubes = []
        for i, field in enumerate(p1["fields"]):
            d = DosCube(cube1)

            basis = get_basis_from_angles(field.get_gantry(),
                                          field.get_couch())
            basis = basis[0]
            basis = np.array([
                basis[0] / cube1.pixel_size, basis[1] / cube1.pixel_size,
                basis[2] / cube1.slice_distance
            ])
            basis /= np.max(np.abs(basis))

            d.cube = pytriplib.create_field_shadow(
                cube1.cube, cube2.cube, np.array(basis, dtype=np.double))
            target_cube -= d
            shadow_cubes.append(d)

        target_cube.cube[target_cube.cube < 0] = 0
        cube2.cube = cube2.cube + target_cube.cube
        # ~ cube2.cube = pytriplib.extend_cube(cube2.cube)
        cube1.cube = cube1.cube - target_cube.cube
        if len(p1["fields"]) == 2:
            a = self.execute_order.pop(1)
            b = self.projectile_dose_level[a]
            self.execute_order.append(a + str(1))
            self.execute_order.append(a + str(2))
            self.projectile_dose_level[a + str(1)] = b
            self.projectile_dose_level[a + str(2)] = b
Beispiel #4
0
    def test_read(self):
        c = DosCube()
        c.read(self.cube000)
        self.assertEqual(c.cube.shape[0], 300)
        self.assertEqual(c.cube.shape[1], 512)
        self.assertEqual(c.cube.shape[2], 512)

        # test method from C extension
        dose_center = pytriplib.calculate_dose_center(np.array(c.cube))
        self.assertEqual(dose_center.shape[0], 3)
        self.assertGreater(dose_center[0], 0.0)
        self.assertGreater(dose_center[1], 0.0)
        self.assertGreater(dose_center[2], 0.0)
Beispiel #5
0
    def test_read(self):
        c = DosCube()
        c.read(self.cube000)
        self.assertEqual(c.cube.shape[0], 300)
        self.assertEqual(c.cube.shape[1], 512)
        self.assertEqual(c.cube.shape[2], 512)

        # test method from C extension
        dose_center = pytriplib.calculate_dose_center(np.array(c.cube))
        self.assertEqual(dose_center.shape[0], 3)
        self.assertGreater(dose_center[0], 0.0)
        self.assertGreater(dose_center[1], 0.0)
        self.assertGreater(dose_center[2], 0.0)
Beispiel #6
0
 def calculate_rest_dose(self, proj):
     self.rest_dose = copy.deepcopy(self.target_dos)
     for k, projectile in self.projectiles.items():
         if k == proj:
             continue
         name = os.path.join(self.path,
                             self.plan_name) + "_" + projectile["name"]
         path = os.path.join(name + ".phys.dos")
         if os.path.exists(path):
             temp = DosCube()
             temp.read(path)
             temp = temp
             self.rest_dose.cube = self.rest_dose.cube - temp.cube
Beispiel #7
0
    def test_dvh_simple(self):
        c = DosCube()
        c.read(self.cube000)
        v = create_sphere(c, name="sph", center=[10, 10, 10], radius=8)
        self.assertIsNotNone(v)

        logger.info("Calculating DVH simple")
        x, y = volume_histogram(c.cube, v)
        self.assertIsNotNone(x)
        self.assertIsNotNone(y)

        logger.info("Calculating DVH simple for entire cube")
        x, y = volume_histogram(c.cube)
        self.assertIsNotNone(x)
        self.assertIsNotNone(y)
Beispiel #8
0
    def test_dvh(self):
        c = DosCube()
        c.read(self.cube000)
        v = create_sphere(c, name="sph", center=[10, 10, 10], radius=8)
        self.assertIsNotNone(v)

        logger.info("Calculating DVH")
        result = c.calculate_dvh(v)
        self.assertIsNotNone(result)
        dvh, min_dose, max_dose, mean, area = result
        self.assertGreater(area, 2.0)
        self.assertEqual(len(dvh.shape), 2)
        self.assertEqual(dvh.shape[1], 2)
        self.assertEqual(dvh.shape[0], 1500)
        self.assertEqual(min_dose, 0.0)
        self.assertEqual(max_dose, 0.001)
Beispiel #9
0
    def test_dvh(self):
        c = DosCube()
        c.read(self.cube000)
        v = create_sphere(c, name="sph", center=[10, 10, 10], radius=8)
        self.assertIsNotNone(v)

        logger.info("Calculating DVH")
        result = c.calculate_dvh(v)
        self.assertIsNotNone(result)
        dvh, min_dose, max_dose, mean, area = result
        self.assertGreater(area, 2.0)
        self.assertEqual(len(dvh.shape), 2)
        self.assertEqual(dvh.shape[1], 2)
        self.assertEqual(dvh.shape[0], 1500)
        self.assertEqual(min_dose, 0.0)
        self.assertEqual(max_dose, 0.001)
Beispiel #10
0
    def get_voi_cube(self):
        """
        This method returns a DosCube object with value 1000 in each voxel within the Voi and zeros elsewhere.
        It can be used as a mask, for selecting certain voxels.
        The function may take some time to execute the first invocation, but is faster for subsequent calls,
        due to caching.

        :returns: a DosCube object which holds the value 1000 in those voxels which are inside the Voi.
        """
        if hasattr(self, "voi_cube"):  # caching: checks if class has voi_cube attribute
            # TODO: add parameter as argument to this function. Note, this needs to be compatible with
            # caching the cube. E.g. the method might be called twice with different arguments.
            return self.voi_cube
        self.voi_cube = DosCube(self.cube)
        self.voi_cube.load_from_structure(self, 1000)
        return self.voi_cube
Beispiel #11
0
    def create_trip_exec_simple(self, no_output=False):
        """
        Generates the .exec script and stores it into self.trip_exec.
        """
        logger.info("Generating the trip_exec script...")
        oar_list = self.oar_list
        targets = self.targets
        fields = self.plan.get_fields()

        projectile = fields[0].get_projectile()
        output = []
        output.extend(self.create_exec_header())
        output.extend(self.create_exec_load_data_files(projectile))
        output.extend(self.create_exec_field(fields))
        output.extend(self.create_exec_oar(oar_list))
        dosecube = None
        if len(targets) > 1:
            dosecube = DosCube(self.images)
            for i, voi in enumerate(targets):
                temp = DosCube(self.images)
                dose_level = int(voi.get_dose() / self.target_dose * 1000)
                if dose_level == 0:
                    dose_level = -1
                temp.load_from_structure(voi.get_voi().get_voi_data(),
                                         dose_level)
                if i == 0:
                    dosecube = temp * 1
                else:
                    dosecube.merge_zero(temp)
            dosecube.cube[dosecube.cube == -1] = int(0)

        if not self.plan.get_target_dose_cube() is None:
            dosecube = self.plan.get_target_dose_cube()

        if dosecube is not None:
            if not no_output:
                dosecube.write(os.path.join(self.path, "target_dose.dos"))
            output.extend(self.create_exec_plan(incube="target_dose"))
        else:
            output.extend(self.create_exec_plan())
        if self.plan.get_optimize():
            output.extend(self.create_exec_opt())

        name = self.plan_name
        output.extend(self.create_exec_output(name, fields))
        out = "\n".join(output) + "\nexit\n"
        self.trip_exec = out
Beispiel #12
0
    def test_write(self):
        c = DosCube()
        c.read(self.cube000)

        fd, outfile = tempfile.mkstemp()
        os.close(fd)  # Windows needs it
        os.remove(outfile)  # we need only temp filename, not the file
        c.write(outfile)
        hed_file = outfile + DosCube.header_file_extension
        dos_file = outfile + DosCube.data_file_extension
        self.assertTrue(os.path.exists(hed_file))
        self.assertTrue(os.path.exists(dos_file))
        logger.info("Checking if output file " + hed_file + " is not empty")
        self.assertGreater(os.path.getsize(hed_file), 1)
        logger.info("Checking if output file " + dos_file + " is not empty")
        self.assertGreater(os.path.getsize(dos_file), 1)
        os.remove(hed_file)
        os.remove(dos_file)
Beispiel #13
0
    def test_dvh_simple(self):
        c = DosCube()
        c.read(self.cube000)
        v = create_sphere(c, name="sph", center=[10, 10, 10], radius=8)
        self.assertIsNotNone(v)

        logger.info("Calculating DVH simple")
        vh = VolHist(c, v)
        self.assertIsNotNone(vh.x)
        self.assertIsNotNone(vh.y)

        outdir = tempfile.mkdtemp()
        c.write_dicom(outdir)

        f1 = os.path.join(outdir, "foobar_1.dvh")
        vh.write(f1, header=True)
        self.assertTrue(os.path.exists(f1))
        self.assertGreater(os.path.getsize(f1), 0)

        f2 = os.path.join(outdir, "foobar_2.dvh")
        vh.write(f2, header=False)
        self.assertTrue(os.path.exists(f2))
        self.assertGreater(os.path.getsize(f2), 0)

        logger.info("Calculating DVH simple for entire cube")
        vh = VolHist(c)
        self.assertIsNotNone(vh.x)
        self.assertIsNotNone(vh.y)

        f3 = os.path.join(outdir, "foobar_3.dvh")
        vh.write(f3, header=True)
        self.assertTrue(os.path.exists(f3))
        self.assertGreater(os.path.getsize(f3), 0)

        f4 = os.path.join(outdir, "foobar_4.dvh")
        vh.write(f4, header=False)
        self.assertTrue(os.path.exists(f4))
        self.assertGreater(os.path.getsize(f4), 0)

        shutil.rmtree(outdir)
Beispiel #14
0
 def load_dose(self, path, _type, target_dose=0.0):
     """ Load and append a new DosCube from path to self.doscubes.
     """
     dos = DosCube()
     dos.read(os.path.splitext(path)[0] + DosCube.data_file_extension)
     dos._type = _type
     dos.target_dose = target_dose
     self.dosecubes.append(dos)
Beispiel #15
0
    def test_dicom_plan(self):
        c = DosCube()
        c.read(self.cube000)

        dp = c.create_dicom_plan()
        self.assertIsNotNone(dp)

        d = c.create_dicom()
        self.assertIsNotNone(d)
Beispiel #16
0
    def _finish(self, plan):
        """ return requested results, copy them back in to plan._working_dir
        """

        for _file_name in plan._out_files:
            _path = os.path.join(plan._temp_dir, _file_name)

            # only copy files back, if we actually have been running TRiP
            if self._norun:
                logger.info("dummy run: would now copy {:s} to {:s}".format(
                    _path, plan._working_dir))
            else:
                logger.info("copy {:s} to {:s}".format(_path,
                                                       plan._working_dir))
                shutil.copy(_path, plan._working_dir)

        for _file_name in plan._out_files:
            _path = os.path.join(plan._temp_dir, _file_name)
            if ".phys.dos" in _file_name:
                _ctx_cube = DosCube()
                if not self._norun:
                    _ctx_cube.read(_path)
                    plan.dosecubes.append(_ctx_cube)

            if ".bio.dos" in _file_name:
                _ctx_cube = CtxCube()
                if not self._norun:
                    _ctx_cube.read(_path)
                    plan.dosecubes.append(_ctx_cube)

            if ".dosemlet.dos" in _file_name:
                _let_cube = LETCube()
                if not self._norun:
                    _let_cube.read(_path)
                    plan.letcubes.append(_let_cube)

            if ".rst" in _file_name:
                logger.warning(
                    "attaching fields to class not implemented yet {:s}".
                    format(_path))
                # TODO
                # need to access the RstClass here for each rst file. This will then need to be attached
                # to the proper field in the list of fields.

        if self._cleanup:
            logger.debug("Delete {:s}".format(plan._temp_dir))
            shutil.rmtree(plan._temp_dir)
Beispiel #17
0
    def test_write_dicom(self):
        c = DosCube()
        c.read(self.cube000)

        outdir = tempfile.mkdtemp()
        c.write_dicom(outdir)
        self.assertTrue(os.path.exists(os.path.join(outdir, "rtdose.dcm")))
        self.assertTrue(os.path.exists(os.path.join(outdir, "rtplan.dcm")))
        self.assertGreater(os.path.getsize(os.path.join(outdir, "rtdose.dcm")),
                           0)
        self.assertGreater(os.path.getsize(os.path.join(outdir, "rtplan.dcm")),
                           0)
        shutil.rmtree(outdir)
Beispiel #18
0
    def test_write(self):
        c = DosCube()
        c.read(self.cube000)

        fd, outfile = tempfile.mkstemp()
        os.close(fd)  # Windows needs it
        os.remove(outfile)  # we need only temp filename, not the file
        c.write(outfile)
        hed_file = outfile + ".hed"
        dos_file = outfile + ".dos"
        self.assertTrue(os.path.exists(hed_file))
        self.assertTrue(os.path.exists(dos_file))
        logger.info("Checking if output file " + hed_file + " is not empty")
        self.assertGreater(os.path.getsize(hed_file), 1)
        logger.info("Checking if output file " + dos_file + " is not empty")
        self.assertGreater(os.path.getsize(dos_file), 1)
        os.remove(hed_file)
        os.remove(dos_file)
Beispiel #19
0
    def test_dvh_simple(self):
        c = DosCube()
        c.read(self.cube000)
        v = create_sphere(c, name="sph", center=[10, 10, 10], radius=8)
        self.assertIsNotNone(v)

        logger.info("Calculating DVH simple")
        vh = VolHist(c, v)
        self.assertIsNotNone(vh.x)
        self.assertIsNotNone(vh.y)

        outdir = tempfile.mkdtemp()
        c.write_dicom(outdir)

        f1 = os.path.join(outdir, "foobar_1.dvh")
        vh.write(f1, header=True)
        self.assertTrue(os.path.exists(f1))
        self.assertGreater(os.path.getsize(f1), 0)

        f2 = os.path.join(outdir, "foobar_2.dvh")
        vh.write(f2, header=False)
        self.assertTrue(os.path.exists(f2))
        self.assertGreater(os.path.getsize(f2), 0)

        logger.info("Calculating DVH simple for entire cube")
        vh = VolHist(c)
        self.assertIsNotNone(vh.x)
        self.assertIsNotNone(vh.y)

        f3 = os.path.join(outdir, "foobar_3.dvh")
        vh.write(f3, header=True)
        self.assertTrue(os.path.exists(f3))
        self.assertGreater(os.path.getsize(f3), 0)

        f4 = os.path.join(outdir, "foobar_4.dvh")
        vh.write(f4, header=False)
        self.assertTrue(os.path.exists(f4))
        self.assertGreater(os.path.getsize(f4), 0)

        shutil.rmtree(outdir)
Beispiel #20
0
class Voi:
    """
    This is a class for handling volume of interests (VOIs). This class can e.g. be found inside the VdxCube object.
    VOIs may for instance be organs (lung, eye...) or targets (PTV, GTV...), or any other volume of interest.
    """

    sagital = 2  #: deprecated, backwards compability to pytripgui, do not use.
    sagittal = 2  #: id for sagittal view
    coronal = 1  #: id for coronal view

    def __init__(self, name, cube=None):
        self.cube = cube
        self.name = name
        self.is_concated = False
        self.type = 90
        self.slice_z = []
        self.slices = {}
        self.color = [0, 230, 0]  # default colour
        self.define_colors()

    def create_copy(self, margin=0):
        """
        Returns an independent copy of the Voi object

        :param margin: (unused)
        :returns: a deep copy of the Voi object
        """
        voi = copy.deepcopy(self)
        if not margin == 0:
            pass
        return voi

    def get_voi_cube(self):
        """
        This method returns a DosCube object with value 1000 in each voxel within the Voi and zeros elsewhere.
        It can be used as a mask, for selecting certain voxels.
        The function may take some time to execute the first invocation, but is faster for subsequent calls,
        due to caching.

        :returns: a DosCube object which holds the value 1000 in those voxels which are inside the Voi.
        """
        if hasattr(self, "voi_cube"):  # caching: checks if class has voi_cube attribute
            # TODO: add parameter as argument to this function. Note, this needs to be compatible with
            # caching the cube. E.g. the method might be called twice with different arguments.
            return self.voi_cube
        self.voi_cube = DosCube(self.cube)
        self.voi_cube.load_from_structure(self, 1000)
        return self.voi_cube

    def add_slice(self, slice):
        """ Add another slice to this VOI, and update self.slice_z table.

        :param Slice slice: the Slice object to be appended.
        """
        key = int(slice.get_position() * 100)
        self.slice_z.append(key)
        self.slices[key] = slice

    def get_name(self):
        """
        :returns: The name of this VOI.
        """
        return self.name

    def calculate_bad_angles(self, voi):
        """
        (Not implemented.)
        """
        pass

    def concat_to_3d_polygon(self):
        """ Concats all contours into a single contour, and writes all data points to sefl.polygon3d.
        """
        self.concat_contour()
        data = []
        for slice in self.slices:
            data.extend(self.slices[slice].contour[0].contour)
        self.polygon3d = np.array(data)

    def get_3d_polygon(self):
        """ Returns a list of points rendering a 3D polygon of this VOI, which is stored in
        sefl.polygon3d. If this attibute does not exist, create it.
        """
        if not hasattr(self, "polygon3d"):
            self.concat_to_3d_polygon()
        return self.polygon3d

    def create_point_tree(self):
        """
        Concats all contours.
        Writes a list of points into self.points describing this VOI.
        """
        points = {}
        self.concat_contour()
        slice_keys = sorted(self.slices.keys())
        for key in slice_keys:
            contour = self.slices[key].contour[0].contour
            p = {}
            for x in contour:
                p[x[0], x[1], x[2]] = []
            points.update(p)
        n_slice = len(slice_keys)
        last_contour = None
        for i, key in enumerate(slice_keys):
            contour = self.slices[key].contour[0].contour
            n_points = len(contour)
            if i < n_slice - 1:
                next_contour = self.slices[slice_keys[i + 1]].contour[0].contour
            else:
                next_contour = None
            for j, point in enumerate(contour):
                j2 = (j + 1) % (n_points - 2)
                point2 = contour[j2]
                points[(point[0], point[1], point[2])].append(point2)
                points[(point2[0], point2[1], point2[2])].append(point)
                if next_contour is not None:
                    point3 = pytrip.res.point.get_nearest_point(point, next_contour)
                    points[(point[0], point[1], point[2])].append(point3)
                    points[(point3[0], point3[1], point3[2])].append(point)
                if last_contour is not None:
                    point4 = pytrip.res.point.get_nearest_point(point, last_contour)
                    if point4 not in points[(point[0], point[1], point[2])]:
                        points[(point[0], point[1], point[2])].append(point4)
                        points[(point4[0], point4[1], point4[2])].append(point)
            last_contour = contour
        self.points = points

    def get_2d_projection_on_basis(self, basis, offset=None):
        """ (TODO: Documentation)
        """
        a = np.array(basis[0])
        b = np.array(basis[1])
        self.concat_contour()
        bas = np.array([a, b])
        data = self.get_3d_polygon()
        product = np.dot(data, np.transpose(bas))

        compare = self.cube.pixel_size
        filtered = pytriplib.filter_points(product, compare / 2.0)
        filtered = np.array(sorted(filtered, key=cmp_to_key(_voi_point_cmp)))
        filtered = pytriplib.points_to_contour(filtered)
        product = filtered

        if offset is not None:
            offset_proj = np.array([np.dot(offset, a), np.dot(offset, b)])
            product = product[:] - offset_proj
        return product

    def get_2d_slice(self, plane, depth):
        """ Gets a 2d Slice object from the contour in either sagittal or coronal plane.
        Contours will be concated.

        :param int plane: either self.sagittal or self.coronal
        :param float depth: position of plane
        :returns: a Slice object.
        """
        self.concat_contour()
        points1 = []
        points2 = []
        for key in sorted(self.slice_z):
            slice = self.slices[key]
            if plane is self.sagittal:
                point = sorted(
                    pytriplib.slice_on_plane(np.array(slice.contour[0].contour), plane, depth), key=lambda x: x[1])
            elif plane is self.coronal:
                point = sorted(
                    pytriplib.slice_on_plane(np.array(slice.contour[0].contour), plane, depth), key=lambda x: x[0])
            if len(point) > 0:
                points2.append(point[-1])
                if len(point) > 1:
                    points1.append(point[0])
        s = None
        if len(points1) > 0:
            points1.extend(reversed(points2))
            points1.append(points1[0])
            s = Slice(cube=self.cube)
            s.add_contour(Contour(points1))
        return s

    def define_colors(self):
        """ Creates a list of default colours [R,G,B] in self.colours.
        """
        self.colors = []
        self.colors.append([0, 0, 255])
        self.colors.append([0, 128, 0])
        self.colors.append([0, 255, 0])
        self.colors.append([255, 0, 0])
        self.colors.append([0, 128, 128])
        self.colors.append([255, 255, 0])

    def calculate_center(self):
        """ Calculates the center of gravity for the VOI.

        :returns: A numpy array[x,y,z] with positions in [mm]
        """
        if hasattr(self, "center_pos"):
            return self.center_pos
        self.concat_contour()
        tot_volume = 0.0
        center_pos = np.array([0.0, 0.0, 0.0])
        for key in self.slices:
            center, area = self.slices[key].calculate_center()
            tot_volume += area
            center_pos += area * center
        self.center_pos = center_pos / tot_volume
        return center_pos / tot_volume

    def get_color(self, i=None):
        """
        :param int i: selects a colour, default if None.
        :returns: a [R,G,B] list.
        """
        if i is None:
            return self.color
        return self.colors[i % len(self.colors)]

    def set_color(self, color):
        """
        :param [3*int]: set a color [R,G,B].
        """
        self.color = color

    def create_dicom_label(self):
        """ Based on self.name and self.type, a Dicom ROI_LABEL is generated.

        :returns: a Dicom ROI_LABEL
        """
        roi_label = Dataset()
        roi_label.ROIObservationLabel = self.name
        roi_label.RTROIInterpretedType = self.get_roi_type_name(self.type)
        return roi_label

    def create_dicom_structure_roi(self):
        """ Based on self.name, an empty Dicom ROI is generated.

        :returns: a Dicom ROI.
        """
        roi = Dataset()
        roi.ROIName = self.name
        return roi

    def create_dicom_contour_data(self, i):
        """ Based on self.slices, Dicom conours are generated for the Dicom ROI.

        :returns: Dicom ROI_CONTOURS
        """
        roi_contours = Dataset()
        contours = []
        for slice in self.slices.values():
            contours.extend(slice.create_dicom_contours())
        roi_contours.Contours = Sequence(contours)
        roi_contours.ROIDisplayColor = self.get_color(i)

        return roi_contours

    def read_vdx_old(self, content, i):
        """ Reads a single VOI from Voxelplan .vdx data from 'content', assuming a legacy .vdx format.
        VDX format 1.2.
        :params [str] content: list of lines with the .vdx content
        :params int i: line number to the list.
        :returns: current line number, after parsing the VOI.
        """
        line = content[i]
        items = line.split()
        self.name = items[1]
        self.type = int(items[3])
        i += 1
        while i < len(content):
            line = content[i]
            if re.match("voi", line) is not None:
                break
            if re.match("slice#", line) is not None:
                s = Slice(cube=self.cube)
                i = s.read_vdx_old(content, i)
                if self.cube is not None:
                    for cont1 in s.contour:
                        for cont2 in cont1.contour:
                            cont2[2] = self.cube.slice_to_z(cont2[2])  # change from slice number to mm
                if s.get_position() is None:
                    raise Exception("cannot calculate slice position")
                # TODO investigate why 100 multiplier is needed
                if self.cube is not None:
                    key = 100 * int((float(s.get_position()) - min(self.cube.slice_pos)))
                else:
                    key = 100 * int(s.get_position())
                self.slice_z.append(key)
                self.slices[key] = s
            if re.match("#TransversalObjects", line) is not None:
                pass
                # slices = int(line.split()[1]) # TODO holds information about number of skipped slices
            i += 1
        return i - 1

    def read_vdx(self, content, i):
        """ Reads a single VOI from Voxelplan .vdx data from 'content'.
        Format 2.0
        :params [str] content: list of lines with the .vdx content
        :params int i: line number to the list.
        :returns: current line number, after parsing the VOI.
        """
        line = content[i]
        self.name = ' '.join(line.split()[1:])
        number_of_slices = 10000
        i += 1
        while i < len(content):
            line = content[i]
            if re.match("key", line) is not None:
                self.key = line.split()[1]
            elif re.match("type", line) is not None:
                self.type = int(line.split()[1])
            elif re.match("number_of_slices", line) is not None:
                number_of_slices = int(line.split()[1])
            elif re.match("slice", line) is not None:
                s = Slice(cube=self.cube)
                i = s.read_vdx(content, i)
                if s.get_position() is None:
                    raise Exception("cannot calculate slice position")
                if self.cube is None:
                    raise Exception("cube not loaded")
                key = int((float(s.get_position()) - min(self.cube.slice_pos)) * 100)
                self.slice_z.append(key)
                self.slices[key] = s
            elif re.match("voi", line) is not None:
                break
            elif len(self.slices) >= number_of_slices:
                break
            i += 1
        return i - 1

    def get_roi_type_number(self, type_name):
        """
        :returns: 1 if GTV or CTV, else 0.
        """
        if type_name == 'EXTERNAL':
            return 0  # TODO: should be 10?
        elif type_name == 'AVOIDANCE':
            return 0
        elif type_name == 'ORGAN':
            return 0
        elif type_name == 'GTV':
            return 1
        elif type_name == 'CTV':
            return 1
        else:
            return 0

    def get_roi_type_name(self, type_id):
        """
        :returns: The type name of the ROI.
        """
        if type_id == 10:
            return "EXTERNAL"
        elif type_id == 2:
            return 'AVOIDANCE'
        elif type_id == 1:
            return 'CTV'
        elif type_id == 0:
            return 'other'
        return ''

    def read_dicom(self, info, data):
        """ Reads a single ROI (= VOI) from a Dicom data set.

        :param info: (not used)
        :param Dicom data: Dicom ROI object which contains the contours.
        """
        if "Contours" not in data.dir() and "ContourSequence" not in data.dir():
            return

        self.type = self.get_roi_type_number(np.typename)
        self.color = data.ROIDisplayColor
        if "Contours" in data.dir():
            contours = data.Contours
        else:
            contours = data.ContourSequence
        for i, contour in enumerate(contours):
            key = int((float(contour.ContourData[2]) - min(self.cube.slice_pos)) * 100)
            if key not in self.slices:
                self.slices[key] = Slice(cube=self.cube)
                self.slice_z.append(key)
            self.slices[key].add_dicom_contour(contour)

    def get_thickness(self):
        """
        :returns: thickness of slice in [mm]. If there is only one slice, 3 mm is returned.
        """
        if len(self.slice_z) <= 1:
            return 3  # TODO: what is this? And shoudn't it be float?
        return abs(float(self.slice_z[1]) - float(self.slice_z[0])) / 100

    def to_voxel_string(self):
        """ Creates the Voxelplan formatted text, which can be written into a .vdx file (format 2.0).

        :returns: a str holding the all lines needed for a Voxelplan formatted file.
        """
        if len(self.slices) is 0:
            return ""

        out = "\n"
        out += "voi %s\n" % (self.name.replace(" ", "_"))
        out += "key empty\n"
        out += "type %s\n" % self.type
        out += "\n"
        out += "contours\n"
        out += "reference_frame\n"
        out += " origin 0.000 0.000 0.000\n"
        out += " point_on_x_axis 1.000 0.000 0.000\n"
        out += " point_on_y_axis 0.000 1.000 0.000\n"
        out += " point_on_z_axis 0.000 0.000 1.000\n"
        out += "number_of_slices %d\n" % self.number_of_slices()
        out += "\n"
        i = 0
        thickness = self.get_thickness()
        for k in self.slice_z:
            sl = self.slices[k]
            pos = sl.get_position()
            out += "slice %d\n" % i
            out += "slice_in_frame %.3f\n" % pos
            out += "thickness %.3f reference " \
                   "start_pos %.3f stop_pos %.3f\n" % \
                   (thickness, pos - 0.5 * thickness, pos + 0.5 * thickness)
            out += "number_of_contours %d\n" % \
                   self.slices[k].number_of_contours()
            out += self.slices[k].to_voxel_string()
            i += 1
        return out

    def get_row_intersections(self, pos):
        """ (TODO: Documentation needed)
        """
        slice = self.get_slice_at_pos(pos[2])
        if slice is None:
            return None
        return np.sort(slice.get_intersections(pos))

    def get_slice_at_pos(self, z):
        """ Returns nearest VOI Slice at position z.

        :param float z: position z in [mm]
        :returns: a Slice object found at position z.
        """
        thickness = self.get_thickness() / 2 * 100
        for key in self.slices.keys():
            key = key
            low = z * 100 - thickness
            high = z * 100 + thickness
            if (low < key < 100 * z) or (high > key >= 100 * z):
                return self.slices[key]
        return None

    def number_of_slices(self):
        """
        :returns: number of slices covered by this VOI.
        """
        return len(self.slices)

    def concat_contour(self):
        """ Concat all contours in all slices found in this VOI.
        """
        if not self.is_concated:
            for k in self.slices.keys():
                self.slices[k].concat_contour()
        self.is_concated = True

    def get_min_max(self):
        """ Set self.temp_min and self.temp_max if they dont exist.

        :returns: minimum and maximum x y coordinates in Voi.
        """
        temp_min, temp_max = None, None
        if hasattr(self, "temp_min"):
            return self.temp_min, self.temp_max
        for key in self.slices:
            if temp_min is None:
                temp_min, temp_max = self.slices[key].get_min_max()
            else:
                min1, max1 = self.slices[key].get_min_max()
                temp_min = pytrip.res.point.min_list(temp_min, min1)
                temp_max = pytrip.res.point.max_list(temp_max, max1)
        self.temp_min = temp_min
        self.temp_max = temp_max
        return temp_min, temp_max
Beispiel #21
0
    def split_fields(self, proj):
        if self.split_proj_key not in self.projectiles.keys():
            return
        name = os.path.join(
            self.path, self.plan_name) + "_" + self.projectiles[proj]["name"]
        path = os.path.join(name + ".phys.dos")
        temp = DosCube()
        temp.read(path)
        temp.version = "2.0"

        self.rest_dose = self.target_dos - temp

        p = self.projectiles[self.split_proj_key]
        p["target_dos"].cube = pytriplib.extend_cube(p["target_dos"].cube)
        if len(p["fields"]) == 2:
            temp.cube = (temp.cube < self.projectiles[proj]["target_dos"].cube) * \
                self.projectiles[proj]["target_dos"].cube + \
                (temp.cube > self.projectiles[proj]["target_dos"].cube) * temp.cube
            dose = self.target_dos - temp
            field1 = p["fields"][0]
            field2 = p["fields"][1]
            d1 = DosCube(temp)
            d2 = DosCube(temp)
            center = pytriplib.calculate_dose_center(p["target_dos"].cube)

            dose.cube[dose.cube < 0] = 0

            temp.cube *= self.target_dos.cube > 0

            basis = get_basis_from_angles(field1.get_gantry(),
                                          field1.get_couch())[0]
            basis = np.array([
                basis[0] / dose.pixel_size, basis[1] / dose.pixel_size,
                basis[2] / dose.slice_distance
            ])
            basis = basis / np.max(np.abs(basis)) * .5
            d1.cube = pytriplib.create_field_shadow(
                dose.cube, temp.cube, np.array(basis, dtype=np.double))
            basis = get_basis_from_angles(field2.get_gantry(),
                                          field2.get_couch())[0]
            basis = np.array([
                basis[0] / dose.pixel_size, basis[1] / dose.pixel_size,
                basis[2] / dose.slice_distance
            ])
            basis /= np.max(np.abs(basis))
            d2.cube = pytriplib.create_field_shadow(
                dose.cube, temp.cube, np.array(basis, dtype=np.double))
            a = d2.cube > d1.cube
            b = d2.cube < d1.cube
            d2.cube = p["target_dos"].cube * a
            d1.cube = p["target_dos"].cube * b

            rest = p["target_dos"].cube * ((a + b) < 1)

            b = pytriplib.split_by_plane(rest, center, basis)
            d1.cube += b
            d2.cube += rest - b
            self.plan.add_dose(d1, "H1")
            self.plan.add_dose(d2, "H2")

            self.projectiles[field1.get_projectile() + str(1)] = {
                "target_dos": d1,
                "fields": [field1],
                "name": field1.get_projectile() + str(1),
                "projectile": field1.get_projectile()
            }
            self.projectiles[field2.get_projectile() + str(2)] = {
                "target_dos": d2,
                "fields": [field2],
                "name": field2.get_projectile() + str(2),
                "projectile": field2.get_projectile()
            }
            del self.projectiles[self.split_proj_key]
Beispiel #22
0
class TripExecuter(object):
    def __init__(self, images, rbe=None):
        logger.debug("Initializing TripExecuter()")
        self.images = images
        self.rbe = rbe
        self.listeners = []
        self.trip_bin_path = "TRiP98"  # where TRiP98 is installed, if not accessible in /usr/local/bin or similar
        self.logfile_stdout = "trip98.stdout"
        self.logfile_stderr = "trip98.stderr"
        self.rsakey_local_path = "~/.ssh/id_rsa"
        self._runtrip = True  # set this to False for a dry run without TRiP98 (for testing purposes)
        self.remote_dir = "."  # remote directory where all will be run

    def delete_workspace(self):
        shutil.rmtree(self.path)

    def cube_in_other_cube(self, outer, inner):
        m = np.max(inner.cube)
        idx = np.where(inner.cube == m)
        idx = [idx[0][0], idx[1][0], idx[2][0]]
        a = outer.cube[idx[0], idx[1]]
        b = inner.cube[idx[0], idx[1]]
        res = a > b
        if True in res[0:idx[2]] and True in res[idx[2]:-1]:
            return True
        return False

    def analyse_cube(self):
        keys = self.projectiles.keys()

        p1 = self.projectiles[keys[0]]
        p2 = self.projectiles[keys[1]]

        cube1 = p1["target_dos"]
        cube2 = p2["target_dos"]

        if not self.cube_in_other_cube(cube1, cube2):
            temp = p1
            p1 = p2
            p2 = temp
            cube1 = p1["target_dos"]
            cube2 = p2["target_dos"]
            self.execute_order = [keys[0], keys[1]]
            self.split_proj_key = keys[1]

        else:
            self.execute_order = [keys[1], keys[0]]
            self.split_proj_key = keys[0]

        target_cube = copy.deepcopy(cube1)
        shadow_cubes = []
        for i, field in enumerate(p1["fields"]):
            d = DosCube(cube1)

            basis = get_basis_from_angles(field.get_gantry(),
                                          field.get_couch())
            basis = basis[0]
            basis = np.array([
                basis[0] / cube1.pixel_size, basis[1] / cube1.pixel_size,
                basis[2] / cube1.slice_distance
            ])
            basis /= np.max(np.abs(basis))

            d.cube = pytriplib.create_field_shadow(
                cube1.cube, cube2.cube, np.array(basis, dtype=np.double))
            target_cube -= d
            shadow_cubes.append(d)

        target_cube.cube[target_cube.cube < 0] = 0
        cube2.cube = cube2.cube + target_cube.cube
        # ~ cube2.cube = pytriplib.extend_cube(cube2.cube)
        cube1.cube = cube1.cube - target_cube.cube
        if len(p1["fields"]) == 2:
            a = self.execute_order.pop(1)
            b = self.projectile_dose_level[a]
            self.execute_order.append(a + str(1))
            self.execute_order.append(a + str(2))
            self.projectile_dose_level[a + str(1)] = b
            self.projectile_dose_level[a + str(2)] = b

    def execute(self, plan, callback=None):
        """
        Executes the plan object using TRiP98.
        """
        self.plan = plan
        self.callback = callback
        self.ini_execute()
        if not self.mult_proj:
            self.execute_simple()
        else:
            self.execute_mult_proj()
        if os.path.exists(
                os.path.join(self.path, self.plan_name) +
                ".bio.dos") and self.plan.get_out_bio_dose():
            self.plan.load_dose(
                os.path.join(self.path, self.plan_name) + ".bio.dos", "bio",
                self.target_dose)
        if os.path.exists(
                os.path.join(self.path, self.plan_name) +
                ".phys.dos") and self.plan.get_out_phys_dose():
            self.plan.load_dose(
                os.path.join(self.path, self.plan_name) + ".phys.dos", "phys",
                self.target_dose)
        if os.path.exists(
                os.path.join(self.path, self.plan_name) +
                ".phys.dos") and self.plan.get_out_dose_mean_let():
            self.plan.load_let(
                os.path.join(self.path, self.plan_name) + ".dosemlet.dos")
        self.finish()

    def ini_execute(self):
        self.split_plan(self.plan)
        self.plan_name = self.plan.get_name().replace(" ", "_")
        self.working_path = os.path.expandvars(self.plan.get_working_dir())
        if not hasattr(self, "folder_name"):
            self.folder_name = str(uuid.uuid4())
        self.path = os.path.join(self.working_path, self.folder_name)
        self.prepare_folder()
        self.convert_files_to_voxelplan()

    def execute_simple(self):
        """
        Simple execution of TRiP, called by self.execute() when self.mult_proj is not set.
        """
        logger.debug("In execute_simple mode")
        self.create_trip_exec_simple()
        self.run_trip()

    def execute_mult_proj(self):
        self.analyse_cube()
        i = 3

        for a in range(i):
            for projectile in self.execute_order:
                self.calculate_rest_dose(projectile)
                if a == i - 1:
                    self.create_trip_exec_mult_proj(projectile, first=(a is 0))
                else:
                    self.create_trip_exec_mult_proj(projectile,
                                                    last=False,
                                                    first=(a is 0))
                self.run_trip()

                self.split_fields(projectile)
        self.post_process()

    def create_trip_exec_simple(self, no_output=False):
        """
        Generates the .exec script and stores it into self.trip_exec.
        """
        logger.info("Generating the trip_exec script...")
        oar_list = self.oar_list
        targets = self.targets
        fields = self.plan.get_fields()

        projectile = fields[0].get_projectile()
        output = []
        output.extend(self.create_exec_header())
        output.extend(self.create_exec_load_data_files(projectile))
        output.extend(self.create_exec_field(fields))
        output.extend(self.create_exec_oar(oar_list))
        dosecube = None
        if len(targets) > 1:
            dosecube = DosCube(self.images)
            for i, voi in enumerate(targets):
                temp = DosCube(self.images)
                dose_level = int(voi.get_dose() / self.target_dose * 1000)
                if dose_level == 0:
                    dose_level = -1
                temp.load_from_structure(voi.get_voi().get_voi_data(),
                                         dose_level)
                if i == 0:
                    dosecube = temp * 1
                else:
                    dosecube.merge_zero(temp)
            dosecube.cube[dosecube.cube == -1] = int(0)

        if not self.plan.get_target_dose_cube() is None:
            dosecube = self.plan.get_target_dose_cube()

        if dosecube is not None:
            if not no_output:
                dosecube.write(os.path.join(self.path, "target_dose.dos"))
            output.extend(self.create_exec_plan(incube="target_dose"))
        else:
            output.extend(self.create_exec_plan())
        if self.plan.get_optimize():
            output.extend(self.create_exec_opt())

        name = self.plan_name
        output.extend(self.create_exec_output(name, fields))
        out = "\n".join(output) + "\nexit\n"
        self.trip_exec = out

    def create_exec_output(self, name, fields, last=True):
        output = []
        window = self.plan.get_window()
        window_str = ""
        if len(window) is 6:
            window_str = " window(%.2f,%.2f,%.2f,%.2f,%.2f,%.2f) " % (
                window[0], window[1], window[2], window[3], window[4],
                window[5])

        if self.plan.get_out_phys_dose() is True:
            output.append('dose "' + name + '." /calculate  alg(' +
                          self.plan.get_dose_algorithm() + ')' + window_str +
                          '  field(*) write')
        if last:
            if self.plan.get_out_bio_dose() is True:
                output.append('dose "' + name + '." /calculate ' + window_str +
                              ' bioalgorithm(' +
                              self.plan.get_bio_algorithm() +
                              ') biological norbe write')
            if self.plan.get_out_dose_mean_let() is True:
                output.append('dose "' + name + '." /calculate ' + window_str +
                              ' field(*) dosemeanlet write')
            if self.plan.get_out_field() is True and self.plan.get_optimize(
            ) is True:
                for i, field in enumerate(fields):
                    output.append(
                        'field %d /write file(%s.rst) reverseorder ' %
                        (i + 1, field.get_name()))
                    field.set_rasterfile(self.working_path + '//' +
                                         self.folder_name + '//' +
                                         field.get_name())
        return output

    def create_exec_plan(self, incube=""):
        output = []
        plan = "plan / dose(%.2f) " % self.target_dose
        if not self.plan.get_target_tissue_type() == "":
            plan += "target(%s) " % (self.plan.get_target_tissue_type())
        if not self.plan.get_res_tissue_type() == "":
            plan += "residual(%s) " % (self.plan.get_res_tissue_type())
        if not incube == "":
            plan += "incube(%s) " % incube
        output.append(plan)
        return output

    def create_exec_opt(self):
        output = []
        opt = "opt / field(*) "
        opt += self.plan.get_opt_method() + " "
        opt += "iterations(" + str(self.plan.get_iterations()) + ") "
        opt += "dosealgorithm(" + self.plan.get_dose_algorithm() + ") "
        opt += "" + self.plan.get_opt_princip() + " "
        opt += "geps(" + str(self.plan.get_geps()) + ") "
        opt += "eps(" + str(self.plan.get_eps()) + ") "
        opt += "optalgorithm(" + self.plan.get_opt_algorithm() + ") "
        opt += "bioalgorithm(" + self.plan.get_bio_algorithm() + ") "

        output.append(opt)
        return output

    def create_exec_header(self):
        output = []
        output.append("time / on")
        output.append("sis  * /delete")
        output.append("hlut * /delete")
        output.append("ddd  * /delete")
        output.append("dedx * /delete")
        output.append('dedx "' + self.plan.dedx_file + '" / read')
        output.append('hlut "' + self.plan.hlut_file + '" / read')
        output.append("scancap / offh2o(1.709) rifi(3) bolus(0.000) "
                      "minparticles(5000) path(none)")
        output.append("random 1000")
        return output

    def create_exec_load_data_files(self, projectile):
        output = []
        ddd_folder = self.plan.get_ddd_folder()
        spc_folder = self.plan.get_spc_folder()
        sis_file = self.plan.get_sis_file()
        if len(ddd_folder) > 0:
            output.append('ddd "%s/*" / read' % ddd_folder)
        if len(spc_folder) > 0:
            output.append('spc "%s/*" / read' % spc_folder)
        if len(sis_file) > 0:
            output.append('sis "%s" / read' % sis_file)
        else:
            output.append('sis / dummy')
        if not (len(spc_folder) > 0 or len(ddd_folder) > 0):
            if projectile == "C":
                output.append('ddd "$TRIP98/DATA/DDD/12C/RF3MM/12C*" / read')
                output.append('spc "$TRIP98/DATA/SPC/12C/RF3MM/12C*" / read')
            elif projectile == "H":
                output.append('ddd "$TRIP98/DATA/DDD/1H/RF3MM/1H*" / read')
                output.append('spc "$TRIP98/DATA/SPC/1H/RF3MM/1H*" / read')
            elif projectile == "O":
                output.append('ddd "$TRIP98/DATA/DDD/16O/RF3MM/16O*" / read')
                output.append('spc "$TRIP98/DATA/SPC/16O/RF3MM/16O*" / read')
            elif projectile == "Ne":
                output.append('ddd "$TRIP98/DATA/DDD/20Ne/RF3MM/20Ne*" / read')
                output.append('spc "$TRIP98/DATA/SPC/20Ne/RF3MM/20Ne*" / read')
        output.append("ct " + self.plan_name + " /read")

        output.append("voi " + self.plan_name + "  /read")
        output.append("voi * /list")

        if not self.plan.get_target_tissue_type() == "":
            rbe = self.rbe.get_rbe_by_name(self.plan.get_target_tissue_type())
            output.append("rbe '%s' /read" % (rbe.get_path()))
            if not self.plan.get_res_tissue_type() == "" and \
                    not self.plan.get_res_tissue_type() \
                    == self.plan.get_target_tissue_type():
                rbe = self.rbe.get_rbe_by_name(
                    self.plan.get_target_tissue_type())
                output.append("rbe %s /read" % (rbe.get_path()))

        return output

    def create_exec_field(self, fields):
        output = []
        if self.plan.get_optimize():
            for i, val in enumerate(fields):
                field = "field " + str(i + 1) + " / new "
                field += "fwhm(%d) " % (val.get_fwhm())
                raster = val.get_rasterstep()
                if not raster[0] is 0 and not raster[1] is 0:
                    field += "raster(%.2f,%.2f) " % (raster[0], raster[1])
                gantry, couch = angles_to_trip(val.get_gantry(),
                                               val.get_couch())
                field += "couch(" + str(couch) + ") "
                field += "gantry(" + str(gantry) + ") "
                target = val.get_target()
                if len(val.get_target()) is not 0:
                    field += "target(%.1f,%.1f,%.1f) " % (target[0], target[1],
                                                          target[2])
                if val.get_doseextension() > 0.0001:
                    field += "doseext(" + str(val.get_doseextension()) + ") "
                field += "contourext(" + str(val.get_contourextension()) + ") "
                if val.get_zsteps() > 0.001:
                    field += "zsteps(" + str(val.get_zsteps()) + ") "
                field += 'proj(' + val.get_projectile() + ')'
                output.append(field)
        else:
            for i, val in enumerate(fields):
                field = 'field 1 /read file(' + val.get_rasterfile() + '.rst)'
                field += "fwhm(%d) " % (val.get_fwhm())
                raster = val.get_rasterstep()
                if not raster[0] is 0 and not raster[1] is 0:
                    field += "raster(%.2f,%.2f) " % (raster[0], raster[1])
                gantry, couch = angles_to_trip(val.get_gantry(),
                                               val.get_couch())
                field += "couch(" + str(couch) + ") "
                field += "gantry(" + str(gantry) + ") "
                if len(val.get_target()) is not 0:
                    field += "target(" + val.get_target() + ") "
                if val.get_doseextension() > 0.0001:
                    field += "doseext(" + str(val.get_doseextension()) + ") "
                field += "contourext(" + str(val.get_contourextension()) + ") "
                if val.get_zsteps() > 0.001:
                    field += "zsteps(" + str(val.get_zsteps()) + ") "
                field += 'proj(' + val.get_projectile() + ')'
                output.append(field)
        return output

    def create_exec_oar(self, oar_list):
        output = []
        for oar in oar_list:
            output.append("voi " + oar.get_name().replace(" ", "_") +
                          " / maxdosefraction(" +
                          str(oar.get_max_dose_fraction()) + ") oarset")
        return output

    def split_fields(self, proj):
        if self.split_proj_key not in self.projectiles.keys():
            return
        name = os.path.join(
            self.path, self.plan_name) + "_" + self.projectiles[proj]["name"]
        path = os.path.join(name + ".phys.dos")
        temp = DosCube()
        temp.read(path)
        temp.version = "2.0"

        self.rest_dose = self.target_dos - temp

        p = self.projectiles[self.split_proj_key]
        p["target_dos"].cube = pytriplib.extend_cube(p["target_dos"].cube)
        if len(p["fields"]) == 2:
            temp.cube = (temp.cube < self.projectiles[proj]["target_dos"].cube) * \
                self.projectiles[proj]["target_dos"].cube + \
                (temp.cube > self.projectiles[proj]["target_dos"].cube) * temp.cube
            dose = self.target_dos - temp
            field1 = p["fields"][0]
            field2 = p["fields"][1]
            d1 = DosCube(temp)
            d2 = DosCube(temp)
            center = pytriplib.calculate_dose_center(p["target_dos"].cube)

            dose.cube[dose.cube < 0] = 0

            temp.cube *= self.target_dos.cube > 0

            basis = get_basis_from_angles(field1.get_gantry(),
                                          field1.get_couch())[0]
            basis = np.array([
                basis[0] / dose.pixel_size, basis[1] / dose.pixel_size,
                basis[2] / dose.slice_distance
            ])
            basis = basis / np.max(np.abs(basis)) * .5
            d1.cube = pytriplib.create_field_shadow(
                dose.cube, temp.cube, np.array(basis, dtype=np.double))
            basis = get_basis_from_angles(field2.get_gantry(),
                                          field2.get_couch())[0]
            basis = np.array([
                basis[0] / dose.pixel_size, basis[1] / dose.pixel_size,
                basis[2] / dose.slice_distance
            ])
            basis /= np.max(np.abs(basis))
            d2.cube = pytriplib.create_field_shadow(
                dose.cube, temp.cube, np.array(basis, dtype=np.double))
            a = d2.cube > d1.cube
            b = d2.cube < d1.cube
            d2.cube = p["target_dos"].cube * a
            d1.cube = p["target_dos"].cube * b

            rest = p["target_dos"].cube * ((a + b) < 1)

            b = pytriplib.split_by_plane(rest, center, basis)
            d1.cube += b
            d2.cube += rest - b
            self.plan.add_dose(d1, "H1")
            self.plan.add_dose(d2, "H2")

            self.projectiles[field1.get_projectile() + str(1)] = {
                "target_dos": d1,
                "fields": [field1],
                "name": field1.get_projectile() + str(1),
                "projectile": field1.get_projectile()
            }
            self.projectiles[field2.get_projectile() + str(2)] = {
                "target_dos": d2,
                "fields": [field2],
                "name": field2.get_projectile() + str(2),
                "projectile": field2.get_projectile()
            }
            del self.projectiles[self.split_proj_key]

    def calculate_rest_dose(self, proj):
        self.rest_dose = copy.deepcopy(self.target_dos)
        for k, projectile in self.projectiles.items():
            if k == proj:
                continue
            name = os.path.join(self.path,
                                self.plan_name) + "_" + projectile["name"]
            path = os.path.join(name + ".phys.dos")
            if os.path.exists(path):
                temp = DosCube()
                temp.read(path)
                temp = temp
                self.rest_dose.cube = self.rest_dose.cube - temp.cube
                # ~ self.rest_dose.cube[self.rest_dose.cube<0] = 0

    def post_process(self):
        phys_dose = None
        bio_dose = None
        dose_mean_let = None
        temp_dos = None
        for projectile in self.projectiles:
            name = os.path.join(
                self.path,
                self.plan_name) + "_" + self.projectiles[projectile]["name"]
            factor = float(self.projectile_dose_level[projectile]) / 1000
            factor = 1
            if os.path.exists(name +
                              ".bio.dos") and self.plan.get_out_bio_dose():
                path = os.path.join(name + ".bio.dos")
                temp = DosCube()
                temp.read(path)
                temp *= factor
                if self.mult_proj:
                    self.plan.add_dose(temp, "bio_%s" % projectile)
                if bio_dose is None:
                    bio_dose = temp
                else:
                    bio_dose += temp

            if os.path.exists(name +
                              ".phys.dos") and self.plan.get_out_phys_dose():
                path = os.path.join(name + ".phys.dos")
                temp_dos = DosCube()
                temp_dos.read(path)
                temp_dos *= factor
                if self.mult_proj:
                    self.plan.add_dose(temp_dos, "phys_%s" % projectile)
                if phys_dose is None:
                    phys_dose = temp_dos
                else:
                    phys_dose += temp_dos
            if os.path.exists(name + ".dosemlet.dos"
                              ) and self.plan.get_out_dose_mean_let():

                path = os.path.join(name + ".dosemlet.dos")
                temp = LETCube()
                temp.read(path)
                if not self.mult_proj:
                    dose_mean_let = temp
                else:
                    if dose_mean_let is None:
                        dose_mean_let = temp * temp_dos
                    else:
                        dose_mean_let = dose_mean_let + temp * temp_dos
        out_path = os.path.join(self.path, self.plan_name)
        if bio_dose is not None:
            bio_dose.write(out_path + ".bio.dos")
        if phys_dose is not None:
            phys_dose.write(out_path + ".phys.dos")
        if dose_mean_let is not None:
            if self.mult_proj:
                dose_mean_let /= phys_dose

            dose_mean_let.write(out_path + ".hed")

    def split_plan(self, plan=None):
        self.targets = []
        self.oar_list = []

        dose = 0
        for voi in self.plan.get_vois():
            if voi.is_oar():
                self.oar_list.append(voi)
            if voi.is_target():
                self.targets.append(voi)
                if voi.get_dose() > dose:
                    dose = voi.get_dose()
        if not len(self.targets):
            raise InputError("No targets")
        if not len(self.plan.get_fields()):
            raise InputError("No fields")
        self.target_dose = dose
        if plan is None:
            plan = self.plan
        proj = []
        self.projectile_dose_level = {}
        for field in plan.fields:
            if field.get_projectile() not in proj:
                proj.append(field.get_projectile())
                self.projectile_dose_level[field.get_projectile()] = 0

        if len(proj) > 1:
            self.mult_proj = True
        else:
            self.mult_proj = False

        if self.mult_proj:
            self.projectiles = {}
            for field in plan.fields:
                if field.get_projectile() not in self.projectiles.keys():
                    self.projectiles[field.get_projectile()] = {
                        "target_dos": DosCube(self.images),
                        "fields": [field],
                        "name": field.get_projectile(),
                        "projectile": field.get_projectile()
                    }
                else:
                    self.projectiles[field.get_projectile()]["fields"].append(
                        field)

            self.target_dos = DosCube(self.images)

            for i, voi in enumerate(self.targets):
                temp = DosCube(self.images)
                voi_dose_level = int(voi.get_dose() / dose * 1000)
                temp.load_from_structure(voi.get_voi().get_voi_data(), 1)
                for projectile, data in self.projectiles.items():
                    dose_percent = self.plan.get_dose_percent(projectile)
                    if not voi.get_dose_percent(projectile) is None:
                        dose_percent = voi.get_dose_percent(projectile)
                    proj_dose_lvl = int(voi.get_dose() / self.target_dose *
                                        dose_percent * 10)
                    if self.projectile_dose_level[projectile] < proj_dose_lvl:
                        self.projectile_dose_level[projectile] = proj_dose_lvl
                    if proj_dose_lvl == 0:
                        proj_dose_lvl = -1
                    if i == 0:
                        data["target_dos"] = temp * proj_dose_lvl
                    else:
                        data["target_dos"].merge_zero(temp * proj_dose_lvl)
                if i == 0:
                    self.target_dos = temp * voi_dose_level
                else:
                    self.target_dos.merge_zero(temp * voi_dose_level)
            for projectile, data in self.projectiles.items():
                data["target_dos"].cube[data["target_dos"].cube == -1] = int(0)
                self.plan.add_dose(data["target_dos"],
                                   "target_%s" % projectile)
            self.rest_dose = copy.deepcopy(self.target_dos)

    def add_log_listener(self, listener):
        self.listeners.append(listener)

    def log(self, txt):
        txt = txt.replace("\n", "")
        for l in self.listeners:
            l.write(txt)

    def prepare_folder(self):
        self.filepath = self.path + self.plan_name
        if os.path.exists(self.path):
            pass
            # shutil.rmtree(self.path)
        else:
            os.makedirs(self.path)

    def run_trip(self):
        if self.plan.remote:
            self.run_trip_remote()
        else:
            self.run_trip_local()

    def run_trip_remote(self):
        logger.info("Run TRiP98 in REMOTE mode.")
        # self.create_remote_run_file()
        self.compress_files()

        self.copy_files_to_server()

        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        # If no password is supplied, try to look for a private key
        if self.plan.get_password() is "" or None:
            rsa_keypath = os.path.expanduser(self.rsakey_local_path)
            if not os.path.isfile(rsa_keypath):
                # login with provided username + empty password
                try:
                    ssh.connect(self.plan.get_server(),
                                username=self.plan.get_username(),
                                password="")
                except:
                    logger.error("Cannot connect to " + self.plan.get_server())
                    logger.error("Check username, password or key in " +
                                 self.rsakey_local_path)
                    raise
            else:
                # login with provided username + private key
                rsa_key = paramiko.RSAKey.from_private_key_file(rsa_keypath)
                try:
                    ssh.connect(self.plan.get_server(),
                                username=self.plan.get_username(),
                                pkey=rsa_key)
                except:
                    logger.error("Cannot connect to " + self.plan.get_server())
                    logger.error("Check username and your key in " +
                                 self.rsakey_local_path)
                    raise
        else:
            # login with provided username + password
            ssh.connect(self.plan.get_server(),
                        username=self.plan.get_username(),
                        password=self.plan.get_password())

        if not self._runtrip:
            norun = "echo "
        else:
            norun = ""

        path_tgz = os.path.join(
            self.remote_dir,
            "temp.tar.gz")  # remote place where temp.tar.gz is stored
        rrun_dir = os.path.join(self.remote_dir,
                                self.folder_name)  # remote run dir

        commands = [
            "cd " + self.remote_dir + ";" + "tar -zxvf " + path_tgz,
            "cd " + rrun_dir + ";" + norun + "bash -lc '" +
            self.trip_bin_path + " < plan.exec '", "cd " + self.remote_dir +
            ";" + "tar -zcvf " + path_tgz + " " + rrun_dir,
            "cd " + self.remote_dir + ";" + "rm -r " + rrun_dir
        ]

        logger.debug("Write stdout and stderr to " +
                     os.path.join(self.working_path, self.folder_name))
        fp_stdout = open(
            os.path.join(self.working_path, self.folder_name,
                         self.logfile_stdout), "w")
        fp_stderr = open(
            os.path.join(self.working_path, self.folder_name,
                         self.logfile_stderr), "w")

        for cmd in commands:
            logger.debug("Execute on remote: " + cmd)
            self.log(cmd)
            stdin, stdout, stderr = ssh.exec_command(cmd)
            answer_stdout = stdout.read()
            answer_stderr = stderr.read()
            logger.debug("Remote answer stdout:" + answer_stdout)
            logger.debug("Remote answer stderr:" + answer_stderr)
            fp_stdout.write(answer_stdout)
            fp_stderr.write(answer_stderr)
            self.log(answer_stdout)
        ssh.close()
        fp_stdout.close()
        fp_stderr.close()

        self.copy_back_from_server()
        self.decompress_data()

    def run_trip_local(self):
        """
        Runs TRiP98 on local computer.
        """
        logger.info("Run TRiP98 in LOCAL mode.")
        logger.debug("Write stdout and stderr to " +
                     os.path.join(self.working_path, self.folder_name))
        fp_stdout = open(
            os.path.join(self.working_path, self.folder_name,
                         self.logfile_stdout), "w")
        fp_stderr = open(
            os.path.join(self.working_path, self.folder_name,
                         self.logfile_stderr), "w")

        os.chdir("%s" % (self.path))

        if not self._runtrip:  # for testing
            norun = "echo "
        else:
            norun = ""
        p = Popen([norun + self.trip_bin_path], stdout=PIPE, stdin=PIPE)

        p.stdin.write(self.trip_exec)
        p.stdin.flush()
        while (True):
            retcode = p.poll()  # returns None while subprocess is running
            answer_stdout = p.stdout.readline()
            answer_stderr = p.stderr.readline()
            logger.debug("Remote answer stdout:" + answer_stdout)
            logger.debug("Remote answer stderr:" + answer_stderr)
            fp_stdout.write(answer_stdout)
            fp_stderr.write(answer_stderr)
            self.log(answer_stdout)
            if retcode is not None:  # runs until process stops
                break  # TODO: check return code
        os.chdir("..")

        fp_stdout.close()
        fp_stderr.close()

    def finish(self):
        pass

    def create_trip_exec_mult_proj(self,
                                   projectile,
                                   no_output=False,
                                   last=True,
                                   first=True):

        fields = self.projectiles[projectile]["fields"]
        oar_list = self.oar_list

        output = []
        output.extend(self.create_exec_header())
        output.extend(
            self.create_exec_load_data_files(
                self.projectiles[projectile]["projectile"]))
        output.extend(self.create_exec_field(fields))
        output.extend(self.create_exec_oar(oar_list))

        dosecube = copy.deepcopy(self.projectiles[projectile]["target_dos"])

        if not no_output:
            if hasattr(self, "rest_dose"):
                dosecube.cube = np.array(
                    (self.rest_dose.cube >= dosecube.cube) * dosecube.cube +
                    (self.rest_dose.cube < dosecube.cube) *
                    self.rest_dose.cube,
                    dtype=np.int16)
                dosecube.cube[dosecube.cube < 0] = 0
                # ~ self.plan.add_dose(self.rest_dose,"rest")
                if not first:
                    a = (self.rest_dose.cube -
                         dosecube.cube) * (dosecube.cube > 0)
                    dosecube.cube += a * dosecube.cube / 500

            dosecube.write(
                os.path.join(
                    self.path, "target_dose_%s.dos" %
                    self.projectiles[projectile]["name"]))

        self.projectile_dose_level[projectile] = np.max(dosecube.cube)
        output.extend(
            self.create_exec_plan(incube="target_dose_%s" %
                                  self.projectiles[projectile]["name"]))
        if self.plan.get_optimize() is True:
            output.extend(self.create_exec_opt())

        name = self.plan_name + "_" + self.projectiles[projectile]["name"]
        output.extend(self.create_exec_output(name, fields))
        out = "\n".join(output) + "\nexit\n"
        self.trip_exec = out

    def convert_files_to_voxelplan(self):
        out_path = os.path.join(self.path, self.plan_name)
        ctx = self.images
        ctx.patient_name = self.plan_name
        ctx.write(os.path.join(out_path + ".ctx"))
        structures = VdxCube("", ctx)
        structures.version = "2.0"
        liste = []
        area = 0
        for voi in self.plan.get_vois():
            voxelplan_voi = voi.get_voi().get_voi_data()

            if voi.is_target():
                mn, mx = voxelplan_voi.get_min_max()
                area_temp = (mx[0] - mn[0]) * (mx[1] - mn[1]) * (mx[2] - mn[2])
                if area_temp > area:
                    area = area_temp
                    liste.insert(0, voxelplan_voi)
                else:
                    liste.append(voxelplan_voi)
                voxelplan_voi.type = '1'
            else:
                liste.append(voxelplan_voi)
                voxelplan_voi.type = '0'
        for voxelplan_voi in liste:
            structures.add_voi(voxelplan_voi)
        structures.write_to_trip(out_path + ".vdx")

    def compress_files(self):
        """
        Builds the tar.gz from what is found in self.path.
        """

        logger.debug("Compressing files in " + self.path)
        self.save_exec(
            os.path.join(self.working_path, self.folder_name, "plan.exec"))
        with tarfile.open(
                os.path.join(self.working_path, self.folder_name + ".tar.gz"),
                "w:gz") as tar:
            tar.add(self.path, arcname=self.folder_name)

    def set_plan(self, plan):
        self.plan = plan
        self.plan_name = self.plan.get_name().replace(" ", "_")

    def save_exec(self, path):
        """
        Writes the .exec script to disk.
        """
        self.split_plan()
        if self.mult_proj:
            path1 = path.replace(".exec", "")
            for projectile in self.projectiles:
                self.create_trip_exec(projectile, True)
                with open(path1 + "_" + projectile + ".exec", "wb+") as fp:
                    fp.write(self.trip_exec)
        else:
            with open(path, "wb+") as fp:
                fp.write(self.trip_exec)

    def save_data(self, path):
        out_path = path
        ctx = self.images
        ctx.patient_name = self.plan_name
        ctx.write(os.path.join(out_path + ".ctx"))
        structures = VdxCube("", ctx)
        structures.version = "2.0"
        for voi in self.plan.get_vois():
            voxelplan_voi = voi.get_voi().get_voi_data()
            structures.add_voi(voxelplan_voi)
            if voi.is_target():
                voxelplan_voi.type = '1'
            else:
                voxelplan_voi.type = '0'
        structures.write_to_trip(out_path + ".vdx")

    def get_transport(self):
        transport = paramiko.Transport((self.plan.get_server(), 22))
        transport.connect(username=self.plan.get_username(),
                          password=self.plan.get_password())
        return transport

    def copy_files_to_server(self):
        """
        Copies the generated tar.gz file to the remote server.
        """
        logger.debug("Copy tar.gz to server:" + self.path + ".tar.gz -> " +
                     os.path.join(self.remote_dir, "temp.tar.gz"))
        transport = self.get_transport()
        sftp = paramiko.SFTPClient.from_transport(transport)
        sftp.put(localpath=self.path + ".tar.gz",
                 remotepath=os.path.join(self.remote_dir, 'temp.tar.gz'))
        sftp.close()
        transport.close()

    def run_ssh_command(self, cmd):
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(self.plan.get_server(),
                    username=self.plan.get_username(),
                    password=self.plan.get_password())
        self.parent.write_to_log("Run Trip\n")
        stdin, stdout, stderr = ssh.exec_command(cmd)
        ssh.close()

    def copy_back_from_server(self):
        transport = self.get_transport()
        sftp = paramiko.SFTPClient.from_transport(transport)
        sftp.get(os.path.join(self.remote_dir, 'temp.tar.gz'),
                 self.path + ".tar.gz")
        sftp.close()
        transport.close()

    def decompress_data(self):
        output_folder = self.path
        if os.path.exists(output_folder):
            shutil.rmtree(output_folder)
        with tarfile.open(self.path + ".tar.gz", "r:gz") as tar:
            tar.extractall(self.working_path)

    def clean_up(self):
        f = "%s" % (self.path + ".tar.gz")
        if os.path.exists(f):
            os.remove(f)
        shutil.rmtree("%s" % self.path)

    def visualize_data(self):
        pass
Beispiel #23
0
    def post_process(self):
        phys_dose = None
        bio_dose = None
        dose_mean_let = None
        temp_dos = None
        for projectile in self.projectiles:
            name = os.path.join(
                self.path,
                self.plan_name) + "_" + self.projectiles[projectile]["name"]
            factor = float(self.projectile_dose_level[projectile]) / 1000
            factor = 1
            if os.path.exists(name +
                              ".bio.dos") and self.plan.get_out_bio_dose():
                path = os.path.join(name + ".bio.dos")
                temp = DosCube()
                temp.read(path)
                temp *= factor
                if self.mult_proj:
                    self.plan.add_dose(temp, "bio_%s" % projectile)
                if bio_dose is None:
                    bio_dose = temp
                else:
                    bio_dose += temp

            if os.path.exists(name +
                              ".phys.dos") and self.plan.get_out_phys_dose():
                path = os.path.join(name + ".phys.dos")
                temp_dos = DosCube()
                temp_dos.read(path)
                temp_dos *= factor
                if self.mult_proj:
                    self.plan.add_dose(temp_dos, "phys_%s" % projectile)
                if phys_dose is None:
                    phys_dose = temp_dos
                else:
                    phys_dose += temp_dos
            if os.path.exists(name + ".dosemlet.dos"
                              ) and self.plan.get_out_dose_mean_let():

                path = os.path.join(name + ".dosemlet.dos")
                temp = LETCube()
                temp.read(path)
                if not self.mult_proj:
                    dose_mean_let = temp
                else:
                    if dose_mean_let is None:
                        dose_mean_let = temp * temp_dos
                    else:
                        dose_mean_let = dose_mean_let + temp * temp_dos
        out_path = os.path.join(self.path, self.plan_name)
        if bio_dose is not None:
            bio_dose.write(out_path + ".bio.dos")
        if phys_dose is not None:
            phys_dose.write(out_path + ".phys.dos")
        if dose_mean_let is not None:
            if self.mult_proj:
                dose_mean_let /= phys_dose

            dose_mean_let.write(out_path + ".hed")
Beispiel #24
0
    def split_plan(self, plan=None):
        self.targets = []
        self.oar_list = []

        dose = 0
        for voi in self.plan.get_vois():
            if voi.is_oar():
                self.oar_list.append(voi)
            if voi.is_target():
                self.targets.append(voi)
                if voi.get_dose() > dose:
                    dose = voi.get_dose()
        if not len(self.targets):
            raise InputError("No targets")
        if not len(self.plan.get_fields()):
            raise InputError("No fields")
        self.target_dose = dose
        if plan is None:
            plan = self.plan
        proj = []
        self.projectile_dose_level = {}
        for field in plan.fields:
            if field.get_projectile() not in proj:
                proj.append(field.get_projectile())
                self.projectile_dose_level[field.get_projectile()] = 0

        if len(proj) > 1:
            self.mult_proj = True
        else:
            self.mult_proj = False

        if self.mult_proj:
            self.projectiles = {}
            for field in plan.fields:
                if field.get_projectile() not in self.projectiles.keys():
                    self.projectiles[field.get_projectile()] = {
                        "target_dos": DosCube(self.images),
                        "fields": [field],
                        "name": field.get_projectile(),
                        "projectile": field.get_projectile()
                    }
                else:
                    self.projectiles[field.get_projectile()]["fields"].append(
                        field)

            self.target_dos = DosCube(self.images)

            for i, voi in enumerate(self.targets):
                temp = DosCube(self.images)
                voi_dose_level = int(voi.get_dose() / dose * 1000)
                temp.load_from_structure(voi.get_voi().get_voi_data(), 1)
                for projectile, data in self.projectiles.items():
                    dose_percent = self.plan.get_dose_percent(projectile)
                    if not voi.get_dose_percent(projectile) is None:
                        dose_percent = voi.get_dose_percent(projectile)
                    proj_dose_lvl = int(voi.get_dose() / self.target_dose *
                                        dose_percent * 10)
                    if self.projectile_dose_level[projectile] < proj_dose_lvl:
                        self.projectile_dose_level[projectile] = proj_dose_lvl
                    if proj_dose_lvl == 0:
                        proj_dose_lvl = -1
                    if i == 0:
                        data["target_dos"] = temp * proj_dose_lvl
                    else:
                        data["target_dos"].merge_zero(temp * proj_dose_lvl)
                if i == 0:
                    self.target_dos = temp * voi_dose_level
                else:
                    self.target_dos.merge_zero(temp * voi_dose_level)
            for projectile, data in self.projectiles.items():
                data["target_dos"].cube[data["target_dos"].cube == -1] = int(0)
                self.plan.add_dose(data["target_dos"],
                                   "target_%s" % projectile)
            self.rest_dose = copy.deepcopy(self.target_dos)
Beispiel #25
0
 def load_dose(self, path, t, target_dose=0.0):
     dos = DosCube()
     dos.read(os.path.splitext(path)[0] + ".dos")
     d = DoseCube(dos, t)
     d.set_dose(target_dose)
     self.add_dose(d)