コード例 #1
0
def fit_objectives(plan=None, beamset=None):
    # Function which adjusts DVH and EUD values in objectives to reflect obtained values.
    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

        if f_type == "MaxDvh":
            roi_name = objective.ForRegionOfInterest.Name
            dose_level = objective.DoseFunctionParameters.DoseLevel
            target_vol = objective.DoseFunctionParameters.PercentVolume
            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] * 100 - 2)

        elif f_type == "MaxEud":
            roi_name = objective.ForRegionOfInterest.Name
            dose_level = objective.DoseFunctionParameters.DoseLevel
            a = objective.DoseFunctionParameters.EudParameterA
            obtained_eud = compute_eud(plan.TreatmentCourse.TotalDose, roi_name, a)
            if obtained_eud > 200:
                objective.DoseFunctionParameters.DoseLevel = int(obtained_eud - 200)
コード例 #2
0
def double_opt_extra():
    
    plan = lib.get_current_plan()
    beamset = lib.get_current_beamset()
    plan.PlanOptimizations[0].OptimizationParameters.Algorithm.MaxNumberOfIterations = 100
    plan.PlanOptimizations[0].RunOptimization()
    plan.PlanOptimizations[0].RunOptimization()
    fit_objectives_orl(plan, beamset)
    plan.PlanOptimizations[0].OptimizationParameters.Algorithm.MaxNumberOfIterations = 29
    plan.PlanOptimizations[0].RunOptimization()  
コード例 #3
0
def display_loc_point():
    try:
        beamset = lib.get_current_beamset()
        loc_point_name = beamset.PatientSetup.LocalizationPoiGeometrySource.LocalizationPoiGeometry.OfPoi.Name
        ui = get_current("ui")
        ui.MenuItem[1].Button_PatientModeling.Click()
        ui.ToolPanel.TabItem['POIs'].Select()
        ui.ToolPanel.PoiList.RayDataGrid.DataGridRow[loc_point_name].Select()
        ui.TabControl_ToolBar.TabItem['POI Tools'].Select()
        ui.TabControl_ToolBar.ToolBarGroup['CURRENT POI'].Button_LocalizePOI.Click()
    except:
        pass
コード例 #4
0
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()  
コード例 #5
0
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()
コード例 #6
0
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()
コード例 #7
0
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    
コード例 #8
0
def export_planar_dose_images(poi_name='planar dose', orientation='coronal', px_size_cm=0.1, size_cm=1):
    """
        Exports planar dose images for the currently selected beamset.

        Each beam will be exported in a separate image.

        BETA/EXPERIMENTAL

        TODO: finalize implementation, allow to choose save path of file.
    """

    beamset = lib.get_current_beamset()

    point = poi.get_poi_coordinates(poi_name)

    n = int(size_cm / px_size_cm)
    points = []

    if orientation == 'coronal':
        # horiz = DICOM x-axis
        # vert = DICOM z axis

        # Initial corner
        pt = point - lib.RSPoint(0.5 * size_cm, 0, 0.5 * size_cm)
        dx = lib.RSPoint(px_size_cm, 0, 0)
        dy = lib.RSPoint(0, 0, px_size_cm)

        for iy in range(0, n + 1):
            for ix in range(0, n + 1):

                points.append(pt.value)
                pt = pt + dx

            pt = pt + dy
            pt = pt - lib.RSPoint(size_cm, 0, 0)

    ret = beamset.FractionDose.InterpolateDoseInPoints(Points=points)

    for c, d in zip(points, ret):
        print c, d

    # logger.warning(x_coord)
    # logger.warning(y_coord)
    # logger.warning(d_value)

    ""
コード例 #9
0
def auto_place_prescription_point():
    """
    Attempts to place automatically a POI on the prescription dose isoline near
    the boundaries of the PTV contour.

    .. rubric::
      PRE-REQUISITES

    1. Prescription dose defined for current beamset.
    2. Existence of a dose specification point (requires manual creation).
    3. Existence of an identifiable PTV contour.
    4. 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,

    - A POI named *PT PRESC* is placed at the final coordinates of the evaluation point.  This POI is created if not already existing.
    - The prescription is changed to prescribe the prescription dose at the final evaluation point, on the prescription dose isoline.  It is thus automatically satisfied.

    .. seealso::
      function `hmrlib.hmrlib.place_prescription_point`

    """
    # patient = lib.get_current_patient()
    # exam = lib.get_current_examination()
    beamset = lib.get_current_beamset()

    # Create PT PRESC if not present
    create_poi({'x': 0, 'y': 0, 'z': 0}, 'PT PRESC')

    ptv_candidates = roi.identify_ptvs2()
    if len(ptv_candidates) > 1:
        logger.warning(
            'More than one possible PTV found: %s.  Trying highest PTV in the list %s.',
            ptv_candidates,
            sorted(ptv_candidates, key=lambda x: x[3:], reverse=True)[0])
    elif len(ptv_candidates) == 0:
        logger.error('No PTV could be found.  Aborting.')
        raise SystemError('No PTV could be found.  Aborting.')

    ptv = sorted(ptv_candidates, key=lambda x: x[3:], reverse=True)[0]
    logger.info('PTV identified as %s', ptv)

    try:
        presc_dose = lib.get_prescription_dose()
        logger.info('Presc dose = %.3f cGy', presc_dose)

        fractions = lib.get_fractions()
        logger.info('Fractions = %s', fractions)

        target_dose = presc_dose / float(fractions)
        logger.info('Target dose = %.3f cGy', target_dose)

    except Exception as e:
        logger.exception(e)
        raise

    point = place_prescription_point(target_dose,
                                     ptv,
                                     'PT PRESC',
                                     beamset=beamset)

    # Set prescription to PT PRESC
    try:
        beamset.AddDosePrescriptionToPoi(PoiName='PT PRESC',
                                         DoseValue=presc_dose)
    except Exception as e:
        logger.exception(e)
        raise

    # Update dose specification point
    try:
        dsp = [x for x in beamset.DoseSpecificationPoints][0]
        dsp.Coordinates = point.value
    except IndexError as e:
        logger.error(
            'You must create a dose specification point manually before executing this script.'
        )
        logger.exception(e)
        raise
    except Exception as e:
        logger.exception(e)
        raise
コード例 #10
0
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
コード例 #11
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()
コード例 #12
0
def create_ac_qa_plans(plan=None, phantom_name='ARCCHECK', iso_name='ISO AC'):

    ui = get_current("ui")
    patient = lib.get_current_patient()
    if plan is None:
        plan = lib.get_current_plan()

    # Change dose color table reference value to dose of one fraction
    rx_dose = plan.BeamSets[0].Prescription.DosePrescriptions[0].DoseValue
    nb_fx = plan.BeamSets[0].FractionationPattern.NumberOfFractions
    patient.CaseSettings.DoseColorMap.ReferenceValue = rx_dose * 1.1 / nb_fx  # Increase ref dose by 10% because small size of ArcCheck typically leads to higher max dose in phantom than in patient

    # RayStation asks you to save the plan before continuing. This is done automatically, because errors can occur if the user doesn't respond promptly and the script continues.
    patient.Save()

    # Create QA plan (for each beamset) - NOPE just this one
    #for bs in plan.BeamSets:
    
    bs = lib.get_current_beamset()
    bs_name = bs.DicomPlanLabel
    if len(bs_name) > 6:  # To prevent crashes if resulting name will be > 16 characters long
        bs_name = bs.DicomPlanLabel[:6]
    
    if iso_name =='ISO AC':
        name = 'QA ' + patient.PatientName[:4] + ' ' + bs_name
    else:
        name = 'PD ' + patient.PatientName[:4] + ' ' + bs_name

    # Open the QA Preparation tab
    if lib.check_version(4.7):
        ui.MenuItem[6].Button_QAPreparation.Click()
    elif lib.check_version(4.6):
        ui.MenuItem[8].Button_QAPreparation.Click()
    # Click button "New QA plan"
    ui.TabControl_ToolBar.ToolBarGroup.__0.Button_NewQAPlan.Click()
    # Enter a name in the plan name text box
    ui.TextBox_VerificationPlanName.Text = name
    # Open the phantom list dropdown and selects phantom
    ui.ComboBox_PhantomList.ToggleButton.Click()
    ui.ComboBox_PhantomList.Popup.ComboBoxItem[phantom_name].Select()
    # ui.ComboBox_PhantomList.Popup.ComboBoxItem['QAVMAT ARCCHECK_2016'].Select() #Phantom for use in Dev database
    # Check the box to compute dose after plan is created
    ui.CheckBox._Compute_dose_when_the_QA_plan_is_created.Click()
    # Select POI position for isocenter, open dropdown menu and select ISO AC
    ui.RayRadionButton._1.Click()
    ui.ComboBox_PointsOfInterests.ToggleButton.Click()
    ui.ComboBox_PointsOfInterests.Popup.ComboBoxItem[iso_name].Select()
    # Marks checkbox to use uniform resolution and set to 0.2cm
    ui.CheckBox._Use_uniform_resolution.Click()
    ui.TextBox_DoseGridResolutionPresentationX.Text = ".2"
    # Collapse all angles couch, gantry and coll to 0 degrees
    if iso_name == '2cm EPID'or iso_name == '4.8cm MapCheck IMF':
        ui.RayRadionButton._4.Click()
        ui.RayRadionButton._6.Click()
        ui.RayRadionButton._8.Click()
    
    ui.Button_OK.Click()
    
    #si c'est des Mapcheck
    if iso_name == '2cm EPID' or iso_name == '4.8cm MapCheck IMF':
        ui.TabControl_ToolBar.ToolBarGroup._DATA_EXPORT.Button_ExportPlan___.Click()
        # Unselect RT Dose for each beam, RT structures and CT Image
        ui.QADicomExportDialogContent.CheckBox[3].Click()
        ui.QADicomExportDialogContent.CheckBox[4].Click()
        # Export plan, total dose and beam doses to Maggie
        ui.QADicomExportDialogContent.PropertyRow[1].RadioButton.Click()  # Select DICOM Store for export
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.ToggleButton.Click()
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.Popup.ComboBoxItem['MAGGIE [11.1.7.5]'].Select()
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.ToggleButton.Click()
        
        
        #to actually click OK for export
        ui.QADicomExportDialogContent.Button['Export'].Click()
        #message.message_window('Les fichiers des plannar doses sont maintenant dans MAGGIE')
    
    # Open the Export QA plan
    else: 
        ui.TabControl_ToolBar.ToolBarGroup._DATA_EXPORT.Button_ExportPlan___.Click()
        # Unselect RT Dose for each beam, RT structures and CT Image
        ui.QADicomExportDialogContent.CheckBox[2].Click()
        ui.QADicomExportDialogContent.CheckBox[3].Click()
        ui.QADicomExportDialogContent.CheckBox[4].Click()
        # Export plan, total dose and beam doses to Maggie
        ui.QADicomExportDialogContent.PropertyRow[1].RadioButton.Click()  # Select DICOM Store for export
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.ToggleButton.Click()
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.Popup.ComboBoxItem['MAGGIE [11.1.7.5]'].Select()
        ui.QADicomExportDialogContent.PropertyRow[1].ComboBox.ToggleButton.Click()
        
        
        #to actually click OK for export
        ui.QADicomExportDialogContent.Button['Export'].Click()
コード例 #13
0
def preparation_qa():

    #QA preparation script for dosimetrists
    # - Creates QA plan for the currently selected beamset on the ArcCheck phantom
    # - Notes dose to MicroLion
    # - Notes max dose and its location
    # - Shifts the isocenter to put the point of max dose on the MicroLion (most of the time)
    # - Recomputes dose and then exports dose to MicroLion before and after shift, along with instructions for the shift

    patient = lib.get_current_patient()
    plan = lib.get_current_plan()
    beamset = lib.get_current_beamset()
    
    output = "Patient: %s\nPlan: %s\nBeamset: %s\n" % (patient.PatientName.replace("^", ", "),plan.Name,beamset.DicomPlanLabel)
    
    #Create QA plan centered on ISO AC
    create_ac_qa_plans()
    ui = get_current("ui")
    ui.MessageBoxWindowContent.Button['OK'].Click() #Clears away dialogue after exporting DICOM files (or failing to)
    vp = find_current_verification_plan(plan,beamset)
    output += "Plan QA: " + vp.BeamSet.DicomPlanLabel + '\n'
    
    #Find coordinates of current isocenter (rounded to a tenth of a millimeter)
    iso_x = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.x, 2)
    iso_y = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.y, 2)
    iso_z = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.z, 2)
    
    #Get total dose at isocenter as well as each beam's contribution
    isocenter_rsp = lib.RSPoint(iso_x, iso_y, iso_z)
    isocenter_dose = vp.BeamSet.FractionDose.InterpolateDoseInPoint(Point=isocenter_rsp.value)
    output += "\nPLAN CENTRÉ\nDose à l'isocentre: %.2fGy" % (isocenter_dose/100.0)
    for beamdose in vp.BeamSet.FractionDose.BeamDoses:
        dose_per_beam = beamdose.InterpolateDoseInPoint(Point=isocenter_rsp.value)
        output += "\n     %s : %.2fGy" % (beamdose.ForBeam.Name, dose_per_beam / 100.0)

    #Determine location of max dose point and the dose it receives
    fraction_dose = vp.BeamSet.FractionDose
    grid = vp.DoseGrid
    max_dose,max_coords = poi.get_max_dose_coordinates(fraction_dose,grid)
    
    #Create a DSP at this point
    vp.BeamSet.CreateDoseSpecificationPoint(Name="DSP", Coordinates={ 'x': max_coords.x, 'y': max_coords.y, 'z': max_coords.z })
    
    # Formula for new isocenter: 2*(iso coordinate) - dose max coordinate (rounded)
    new_iso = lib.RSPoint(0, 0, 0)
    new_iso.x = 2 * iso_x - round(max_coords.x, 2)
    new_iso.y = 2 * iso_y - round(max_coords.y, 2)
    new_iso.z = 2 * iso_z - round(max_coords.z, 2)

    # Round DSP coordinates to avoid sub-millimeter table displacements
    new_iso.x = round(new_iso.x, 1) + (iso_x - round(iso_x, 1))
    new_iso.y = round(new_iso.y, 1) + (iso_y - round(iso_y, 1))  # If iso_y = 2.75, then you get (2.75-2.8) = -0.05
    new_iso.z = round(new_iso.z, 1) + (iso_z - round(iso_z, 1))

    # Eliminate displacements that are 2mm or less
    if abs(new_iso.x - iso_x) < 0.21:
        new_iso.x = iso_x
    if abs(new_iso.y - iso_y) < 0.21:
        new_iso.y = iso_y
    if abs(new_iso.z - iso_z) < 0.21:
        new_iso.z = iso_z
    
    #Set all beams to the new isocenter
    for beam in vp.BeamSet.Beams:
        beam.PatientToBeamMapping.IsocenterPoint = {'x': new_iso.x, 'y': new_iso.y, 'z': new_iso.z}    
    
    #Now that we're done with our DSP, set it back to the original isocenter coordinates to get dose to MicroLion
    dsp = vp.BeamSet.DoseSpecificationPoints[0]
    dsp.Name = "ISO MicroLion"
    dsp.Coordinates = {'x': iso_x, 'y': iso_y, 'z': iso_z}        

    for beam in vp.BeamSet.Beams:
        beam.SetDoseSpecificationPoint(Name="ISO MicroLion")

    # Compute dose (this is done last, because otherwise changing beam spec points erases dose)
    vp.BeamSet.ComputeDose(ComputeBeamDoses=True, DoseAlgorithm="CCDose", ForceRecompute=False)
    
    #Calculate shift
    shift_x = new_iso.x - iso_x    
    shift_y = new_iso.y - iso_y
    shift_z = new_iso.z - iso_z
    
    if shift_x == 0 and shift_y == 0 and shift_z == 0:
        output += "\n\nL'isocentre se trouve sur le point de dose max. Aucun shift automatique a été calculé."
    else:
        if (isocenter_dose/max_dose) < 0.75:
            output += "\n\nLa dose à l'isocentre est inférieure à 75% de la dose maximale.\nIl est nécessaire de mesurer la dose au MicroLion avec le shift suivant:\n\n"
        else:
            output += "\n\nLa dose à l'isocentre est supérieure à 75% de la dose maximale.\nIl n'est probablement pas nécessaire de faire un shift, mais voici une possibilité de shift au cas ou:\n\n"
        
        if shift_x > 0:
            direction_x = 'vers A'
        elif shift_x < 0:
            direction_x = 'vers B'
        if shift_x != 0:
            output += '          LATERAL: ' + str(abs(shift_x)) + 'cm ' + direction_x + '\n'
        else:
            output += '          LATERAL: Aucun shift\n'

        if shift_y > 0:
            direction_y = 'UP'
        elif shift_y < 0:
            direction_y = 'DOWN'
        if shift_y != 0:
            output += '          HAUTEUR: ' + str(abs(shift_y)) + 'cm ' + direction_y + '\n'
        else:
            output += '          HAUTEUR: Aucun shift\n'
            
        if shift_z < 0:
            direction_z = 'IN'
        else:
            direction_z = 'OUT' #We have to account for 0 shift because I call on this variable later (which isn't true for the other axes)
        if shift_z != 0:
            output += '     LONGITUDINAL: ' + str(abs(shift_z)) + 'cm ' + direction_z + '\n'
        else:
            output += '     LONGITUDINAL: Aucun shift\n'            
            
        if direction_z == 'IN':
            output += "ATTENTION: Suite au déplacement vers IN, assurez-vous que le faisceau n'irradierais pas l'électronique du ArcCheck\n"

        # Print dose to MicroLion with AC shift
        isocenter_dose = vp.BeamSet.FractionDose.InterpolateDoseInPoint(Point=isocenter_rsp.value)
        output += "\nPLAN AVEC SHIFT\nDose à l'isocentre: %.2fGy" % (isocenter_dose/100.0)
        for beamdose in vp.BeamSet.FractionDose.BeamDoses:
            dose_per_beam = beamdose.InterpolateDoseInPoint(Point=isocenter_rsp.value)
            output += "\n     %s : %.2fGy" % (beamdose.ForBeam.Name, dose_per_beam / 100.0)  
            
    
    #For IMRT plans, make a second QA plan to create EPIDStan files
    if vp.BeamSet.DeliveryTechnique == "SMLC":
        num_beams = 0
        num_segments = 0
        for beam in vp.BeamSet.Beams:
            num_beams += 1
            for seg in beam.Segments:
                num_segments += 1
        
        if num_segments > num_beams: #If this is an IMRT plan and not a 3DC plan
            if lib.check_version(4.7):
                phantom_name = '48x48x48 FANTOME'
            elif lib.check_version(4.6):
                phantom_name = '48x48x48 FANTOME'
            create_ac_qa_plans(plan=None,phantom_name=phantom_name,iso_name='2cm EPID')
            ui = get_current("ui")
            ui.MessageBoxWindowContent.Button['OK'].Click() #Clears away dialogue after exporting DICOM files (or failing to)
            output += "\n\nLe plan est fait en IMRT, n'oubliez pas de vérifier la dose 2D à l'aide de EPIDStan\nLes fichiers EPID se trouvent sur MAGGIE"
    
    #Print all results to text file
    try:
        patient_ID = patient.PatientID
        patient_name = patient.PatientName.replace("^", ", ")
        file_path = r'\\radonc.hmr\Departements\Physiciens\Clinique\IMRT\QA'
        file_path += '\\' + patient_name + '~' + patient_ID + '-TESTSUPERBRIDGE'    
        with open(file_path + '\\QA patient ' + vp.BeamSet.DicomPlanLabel + ".txt", 'w') as dvh_file:
            dvh_file.write(output)
    except Exception as e:
        texte = 'Impossible de créer le fichier texte avec les résultats.\n%s\nSVP, notez les valeurs ci-dessous:\n\n' % e
        message.message_window(texte + output)
コード例 #14
0
def shift_plans_QA(print_results=True):
    """
    Checks all existing QA plans to see if they have a dose specification
    point (which should correspond to the point of dose max).

    If a DSP is found, the isocenter is shifted and the plan is recalculated,
    which should usually move the MicroLion to the point of dose max.

    This script should only be used if the beams are currently on the phantom isocenter point
    (where the MicroLion is situated). If not, then the displacements printed to file may be incorrect!
    """
    patient = lib.get_current_patient()
    plan = lib.get_current_plan()
    beamset = lib.get_current_beamset()
    grid = {'x': 0.2, 'y': 0.2, 'z': 0.2}

    #Determine which verification plan we are currently
    vp = None
    for vpl in plan.VerificationPlans:
        if vpl.ForTreatmentPlan.Name == plan.Name and vpl.OfRadiationSet.DicomPlanLabel == beamset.DicomPlanLabel:
            vp = vpl
            break
    
    #If no appropriate verification plan is found, do nothing
    if vp == None:
        message.message_window('Aucun plan QA trouvé')
        return
    
    message.message_window('Le nom du plan QA trouvé est ' + vpl.BeamSet.DicomPlanLabel)
    
    #for vp in plan.VerificationPlans:
    try:
        p = lib.RSPoint(point=vp.BeamSet.DoseSpecificationPoints[0].Coordinates)  # get coords of first DSP (if it exists)
    except:
        message.message_window('Aucun DSP trouvé dans le plan QA')
        return

    # Find coordinates of current isocenter (rounded to a tenth of a millimeter)
    iso_x = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.x, 2)
    iso_y = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.y, 2)
    iso_z = round(vp.BeamSet.Beams[0].PatientToBeamMapping.IsocenterPoint.z, 2)

    # Formula for new isocenter: 2*(iso coordinate) - dose max coordinate (rounded)
    p.x = 2 * iso_x - round(p.x, 2)
    p.y = 2 * iso_y - round(p.y, 2)  # difference between DICOM and patient coordinate systems, y=-z
    p.z = 2 * iso_z - round(p.z, 2)  # difference between DICOM and patient coordinate systems, z=y

    # Round DSP coordinates to avoid sub-millimeter table displacements
    p.x = round(p.x, 1) + (iso_x - round(iso_x, 1))
    p.y = round(p.y, 1) + (iso_y - round(iso_y, 1))  # If iso_y = 2.75, then you get (2.75-2.8) = -0.05
    p.z = round(p.z, 1) + (iso_z - round(iso_z, 1))

    # Eliminate displacements that are 2mm or less
    if abs(p.x - iso_x) < 0.21:
        p.x = iso_x
    if abs(p.y - iso_y) < 0.21:
        p.y = iso_y
    if abs(p.z - iso_z) < 0.21:
        p.z = iso_z

    # Add SHIFT to plan name (truncate name if resulting name will be >16 characters)
    if len(vp.BeamSet.DicomPlanLabel) > 9:  # To prevent crashes if resulting name will be > 16 characters long
        name = vp.BeamSet.DicomPlanLabel[:9] + ' SHIFT'
    else:
        name = vp.BeamSet.DicomPlanLabel + ' SHIFT'
    vp.BeamSet.DicomPlanLabel = name

    # Shift isocenter
    #plan.BeamSets[vp.OfRadiationSet.Number-1].CreateQAPlan(PhantomName='QA VMAT ARCCHECK', QAPlanName=name, IsoCenter=p.value, DoseGrid=grid, ComputeDoseWhenPlanIsCreated=True)
    for beam in vp.BeamSet.Beams:
        beam.PatientToBeamMapping.IsocenterPoint = {'x': p.x, 'y': p.y, 'z': p.z}

    # Move DSP to isocenter coordinates and assign as spec point for all beams
    try:
        dsp = vp.BeamSet.DoseSpecificationPoints[0]
        dsp.Name = "ISO MicroLion"
        dsp.Coordinates = {'x': iso_x, 'y': iso_y, 'z': iso_z}
    except IndexError as e:
        logger.error('You must create a dose specification point manually before executing this script.')
        logger.exception(e)
        raise
    except Exception as e:
        logger.exception(e)
        raise

    for beam in vp.BeamSet.Beams:
        beam.SetDoseSpecificationPoint(Name="ISO MicroLion")

    # Compute dose (this is done last, because otherwise changing beam spec points erases dose)
    vp.BeamSet.ComputeDose(ComputeBeamDoses=True, DoseAlgorithm="CCDose", ForceRecompute=False)

    # Write displacement to file
    # Get demographic information
    patient_ID = patient.PatientID
    patient_name = patient.PatientName.replace("^", ", ")

    if print_results is True:        
        try:
            file_path = r'\\radonc.hmr\Departements\Physiciens\Clinique\IMRT\QA'
            file_path += '\\' + patient_name + '~' + patient_ID + '-TESTSUPERBRIDGE'

            # Write to file

            with open(file_path + '\\Déplacement ArcCheck ' + name + ".txt", 'w') as dvh_file:
                dvh_file.write('Patient:                    ' + patient_name + '\n')
                dvh_file.write('No. HMR:                    ' + patient_ID + '\n\n\n')
                dvh_file.write('Déplacement ArcCheck pour QA plan: ' + name + '\n\n')

                shift_x = p.x - iso_x
                if shift_x > 0:
                    direction_x = 'vers A'
                elif shift_x < 0:
                    direction_x = 'vers B'
                if shift_x != 0:
                    dvh_file.write('          LATERAL: ' + str(abs(shift_x)) + 'cm ' + direction_x + '\n')
                else:
                    dvh_file.write('          LATERAL: Aucun shift\n')

                shift_z = p.z - iso_z
                if shift_z > 0:
                    direction_z = 'OUT'
                elif shift_z < 0:
                    direction_z = 'IN'
                if shift_z != 0:
                    dvh_file.write('     LONGITUDINAL: ' + str(abs(shift_z)) + 'cm ' + direction_z + '\n')
                else:
                    dvh_file.write('     LONGITUDINAL: Aucun shift\n')

                shift_y = p.y - iso_y
                if shift_y > 0:
                    direction_y = 'UP'
                elif shift_y < 0:
                    direction_y = 'DOWN'
                if shift_y != 0:
                    dvh_file.write('          HAUTEUR: ' + str(abs(shift_y)) + 'cm ' + direction_y + '\n')
                else:
                    dvh_file.write('          HAUTEUR: Aucun shift\n')

                # Print dose to MicroLion with AC shift
                dsp_temp = lib.RSPoint(dsp.Coordinates.x, dsp.Coordinates.y, dsp.Coordinates.z)
                dsp_dose = vp.BeamSet.FractionDose.InterpolateDoseInPoint(Point=dsp_temp.value)
                dvh_file.write("\n\nNouveau dose au point %s : %.3fGy" % (dsp.Name, dsp_dose / 100.0))
                dvh_file.write("\n\nDose par faisceau: ")
                for beamdose in vp.BeamSet.FractionDose.BeamDoses:
                    dose_per_beam = beamdose.InterpolateDoseInPoint(Point=dsp_temp.value)
                    dvh_file.write("\n     %s : %.3fGy" % (beamdose.ForBeam.Name, dose_per_beam / 100.0))
    
        except:
            texte = 'Impossible de créer le fichier texte avec les déplacements. SVP, notez les valeurs ci-dessous:\n\n'
            shift_x = p.x - iso_x
            if shift_x > 0:
                direction_x = 'vers A'
            elif shift_x < 0:
                direction_x = 'vers B'
            if shift_x != 0:
                texte += '          LATERAL: ' + str(abs(shift_x)) + 'cm ' + direction_x + '\n'
            else:
                texte += '          LATERAL: Aucun shift\n'

            shift_z = p.z - iso_z
            if shift_z > 0:
                direction_z = 'OUT'
            elif shift_z < 0:
                direction_z = 'IN'
            if shift_z != 0:
                texte += '     LONGITUDINAL: ' + str(abs(shift_z)) + 'cm ' + direction_z + '\n'
            else:
                texte += '     LONGITUDINAL: Aucun shift\n'

            shift_y = p.y - iso_y
            if shift_y > 0:
                direction_y = 'UP'
            elif shift_y < 0:
                direction_y = 'DOWN'
            if shift_y != 0:
                texte += '          HAUTEUR: ' + str(abs(shift_y)) + 'cm ' + direction_y + '\n'
            else:
                texte += '          HAUTEUR: Aucun shift\n'

            message.message_window(texte)