Exemple #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)
Exemple #2
0
def _add_objective(roi_name, obj_type, is_constraint=False, plan=None, plan_opt=0):
    """
    Private function used to add an optimization objective.

    Args:
        roi_name (str): the name of the ROI for which to add the optimization objective
        obj_type (str): the type of the optimization objective
        is_constraint (bool): whether or not the objective is a constraint
        plan (RayStation plan object, optional): the patient plan.  If not specified, will get the currently selected one.
        plan_opt (int): index of the PlanOptimization to use - allows for the addition of objectives to beamsets other than the first
                            (note that this value can be obtained by subtracting 1 from beamset.Number)

    Returns:
        the RayStation objective object
    """
    if plan is None:
        plan = lib.get_current_plan()

    try:
        with CompositeAction('Add Optimization Function'):
            retval_1 = plan.PlanOptimizations[plan_opt].AddOptimizationFunction(FunctionType=obj_type, RoiName=roi_name, IsConstraint=is_constraint,
                                                                                RestrictAllBeamsIndividually=False, RestrictToBeam=None, IsRobust=False, RestrictToBeamSet=None)
        logger.info('Objective of type %s for ROI "%s" added.', obj_type, roi_name)
    except SystemError as e:
        if str(e).startswith('No ROI named'):
            logger.warning('Warning: no ROI named "%s".  Skip adding objective.', roi_name)
            return None
        else:
            logger.exception(e)
            raise
    return retval_1
Exemple #3
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()  
Exemple #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()  
Exemple #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()
Exemple #6
0
def display_external_roi(): # WIP, do not use yet
    patient = lib.get_current_patient()
    plan = lib.get_current_plan()
    
    for roi in patient.PatientModel.StructureSets[exam.Name].RoiGeometries:
        if roi.OfRoi.Type == "External":
            external_roi = roi.OfRoi.Name
        
    try:
        ui = get_current("ui")
        ui.MenuItem[1].Button_PatientModeling.Click()       
    except:
        pass
Exemple #7
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()
Exemple #8
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    
Exemple #9
0
def add_mindose_objective(roi_name, dose, weight=1.0, plan=None, plan_opt=0):
    """
    Adds a min dose objective to a ROI.

    Args:
        roi_name (str): the name of the ROI
        dose (float): the dose value in cGy
        weight (float, optional): the weight of the objective (default 1.0)
        plan (RayStation plan object, optional): the patient plan.  If not specified, will get the currently selected one.
    """
    if plan is None:
        plan = lib.get_current_plan()

    r = _add_objective(roi_name, 'MinDose', plan=plan, plan_opt=plan_opt)
    if r:
        r.DoseFunctionParameters.DoseLevel = dose
        if weight != 1.0:
            r.DoseFunctionParameters.Weight = weight
Exemple #10
0
def compute_qa_plan_dose(compute_beam_doses=False, dose_algorithm='CCDose', scale_to_prescription=False, plan=None):
    """
        Calculates the QA plan dose for QA plans associated with the currently
        selected plan.

        If the dose is already computed with the same algorithm, this function
        will not force recomputation.

        Args:
            compute_beam_doses (bool, optional): whether or not to compute individual beam doses (default *False*)
            dose_algorithm (str, optional): one onef *CCDose* (accurate) or *SVD* (fast) (default *CCDose*)
            scale_to_prescription (bool, optional): whether or not to scale MUs to dose prescription ? (default *False*)
    """

    if plan is None:
        plan = lib.get_current_plan()

    for vp in plan.VerificationPlans:
        if vp.ForTreatmentPlan.Name == plan.Name:
            vp.BeamSet.ComputeDose(ComputeBeamDoses=compute_beam_doses, DoseAlgorithm=dose_algorithm, ScaleToPrescription=scale_to_prescription)
Exemple #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()
Exemple #12
0
def set_vmat_conversion_parameters(max_leaf_travel_per_degree=0.1, constrain_leaf_motion=True, arc_gantry_spacing=4, max_arc_delivery_time=350.0, plan=None, beamset=None):
    """
    Sets the VMAT conversion parameters.

    **RESETS OPTIMIZATION IF ALREADY STARTED**

    Args:
        max_leaf_travel_per_degree (float, optional): maximum distance a leaf can move in cm per degree of gantry movement (default: 0.1 cm/degree)
        constrain_leaf_motion (bool, optional): whether or not to impose the *max_leaf_travel_per_degree* constraint (default: True)
        arc_gantry_spacing (int, optional): the spacing in degrees between control points of the arc (default: 4 degrees)
        max_arc_delivery_time (float, optional): the maximum are delivery time in seconds (defautl 350.0 s)
        plan (RS object, optional): the RayStation plan object (if none specified, will get current)
        beamset (str or int, optional): the name of the beamset to modify.  If not used, all beamsets in the plan are affected.  An int can be provided as well (1 for first beamset, 2 for second beamset, etc.).
    """

    if plan is None:
        plan = lib.get_current_plan()

    if beamset is None:
        logger.info('No beamset specified.  All beamsets will be modified.')
        l = 0
        for po in plan.PlanOptimizations:
            l += 1
        indices = range(l)
    elif isinstance(beamset, str):
        # figure out what index of PlanOptimizations is this beamset
        i = 0
        for po in plan.PlanOptimizations:
            for obs in po.OptimizedBeamSets:
                if beamset.startswith(obs.DicomPlanLabel):
                    break
            break
            i += 1

        indices = [i]
    elif isinstance(beamset, int):
        try:
            indices = [beamset - 1]
            test = plan.PlanOptimizations[beamset - 1]
        except IndexError as e:
            lib.error('The index provided for the beamset argument was out of range. Specify the beamset number in the plan (i.e. 1, 2, ...) or provide the beamset name as a string.')

    if arc_gantry_spacing not in [2, 3, 4]:
        lib.error('The arc gantry spacing must be of 2, 3 or 4 degrees.')

    try:
        for i in indices:
            if plan.PlanOptimizations[i].ProgressOfOptimization is not None:
                # Reset optimization because this changes parameters that RS doesn't
                # allow changing without resetting.
                plan.PlanOptimizations[i].ResetOptimization()
                logger.info('Optimization reset.')

            po = plan.PlanOptimizations[i]

            logger.info('PlanOptimizations[%s] selected.', i)

            po.OptimizationParameters.SegmentConversion.ArcConversionProperties.MaxLeafTravelDistancePerDegree = max_leaf_travel_per_degree
            logger.info('Max leaf travel per degree set to %s cm/deg.', max_leaf_travel_per_degree)

            po.OptimizationParameters.SegmentConversion.ArcConversionProperties.UseMaxLeafTravelDistancePerDegree = constrain_leaf_motion
            logger.info('Constrain leaf motion set to %s.', constrain_leaf_motion)

            for ts in po.OptimizationParameters.TreatmentSetupSettings:
                for bs in ts.BeamSettings:
                    bs.ArcConversionPropertiesPerBeam.FinalArcGantrySpacing = arc_gantry_spacing
                    logger.info('Arc gantry spacing set to %s degrees for arc "%s".', arc_gantry_spacing, bs.ForBeam.Name)

                    bs.ArcConversionPropertiesPerBeam.MaxArcDeliveryTime = max_arc_delivery_time
                    logger.info('Max arc delivery time set to %s s for arc "%s".', max_arc_delivery_time, bs.ForBeam.Name)
    except Exception as e:
        logger.exception(e)
        raise
Exemple #13
0
def set_optimization_parameters(fluence_iterations=60, max_iterations=100, optimality_tolerance=1E-09, compute_intermediate_dose=True, compute_final_dose=True, plan=None, beamset=None):
    """
    Sets the optimization parameters for inverse planning.

    **RESETS OPTIMIZATION IF ALREADY STARTED**

    Args:
        fluence_iterations (int, optional): the number of fluence iterations (default: 60)
        max_iterations (int, optional): the maximum number of iterations (default: 100)
        optimality_tolerance (float, optional): the tolerance of the optimizer such that it has reached an optimal solution with respect to the objective value (default: 1E-9)
        compute_intermediate_dose (bool, optional): whether of not to compute clinical dose after completing fluence iterations.  This dose is taken into account in the following optimization iterations (default: True).
        compute_final_dose (bool, optional): whether or not to compute clinical dose after optimization is done (default: True)
        plan (RS Object, optional): the RayStation plan object (if none specified, will get current)
        beamset (str or int, optional): the name of the beamset to modify.  If not used, all beamsets in the plan are affected.  An int can be provided as well (1 for first beamset, 2 for second beamset, etc.).
    """
    if plan is None:
        plan = lib.get_current_plan()

    if fluence_iterations > max_iterations:
        lib.error('Fluence iterations cannot be greater than the maximum number of iterations.')

    if beamset is None:
        logger.info('No beamset specified.  All beamsets will be modified.')
        l = 0
        for po in plan.PlanOptimizations:
            l += 1
        indices = range(l)
    elif isinstance(beamset, str):
        # figure out what index of PlanOptimizations is this beamset
        i = 0
        for po in plan.PlanOptimizations:
            for obs in po.OptimizedBeamSets:
                if beamset.startswith(obs.DicomPlanLabel):
                    break
            break
            i += 1

        indices = [i]
    elif isinstance(beamset, int):
        try:
            indices = [beamset - 1]
            test = plan.PlanOptimizations[beamset - 1]
        except IndexError as e:
            lib.error('The index provided for the beamset argument was out of range. Specify the beamset number in the plan (i.e. 1, 2, ...) or provide the beamset name as a string.')

    # TODO Ask confirmation with yes/no dialog before proceeding.
    try:
        for i in indices:
            #if not plan.PlanOptimizations[i].ProgressOfOptimization is None:
                # Reset optimization because this changes parameters that RS doesn't
                # allow changing without resetting.
                #plan.PlanOptimizations[i].ResetOptimization()
                #logger.info('Optimization was reset.')

            logger.info('PlanOptimizations[%s] selected.', i)

            op = plan.PlanOptimizations[i].OptimizationParameters
            op.DoseCalculation.IterationsInPreparationsPhase = fluence_iterations
            logger.info('Fluence iterations set to %s.', fluence_iterations)

            op.Algorithm.MaxNumberOfIterations = max_iterations
            logger.info('Max iterations set to %s.', max_iterations)

            op.Algorithm.OptimalityTolerance = optimality_tolerance
            logger.info('Optimality tolerance set to %s.', optimality_tolerance)

            op.DoseCalculation.ComputeIntermediateDose = compute_intermediate_dose
            logger.info('Compute intermediate dose set to %s.', compute_intermediate_dose)

            op.DoseCalculation.ComputeFinalDose = compute_final_dose
            logger.info('Compute final dose set to %s.', compute_final_dose)
    except Exception as e:
        logger.exception(e)
        raise
Exemple #14
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()
Exemple #15
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)
Exemple #16
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)