├── README.md ├── plan_evaluation ├── 3DCRT_plan_summary.py ├── 3DCRT_plan_summary_all.py ├── dose_statistics.py ├── get_dvhs.py └── get_dvhs_all_plans.py └── roi_poi_scripts ├── isocenter_poi.py ├── overlapping_structures.py ├── roi_characteristics.py ├── roi_comparison.py ├── roi_poi_list.py └── round_poi_coordinates.py /README.md: -------------------------------------------------------------------------------- 1 | # RayStation-scripts 2 | This repository contains a set of IronPython 2.7 scripts for the RayStation Treatment Planning System for External Beam Radiotherapy. The scripts provide basic functionality and serve as examples of how the scripting environment works. The scripts are divided into two sets, one for ROI (Region of Interest) and POI (Point of Interest) functions and another one for extracting information on the plans. 3 | 4 | ROI and POI Scripts: 5 | - **isocenter_poi.py**: Automatically creates a POI from the center of the planning target volume ROI. 6 | - **overlapping_structures.py**: Checks which ROIs overlap with the PTV (plus a margin) and creates control structures that don't overlap with the PTV, for each of the 7 | overlapping ROIs. 8 | - **roi_characteristics.py**: Gets a summary of data on all the ROIs: center coordinates, volumes, types, materials, and biological parameters. 9 | - **roi_comparison.py**: Compares 2 ROIs and check if they overlap. Calculates dice similarity coefficient, prevision, sensitivity, specificity, 10 | maximum and mean distance to agreement. 11 | - **roi_poi_list.py**: Gets a list with the names of all the POIs and ROIs, checks if a ROI or POI name exists. 12 | - **round_poi_coordinates.py**: Rounds the coordinates of the isocenter POI. 13 | 14 | Plan information scripts: 15 | - **3DCRT_plan_summary.py**: Exports a summary of the current beam set for a 3DCRT plan. 16 | - **3DCRT_plan_summary_all.py**: Exports a summary of all plans for a patient. 17 | - **dose_statistics.py**: Retrieves dose statistics of all ROIs in a plan. 18 | - **get_dvhs.py**: Retrieves Dose Volume Histograms (DVH) from the current plan ROIs. 19 | - **get_dvhs_all_plans.py**: Retrieves Dose Volume Histograms (DVH) from all ROIs in the current patient case. 20 | -------------------------------------------------------------------------------- /plan_evaluation/3DCRT_plan_summary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Fri May 15 18:24:40 2020 5 | @author: Edith Villegas 6 | 7 | Export a summary of the current treatment plan beam set, 3DCRT 8 | """ 9 | 10 | from connect import * 11 | import pickle 12 | 13 | patient = get_current('Patient') 14 | case = get_current('Case') 15 | examination = get_current('Examination') 16 | plan = get_current('Plan') 17 | 18 | #store plan data in a dictionary 19 | plan_data = {} 20 | plan_data['name'] = plan.Name 21 | plan_data['planner'] = plan.PlannedBy 22 | plan_data['comments'] = plan.Comments 23 | plan_data['beamsets'] = [beamset.DicomPlanLabel for beamset in plan.BeamSets] 24 | 25 | #get current beamset 26 | beam_set = get_current('BeamSet') 27 | 28 | #store beam set data in a dictionary 29 | beamset_data = {} 30 | beamset_data['name'] = beam_set.DicomPlanLabel 31 | beamset_data['modality'] = beam_set.Modality 32 | beamset_data['patient position'] = beam_set.PatientPosition 33 | beamset_data['technique'] = beam_set.PlanGenerationTechnique 34 | beamset_data['treatment machine'] = beam_set.MachineReference.MachineName 35 | beamset_data['beams'] = {} 36 | 37 | #get data for each individual beam 38 | for beam in beam_set.Beams: 39 | beamset_data['beams'][beam.Name] = {'gantry angle': beam.GantryAngle, 40 | 'collimator angle': beam.InitialCollimatorAngle, 41 | 'initial jaw positions': list(beam.InitialJawPositions), 42 | 'beam energy': beam.BeamQualityId, 43 | 'isocenter': [beam.Isocenter.Position.x, 44 | beam.Isocenter.Position.y, 45 | beam.Isocenter.Position.z]} 46 | 47 | if beam.Wedge == None: 48 | continue 49 | 50 | beamset_data['beams'][beam.Name]['wedge'] = {'angle': beam.Wedge.Angle, 51 | 'orientation': beam.Wedge.Orientation, 52 | 'type': beam.Wedge.Type} 53 | 54 | 55 | #save data 56 | file_beamset = patient.Name + '_' + beam_set.DicomPlanLabel + '.pickle' 57 | 58 | with open(file_beamset, 'wb') as f: 59 | pickle.dump([plan_data, beamset_data], f) 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /plan_evaluation/3DCRT_plan_summary_all.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Fri May 15 23:18:49 2020 5 | @author: Edith Villegas 6 | """ 7 | 8 | from connect import * 9 | import pickle 10 | 11 | patient = get_current('Patient') 12 | case = get_current('Case') 13 | examination = get_current('Examination') 14 | plan = get_current('Plan') 15 | beam_set = get_current('BeamSet') 16 | 17 | #store plan data in a dictionary 18 | def get_plan_data(plan): 19 | plan_data = {} 20 | plan_data['name'] = plan.Name 21 | plan_data['planner'] = plan.PlannedBy 22 | plan_data['comments'] = plan.Comments 23 | plan_data['beamsets'] = [beamset.DicomPlanLabel for beamset in plan.BeamSets] 24 | return plan_data 25 | 26 | #store beam set data in a dictionary 27 | def get_beamset_summary(beam_set): 28 | beamset_data = {} 29 | beamset_data['name'] = beam_set.DicomPlanLabel 30 | beamset_data['modality'] = beam_set.Modality 31 | beamset_data['patient position'] = beam_set.PatientPosition 32 | beamset_data['technique'] = beam_set.PlanGenerationTechnique 33 | beamset_data['treatment machine'] = beam_set.MachineReference.MachineName 34 | beamset_data['beams'] = {} 35 | 36 | #get data for each individual beam 37 | for beam in beam_set.Beams: 38 | beamset_data['beams'][beam.Name] = {'gantry angle': beam.GantryAngle, 39 | 'collimator angle': beam.InitialCollimatorAngle, 40 | 'initial jaw positions': list(beam.InitialJawPositions), 41 | 'beam energy': beam.BeamQualityId, 42 | 'isocenter': [beam.Isocenter.Position.x, 43 | beam.Isocenter.Position.y, 44 | beam.Isocenter.Position.z]} 45 | 46 | if beam.Wedge == None: 47 | continue 48 | 49 | beamset_data['beams'][beam.Name]['wedge'] = {'angle': beam.Wedge.Angle, 50 | 'orientation': beam.Wedge.Orientation, 51 | 'type': beam.Wedge.Type} 52 | 53 | #return dictionary 54 | return beamset_data 55 | 56 | #GET THE DATA FOR THE PATIENT PLANS 57 | #get data for all plans in the patient 58 | treatment_plans_summary = [] 59 | for treatment_plan in case.TreatmentPlans: 60 | treatment_plans_summary.append(get_plan_data(treatment_plan)) 61 | 62 | #get data for all beamsets in all plans 63 | beamsets_summary = {} 64 | for treatment_plan in case.TreatmentPlans: 65 | beamsets_summary[treatment_plan.Name] = [] 66 | #conformal= [beamset for beamset in treatment_plan.BeamSets if beamset.PlanGenerationTechnique is 'Conformal'] 67 | 68 | for beamset in treatment_plan.BeamSets: 69 | if beamset.PlanGenerationTechnique != 'Conformal': 70 | continue 71 | else: 72 | beamsets_summary[treatment_plan.Name].append(get_beamset_summary(beamset)) 73 | 74 | #save data 75 | file = patient.Name + '_treatment_plans.pickle' 76 | with open(file, 'wb') as f: 77 | pickle.dump(treatment_plans_summary, f) 78 | 79 | file = patient.Name + '_beam_sets_s.pickle' 80 | with open(file, 'wb') as f: 81 | pickle.dump(beamsets_summary, f) 82 | 83 | -------------------------------------------------------------------------------- /plan_evaluation/dose_statistics.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Thu May 14 2020 5 | @author: Edith Villegas 6 | 7 | Summarize dose statistics in all ROIs of a plan 8 | """ 9 | 10 | from connect import * 11 | import pickle 12 | 13 | #get current patient, case, plan and beam set 14 | patient = get_current("Patient") 15 | case = get_current("Case") 16 | examination = get_current("Examination") 17 | plan = get_current("Plan") 18 | beam_set = get_current("BeamSet") 19 | 20 | #ROIs in the current patient model 21 | rois = case.PatientModel.RegionsOfInterest 22 | roi_geometry = case.PatientModel.StructureSets[examination.Name].RoiGeometries 23 | 24 | #dose with the current plan 25 | totaldose = plan.TreatmentCourse.TotalDose 26 | fdose = beam_set.FractionDose 27 | 28 | #make an epmty dictionary to store all ROIs 29 | roi_dic = {} 30 | #volume levels to calculate dose at volume statistics 31 | volume_levels = [0, 0.01, 0.02, 0.50, 0.95, 0.98, 0.99, 1] 32 | 33 | for roi in rois: 34 | name = roi.Name 35 | #calculate the dose at relative volumes for each ROI 36 | dose_atv = list(totaldose.GetDoseAtRelativeVolumes(RoiName = name, 37 | RelativeVolumes=volume_levels)) 38 | #for each ROI, store dose statistics in a dictionary 39 | roi_dic[name] = {'mean dose': totaldose.GetDoseStatistic(RoiName=name, DoseType='Average'), 40 | 'minimum dose': totaldose.GetDoseStatistic(RoiName=name, DoseType='Min'), 41 | 'maximum dose': totaldose.GetDoseStatistic(RoiName=name, DoseType='Max'), 42 | 'mean fraction dose': fdose.GetDoseStatistic(RoiName=name, DoseType='Average'), 43 | 'minimum fraction dose': fdose.GetDoseStatistic(RoiName=name, DoseType='Min'), 44 | 'maximum fraction dose': fdose.GetDoseStatistic(RoiName=name, DoseType='Max'), 45 | 'dose at volume': dose_atv, 46 | 'ROI volume': roi_geometry[name].GetRoiVolume() #cm^4 47 | } 48 | 49 | #save the data 50 | file_name = patient.Name + 'ROI_dose' + '.pickle' 51 | with open(file_name, 'wb') as f: 52 | pickle.dump(roi_dic, f) -------------------------------------------------------------------------------- /plan_evaluation/get_dvhs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Fri May 15 00:29:11 2020 5 | @author: Edith Villegas 6 | 7 | Get DVHs from ROIs 8 | """ 9 | 10 | from connect import * 11 | import pickle 12 | 13 | #get current patient, case, plan and beam set 14 | patient = get_current("Patient") 15 | case = get_current("Case") 16 | examination = get_current("Examination") 17 | plan = get_current("Plan") 18 | beam_set = get_current("BeamSet") 19 | 20 | #dose parameters 21 | prescribed_dose = beam_set.Prescription.PrimaryDosePrescription.DoseValue 22 | 23 | #DVH dose range 24 | start_dose = int(prescribed_dose*0) 25 | stop_dose = int(prescribed_dose*1.10) 26 | step_size = int(prescribed_dose*0.01) 27 | 28 | #plan dose 29 | totaldose = plan.TreatmentCourse.TotalDose 30 | 31 | #case ROIs 32 | rois = case.PatientModel.RegionsOfInterest 33 | roi_geometry = case.PatientModel.StructureSets[examination.Name].RoiGeometries 34 | 35 | #dose axis for DVH 36 | dose_levels = [x for x in range(start_dose, stop_dose+step_size, step_size)] 37 | 38 | #calculate volumes at dose 39 | dvh_set = {} 40 | 41 | for roi in rois: 42 | roiname = roi.Name 43 | volumes = totaldose.GetRelativeVolumeAtDoseValues(RoiName=roiname, 44 | DoseValues=dose_levels) 45 | dvh_set[roiname] = {'volume levels' : list(volumes), 46 | 'absolute volume': roi_geometry[roiname].GetRoiVolume(), 47 | 'ROI type': roi.Type} 48 | 49 | #save the data 50 | file_name = patient.Name+ 'DVH' + '.pickle' 51 | with open(file_name, 'wb') as f: 52 | pickle.dump(dvh_set, f) -------------------------------------------------------------------------------- /plan_evaluation/get_dvhs_all_plans.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Fri May 15 00:29:11 2020 5 | @author: Edith Villegas 6 | 7 | Get DVHs of all ROIs from all plans in current patient case 8 | """ 9 | 10 | from connect import * 11 | import pickle 12 | 13 | #get current patient, case and examination 14 | patient = get_current("Patient") 15 | case = get_current("Case") 16 | examination = get_current("Examination") 17 | 18 | #get plan list 19 | plans = case.TreatmentPlans 20 | 21 | #case ROIs 22 | rois = case.PatientModel.RegionsOfInterest 23 | roi_geometry = case.PatientModel.StructureSets[examination.Name].RoiGeometries 24 | 25 | #empty dictionary to store DVH 26 | dvh_set = {} 27 | 28 | for plan in plans: 29 | 30 | #plan dose 31 | totaldose = plan.TreatmentCourse.TotalDose 32 | beam_set = plan.BeamSets[0] 33 | 34 | #dose parameters 35 | try: 36 | prescribed_dose = beam_set.Prescription.PrimaryDosePrescription.DoseValue 37 | except: 38 | prescribed_dose = 8000 #if there is no dose prescription, take this 39 | 40 | #DVH dose range 41 | start_dose = int(prescribed_dose*0) 42 | stop_dose = int(prescribed_dose*1.10) 43 | step_size = int(prescribed_dose*0.01) 44 | 45 | #dose axis for DVH 46 | dose_levels = [x for x in range(start_dose, stop_dose+step_size, step_size)] 47 | 48 | #calculate volumes at dose for each ROI 49 | dvh_set[plan.Name] = {} 50 | for roi in rois: 51 | roiname = roi.Name 52 | volumes = totaldose.GetRelativeVolumeAtDoseValues(RoiName=roiname, 53 | DoseValues=dose_levels) 54 | dvh_set[plan.Name][roiname] = {'volume levels' : list(volumes), 55 | 'dose levels': dose_levels, 56 | 'absolute volume': roi_geometry[roiname].GetRoiVolume(), 57 | 'ROI type': roi.Type, 'prescribed dose': prescribed_dose} 58 | 59 | #save the data 60 | file_name = patient.Name+ 'DVH_all_plans' + '.pickle' 61 | with open(file_name, 'wb') as f: 62 | pickle.dump(dvh_set, f) -------------------------------------------------------------------------------- /roi_poi_scripts/isocenter_poi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Tue May 12 22:58:45 2020 5 | @author: Edith Villegas 6 | 7 | Creates a point of interest from the center of the target ROI 8 | """ 9 | 10 | from connect import * 11 | import sys 12 | 13 | #get current case, patient and examination 14 | case = get_current("Case") 15 | patient = get_current("Patient") 16 | examination = get_current("Examination") 17 | 18 | ptv_name = 'PTV_ICTP' #name of the target ROI 19 | poi_name = "isocenter" 20 | 21 | # Get ROI geometries 22 | roi_geometries = case.PatientModel.StructureSets[examination.Name].RoiGeometries 23 | 24 | #get the center of the PTV ROI 25 | try: 26 | ptv_center = roi_geometries[ptv_name].GetCenterOfRoi() 27 | except: 28 | print 'Cannot access center of ROI {0}. Exiting script.'.format(ptv_name) 29 | sys.exit() 30 | 31 | #save the coordinates in a dictionary 32 | isocenter_coordinates = {'x':ptv_center.x, 'y':ptv_center.y, 'z':ptv_center.z} 33 | 34 | #delete POI if it already exists 35 | pois = case.PatientModel.PointsOfInterest 36 | try: 37 | pois[poi_name].DeleteRoi() 38 | print 'deleted previous POI' 39 | except: 40 | print 'creating POI' 41 | 42 | #Create a POI from the coordinates 43 | with CompositeAction('Create isocenter POI'): 44 | isocenter_poi = case.PatientModel.CreatePoi(Examination=examination, 45 | Point=isocenter_coordinates, 46 | Volume=0, Name=poi_name, Color="Red", VisualizationDiameter=1, 47 | Type="Isocenter") 48 | 49 | patient.Save() #save changes (created POI) -------------------------------------------------------------------------------- /roi_poi_scripts/overlapping_structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | IronPython 2.7 4 | Created on Wed May 13 21:27:33 2020 5 | @author: Edith Villegas 6 | 7 | 1.Checks which ROIs overlap with the PTV (plus a margin), 8 | 2.Creates control structures that dont overlap with the PTV, for each of the 9 | overlapping ROIs. 10 | """ 11 | 12 | from connect import * 13 | import sys 14 | 15 | #ROIs to exclude from the comparisons 16 | ptv_name = 'PTV_ICTP' 17 | ctv_name = 'CTV_ICTP' 18 | external_name = 'External_ICTP' 19 | exclusion_list = [ptv_name, ctv_name, external_name] 20 | #margin (maximum distance to PTV) 21 | margin = 0.5 #cm 22 | 23 | #get current case and examination 24 | case = get_current("Case") 25 | examination = get_current("Examination") 26 | 27 | #structures in the current examination 28 | structures = case.PatientModel.StructureSets[examination.Name] 29 | #ROIs in the current case 30 | rois = case.PatientModel.RegionsOfInterest 31 | 32 | #1------------------------------------------------------------------------------ 33 | #create empty dictionary to store results 34 | roi_distances = {} 35 | 36 | #loop over all rois to calculate distance to PTV 37 | for roi in rois: 38 | if roi.Name in exclusion_list : #exclude ROIs from the comparison 39 | continue 40 | else: 41 | distance = structures.RoiSurfaceToSurfaceDistanceBasedOnDT( 42 | ReferenceRoiName = ptv_name, TargetRoiName = roi.Name) 43 | roi_distances[roi.Name] = distance['Min'] #save just the min distance 44 | 45 | #select the ROIs that are within a certain distance of the PTV 46 | selected_rois = [] 47 | 48 | for roi in roi_distances: 49 | if roi_distances[roi]