def get_roi_volume(roi_name, exam=None): """ Returns the ROI volume in cc. If the ROI has no contours on the selected examination, returns 0. Gets the ROI from the currently selected examination. Args: roi_name (str): the name of the ROI exam (RS Examination object): The CT on which the ROI is contoured Returns: the volume in cc """ if exam is None: exam = lib.get_current_examination() patient = lib.get_current_patient() # volume = patient.PatientModel.StructureSets[exam.Name].RoiGeometries[roi_name].GetRoiVolume() try: volume = patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[roi_name].GetRoiVolume() except: volume = 0 return volume
def poi_exists(poi_name, examination=None): """ Checks if a POI exists. 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 it exists, False if not. """ 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] except: return False if abs(poi.Point.x) == sys.float_info.max or abs( poi.Point.x) == sys.float_info.min: # Not segmented return False return True
def find_most_intersecting_roi(target_roi_name, roi_name_list, examination=None): """ Find, out if a list of ROIs, which one intersects the most, in terms of volume, with a given ROI. Args: target_roi_name (str): the name of the ROI to test intersection against roi_name_list (list of str): the list of names of ROIs out of which to find the most intersecting one examination: RayStation examination object (used to determine on which CT the volumes are evaluated) Returns: str: the name of the ROI that shares the most volume with *target_roi_name* """ if examination is None: examination = lib.get_current_examination() vol = -1 return_roi = '' for roi_name in roi_name_list: test_vol = get_intersecting_volume(roi_name, target_roi_name, examination) if test_vol > vol: vol = test_vol return_roi = roi_name return return_roi
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 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 set_poi_coordinates(poi_name, point, examination=None): """ Sets POI DICOM coordinates for POI poi_name to those specified in point. Args: point (RSPoint or RayStation ExpandoObject): the new DICOM coordinates of the point """ if examination is None: examination = lib.get_current_examination() poi = get_poi(poi_name, examination=examination) # Need to check if POI is approved. If it it, setting the coordinates # will crash RayStation. if get_poi_approval(poi_name): logger.warning( 'POI "%s" is approved and therefore cannot be changed.' % poi_name) return try: # ==================================================================== # For some reason this doesn't update point coords !! # but also doesn't produce an error. # poi.Point.x = point.x # poi.Point.y = point.y # poi.Point.z = point.z # ==================================================================== # ... but this does update the coordinates, also silently. poi.Point = {'x': point.x, 'y': point.y, 'z': point.z} logger.info('Set coordinates of POI "%s" to %s.', poi_name, point) except Exception as e: logger.exception(e) raise
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 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_iso(exam=None): """ Checks to see if a point named ISO exists. If not, the script creates a copy of REF SCAN to serve as the isocenter. Args: exam : RayStation examination object """ if exam is None: exam = lib.get_current_examination() if not poi_exists("ISO", exam): if poi_exists("REF SCAN", exam): REF_SCAN_coords = get_poi_coordinates('REF SCAN', exam) create_poi(REF_SCAN_coords, 'ISO', 'Blue', 'Isocenter', exam)
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 convert_absolute_volume_to_percentage(roi_name, volume_cc=0.1, examination=None): """ Converts an absolute volume to a relative value in percentage for ROI with name roi_name. Assumes the volume is calculated on the current examination. Args: roi_name (str): the name of the ROI for which to convert the volume value volume_cc (float, optional): the volume value in cc to convert to relative volume (default 0.1 cc) Returns: float: the volume value relative to the whole ROI volume in %, rounded to two decimal places """ if examination is None: examination = lib.get_current_examination() volume = get_roi_volume(roi_name, exam=examination) cc_percentage = round(100.0 * volume_cc / volume, 2) return cc_percentage
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 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 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 subtract_roi_ptv(roi_name, ptv_name, color="Yellow", examination=None, margeptv=0, output_name="PTV"): """Creates ROI that is the subtraction of the given PTV from the specified ROI. Args: roi_name (str): The name of the ROI to use 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) : Optional uniform margin around PTV in cm. Default is 0 cm. Returns: RS region of interest structure """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() if margeptv == 0: newname = "%s hors %s" % (roi_name, output_name) else: newname = "%s hors %s+%scm" % (roi_name, output_name, margeptv) patient.PatientModel.CreateRoi(Name=newname, Color=color, Type="Organ", TissueName=None, RoiMaterial=None) patient.PatientModel.RegionsOfInterest[newname].SetAlgebraExpression( ExpressionA={ 'Operation': "Union", 'SourceRoiNames': [roi_name], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ExpressionB={ 'Operation': "Union", 'SourceRoiNames': [ptv_name], 'MarginSettings': { 'Type': "Expand", 'Superior': margeptv, 'Inferior': margeptv, 'Anterior': margeptv, 'Posterior': margeptv, 'Right': margeptv, 'Left': margeptv } }, 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]
def get_intersecting_volume(roi_name_1, roi_name_2, examination=None): """ Returns the volume of the intersection of two volumes in cc. Args: roi_name_1 (str): the name of the first ROI roi_name_2 (str): the name of the second ROI Returns: float: The volume in cc of the intersection of the two volumes. If the volumes are disjoint, returns 0. """ patient = lib.get_current_patient() if examination is None: examination = lib.get_current_examination() temp_roi_name = "temp_roi_" + roi_name_1 + "_" + roi_name_2 with CompositeAction('ROI Algebra (%s, Image set: %s)' % (temp_roi_name, examination.Name)): retval_0 = patient.PatientModel.CreateRoi(Name=temp_roi_name, Color="Orange", Type="Organ", TissueName=None, RoiMaterial=None) retval_0.CreateAlgebraGeometry( Examination=patient.Examinations[examination.Name], ExpressionA={ 'Operation': "Union", 'SourceRoiNames': [roi_name_1], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ExpressionB={ 'Operation': "Union", 'SourceRoiNames': [roi_name_2], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ResultOperation="Intersection", ResultMarginSettings={ 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 }) try: vol = patient.PatientModel.StructureSets[ examination.Name].RoiGeometries[temp_roi_name].GetRoiVolume() retval_0.DeleteRoi() except SystemError as e: if str(e).startswith( 'There is no geometry set for ROI'): # disjoint volumes retval_0.DeleteRoi() return 0.0 else: raise except Exception as e: print e raise return vol
def generate_BodyRS_plus_Table(threshold=-750, struct=0, planche_seulement=False): """ This script creates a new external contour named BodyRS, combines it with the table to create BodyRS+Table and then simplifies the resulting contour to remove any holes that might prevent optimization from working. This new contours is designated as the external for the patient. The script also renames the Pinnacle BODY contour "BODY Pinnacle" and erases any Body+Table contours imported from Pinnacle. Note that if a BodyRS+Table contour already exists, another one will not be created and no change will be made to the existing one. The script as currently designed will only work if there is ONLY ONE SCAN for the patient. ARGS: threshold (int) : The lower CT number limit to use when generating the external contour. Default is -750. struct (int) : The index of the structure set to use (0 for cases with only one scan, 1 for lung cases) """ patient = lib.get_current_patient() examination = lib.get_current_examination() # Rename BODY contour to BODY Pinnacle if roi_exists("BODY"): patient.PatientModel.RegionsOfInterest["BODY"].Name = "BODY Pinnacle" # Delete BODY+Table contour if roi_exists("Body+Table"): patient.PatientModel.RegionsOfInterest['Body+Table'].DeleteRoi() if roi_exists("BODY+Table"): patient.PatientModel.RegionsOfInterest['BODY+Table'].DeleteRoi() # Create new Body contour named BodyRS (in case BODY contains holes which prevent plan optimization) if not roi_exists("BodyRS"): retval_0 = patient.PatientModel.CreateRoi(Name="BodyRS", Color="Green", Type="Organ", TissueName="", RoiMaterial=None) retval_0.CreateExternalGeometry(Examination=examination, ThresholdLevel=threshold) if planche_seulement is False: # Create BodyRS+Table (unless it exists, in which case it is assumed to be already set as the external contour) if not roi_exists("BodyRS+Table"): retval_0 = patient.PatientModel.CreateRoi(Name="BodyRS+Table", Color="Green", Type="Organ", TissueName=None, RoiMaterial=None) retval_0.SetAlgebraExpression(ExpressionA={ 'Operation': "Union", 'SourceRoiNames': ["Table"], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ExpressionB={ 'Operation': "Union", 'SourceRoiNames': ["BodyRS"], 'MarginSettings': { 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 } }, ResultOperation="Union", ResultMarginSettings={ 'Type': "Expand", 'Superior': 0, 'Inferior': 0, 'Anterior': 0, 'Posterior': 0, 'Right': 0, 'Left': 0 }) retval_0.UpdateDerivedGeometry(Examination=examination) # Set BodyRS+Table as external contour patient.PatientModel.RegionsOfInterest[ 'BodyRS+Table'].Type = "External" retval_0.SetAsExternal() # Remove holes from external contour prior to optimization (not possible in RayStation 4.0) #if lib.check_version(4.7): if planche_seulement is False: patient.PatientModel.StructureSets[struct].SimplifyContours( RoiNames=["BodyRS+Table"], RemoveHoles3D=True) else: patient.PatientModel.StructureSets[struct].SimplifyContours( RoiNames=["BodyRS"], RemoveHoles3D=True)
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]
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 essai_autre_technique(): #pour faire un plan qui essai une autre technique (si VMAT essai IMRT et si IMRT essai VMAT) #mais en gardant les meme objectifs d'optimisations et clinical goals patient = lib.get_current_patient() exam = lib.get_current_examination() plan = lib.get_current_plan() beamset = lib.get_current_beamset() #va chercher l'info du plan original plan_name = plan.Name nb_fx = beamset.FractionationPattern.NumberOfFractions ptv = beamset.Prescription.DosePrescriptions[0].OnStructure.Name rx_dose = beamset.Prescription.DosePrescriptions[0].DoseValue actual_technique = beamset.DeliveryTechnique patient_position = beamset.PatientPosition planner_name = plan.PlannedBy #crée un plan VMAT si le plan est IMRT if actual_technique == 'SMLC': new_plan = patient.AddNewPlan(PlanName= plan_name+' VMAT', PlannedBy=planner_name, Comment="", ExaminationName=exam.Name, AllowDuplicateNames=False) new_plan.SetDefaultDoseGrid(VoxelSize={'x': 0.2, 'y': 0.2, 'z': 0.2}) new_beamset = new_plan.AddNewBeamSet(Name='VMAT', ExaminationName=exam.Name, MachineName='BeamMod', NominalEnergy=None,Modality="Photons", TreatmentTechnique='VMAT', PatientPosition=patient_position, NumberOfFractions=nb_fx, CreateSetupBeams=False, Comment='VMAT') new_beamset.AddDosePrescriptionToRoi(RoiName=ptv, DoseVolume=99, PrescriptionType="DoseAtVolume", DoseValue=rx_dose, RelativePrescriptionLevel=1) new_beams = beams.add_beams_brain_stereo(beamset=new_beamset) set_optimization_parameters(plan=new_plan) set_vmat_conversion_parameters(max_arc_delivery_time=350.0, plan=new_plan) #crée un plan IMRT si le plan est VMAT else: new_plan = patient.AddNewPlan(PlanName=plan_name+' IMRT', PlannedBy=planner_name, Comment="", ExaminationName=exam.Name, AllowDuplicateNames=False) new_plan.SetDefaultDoseGrid(VoxelSize={'x': 0.2, 'y': 0.2, 'z': 0.2}) new_beamset = new_plan.AddNewBeamSet(Name='IMRT', ExaminationName=exam.Name, MachineName='BeamMod', NominalEnergy=None,Modality="Photons", TreatmentTechnique='SMLC', PatientPosition=patient_position, NumberOfFractions=nb_fx, CreateSetupBeams=False, Comment='VMAT') new_beamset.AddDosePrescriptionToRoi(RoiName=ptv, DoseVolume=99, PrescriptionType="DoseAtVolume", DoseValue=rx_dose, RelativePrescriptionLevel=1) new_beams = beams.add_beams_brain_static(beamset=new_beamset) new_plan.PlanOptimizations[0].OptimizationParameters.Algorithm.OptimalityTolerance = 1E-10 new_plan.PlanOptimizations[0].OptimizationParameters.Algorithm.MaxNumberOfIterations = 100 new_plan.PlanOptimizations[0].OptimizationParameters.DoseCalculation.IterationsInPreparationsPhase = 60 new_plan.PlanOptimizations[0].OptimizationParameters.DoseCalculation.ComputeFinalDose = True new_plan.PlanOptimizations[0].OptimizationParameters.SegmentConversion.MinSegmentMUPerFraction = 20 new_plan.PlanOptimizations[0].OptimizationParameters.SegmentConversion.MinLeafEndSeparation = 1 new_plan.PlanOptimizations[0].OptimizationParameters.SegmentConversion.MinNumberOfOpenLeafPairs = 3 new_plan.PlanOptimizations[0].OptimizationParameters.SegmentConversion.MinSegmentArea = 2 new_plan.PlanOptimizations[0].OptimizationParameters.SegmentConversion.MaxNumberOfSegments = 40 #lit les objectifs d'optimisation du plan original et en crée des pareils dans le nouveau plan for objective in plan.PlanOptimizations[beamset.Number-1].Objective.ConstituentFunctions: nom_roi = objective.ForRegionOfInterest.Name nweight = objective.DoseFunctionParameters.Weight if hasattr(objective.DoseFunctionParameters, 'FunctionType'): #les critères dose fall off n'ont pas de FunctionType, les autres en ont tous un type = objective.DoseFunctionParameters.FunctionType if type in ('MinDose','MaxDose','MinDvh','MaxDvh','UniformDose') : pourcent = objective.DoseFunctionParameters.PercentVolume dose_level = objective.DoseFunctionParameters.DoseLevel new_objective = new_plan.PlanOptimizations[new_beamset.Number-1].AddOptimizationFunction(FunctionType=type, RoiName=nom_roi, IsConstraint=False,RestrictAllBeamsIndividually=False, RestrictToBeam=None, IsRobust=False, RestrictToBeamSet=None) new_objective.DoseFunctionParameters.DoseLevel = dose_level new_objective.DoseFunctionParameters.Weight = nweight new_objective.DoseFunctionParameters.PercentVolume = pourcent if type in ('MinEud','MaxEud'): dose_level = objective.DoseFunctionParameters.DoseLevel parametre_a = objective.DoseFunctionParameters.EudParameterA new_objective = new_plan.PlanOptimizations[new_beamset.Number-1].AddOptimizationFunction(FunctionType=type, RoiName=nom_roi, IsConstraint=False,RestrictAllBeamsIndividually=False, RestrictToBeam=None, IsRobust=False, RestrictToBeamSet=None) new_objective.DoseFunctionParameters.DoseLevel = dose_level new_objective.DoseFunctionParameters.Weight = nweight new_objective.DoseFunctionParameters.EudParameterA = parametre_a else: # si pas de FunctionType on suppose que c'est un dose fall off high_dose_level = objective.DoseFunctionParameters.HighDoseLevel low_dose_level = objective.DoseFunctionParameters.LowDoseLevel low_dose_distance = objective.DoseFunctionParameters.LowDoseDistance new_objective = new_plan.PlanOptimizations[new_beamset.Number-1].AddOptimizationFunction(FunctionType='DoseFallOff', RoiName=nom_roi, IsConstraint=False,RestrictAllBeamsIndividually=False, RestrictToBeam=None, IsRobust=False, RestrictToBeamSet=None) new_objective.DoseFunctionParameters.HighDoseLevel = high_dose_level new_objective.DoseFunctionParameters.LowDoseLevel = low_dose_level new_objective.DoseFunctionParameters.LowDoseDistance = low_dose_distance new_objective.DoseFunctionParameters.Weight = nweight for goal in plan.TreatmentCourse.EvaluationSetup.EvaluationFunctions: nom_roi = goal.ForRegionOfInterest.Name goal_criteria = goal.PlanningGoal.GoalCriteria goal_type = goal.PlanningGoal.Type acceptance_level = goal.PlanningGoal.AcceptanceLevel parameter_value = goal.PlanningGoal.ParameterValue new_goal = new_plan.TreatmentCourse.EvaluationSetup.AddClinicalGoal(RoiName=nom_roi, GoalCriteria=goal_criteria, GoalType=goal_type, AcceptanceLevel=acceptance_level, ParameterValue=parameter_value, IsComparativeGoal=False) # Optimize plan twice new_plan.PlanOptimizations[new_beamset.Number-1].RunOptimization() new_plan.PlanOptimizations[new_beamset.Number-1].RunOptimization()