def get_roi_approval(roi_name, examination=None): """ Checks if ROI named *roi_name* is approved (in any of the existing beamsets). Args: roi_name (str): the name of the ROI examination (RS examination object, optional): the examination (CT or StructureSet) for which the ROI is defined; if left out the currently selected examination is used. Returns: True if the ROI is approved, False if not. """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() try: for beamset_approved_structure_set in patient.PatientModel.StructureSets[ examination.Name].ApprovedStructureSets: for beamset_approved_rois in beamset_approved_structure_set.ApprovedRoiStructures: if beamset_approved_rois.OfRoi.Name == roi_name: logger.info('ROI %s is approved.', roi_name) return True logger.info('ROI %s is NOT approved.', roi_name) return False except Exception as e: logger.exception(e) raise
def auto_assign_roi_types_v2(): """ Attempts to automatically assign correct ROI types. This functions makes several assumptions and ignores case in ROI names. - Any ROI with a name containing *PTV* or *CTV* or *GTV* or *ITV* with the absence of "-" character is considered a *Target*. Thus, ROIs named such as *RIBS-PTV* will not be labeled as *Target*. - Any ROI with a name starting with *BOLUS* will have its type changed as such. - All other ROIs will be set by default to *Organ* type and *OrganAtRisk* organ type. """ patient = lib.get_current_patient() roi_names = [x.Name for x in patient.PatientModel.RegionsOfInterest] for name in roi_names: n = name.replace(' ', '').upper() if 'PTV' in n and '-' not in n: set_roi_type(name, 'Ptv', 'Target') elif 'CTV' in n and '-' not in n: set_roi_type(name, 'Ctv', 'Target') elif 'GTV' in n and '-' not in n: set_roi_type(name, 'Gtv', 'Target') elif 'ITV' in n and '-' not in n: set_roi_type(name, 'TreatedVolume', 'Target') elif n == 'BODY': set_roi_type(name, organ_type='Other') elif n == 'BODY+TABLE': set_roi_type(name, organ_type='Other') elif n.startswith('BOLUS'): set_roi_type(name, 'Bolus', 'Other') else: set_roi_type(name, 'Organ', 'OrganAtRisk')
def set_roi_type(roi_name, roi_type=None, organ_type=None): """ Sets a ROI to a given type of ROI and/or to a given organ type. Args: roi_name (str): the name of the ROI roi_type (str, optional): the new type of ROI. Must be one of the supported ROI types in RayStation: *Ptv*, *Gtv*, *TreatedVolume*, *Organ*, etc. organ_type (str, optional): the organ type of the ROI. Must be one of the supported ROI organ types in RayStation: *OrganAtRisk*, *Target*, *Other*, etc. """ if get_roi_approval(roi_name): logger.warning( 'ROI "%s" is approved and therefore cannot be changed.' % roi_name) return patient = lib.get_current_patient() try: with CompositeAction('Apply ROI changes (' + roi_name + ')'): if roi_type: patient.PatientModel.RegionsOfInterest[ roi_name].Type = roi_type logger.info('Type of ROI "%s" set as %s', roi_name, roi_type) if organ_type: patient.PatientModel.RegionsOfInterest[ roi_name].OrganData.OrganType = organ_type logger.info('OrganType of ROI "%s" set as %s', roi_name, organ_type) except Exception as e: logger.exception(e) raise
def roi_exists(roi_name, examination=None): """ Checks if a ROI exists. Args: roi_name (str): the name of the ROI to check examination (RayStation object, optional): the RayStation object representing the examination Returns: True if it exists, False if not. """ patient = lib.get_current_patient() if examination is None: exam = lib.get_current_examination() else: exam = examination try: roi = patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[roi_name] except: return False if roi.PrimaryShape is None: # is not segmented on current examination. return False return True
def process_ac_qa(): patient = lib.get_current_patient() print '{process_ac_qa}\t\t\tPatient : %s, ID : %s' % (patient.PatientName, patient.PatientID) for tp in patient.TreatmentPlans: if (tp.Name.startswith('D2IS_')): create_ac_qa_plan(tp, phantom_name='QAVMAT ARCCHECK') compute_ac_qa_plan_dose((vp for vp in tp.VerificationPlans if (vp.ForTreatmentPlan.Name == tp.Name)).next()) print 'done ...'
def erase_pois_not_in_list(poi_list=None): patient = lib.get_current_patient() if poi_list is None: poi_list = ['ISO', 'REF SCAN'] for poi in patient.PatientModel.PointsOfInterest: if not poi.Name.upper() in poi_list: poi.DeleteRoi()
def double_optimization(plan=None, beamset=None): # Function which runs two consecutive optimizations. patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() # Beamsets are numbered starting at 1 whereas Plan Optimizations are numbered starting at 0. # Determine what the number of the selected beamset is, then subtract one to locate the associated Plan Optimization. plan.PlanOptimizations[beamset.Number - 1].RunOptimization() plan.PlanOptimizations[beamset.Number - 1].RunOptimization()
def optimization_90_30(plan=None, beamset=None): patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() opt = plan.PlanOptimizations[beamset.Number - 1] set_optimization_parameters(plan=plan,fluence_iterations=30, max_iterations=90, compute_intermediate_dose=False) opt.RunOptimization() set_optimization_parameters(plan=plan,fluence_iterations=30, max_iterations=30, compute_intermediate_dose=False) opt.RunOptimization()
def create_expanded_ptv(ptv_name, color="Yellow", examination=None, margeptv=0, output_name=None, operation='Expand'): """Creates an expansion of the specified PTV with a uniform margin. Note that the maximum margin permitted by RayStation is 5cm, so the operation only continues if the margin <=5cm. Args: ptv_name (str): The name of the PTV to use color (str): The color of the resulting ROI examination (RS Examination object): The CT on which the source and resulting geometries are contoured margeptv (float) : Uniform margin around PTV in cm. Default is 0. Returns: RS region of interest structure """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() if output_name is None: if operation == 'Expand': name = "PTV+%scm" % margeptv else: name = "PTV-%scm" % margeptv else: if operation == 'Expand': name = output_name + "+%scm" % margeptv else: name = output_name + "-%scm" % margeptv if margeptv <= 5: patient.PatientModel.CreateRoi(Name=name, Color=color, Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[name].SetMarginExpression( SourceRoiName=ptv_name, MarginSettings={ 'Type': operation, 'Superior': margeptv, 'Inferior': margeptv, 'Anterior': margeptv, 'Posterior': margeptv, 'Right': margeptv, 'Left': margeptv }) patient.PatientModel.RegionsOfInterest[name].UpdateDerivedGeometry( Examination=examination)
def create_expanded_roi(roi_name, color="Yellow", examination=None, marge_lat=0, marge_sup_inf=0, output_name=None, operation='Expand'): """Creates an expansion of the specified ROI with a variable margin. Note that the maximum margin permitted by RayStation is 5cm, so the operation only continues if the margin <=5cm. Args: roi_name (str): The name of the ROI to use color (str): The color of the resulting ROI examination (RS Examination object): The CT on which the source and resulting geometries are contoured marge_lat (float) : The margin to use in the LAT and A-P directions. Default is 0. marge_sup_inf (float) : The margin to use in the SUP-INF direction. Default is 0. operation(str) : Whether to expand or contract the ROI Returns: RS region of interest structure """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() if output_name is None: if operation == 'Expand': name = "%s+%scm" % (roi_name, marge_lat) else: name = "%s-%scm" % (roi_name, marge_lat) else: name = output_name if marge_lat <= 5 and marge_sup_inf <= 5: patient.PatientModel.CreateRoi(Name=name, Color=color, Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[name].SetMarginExpression( SourceRoiName=roi_name, MarginSettings={ 'Type': operation, 'Superior': marge_sup_inf, 'Inferior': marge_sup_inf, 'Anterior': marge_lat, 'Posterior': marge_lat, 'Right': marge_lat, 'Left': marge_lat }) patient.PatientModel.RegionsOfInterest[name].UpdateDerivedGeometry( Examination=examination)
def display_external_roi(): # WIP, do not use yet patient = lib.get_current_patient() plan = lib.get_current_plan() for roi in patient.PatientModel.StructureSets[exam.Name].RoiGeometries: if roi.OfRoi.Type == "External": external_roi = roi.OfRoi.Name try: ui = get_current("ui") ui.MenuItem[1].Button_PatientModeling.Click() except: pass
def double_opt_save(plan=None, beamset=None): # Function which runs two consecutive optimizations and saves the plan afterwards (NB this removes the ability to undo the optimizations). patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() # Beamset.Number starts at 1 whereas Plan Optimizations are numbered starting at 0. # Determine what the number of the selected beamset is, then subtract one to locate the associated Plan Optimization. plan.PlanOptimizations[beamset.Number - 1].RunOptimization() plan.PlanOptimizations[beamset.Number - 1].RunOptimization() patient.Save()
def create_ring(roi_name, r2, r1=0, new_name=None): """ Creates a ring ROI around another source ROI. The ring is specified by two distance parameters, :math:`r_1` and :math:`r_2`. A ring of uniform thickness is assumed. - By default, :math:`r_1`, the inward distance, is set at 0, which makes the ring ROI inner surface coincide with the source roi surface. - The outward distance, :math:`r_2`, must always be specified. It corresponds to the outward distance from the source ROI surface. If set at zero, the outer surface of the ring ROI will correspond to the source ROI surface. - The sum :math:`r_1 + r_2` therefore gives the ring thickness. Args: r2 (float): the outward distance in cm r1 (float, optional): the inward distance in cm (default 0) new_name (str, optional): the name of the newly created ring ROI. If unspecified, the name will automatically be generated based on *r2* and *r1*. Returns: the ROI RayStation object corresponding to the new ring ROI """ if r1 < 0 or r2 < 0: lib.error('Cannot have a negative distance.') if new_name is None: if r1 == 0: new_name = "%s_ring_%dmm" % (roi_name, r2 * 10) else: new_name = "%s_ring_%d-%dmm" % (roi_name, r1 * 10, r2 * 10) patient = lib.get_current_patient() examination = lib.get_current_examination() source_roi = get_roi(roi_name) # with CompositeAction('ROI Algebra (%s)' % roi_name): # ring_roi = patient.PatientModel.CreateRoi(Name=new_name, Type="Organ", TissueName=None, RoiMaterial=None) # ring_roi.SetRoiMaterial(Material=source_roi.RoiMaterial) # ring_roi.SetAlgebraExpression(ExpressionA=dict(Operation="Union", SourceRoiNames=[roi_name], MarginSettings=get_margin_settings(r2)), # ExpressionB=dict(Operation="Union", SourceRoiNames=[roi_name], MarginSettings=get_margin_settings(r1)), # ResultOperation="Subtraction", # ResultMarginSettings=get_margin_settings(0)) # ring_roi.UpdateDerivedGeometry(Examination=examination) with CompositeAction('Create Wall (%s, Image set: %s)' % (roi_name, examination)): ring_roi = patient.PatientModel.CreateRoi(Name=new_name, Color="White", Type="Organ", TissueName=None, RoiMaterial=None) ring_roi.SetRoiMaterial(Material=source_roi.OfRoi.RoiMaterial) ring_roi.SetWallExpression(SourceRoiName=roi_name, OutwardDistance=r2, InwardDistance=r1) ring_roi.UpdateDerivedGeometry(Examination=examination) return ring_roi
def show_all_poi_coordinates(): """ In a popup window, shows DICOM coordinates of all POIs defined to the current patient model. """ import gui patient = lib.get_current_patient() poi_names = [r.Name for r in patient.PatientModel.PointsOfInterest] msg = '' for poi_name in poi_names: coords = get_poi_coordinates(poi_name) #msg += 'Les coordonnées DICOM du point "%s" sont : [x = %.2f cm, y = %.2f cm, z = %.2f cm]\n' % (poi_name, coords.x, coords.y, coords.z) msg += '%s: [x = %.2f cm, y = %.2f cm, z = %.2f cm]\n' % ( poi_name, coords.x, coords.y, coords.z) #gui.show_log_window(msg) return msg
def fit_objectives_orl(plan=None, beamset=None): # Function which adjusts DVH and EUD values in objectives to reflect obtained values. patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() for objective in plan.PlanOptimizations[beamset.Number - 1].Objective.ConstituentFunctions: try: f_type = objective.DoseFunctionParameters.FunctionType # Dose falloff objectives do not have a FunctionType and must be skipped except: continue roi_name = objective.ForRegionOfInterest.Name if roi_name in ['BLOC MOELLE','MOELLE','prv5mmMOELLE']: continue else: if f_type == "MaxDvh": dose_level = objective.DoseFunctionParameters.DoseLevel obtained_vol = plan.TreatmentCourse.TotalDose.GetRelativeVolumeAtDoseValues(RoiName=roi_name, DoseValues=[dose_level]) if obtained_vol[0] >= 0.02: objective.DoseFunctionParameters.PercentVolume = int(obtained_vol[0] * 90) if obtained_vol[0] < 0.02: objective.DoseFunctionParameters.DoseLevel = int(dose_level*0.9) elif f_type == "MaxEud": dose_level = objective.DoseFunctionParameters.DoseLevel a = objective.DoseFunctionParameters.EudParameterA obtained_eud = compute_eud(plan.TreatmentCourse.TotalDose, roi_name, a) if obtained_eud > 100: objective.DoseFunctionParameters.DoseLevel = round(obtained_eud*0.99,-1) elif f_type == "MaxDose": dose_level = objective.DoseFunctionParameters.DoseLevel vol_roi=patient.PatientModel.StructureSets['CT 1'].RoiGeometries[roi_name].GetRoiVolume() if vol_roi >= 0.1: vol_cc=0.1/vol_roi max_dose_obtenue = plan.TreatmentCourse.TotalDose.GetDoseAtRelativeVolumes(RoiName=roi_name, RelativeVolumes=[vol_cc]) else: max_dose_obtenue = plan.TreatmentCourse.TotalDose.GetDoseAtRelativeVolumes(RoiName=roi_name, RelativeVolumes=[vol_roi]) if max_dose_obtenue[0] < (dose_level/1.05) and max_dose_obtenue[0] > 500: objective.DoseFunctionParameters.DoseLevel = int(max_dose_obtenue[0]*1.05) elif max_dose_obtenue[0] < (dose_level/1.05) and max_dose_obtenue[0] <= 500: objective.DoseFunctionParameters.DoseLevel = 500
def auto_assign_poi_types(): """ Attempts to automatically assign correct POI types. Assumes localization point has string *SCAN* as part of its name. If no POI with *SCAN* in its name ca be found, the isocenter will be assumed to be the proper localization point. Isocenter is set to the type *isocenter*, save for the case above. All other POIs are set to type *marker*. """ patient = lib.get_current_patient() poi_names = [p.Name for p in patient.PatientModel.PointsOfInterest] iso_name = identify_isocenter_poi() set_poi_type(iso_name, 'Isocenter') loc_name = [ p.Name for p in patient.PatientModel.PointsOfInterest if 'SCAN' in p.Name.upper() ] if len(loc_name) > 1: logger.error( 'More than one POI found for possible localization points. Only one POI should exist with "SCAN" in its name.' ) raise SystemError( 'More than one POI found for possible localization points. Only one POI should exist with "SCAN" in its name.' ) elif len(loc_name) == 0: logger.warning( 'No localization point could be found. Using the isocenter point for localization.' ) loc_name = iso_name set_poi_type(loc_name, 'LocalizationPoint') else: loc_name = loc_name[0] set_poi_type(loc_name, 'LocalizationPoint') done_pois = [iso_name, loc_name] for p in poi_names: if p not in done_pois: set_poi_type(p, 'Marker')
def identify_body(): """ Attempts to identify which is the body ROI that should be set as external in RayStation. However, it does not set this ROI as external. Returns: str: the name of the ROI identified as body. Returns `None` if it could not be identified. """ patient = lib.get_current_patient() roi_names = [x.Name for x in patient.PatientModel.RegionsOfInterest] for r in roi_names: if r.upper().replace(' ', '') == 'BODY+TABLE': return r if r.upper().replace(' ', '') == 'BODY': return r logger.warning( 'No ROI named in a standard way for the body could be found.') return None
def delete_roi(roi_name): """ Deletes an ROI based on its name. Args: roi_name (str): the name of the ROI to delete. """ patient = lib.get_current_patient() try: for r in patient.PatientModel.RegionsOfInterest: if r.Name == roi_name: r.DeleteRoi() logger.info('Deleted ROI %s.', roi_name) break logger.warning('Could not delete ROI %s (not found).', roi_name) except Exception as e: logger.exception(e) raise e
def set_poi_type(poi_name, poi_type='Marker'): """ Sets a POI to a given POI type. Args: poi_name (str): the name of the POI for which to set the type poi_type (str, optional): the type of the POI. By default, will set to type *Marker*. """ if get_poi_approval(poi_name): logger.warning( 'POI "%s" is approved and therefore cannot be changed.' % poi_name) return patient = lib.get_current_patient() try: patient.PatientModel.PointsOfInterest[poi_name].Type = poi_type logger.info('Type of POI "%s" set as %s', poi_name, poi_type) except Exception as e: logger.exception(e) raise
def generate_BodyRS_using_threshold(): patient = lib.get_current_patient() examination = lib.get_current_examination() structure_set = 0 if not roi_exists("BodyRS"): patient.PatientModel.CreateRoi(Name="BodyRS", Color="Green", Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest['BodyRS'].GrayLevelThreshold( Examination=patient.Examinations['CT 1'], LowThreshold=-650, HighThreshold=5000, PetUnit="", BoundingBox=None) patient.PatientModel.RegionsOfInterest['BodyRS'].Type = "External" patient.PatientModel.RegionsOfInterest['BodyRS'].SetAsExternal() patient.PatientModel.StructureSets[structure_set].SimplifyContours( RoiNames=["BodyRS"], RemoveHoles3D=True)
def identify_ptvs2(): """ Attempts to identify PTVs. Ignores case. Expects 'PTV' at the beginning of the ROI name, then characters which can be represented as a number (the prescription dose). """ patient = lib.get_current_patient() roi_names = [x.Name for x in patient.PatientModel.RegionsOfInterest] candidates = [ x for x in roi_names if (x.replace(' ', '').upper().startswith('PTV') and lib.is_number(x.replace(' ', '').upper()[3:])) ] if len(candidates) == 0: logger.warning('No ROIs could be identified as a PTV.') return None logger.info('Possible PTVs identified as %s', candidates) return candidates
def create_poi(point, name, color='Green', poi_type='Marker', examination=None): """ Creates a new POI. Args: point (dict or RSPoint): the (x, y, z) DICOM coordinates of the new point name (str): the name of the new POI color (str, optional): the color of the new POI (default: *Green*) poi_type (str, optional): the type of the new POI (default: *Marker*) Returns: the RayStation POI object corresponding to the newly created POI """ # Must used dictionary representation of the point if an RSPoint if isinstance(point, lib.RSPoint): point = point.value patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() try: poi = patient.PatientModel.CreatePoi(Examination=examination, Point=point, Volume=0, Name=name, Color=color, Type=poi_type) return poi except SystemError as e: if str(e) == 'Name not unique': logger.warning('A POI named %s already exists.' % name) except Exception as e: logger.exception(e) raise
def create_expansion(roi_name, sup, inf=None, ant=None, post=None, left=None, right=None, new_name=None): """ Create a new ROI which is the expansion of another existing ROI. Args: roi_name (str): the source ROI name sup (float): the margin in cm in the superior direction inf (float, optional): the margin in cm in the inferior direction, equal to sup if unspecified ant (float, optional): the margin in cm in the anterior direction, equal to sup if unspecified post (float, optional): the margin in cm in the posterior direction, equal to sup if unspecified left (float, optional): the margin in cm in the patient left direction, equal to sup if unspecified right (float, optional): the margin in cm in the patient right direction, equal to sup if unspecified new_name (str, optional): the name of the newly created expanded ROI. If unspecified, will default to the *<roi_name>+<sup>mm*. Returns: the ROI RayStation object corresponding to the expanded volume """ if new_name is None: new_name = "%s+%dmm" % (roi_name, sup * 10) margins = get_margin_settings(sup, inf, ant, post, left, right) patient = lib.get_current_patient() examination = lib.get_current_examination() source_roi = get_roi(roi_name) with CompositeAction('Expand'): expanded_roi = patient.PatientModel.CreateRoi(Name=new_name, Type="Organ", TissueName=None, RoiMaterial=None) expanded_roi.SetRoiMaterial(Material=source_roi.OfRoi.RoiMaterial) expanded_roi.SetMarginExpression(SourceRoiName=roi_name, MarginSettings=margins) expanded_roi.UpdateDerivedGeometry(Examination=examination) return expanded_roi
def identify_isocenter_poi(): """ Attempts to identify isocenter POI. The expected name of the isocenter POI is either *ISO* or *ISO SCAN*. If either of the two is found, it is returned, with priority to *ISO*. If not, the shortest string in the name candidates is returned. Returns: str: Name of most probable isocenter POI if found, `None` if not. """ patient = lib.get_current_patient() poi_names = [r.Name for r in patient.PatientModel.PointsOfInterest] iso_candidates = [x for x in poi_names if x.upper().startswith('ISO')] # No candidates if len(iso_candidates) == 0: logger.warning('No isocenter POI could be found.') return None # Multiple candidates if len(iso_candidates) > 1: for i in sorted(iso_candidates, key=lambda x: len(x)): if i.upper() == 'ISO': logger.info('Isocenter POI identified as "%s"', i) return i if i.upper() == 'ISO SCAN': logger.info('Isocenter POI identified as "%s"', i) return i # If all else fails, return shortest string. guess = sorted(iso_candidates, key=lambda x: len(x))[0] logger.warning('Best isocenter POI candidate is "%s"', guess) return guess # Case where there is only 1 candidates logger.info('Isocenter POI identified as "%s"', iso_candidates[0]) return iso_candidates[0]
def get_roi(roi_name, examination=None): """ Returns ROI object from an ROI name for the current structure set. Args: roi_name (str): the name of the ROI to be returned examination (RayStation object, optional): the RayStation object representing the examination Returns: The ROI RayStation object. """ patient = lib.get_current_patient() if examination is None: exam = lib.get_current_examination() else: exam = examination try: roi = patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[roi_name] if roi.PrimaryShape is None: lib.error( 'ROI %s is not segmented on currently selected examination. Select correct CT and try again.' % roi_name) except Exception as e: logger.exception(e) raise # Could have returned roi.OfRoi, but returning roi lets once access the # DicomImportHistory, PrimaryShape and GetCenterOfRoi() and GetRoiVolume() # functions. # # Under roi.OfRoi, we have among others Name, Type, Color, RoiMaterial and # several functions. Launch StateTree to see. return roi
def get_poi_approval(poi_name, examination=None): """ Checks if POI named *poi_name* is approved (in any of the existing beamsets). TODO: handle the case where no beamsets or no plans exists, implying that the structures cannot be approved ? Args: poi_name (str): the name of the POI examination (str, optional): the name of the examination (CT or StructureSet) for which the poi is defined; if left out the currently selected examination is used. Returns: True if the POI is approved, False if not. """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() # print 'before' try: for beamset_approved_structure_set in patient.PatientModel.StructureSets[ examination.Name].ApprovedStructureSets: for beamset_approved_pois in beamset_approved_structure_set.ApprovedPoiStructures: if beamset_approved_pois.OfPoi.Name == poi_name: # print 'after True' logger.info('POI %s is approved.', poi_name) return True # print 'after False' logger.info('POI %s is NOT approved.', poi_name) return False except Exception as e: logger.exception(e) raise
def get_poi(poi_name, examination=None): """ Returns POI object from a POI name. Args: poi_name (str): the name of the POI examination (str, optional): the name of the examination (CT or StructureSet) for which the poi is defined; if left out the currently selected examination is used. Returns: the RayStation POI object """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() try: poi = [ x for x in patient.PatientModel.StructureSets[ examination.Name].PoiGeometries if x.OfPoi.Name == poi_name ][0] if abs(poi.Point.x) == sys.float_info.max or abs( poi.Point.x) == sys.float_info.min: lib.error( 'POI %s is not segmented on current examination. Select correct CT and try again.' % poi_name) return poi except IndexError as e: logger.error('No POI found named %s', poi_name) logger.exception(e) raise except Exception as e: logger.exception(e) raise
def check_eccentricity(poi_name): #Script to check if a current point can be used as an isocenter without a collision occurring between the gantry and the patient. #Simply creates a large cylinder centered on the point and sets the window/level to Lung so the planner can confirm visually that the clearance is adequate. #Currently assumes a safe radius of 40cm for all types of linac, this number may change if further measurements are made. patient = lib.get_current_patient() exam = lib.get_current_examination() lung_dict = dict(x=-600, y=1600) exam.Series[0].LevelWindow = lung_dict if roi.roi_exists("verif_excentricite", exam): patient.PatientModel.RegionsOfInterest["verif_excentricite"].DeleteRoi( ) center = get_poi_coordinates(poi_name, exam) patient.PatientModel.CreateRoi(Name="verif_ex_temp1", Color="Green", Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[ "verif_ex_temp1"].CreateCylinderGeometry(Radius=30, Axis={ 'x': 0, 'y': 0, 'z': 1 }, Length=50, Examination=exam, Center={ 'x': center.x, 'y': center.y, 'z': center.z }) patient.PatientModel.CreateRoi(Name="verif_ex_temp2", Color="Pink", Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[ "verif_ex_temp2"].SetMarginExpression(SourceRoiName="verif_ex_temp1", MarginSettings={ 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 5, 'Posterior': 5, 'Right': 5, 'Left': 5 }) patient.PatientModel.RegionsOfInterest[ "verif_ex_temp2"].UpdateDerivedGeometry(Examination=exam) patient.PatientModel.CreateRoi(Name="verif_excentricite", Color="Red", Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[ "verif_excentricite"].SetMarginExpression( SourceRoiName="verif_ex_temp2", MarginSettings={ 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 5, 'Posterior': 5, 'Right': 5, 'Left': 5 }) patient.PatientModel.RegionsOfInterest[ "verif_excentricite"].UpdateDerivedGeometry(Examination=exam) patient.PatientModel.RegionsOfInterest["verif_ex_temp1"].DeleteRoi() patient.PatientModel.RegionsOfInterest["verif_ex_temp2"].DeleteRoi()
def place_prescription_point(target_fraction_dose, ptv_name, poi_name, beamset=None, exam=None): """ Attempts to place automatically a POI on a target per-fraction isodose line. .. rubric:: PRE-REQUISITES - Existence of an identifiable PTV contour. - Dose is calculated. .. rubric:: Algorithm description - A point on the PTV contour on a slice near the PTV center is found. - The dose is evaluated at that point. - An approximate unit vector towards the PTV center is calculated from that position. + If the evaluated dose is smaller than the prescription dose, the evaluation point is moved a short distance towards the PTV center. + If the evaluated dose is greater than the prescription dose, the evaluation point is moved a short distance away from the PTV center. - If the prescription dose is overshot by this procedure, the direction of movement is reversed and the move distance halved. - This process is repeated until evaluated dose equals the prescription dose to 3 decimal figures or 100 iterations are reached. Finally, - The specified POI is placed at the found coordinates. .. seealso:: function `hmrlib.hmrlib.auto_place_prescription_point` """ patient = lib.get_current_patient() if exam is None: exam = lib.get_current_examination() if beamset is None: beamset = lib.get_current_beamset() try: # Get PTV center ptv_center = lib.RSPoint(point=patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].GetCenterOfRoi()) # Get slice thickness from CT slice_width = abs(exam.Series[0].ImageStack.SlicePositions[1] - exam.Series[0].ImageStack.SlicePositions[0]) logger.info('CT slice width = %s cm', slice_width) initial_point = None # Find contour point on a slice close to PTV center for c in patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].PrimaryShape.Contours: #if lib.check_version(4.7): if c[0].z < ptv_center.z + slice_width and c[ 0].z > ptv_center.z - slice_width: initial_point = lib.RSPoint(c[0].x, c[0].y, c[0].z) break #else: # if c.ContourData[0].z < ptv_center.z + slice_width and c.ContourData[0].z > ptv_center.z - slice_width: # initial_point = lib.RSPoint(c.ContourData[0].x, c.ContourData[0].y, c.ContourData[0].z) # break if initial_point is None: logger.info( 'Could not find a point on the same slice as the ROi center. Disjoint/noncoplanar PTV?' ) logger.info('Trying with first point in contour shape.') c = patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].PrimaryShape.Contours[0] #if lib.check_version(4.7): initial_point = lib.RSPoint(c[0].x, c[0].y, c[0].z) #else: # initial_point = lib.RSPoint(c.ContourData[0].x, c.ContourData[0].y, c.ContourData[0].z) logger.info('Initial point = %s cm', initial_point) u = lib.RSPoint(point=lib.unit_vector(ptv_center - initial_point)) logger.info('Unit vector towards PTV center = %s cm', u) # Change unit vector so that we stay on the same transverse slice u.z = 0 logger.info( 'Approximate unit vector towards PTV center on single CT slice = %s cm', u) def move_point(point, direction, target_fraction_dose, initial_dose, step=0.05, iteration=0): point = point + step * direction dose = beamset.FractionDose.InterpolateDoseInPoint( Point=point.value) logger.info('Dose at %s = %.3f cGy', point, dose) if round(dose, 3) == round(target_fraction_dose, 3) or iteration > 100: # Found a suitable point or reached max iterations return point elif (initial_dose < target_fraction_dose and dose > target_fraction_dose) or (initial_dose > target_fraction_dose and dose < target_fraction_dose): # We overshot the point, so inverse direction and reduce step return move_point(point, -direction, target_fraction_dose, dose, step=0.5 * step, iteration=iteration + 1) else: # Keep going in the same direction with same step return move_point(point, direction, target_fraction_dose, dose, step=step, iteration=iteration + 1) dose = beamset.FractionDose.InterpolateDoseInPoint( Point=initial_point.value) if math.isnan(dose): lib.error('No dose value available. Check if dose is calculated.') logger.info('Dose per fraction at initial point = %.3f cGy', dose) # Assume dose rises when moving towards center of PTV if dose < target_fraction_dose: point = move_point(initial_point, u, target_fraction_dose, dose) else: point = move_point(initial_point, -u, target_fraction_dose, dose) logger.info('Final point = %s cm', point) except Exception as e: logger.exception(e) raise if get_poi_approval(poi_name): logger.warning('POI %s is approved; cannot continue.', poi_name) lib.error( 'POI %s exists and is approved; cannot continue. Unapprove %s before running script.' % (poi_name, poi_name)) set_poi_coordinates(poi_name, point, examination=exam) return point
def subtract_rois(big_roi_name, small_roi_name, color="Yellow", examination=None, output_name=None): """Creates ROI that is the subtraction of the two ROIs (useful for creating rings from PTV expansions). Args: big_roi_name (str): The name of the larger ROI small_roi_name (str): The name of the smaller ROI color (str): The color of the resulting ROI examination (RS Examination object): The CT on which the source and resulting geometries are contoured output_name (str): The name that the new contour will have Returns: RS region of interest structure """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() if output_name is None: newname = "%s-%s" % (big_roi_name, small_roi_name) else: newname = output_name patient.PatientModel.CreateRoi(Name=newname, Color=color, Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[newname].SetAlgebraExpression( ExpressionA={ 'Operation': "Union", 'SourceRoiNames': [big_roi_name], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ExpressionB={ 'Operation': "Union", 'SourceRoiNames': [small_roi_name], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ResultOperation="Subtraction", ResultMarginSettings={ 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 }) patient.PatientModel.RegionsOfInterest[newname].UpdateDerivedGeometry( Examination=examination) return patient.PatientModel.RegionsOfInterest[newname]