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)
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)
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 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)
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
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)
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)
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 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 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)
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)
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)
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)
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)
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
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]
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
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 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)