def create_rois(self): # Delete pre-existing ROIs (except those which are manually contoured) in sorted order: group = self.grouped_rois() for key in sorted(iter(group)): for roi in group[key]: PMF.delete_matching_roi_except_manually_contoured( self.pm, self.ss, roi) group = self.grouped_rois() # Create ROIs (in reverse sorted order): for key in reversed(sorted(iter(group))): for roi in group[key]: # Only create ROI if it doesn't already exist: if not PMF.has_roi(self.pm, roi.name): if roi.__class__.__name__ == 'ROI': if roi.model: PMF.create_model_roi(self.pm, self.examination, roi) else: PMF.create_empty_roi(self.pm, roi) elif roi.__class__.__name__ == 'ROIExpanded': PMF.create_expanded_roi(self.pm, self.examination, self.ss, roi) elif roi.__class__.__name__ == 'ROIAlgebra': PMF.create_algebra_roi(self.pm, self.examination, self.ss, roi) elif roi.__class__.__name__ == 'ROIWall': PMF.create_wall_roi(self.pm, self.examination, self.ss, roi)
def roi_overlap(pm, examination, ss, roi1, roi2, threshold): subtraction = ROI.ROIAlgebra(roi1.name + '-' + roi2.name, 'Undefined', 'Black', sourcesA=[roi1], sourcesB=[roi2], operator='Subtraction') # In the rare case that this ROI already exists, delete it (to avoid a crash): PMF.delete_roi(pm, subtraction.name) PMF.create_algebra_roi(pm, examination, ss, subtraction) # Is overlapping volume less than threshold? overlap = False if ss.RoiGeometries[roi1.name].GetRoiVolume() - ss.RoiGeometries[ subtraction.name].GetRoiVolume() > threshold: overlap = True PMF.delete_roi(pm, subtraction.name) return overlap
def create_roi_subtraction(pm, examination, ss, roi1, roi2, subtraction_name, threshold): overlap = False if has_named_roi_with_contours( ss, roi1.name) and has_named_roi_with_contours(ss, roi2.name): subtraction = ROI.ROIAlgebra(subtraction_name, 'Undefined', 'Black', sourcesA=[roi1], sourcesB=[roi2], operator='Subtraction') # In the rare case that this ROI already exists, delete it (to avoid a crash): PMF.delete_roi(pm, subtraction.name) PMF.create_algebra_roi(pm, examination, ss, subtraction) # Is overlapping volume less than threshold? if has_named_roi_with_contours(ss, subtraction.name): if ss.RoiGeometries[roi1.name].GetRoiVolume() - ss.RoiGeometries[ subtraction.name].GetRoiVolume() > threshold: overlap = True else: GUIF.handle_missing_roi_for_derived_rois(subtraction_name, roi2.name) return overlap
def create_expanded_and_intersected_volume(pm, examination, ss, source_roi, intersect_roi, expanded_roi_name, threshold_volume): # Volume of source roi volume1 = ss.RoiGeometries[source_roi.name].GetRoiVolume() # Estimated radius of source roi calculated from volume (assuming perfect sphere) radius1 = math.pow((volume1 * 3) / (4 * math.pi), 1.0 / 3.0) # Estimated radius of expanded roi calculated from thresgold volume (assuming perfect sphere) radius2 = math.pow((threshold_volume * 3) / (4 * math.pi), 1.0 / 3.0) # Expansion radius r = round(radius2 - radius1, 1) # Expanded roi object expanded_roi = ROI.ROIAlgebra(expanded_roi_name, 'Undefined', 'Black', sourcesA=[source_roi], sourcesB=[intersect_roi], operator='Intersection', marginsA=MARGIN.Expansion(r, r, r, r, r, r), marginsB=MARGINS.zero) # Deletes roi if it already exists in RayStation PMF.delete_roi(pm, expanded_roi.name) # Create ROI in RayStation PMF.create_algebra_roi(pm, examination, ss, expanded_roi)
PMF.create_posterior_half(pm, examination, ss, ROIS.rectum, ROIS.dorso_rectum) if selected_oar_list.get(ROIS.cornea_l) and selected_oar_list.get(ROIS.retina_l): PMF.create_retina_and_cornea(pm, examination, ss, ROIS.lens_l, ROIS.box_l, ROIS.eye_l, ROIS.retina_l, ROIS.cornea_l) elif selected_oar_list.get(ROIS.retina_l) and not selected_oar_list.get(ROIS.cornea_l): PMF.create_retina(pm, examination, ss, ROIS.lens_l, ROIS.box_l, ROIS.eye_l, ROIS.retina_l) elif selected_oar_list.get(ROIS.cornea_l) and not selected_oar_list.get(ROIS.retina_l): PMF.create_cornea(pm, examination, ss, ROIS.lens_l, ROIS.box_l, ROIS.eye_l, ROIS.cornea_l) if selected_oar_list.get(ROIS.cornea_r) and selected_oar_list.get(ROIS.retina_r): PMF.create_retina_and_cornea(pm, examination, ss, ROIS.lens_r, ROIS.box_r, ROIS.eye_r, ROIS.retina_r, ROIS.cornea_r) elif selected_oar_list.get(ROIS.retina_r) and not selected_oar_list.get(ROIS.cornea_r): PMF.create_retina(pm, examination, ss, ROIS.lens_r, ROIS.box_r, ROIS.eye_r, ROIS.retina_r) elif selected_oar_list.get(ROIS.cornea_r) and not selected_oar_list.get(ROIS.retina_r): PMF.create_cornea(pm, examination, ss, ROIS.lens_r, ROIS.box_r, ROIS.eye_r, ROIS.cornea_r) # Create ROIs: for roi in reversed(list(selected_oar_list)): # Only create ROI if it doesn't already exist: if not PMF.has_roi(pm, roi.name): if roi.__class__.__name__ == 'ROI': if roi.model: PMF.create_model_roi(pm, examination, roi) else: PMF.create_empty_roi(pm, roi) elif roi.__class__.__name__ == 'ROIExpanded': PMF.create_expanded_roi(pm, examination, ss, roi) elif roi.__class__.__name__ == 'ROIAlgebra': PMF.create_algebra_roi(pm, examination, ss, roi) elif roi.__class__.__name__ == 'ROIWall': PMF.create_wall_roi(pm, examination, ss, roi)
def create_brain_objectives(pm, examination, ss, plan, total_dose, nr_fractions): if nr_fractions in [1, 3]: # Stereotactic brain nr_targets = SSF.determine_nr_of_indexed_ptvs(ss) for i in range(0, nr_targets): OF.max_eud(ss, plan, ROIS.brain_ptv.name, 0.08 * total_dose * 100, 1.3, 1, beam_set_index=i) OF.max_eud(ss, plan, ROIS.brain_ptv.name, 0.06 * total_dose * 100, 1, 1, beam_set_index=i) OF.fall_off(ss, plan, ROIS.body.name, total_dose * 100, total_dose * 100 / 2, 0.8, 25, beam_set_index=i) if nr_targets == 1: # one target OF.min_dose(ss, plan, ROIS.ptv.name, total_dose * 100, 200, beam_set_index=0) OF.fall_off(ss, plan, ROIS.z_ptv_wall.name, total_dose * 100, 0.7 * total_dose * 100, 0.6, 25, beam_set_index=0) else: for i in range(0, nr_targets): OF.min_dose(ss, plan, ROIS.ptv.name + str(i + 1), total_dose * 100, 200, beam_set_index=i) OF.fall_off(ss, plan, "zPTV" + str(i + 1) + "_Wall", total_dose * 100, 0.7 * total_dose * 100, 0.6, 25, beam_set_index=i) else: # Partial brain OF.max_dose(ss, plan, ROIS.ptv.name, total_dose * 100 * 1.05, 80) OF.fall_off(ss, plan, ROIS.external.name, total_dose * 100, total_dose * 100 / 2, 1.5, 30) OF.max_dose(ss, plan, ROIS.external.name, total_dose * 100 * 1.05, 30) # Objectives for prioritized OARs: OF.max_dose( ss, plan, ROIS.brainstem_surface.name, (TOL.brainstem_surface_v003_adx.equivalent(nr_fractions) * 100) - 50, 60) OF.max_dose( ss, plan, ROIS.brainstem_core.name, (TOL.brainstem_core_v003_adx.equivalent(nr_fractions) * 100) - 50, 80) OF.max_dose( ss, plan, ROIS.optic_chiasm.name, (TOL.optic_chiasm_v003_adx.equivalent(nr_fractions) * 100) - 50, 40) OF.max_dose(ss, plan, ROIS.optic_nrv_l.name, (TOL.optic_nrv_v003_adx.equivalent(nr_fractions) * 100) - 50, 20) OF.max_dose(ss, plan, ROIS.optic_nrv_r.name, (TOL.optic_nrv_v003_adx.equivalent(nr_fractions) * 100) - 50, 20) prioritized_oars = [ ROIS.brainstem_core, ROIS.brainstem_surface, ROIS.optic_chiasm, ROIS.optic_nrv_l, ROIS.optic_nrv_r ] tolerances = [ TOL.brainstem_core_v003_adx, TOL.brainstem_surface_v003_adx, TOL.optic_chiasm_v003_adx, TOL.optic_nrv_v003_adx, TOL.optic_nrv_v003_adx ] conflict_oars = [] for i in range(len(prioritized_oars)): if tolerances[i].equivalent(nr_fractions) < total_dose * 0.95: conflict_oars.append(prioritized_oars[i]) # Setup of min and uniform doses depends on presence of critical overlaps or not: if len(conflict_oars) > 0: # Create subtraction and intersect ROIs for planning of conflicting sites: ctv_oars = ROI.ROIAlgebra(ROIS.ctv_oars.name, ROIS.ctv_oars.type, ROIS.ctv.color, sourcesA=[ROIS.ctv], sourcesB=conflict_oars, operator='Subtraction', marginsA=MARGINS.zero, marginsB=MARGINS.uniform_2mm_expansion) ptv_oars = ROI.ROIAlgebra(ROIS.ptv_oars.name, ROIS.ptv_oars.type, ROIS.ptv.color, sourcesA=[ROIS.ptv], sourcesB=conflict_oars, operator='Subtraction', marginsA=MARGINS.zero, marginsB=MARGINS.uniform_2mm_expansion) ptv_and_oars = ROI.ROIAlgebra(ROIS.ptv_and_oars.name, ROIS.ptv_and_oars.type, ROIS.other_ptv.color, sourcesA=[ROIS.ptv], sourcesB=conflict_oars, operator='Intersection') rois = [ctv_oars, ptv_oars, ptv_and_oars] PMF.delete_matching_rois(pm, rois) for i in range(len(rois)): PMF.create_algebra_roi(pm, examination, ss, rois[i]) PMF.exclude_roi_from_export(pm, rois[i].name) # Create objectives for the subtraction/intersect ROIs: OF.uniform_dose( ss, plan, ROIS.ptv_and_oars.name, (tolerances[0].equivalent(nr_fractions) * 100 - 50), 5 ) # (Note that this assumes our OARs have the same tolerance dose...) OF.uniform_dose(ss, plan, ROIS.ctv_oars.name, total_dose * 100, 30) OF.min_dose(ss, plan, ROIS.ptv_oars.name, total_dose * 100 * 0.95, 150) else: OF.uniform_dose(ss, plan, ROIS.ctv.name, total_dose * 100, 30) OF.min_dose(ss, plan, ROIS.ptv.name, total_dose * 100 * 0.95, 150) # Setup of objectives for less prioritized OARs: other_oars = [ ROIS.cochlea_l, ROIS.cochlea_r, ROIS.hippocampus_l, ROIS.hippocampus_r, ROIS.lens_l, ROIS.lens_r, ROIS.lacrimal_l, ROIS.lacrimal_r, ROIS.retina_l, ROIS.retina_r, ROIS.cornea_r, ROIS.cornea_l, ROIS.pituitary ] tolerances = [ TOL.cochlea_mean_tinnitus, TOL.cochlea_mean_tinnitus, TOL.hippocampus_v40, TOL.hippocampus_v40, TOL.lens_v003_adx, TOL.lens_v003_adx, TOL.lacrimal_mean, TOL.lacrimal_mean, TOL.retina_v003_adx, TOL.retina_v003_adx, TOL.cornea_v003_adx, TOL.cornea_v003_adx, TOL.pituitary_mean ] for i in range(len(other_oars)): if SSF.has_named_roi_with_contours(ss, other_oars[i].name): weight = None # Conflict with dose? if tolerances[i].equivalent(nr_fractions) < total_dose * 0.95: # Conflict with dose: if not SSF.roi_overlap(pm, examination, ss, ROIS.ptv, other_oars[i], 2): if ROIF.roi_vicinity_approximate( SSF.rg(ss, ROIS.ptv.name), SSF.rg(ss, other_oars[i].name), 2): # OAR is close, but not overlapping: weight = 2 else: weight = 20 else: # No conflict with dose: weight = 20 # Create objective if indicated: if weight: if other_oars[i].name in [ ROIS.cochlea_r.name, ROIS.cochlea_l.name, ROIS.lacrimal_l.name, ROIS.lacrimal_r.name, ROIS.hippocampus_l.name, ROIS.hippocampus_r.name ]: OF.max_eud( ss, plan, other_oars[i].name, tolerances[i].equivalent(nr_fractions) * 100 - 50, 1, weight) else: OF.max_dose( ss, plan, other_oars[i].name, (tolerances[i].equivalent(nr_fractions) * 100) - 50, weight) else: GUIF.handle_missing_roi_for_objective(other_oars[i].name)