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)
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()
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
def optimization_90_30(plan=None, beamset=None): patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() opt = plan.PlanOptimizations[beamset.Number - 1] set_optimization_parameters(plan=plan,fluence_iterations=30, max_iterations=90, compute_intermediate_dose=False) opt.RunOptimization() set_optimization_parameters(plan=plan,fluence_iterations=30, max_iterations=30, compute_intermediate_dose=False) opt.RunOptimization()
def double_optimization(plan=None, beamset=None): # Function which runs two consecutive optimizations. patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() # Beamsets are numbered starting at 1 whereas Plan Optimizations are numbered starting at 0. # Determine what the number of the selected beamset is, then subtract one to locate the associated Plan Optimization. plan.PlanOptimizations[beamset.Number - 1].RunOptimization() plan.PlanOptimizations[beamset.Number - 1].RunOptimization()
def double_opt_save(plan=None, beamset=None): # Function which runs two consecutive optimizations and saves the plan afterwards (NB this removes the ability to undo the optimizations). patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() # Beamset.Number starts at 1 whereas Plan Optimizations are numbered starting at 0. # Determine what the number of the selected beamset is, then subtract one to locate the associated Plan Optimization. plan.PlanOptimizations[beamset.Number - 1].RunOptimization() plan.PlanOptimizations[beamset.Number - 1].RunOptimization() patient.Save()
def fit_objectives_orl(plan=None, beamset=None): # Function which adjusts DVH and EUD values in objectives to reflect obtained values. patient = lib.get_current_patient() if plan is None: plan = lib.get_current_plan() if beamset is None: beamset = lib.get_current_beamset() for objective in plan.PlanOptimizations[beamset.Number - 1].Objective.ConstituentFunctions: try: f_type = objective.DoseFunctionParameters.FunctionType # Dose falloff objectives do not have a FunctionType and must be skipped except: continue roi_name = objective.ForRegionOfInterest.Name if roi_name in ['BLOC MOELLE','MOELLE','prv5mmMOELLE']: continue else: if f_type == "MaxDvh": dose_level = objective.DoseFunctionParameters.DoseLevel obtained_vol = plan.TreatmentCourse.TotalDose.GetRelativeVolumeAtDoseValues(RoiName=roi_name, DoseValues=[dose_level]) if obtained_vol[0] >= 0.02: objective.DoseFunctionParameters.PercentVolume = int(obtained_vol[0] * 90) if obtained_vol[0] < 0.02: objective.DoseFunctionParameters.DoseLevel = int(dose_level*0.9) elif f_type == "MaxEud": dose_level = objective.DoseFunctionParameters.DoseLevel a = objective.DoseFunctionParameters.EudParameterA obtained_eud = compute_eud(plan.TreatmentCourse.TotalDose, roi_name, a) if obtained_eud > 100: objective.DoseFunctionParameters.DoseLevel = round(obtained_eud*0.99,-1) elif f_type == "MaxDose": dose_level = objective.DoseFunctionParameters.DoseLevel vol_roi=patient.PatientModel.StructureSets['CT 1'].RoiGeometries[roi_name].GetRoiVolume() if vol_roi >= 0.1: vol_cc=0.1/vol_roi max_dose_obtenue = plan.TreatmentCourse.TotalDose.GetDoseAtRelativeVolumes(RoiName=roi_name, RelativeVolumes=[vol_cc]) else: max_dose_obtenue = plan.TreatmentCourse.TotalDose.GetDoseAtRelativeVolumes(RoiName=roi_name, RelativeVolumes=[vol_roi]) if max_dose_obtenue[0] < (dose_level/1.05) and max_dose_obtenue[0] > 500: objective.DoseFunctionParameters.DoseLevel = int(max_dose_obtenue[0]*1.05) elif max_dose_obtenue[0] < (dose_level/1.05) and max_dose_obtenue[0] <= 500: objective.DoseFunctionParameters.DoseLevel = 500
def 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) ""
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
def place_prescription_point(target_fraction_dose, ptv_name, poi_name, beamset=None, exam=None): """ Attempts to place automatically a POI on a target per-fraction isodose line. .. rubric:: PRE-REQUISITES - Existence of an identifiable PTV contour. - Dose is calculated. .. rubric:: Algorithm description - A point on the PTV contour on a slice near the PTV center is found. - The dose is evaluated at that point. - An approximate unit vector towards the PTV center is calculated from that position. + If the evaluated dose is smaller than the prescription dose, the evaluation point is moved a short distance towards the PTV center. + If the evaluated dose is greater than the prescription dose, the evaluation point is moved a short distance away from the PTV center. - If the prescription dose is overshot by this procedure, the direction of movement is reversed and the move distance halved. - This process is repeated until evaluated dose equals the prescription dose to 3 decimal figures or 100 iterations are reached. Finally, - The specified POI is placed at the found coordinates. .. seealso:: function `hmrlib.hmrlib.auto_place_prescription_point` """ patient = lib.get_current_patient() if exam is None: exam = lib.get_current_examination() if beamset is None: beamset = lib.get_current_beamset() try: # Get PTV center ptv_center = lib.RSPoint(point=patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].GetCenterOfRoi()) # Get slice thickness from CT slice_width = abs(exam.Series[0].ImageStack.SlicePositions[1] - exam.Series[0].ImageStack.SlicePositions[0]) logger.info('CT slice width = %s cm', slice_width) initial_point = None # Find contour point on a slice close to PTV center for c in patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].PrimaryShape.Contours: #if lib.check_version(4.7): if c[0].z < ptv_center.z + slice_width and c[ 0].z > ptv_center.z - slice_width: initial_point = lib.RSPoint(c[0].x, c[0].y, c[0].z) break #else: # if c.ContourData[0].z < ptv_center.z + slice_width and c.ContourData[0].z > ptv_center.z - slice_width: # initial_point = lib.RSPoint(c.ContourData[0].x, c.ContourData[0].y, c.ContourData[0].z) # break if initial_point is None: logger.info( 'Could not find a point on the same slice as the ROi center. Disjoint/noncoplanar PTV?' ) logger.info('Trying with first point in contour shape.') c = patient.PatientModel.StructureSets[ exam.Name].RoiGeometries[ptv_name].PrimaryShape.Contours[0] #if lib.check_version(4.7): initial_point = lib.RSPoint(c[0].x, c[0].y, c[0].z) #else: # initial_point = lib.RSPoint(c.ContourData[0].x, c.ContourData[0].y, c.ContourData[0].z) logger.info('Initial point = %s cm', initial_point) u = lib.RSPoint(point=lib.unit_vector(ptv_center - initial_point)) logger.info('Unit vector towards PTV center = %s cm', u) # Change unit vector so that we stay on the same transverse slice u.z = 0 logger.info( 'Approximate unit vector towards PTV center on single CT slice = %s cm', u) def move_point(point, direction, target_fraction_dose, initial_dose, step=0.05, iteration=0): point = point + step * direction dose = beamset.FractionDose.InterpolateDoseInPoint( Point=point.value) logger.info('Dose at %s = %.3f cGy', point, dose) if round(dose, 3) == round(target_fraction_dose, 3) or iteration > 100: # Found a suitable point or reached max iterations return point elif (initial_dose < target_fraction_dose and dose > target_fraction_dose) or (initial_dose > target_fraction_dose and dose < target_fraction_dose): # We overshot the point, so inverse direction and reduce step return move_point(point, -direction, target_fraction_dose, dose, step=0.5 * step, iteration=iteration + 1) else: # Keep going in the same direction with same step return move_point(point, direction, target_fraction_dose, dose, step=step, iteration=iteration + 1) dose = beamset.FractionDose.InterpolateDoseInPoint( Point=initial_point.value) if math.isnan(dose): lib.error('No dose value available. Check if dose is calculated.') logger.info('Dose per fraction at initial point = %.3f cGy', dose) # Assume dose rises when moving towards center of PTV if dose < target_fraction_dose: point = move_point(initial_point, u, target_fraction_dose, dose) else: point = move_point(initial_point, -u, target_fraction_dose, dose) logger.info('Final point = %s cm', point) except Exception as e: logger.exception(e) raise if get_poi_approval(poi_name): logger.warning('POI %s is approved; cannot continue.', poi_name) lib.error( 'POI %s exists and is approved; cannot continue. Unapprove %s before running script.' % (poi_name, poi_name)) set_poi_coordinates(poi_name, point, examination=exam) return point
def 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()
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()
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)
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)