Beispiel #1
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #10
0
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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #14
0
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
Beispiel #15
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
Beispiel #18
0
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]
Beispiel #19
0
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
Beispiel #20
0
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
Beispiel #22
0
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()
Beispiel #24
0
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()