def add_required_elements_to_ds(ds: FileDataset): dt = datetime.datetime.now() # Append data elements required by the DICOM standarad ds.SpecificCharacterSet = 'ISO_IR 100' ds.InstanceCreationDate = dt.strftime('%Y%m%d') ds.InstanceCreationTime = dt.strftime('%H%M%S.%f') ds.StructureSetLabel = 'RTstruct' ds.StructureSetDate = dt.strftime('%Y%m%d') ds.StructureSetTime = dt.strftime('%H%M%S.%f') ds.Modality = 'RTSTRUCT' ds.Manufacturer = 'Qurit Lab' ds.ManufacturerModelName = 'rt-utils' ds.InstitutionName = 'BC Cancer Research Center' # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Set values already defined in the file meta ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID ds.ApprovalStatus = 'UNAPPROVED'
def add_required_elements_to_ds(ds: FileDataset): dt = datetime.datetime.now() # Append data elements required by the DICOM standarad ds.SpecificCharacterSet = "ISO_IR 100" ds.InstanceCreationDate = dt.strftime("%Y%m%d") ds.InstanceCreationTime = dt.strftime("%H%M%S.%f") ds.StructureSetLabel = "RTstruct" ds.StructureSetDate = dt.strftime("%Y%m%d") ds.StructureSetTime = dt.strftime("%H%M%S.%f") ds.Modality = "RTSTRUCT" ds.Manufacturer = "Qurit" ds.ManufacturerModelName = "rt-utils" ds.InstitutionName = "Qurit" # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Set values already defined in the file meta ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID ds.ApprovalStatus = "UNAPPROVED"
def convert_struct(plan, export_path): # Check that the plan has a primary image, as we can't create a meaningful RTSTRUCT without it: if not plan.primary_image: plan.logger.error( "No primary image found for plan. Unable to generate RTSTRUCT.") return patient_info = plan.pinnacle.patient_info struct_sop_instuid = plan.struct_inst_uid # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = RTStructSOPClassUID file_meta.TransferSyntaxUID = GTransferSyntaxUID file_meta.MediaStorageSOPInstanceUID = struct_sop_instuid file_meta.ImplementationClassUID = GImplementationClassUID struct_filename = "RS." + struct_sop_instuid + ".dcm" ds = FileDataset(struct_filename, {}, file_meta=file_meta, preamble=b"\x00" * 128) ds = FileDataset(struct_filename, {}, file_meta=file_meta, preamble=b"\x00" * 128) struct_series_instuid = pydicom.uid.generate_uid() ds.ReferencedStudySequence = Sequence() # not sure what I want here, going off of template dicom file ds.SpecificCharacterSet = "ISO_IR 100" ds.InstanceCreationDate = time.strftime("%Y%m%d") ds.InstanceCreationTime = time.strftime("%H%M%S") ds.SOPClassUID = RTStructSOPClassUID ds.SOPInstanceUID = struct_sop_instuid ds.Modality = RTSTRUCTModality ds.AccessionNumber = "" ds.Manufacturer = Manufacturer # from sample dicom file, maybe should change? # not sure where to get information for this element can find this and # read in from ds.StationName = "adacp3u7" # ds.ManufacturersModelName = 'Pinnacle3' ReferencedStudy1 = Dataset() ds.ReferencedStudySequence.append(ReferencedStudy1) # Study Component Management SOP Class (chosen from template) ds.ReferencedStudySequence[ 0].ReferencedSOPClassUID = "1.2.840.10008.3.1.2.3.2" ds.ReferencedStudySequence[ 0].ReferencedSOPInstanceUID = plan.primary_image.image_info[0][ "StudyInstanceUID"] ds.StudyInstanceUID = plan.primary_image.image_info[0]["StudyInstanceUID"] ds.SeriesInstanceUID = struct_series_instuid ds.PatientID = patient_info["MedicalRecordNumber"] ds.ReferringPhysiciansName = patient_info["ReferringPhysician"] ds.PhysiciansOfRecord = patient_info["RadiationOncologist"] ds.StudyDescription = patient_info["Comment"] ds.PatientSex = patient_info["Gender"][0] ds.PatientBirthDate = patient_info["DOB"] ds.StructureSetLabel = plan.plan_info["PlanName"] ds.StudyID = plan.primary_image.image["StudyID"] datetimesplit = plan.plan_info["ObjectVersion"]["WriteTimeStamp"].split() # Read more accurate date from trial file if it is available trial_info = plan.trial_info if trial_info: datetimesplit = trial_info["ObjectVersion"]["WriteTimeStamp"].split() study_date = datetimesplit[0].replace("-", "") study_time = datetimesplit[1].replace(":", "") ds.StructureSetDate = study_date ds.StructureSetTime = study_time ds.StudyDate = study_date ds.StudyTime = study_time ds.ManufacturersModelName = plan.plan_info["ToolType"] ds.SoftwareVersions = plan.plan_info["PinnacleVersionDescription"] ds.StructureSetName = "POIandROI" ds.SeriesNumber = "1" ds.PatientName = patient_info["FullName"] ds.ReferencedFrameOfReferenceSequence = Sequence() ReferencedFrameofReference = Dataset() ds.ReferencedFrameOfReferenceSequence.append(ReferencedFrameofReference) ds.ReferencedFrameOfReferenceSequence[ 0].FrameOfReferenceUID = plan.primary_image.image_info[0]["FrameUID"] ds.ReferencedFrameOfReferenceSequence[ 0].RTReferencedStudySequence = Sequence() RTReferencedStudy = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence.append( RTReferencedStudy) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPClassUID = "1.2.840.10008.3.1.2.3.2" ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPInstanceUID = plan.primary_image.image_info[0][ "StudyInstanceUID"] ds.StudyInstanceUID = plan.primary_image.image_info[0]["StudyInstanceUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence = Sequence() RTReferencedSeries = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence.append(RTReferencedSeries) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[ 0].SeriesInstanceUID = plan.primary_image.image_info[0][ "SeriesUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence = Sequence() for info in plan.primary_image.image_info: contour_image = Dataset() contour_image.ReferencedSOPClassUID = "1.2.840.10008.5.1.4.1.1.2" contour_image.ReferencedSOPInstanceUID = info["InstanceUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence.append( contour_image) ds.ROIContourSequence = Sequence() ds.StructureSetROISequence = Sequence() ds.RTROIObservationsSequence = Sequence() # Determine ISO Center find_iso_center(plan) ds = read_points(ds, plan) ds = read_roi(ds, plan) # find out where to get if its been approved or not # find out how to insert proper 'CodeString' here ds.ApprovalStatus = "UNAPPROVED" # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Save the RTDose Dicom File output_file = os.path.join(export_path, struct_filename) plan.logger.info("Creating Struct file: %s \n" % (output_file)) ds.save_as(output_file)
def main(dir, args): energies = [90 + i*5 for i in range(0,29)] # in MeV, they go from 90 to 230 in steps of 5 MeV lat_sigma = [6 for energy in energies] # in mm print(energies, lat_sigma) parser = argparse.ArgumentParser(description='Generate an ideal CT of a water cube and a dummy RTplan with certain energy layers, defined in script. Every energy layer has a single spot at (0,0)') parser.add_argument('--outdir', dest='outdir' , type=str, required=True ) parser.add_argument('--inDate', dest='inDate' , type=str, required=False, default='20191011') parser.add_argument('--stDate', dest='stDate' , type=str, required=False, default='20191011') parser.add_argument('--seDate', dest='seDate' , type=str, required=False, default='20191011') parser.add_argument('--acDate', dest='acDate' , type=str, required=False, default='20191011') parser.add_argument('--coDate', dest='coDate' , type=str, required=False, default='20191011') parser.add_argument('--inTime', dest='inTime' , type=str, required=False, default='150000') parser.add_argument('--stTime', dest='stTime' , type=str, required=False, default='150000') parser.add_argument('--seTime', dest='seTime' , type=str, required=False, default='150000') parser.add_argument('--acTime', dest='acTime' , type=str, required=False, default='150000') parser.add_argument('--coTime', dest='coTime' , type=str, required=False, default='150000') parser.add_argument('--access', dest='access' , type=str, required=False, default='0') parser.add_argument('--manuf' , dest='manuf' , type=str, required=False, default='MGH Physics Research') parser.add_argument('--station',dest='station' , type=str, required=False, default='Nashua') parser.add_argument('--institution' , dest='institution', type=str, required=False, default='rbe') parser.add_argument('--instaddr' , dest='instaddr', type=str, required=False, default='30 Fruit St, Boston MA 02114, USA') parser.add_argument('--physician' , dest='physician', type=str, required=False, default='') parser.add_argument('--stDesc', dest='stDesc' , type=str, required=False, default='For commissioning') parser.add_argument('--seDesc', dest='seDesc' , type=str, required=False, default='Water cube 50x50x50cm3') parser.add_argument('--operat', dest='operat' , type=str, required=False, default='FHG') parser.add_argument('--manmod', dest='manmod' , type=str, required=False, default='2019') parser.add_argument('--patname',dest='patname' , type=str, required=False, default='Cube^Water') parser.add_argument('--patid' , dest='patid' , type=str, required=False, default='WaterCube50') parser.add_argument('--birth' , dest='birth' , type=str, required=False, default='N/A') parser.add_argument('--age' , dest='age' , type=str, required=False, default='N/A') parser.add_argument('--sex' , dest='sex' , type=str, required=False, default='N/A') parser.add_argument('--anon' , dest='anon' , type=str, required=False, default='NO') parser.add_argument('--sw' , dest='sw' , type=str, required=False, default='pydicom1.2.2') parser.add_argument('--lastCal',dest='lastCal' , type=str, required=False, default='') parser.add_argument('--stuid' , dest='stuid' , type=str, required=False, default=uid.generate_uid()) parser.add_argument('--seuid' , dest='seuid' , type=str, required=False, default=uid.generate_uid()) parser.add_argument('--stid' , dest='stid' , type=str, required=False, default='1') parser.add_argument('--sen' , dest='sen' , type=str, required=False, default='1') parser.add_argument('--acqn' , dest='acqn' , type=str, required=False, default='1') parser.add_argument('--forUID', dest='forUID' , type=str, required=False, default=uid.generate_uid()) parser.add_argument('--RTlabel',dest='RTlabel' , type=str, required=False, default='Test') parser.add_argument('--RTname', dest='RTname' , type=str, required=False, default='Pristine test') parser.add_argument('--RTdesc', dest='RTdesc' , type=str, required=False, default='Dummy energies') parser.add_argument('--RTdate', dest='RTdate' , type=str, required=False, default='20191011') parser.add_argument('--RTtime', dest='RTtime' , type=str, required=False, default='150000') parser.add_argument('--RTgeom', dest='RTgeom' , type=str, required=False, default='PATIENT') parser.add_argument('--sadX' , dest='sadX' , type=float, required=False, default=2000.0) parser.add_argument('--sadY' , dest='sadY' , type=float, required=False, default=1800.0) parser.add_argument('--nBlocks',dest='nBlocks' , type=int, required=False, default=0) parser.add_argument('--isoX' , dest='isoX' , type=float, required=False, default=0.0) parser.add_argument('--isoY' , dest='isoY' , type=float, required=False, default=0.0) parser.add_argument('--isoZ' , dest='isoZ' , type=float, required=False, default=0.0) parser.add_argument('--snout' , dest='snout' , type=str, required=False, default='') parser.add_argument('--nRS' , dest='nRS' , type=int, required=False, default=0) parser.add_argument('--rsID' , dest='rsID' , type=str, required=False, default='') parser.add_argument('--snPos' , dest='snPos' , type=float, required=False, default=200) parser.add_argument('--seX' , dest='seX' , type=float, required=False, default=0.0) parser.add_argument('--seY' , dest='seY' , type=float, required=False, default=0.0) parser.add_argument('--seZ' , dest='seZ' , type=float, required=False, default=0.0) parser.add_argument('--ssd' , dest='ssd' , type=float, required=False, default=2000.0) parser.add_argument('--beam' , dest='beam' , type=str , required=False, nargs='?', default='G000' ) parser.add_argument('--machine' , dest='machine' , type=str , required=False, nargs='?', default='1.1') parser.add_argument('--dosimeter' , dest='dosimeter' , type=str , required=False, nargs='?', default='NP' ) parser.add_argument('--tuneid' , dest='tuneid' , type=str , required=False, nargs='?', default='Tune' ) parser.add_argument('--gangle' , dest='gangle' , type=int , required=False, nargs='?', default=0 ) args = parser.parse_args(args) # Check output dir if not os.path.exists(args.outdir): print('Creating output folder',args.outdir) os.mkdir(args.outdir) os.mkdir(os.path.join(args.outdir,'ct')) else: print('Writing to preexisting output folder',args.outdir) # Define geometry of water cube rows = 512 columns = 512 slices = 512 wrows = 1 # mm wcolumns = 1 # mm wslices = 1 # mm margin = 6 # pixels of air around water cube, so that water cube is 50cm * 50cm * 50cm cube = np.full((slices, rows, columns), -1000, dtype='int16') # -1000 HU as initialization value (air) cube[margin:-margin,margin:-margin,margin:-margin] = 0 # 0 HU cube (water) # Generate CT image for sl in range(slices): # ~ break output_file = os.path.join(args.outdir,'ct',str(sl+1)+'.dcm') image = cube[sl] print(output_file,image.shape) meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'#'CT Image Storage' meta.MediaStorageSOPInstanceUID = uid.generate_uid() meta.ImplementationClassUID = uid.PYDICOM_IMPLEMENTATION_UID meta.TransferSyntaxUID = uid.ImplicitVRLittleEndian ds = FileDataset(output_file, {}, file_meta=meta, preamble=b"\0" * 128) ds.SpecificCharacterSet = 'ISO_IR 100' ds.ImageType = ['DERIVED','SECONDARY','AXIAL'] ds.InstanceCreationDate = args.inDate ds.InstanceCreationTime = args.inTime ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID ds.StudyDate = args.stDate ds.SeriesDate = args.seDate ds.AcquisitionDate = args.acDate ds.ContentDate = args.coDate ds.StudyTime = args.stTime ds.SeriesTime = args.seTime ds.AcquisitionTime = args.acTime ds.ContentTime = args.coTime ds.AccessionNumber = args.access ds.Modality = 'CT' ds.Manufacturer = args.manuf ds.InstitutionName = args.institution ds.InstitutionAddress = args.instaddr ds.ReferringPhysicianName = args.physician ds.StationName = args.station ds.StudyDescription = args.stDesc ds.SeriesDescription = args.seDesc ds.OperatorsName = args.operat ds.ManufacturerModelName = args.manmod ds.PatientName = args.patname ds.PatientID = args.patid ds.PatientBirthDate = args.birth ds.PatientSex = args.sex ds.PatientAge = args.age ds.PatientIdentityRemoved = args.anon ds.AdditionalPatientHistory = 'Pseudo-CT' if args.anon == "YES": ds.DeidentificationMethod = "Manual" ds.SliceThickness = wslices # ~ ds.FocalSpots # ~ ds.KVP # ~ ds.DataCollectionDiameter # ~ ds.ReconstructionDiameter ds.SoftwareVersions = args.sw # ~ ds.DistanceSourceToDetector # ~ ds.DistanceSourceToPatient # ~ ds.GantryDetectorTilt # ~ ds.ExposureTime # ~ ds.XRayTubeCurrent # ~ ds.RotationDirection # ~ ds.ConvolutionKerne # ~ ds.FilterType ds.ProtocolName = 'RESEARCH' # ~ ds.ScanOptions = 'AXIAL MODE' ds.DateOfLastCalibration = args.lastCal ds.PatientPosition = 'HFS' ds.StudyInstanceUID = args.stuid ds.SeriesInstanceUID = args.seuid ds.StudyID = args.stid ds.SeriesNumber = args.sen ds.AcquisitionNumber = args.acqn ds.InstanceNumber = sl + 1 # Zero (origin) is at surface of water on anterior side, and centered in the other axes. Image position refers to center point of corner voxel. ds.ImagePositionPatient = [(-columns/2+0.5)*wcolumns,(-margin+0.5)*wrows, (slices/2-0.5-sl)*wslices] ds.ImageOrientationPatient = ['1', '0', '0', '0', '1', '0'] ds.FrameOfReferenceUID = args.forUID ds.PositionReferenceIndicator = ''#'OM' ds.SliceLocation = ds.ImagePositionPatient[2] ds.SamplesPerPixel = 1 ds.PhotometricInterpretation= 'MONOCHROME2' ds.Rows = rows ds.Columns = columns ds.PixelSpacing = [wcolumns, wrows] ds.BitsAllocated = 16 ds.BitsStored = 16 ds.HighBit = 15 ds.PixelRepresentation = 1 ds.SmallestImagePixelValue = np.amin(image).tobytes() ds.LargestImagePixelValue = np.amax(image).tobytes() ds.WindowCenter = 0 ds.WindowWidth = 1000 ds.RescaleIntercept = 0 ds.RescaleSlope = 1 ds.RescaleType = "HU" ds.PixelData = image.tobytes() # ~ ds.PixelPaddingValue = -2000 ds.save_as(output_file, False) # Generate RTplan with certain energies meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.8' #RT Ion Plan Storage meta.MediaStorageSOPInstanceUID = uid.generate_uid() meta.ImplementationClassUID = uid.PYDICOM_IMPLEMENTATION_UID meta.TransferSyntaxUID = uid.ImplicitVRLittleEndian output_file = os.path.join(args.outdir,'rtplan.dcm') ds = FileDataset(output_file, {}, file_meta=meta, preamble=b"\0" * 128) ds.SpecificCharacterSet = 'ISO_IR 100' ds.InstanceCreationDate = args.inDate ds.InstanceCreationTime = args.inTime ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID ds.StudyDate = args.stDate ds.SeriesDate = args.seDate ds.StudyTime = args.stTime ds.SeriesTime = args.seTime ds.AccessionNumber = '' ds.Modality = 'RTPLAN' ds.Manufacturer = args.manuf ds.InstitutionName = args.institution ds.ReferringPhysicianName = args.physician ds.StudyDescription = args.stDesc ds.SeriesDescription = args.seDesc ds.OperatorsName = args.operat ds.ManufacturerModelName = args.manmod ds.PatientName = args.patname ds.PatientID = args.patid ds.PatientBirthDate = args.birth ds.PatientSex = args.sex ds.PatientAge = args.age ds.PatientIdentityRemoved = args.anon if args.anon=="YES": ds.DeidentificationMethod = "Manual" ds.SoftwareVersions = args.sw ds.DateOfLastCalibration = args.lastCal ds.StudyInstanceUID = args.stuid ds.SeriesInstanceUID = args.seuid ds.StudyID = args.stid ds.SeriesNumber = args.sen ds.InstanceNumber = args.acqn ds.FrameOfReferenceUID = args.forUID ds.PositionReferenceIndicator = '' ds.RTPlanLabel = args.RTlabel ds.RTPlanName = args.RTname ds.RTPlanDescription = args.RTdesc ds.RTPlanDate = args.RTdate ds.RTPlanTime = args.RTtime ds.RTPlanGeometry = args.RTgeom ds.FractionGroupSequence = Sequence() dsfx = Dataset() dsfx.FractionGroupNumber = 1 dsfx.FractionGroupDescription = '' dsfx.NumberOfFractionsPlanned = 1 dsfx.NumberOfBeams = 1 dsfx.NumberOfBrachyApplicationSetups = 0 dsfx.ReferencedBeamSequence = Sequence() dsfx_b = Dataset() dsfx_b.BeamDoseSpecificationPoint = [args.isoX,args.isoY,args.isoZ] dsfx_b.BeamDose = 1 #dummy dsfx_b.BeamMeterset = float(len(energies)*1e9)#1e9 protons per spot dsfx_b.ReferencedBeamNumber = 1 dsfx.ReferencedBeamSequence.append(dsfx_b) ds.FractionGroupSequence.append(dsfx) ds.PatientSetupSequence = Sequence() pss = Dataset() pss.PatientPosition = 'HFS' pss.PatientSetupNumber = 1 pss.PatientSetupLabel = 'Standard' ds.PatientSetupSequence.append(pss) ds.IonBeamSequence = Sequence() be = Dataset() ds.IonBeamSequence.append(be) be.BeamName = args.beam be.IonControlPointSequence = Sequence() be.TreatmentMachineName = args.machine be.InstitutionName = args.institution be.PrimaryDosimeterUnit = args.dosimeter be.Manufacturer = args.manuf be.InstitutionName = args.institution be.ManufacturerModelName = args.machine be.InstitutionAddress = args.instaddr be.TreatmentMachineName = args.machine be.PrimaryDosimeterUnit = args.dosimeter be.BeamNumber = '1' be.BeamName = args.beam be.BeamDescription = 'Gantry from top' be.BeamType = 'STATIC' be.RadiationType = 'PROTON' be.TreatmentDeliveryType = 'TREATMENT' be.NumberOfWedges = 0 be.NumberOfCompensators = 0 be.NumberOfBoli = 0 be.NumberOfBlocks = args.nBlocks be.FinalCumulativeMetersetWeight = int(1e9*len(energies)) # 1e9 protons per spot (energy) be.NumberOfControlPoints = 2*len(energies) be.ScanMode = 'MODULATED' be.VirtualSourceAxisDistances = [args.sadX, args.sadY] if not args.snout == '': be.SnoutSequence = Sequence() sds = Dataset() sds.AccessoryCode = args.snout sds.SnoutID = args.snout be.SnoutSequence.append(sds) be.NumberOfRangeShifters = args.nRS if args.nRS == 1: be.RangeShifterSequence = Sequence() rsds = Dataset() rsds.AccessoryCode = 'Undefined Accessory Code' rsds.RangeShifterNumber = 1 rsds.RangeShifterID = args.rsID rsds.RangeShifterType = 'BINARY' be.RangeShifterSequence.append(rsds) be.NumberOfLateralSpreadingDevices = 0 be.NumberOfRangeModulators = 0 be.PatientSupportType = 'TABLE' cweight = 0 for i,energy in enumerate(energies[::-1]): for j in range(2): icpoi = Dataset() icpoi.NominalBeamEnergyUnit = 'MEV' icpoi.ControlPointIndex = i*2 + j icpoi.NominalBeamEnergy = str(energy) if j == 0: icpoi.GantryAngle = args.gangle icpoi.GantryRotationDirection = 'NONE' icpoi.BeamLimitingDeviceAngle = 0 icpoi.BeamLimitingDeviceRotationDirection = 'NONE' icpoi.PatientSupportAngle = 0 icpoi.PatientSupportRotationDirection = 'NONE' icpoi.TableTopVerticalPosition = 0 icpoi.TableTopLongitudinalPosition = 0 icpoi.TableTopLateralPosition = 0 icpoi.IsocenterPosition = [args.isoX,args.isoY,args.isoZ] icpoi.SurfaceEntryPoint = [args.seX,args.seY,args.seZ] icpoi.SourceToSurfaceDistance = args.ssd icpoi.CumulativeMetersetWeight = cweight if j == 0: icpoi.TableTopPitchAngle = 0 icpoi.TableTopPitchRotationDirection = 'NONE' icpoi.TableTopRollAngle = 0 icpoi.TableTopRollRotationDirection = 'NONE' icpoi.GantryPitchAngle = 0.0 icpoi.GantryPitchRotationDirection = 'NONE' icpoi.SnoutPosition = args.snPos icpoi.ScanSpotTuneID = args.tuneid icpoi.NumberOfScanSpotPositions = 1 icpoi.ScanSpotPositionMap = [0.0, 0.0] icpoi.ScanSpotMetersetWeights = 1e9 if j == 0 else 0 cweight += icpoi.ScanSpotMetersetWeights icpoi.ScanningSpotSize = [lat_sigma[len(energies)-i-1],lat_sigma[len(energies)-i-1]] icpoi.NumberOfPaintings = 1 be.IonControlPointSequence.append(icpoi) be.ReferencedPatientSetupNumber = 1 be.ReferencedToleranceTableNumber = 0 ds.ApprovalStatus = 'UNAPPROVED' # ~ ds.ReviewDate = args.inDate # ~ ds.ReviewTime = args.inTime # ~ ds.ReviewerName = 'You' ds.save_as(output_file, False) print('Done, saved to',output_file)
def convert_plan(plan, export_path): # Check that the plan has a primary image, as we can't create a meaningful RTPLAN without it: if not plan.primary_image: plan.logger.error( "No primary image found for plan. Unable to generate RTPLAN.") return # TODO Fix the RTPLAN export functionality and remove this warning plan.logger.warning( "RTPLAN export functionality is currently not validated and not stable. Use with caution." ) patient_info = plan.pinnacle.patient_info plan_info = plan.plan_info trial_info = plan.trial_info image_info = plan.primary_image.image_info[0] machine_info = plan.machine_info patient_position = plan.patient_position # Get the UID for the Plan planInstanceUID = plan.plan_inst_uid # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = RTPlanSOPClassUID file_meta.TransferSyntaxUID = GTransferSyntaxUID file_meta.MediaStorageSOPInstanceUID = planInstanceUID file_meta.ImplementationClassUID = GImplementationClassUID # Create the FileDataset instance (initially no data elements, but # file_meta supplied) RPfilename = "RP." + file_meta.MediaStorageSOPInstanceUID + ".dcm" ds = FileDataset(RPfilename, {}, file_meta=file_meta, preamble=b"\x00" * 128) ds.SpecificCharacterSet = "ISO_IR 100" ds.InstanceCreationDate = time.strftime("%Y%m%d") ds.InstanceCreationTime = time.strftime("%H%M%S") ds.SOPClassUID = RTPlanSOPClassUID # RT Plan Storage ds.SOPInstanceUID = planInstanceUID datetimesplit = plan_info["ObjectVersion"]["WriteTimeStamp"].split() datetimesplit = plan_info["ObjectVersion"]["WriteTimeStamp"].split() # Read more accurate date from trial file if it is available trial_info = plan.trial_info if trial_info: datetimesplit = trial_info["ObjectVersion"]["WriteTimeStamp"].split() ds.StudyDate = datetimesplit[0].replace("-", "") ds.StudyTime = datetimesplit[1].replace(":", "") ds.AccessionNumber = "" ds.Modality = RTPLANModality ds.Manufacturer = Manufacturer ds.OperatorsName = "" ds.ManufacturersModelName = plan_info["ToolType"] ds.SoftwareVersions = [plan_info["PinnacleVersionDescription"]] ds.PhysiciansOfRecord = patient_info["RadiationOncologist"] ds.PatientName = patient_info["FullName"] ds.PatientBirthDate = patient_info["DOB"] ds.PatientID = patient_info["MedicalRecordNumber"] ds.PatientSex = patient_info["Gender"][0] ds.StudyInstanceUID = image_info["StudyInstanceUID"] ds.SeriesInstanceUID = planInstanceUID ds.StudyID = plan.primary_image.image["StudyID"] ds.FrameOfReferenceUID = image_info["FrameUID"] ds.PositionReferenceIndicator = "" ds.RTPlanLabel = plan.plan_info["PlanName"] + ".0" ds.RTPlanName = plan.plan_info["PlanName"] ds.RTPlanDescription = plan.pinnacle.patient_info["Comment"] ds.RTPlanDate = ds.StudyDate ds.RTPlanTime = ds.StudyTime # ds.PlanIntent = "" #Not sure where to get this informationd, will likely # be 'CURATIVE' or 'PALIATIVE' ds.RTPlanGeometry = "PATIENT" # ds.DoseReferenceSequence = Sequence() #figure out what goes in DoseReferenceSequence... Should be like a target volume and reference point I think... # ds.ToleranceTableSequence = Sequence() #figure out where to get this # information ds.FractionGroupSequence = Sequence() ds.BeamSequence = Sequence() ds.PatientSetupSequence = Sequence() # need one per beam ds.ReferencedStructureSetSequence = Sequence() ReferencedStructureSet1 = Dataset() ds.ReferencedStructureSetSequence.append(ReferencedStructureSet1) ds.ReferencedStructureSetSequence[ 0].ReferencedSOPClassUID = RTStructSOPClassUID ds.ReferencedStructureSetSequence[ 0].ReferencedSOPInstanceUID = plan.struct_inst_uid ds.ApprovalStatus = "UNAPPROVED" # find out where to get this information ds.FractionGroupSequence.append(Dataset()) ds.FractionGroupSequence[0].ReferencedBeamSequence = Sequence() metersetweight = ["0"] num_fractions = 0 beam_count = 0 beam_list = trial_info["BeamList"] if trial_info["BeamList"] else [] if len(beam_list) == 0: plan.logger.warning( "No Beams found in Trial. Unable to generate RTPLAN.") return None for beam in beam_list: beam_count = beam_count + 1 plan.logger.info("Exporting Plan for beam: " + beam["Name"]) ds.PatientSetupSequence.append(Dataset()) ds.PatientSetupSequence[beam_count - 1].PatientPosition = patient_position ds.PatientSetupSequence[beam_count - 1].PatientSetupNumber = beam_count ds.FractionGroupSequence[0].ReferencedBeamSequence.append(Dataset()) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].ReferencedBeamNumber = beam_count ds.BeamSequence.append(Dataset()) # figure out what to put here ds.BeamSequence[beam_count - 1].Manufacturer = Manufacturer ds.BeamSequence[beam_count - 1].BeamNumber = beam_count ds.BeamSequence[beam_count - 1].TreatmentDeliveryType = "TREATMENT" ds.BeamSequence[beam_count - 1].ReferencedPatientSetupNumber = beam_count ds.BeamSequence[beam_count - 1].SourceAxisDistance = "1000" ds.BeamSequence[beam_count - 1].FinalCumulativeMetersetWeight = "1" ds.BeamSequence[beam_count - 1].PrimaryDosimeterUnit = "MU" ds.BeamSequence[beam_count - 1].PrimaryFluenceModeSequence = Sequence() ds.BeamSequence[beam_count - 1].PrimaryFluenceModeSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].PrimaryFluenceModeSequence[0].FluenceMode = "STANDARD" ds.BeamSequence[beam_count - 1].BeamName = beam["FieldID"] ds.BeamSequence[beam_count - 1].BeamDescription = beam["Name"] if "Photons" in beam["Modality"]: ds.BeamSequence[beam_count - 1].RadiationType = "PHOTON" elif "Electrons" in beam["Modality"]: ds.BeamSequence[beam_count - 1].RadiationType = "ELECTRON" else: ds.BeamSequence[beam_count - 1].RadiationType = "" if "STATIC" in beam["SetBeamType"].upper(): ds.BeamSequence[beam_count - 1].BeamType = beam["SetBeamType"].upper() else: ds.BeamSequence[beam_count - 1].BeamType = "DYNAMIC" ds.BeamSequence[beam_count - 1].TreatmentMachineName = beam[ "MachineNameAndVersion"].partition(":")[0] doserefpt = None for point in plan.points: if point["Name"] == beam["PrescriptionPointName"]: doserefpt = plan.convert_point(point) plan.logger.debug("Dose reference point found: " + point["Name"]) if not doserefpt: plan.logger.debug("No dose reference point, setting to isocenter") doserefpt = plan.iso_center plan.logger.debug("Dose reference point: " + str(doserefpt)) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamDoseSpecificationPoint = doserefpt ds.BeamSequence[beam_count - 1].ControlPointSequence = Sequence() cp_manager = {} if "CPManagerObject" in beam["CPManager"]: cp_manager = beam["CPManager"]["CPManagerObject"] else: cp_manager = beam["CPManager"] numctrlpts = cp_manager["NumberOfControlPoints"] currentmeterset = 0.0 plan.logger.debug("Number of control points: " + str(numctrlpts)) x1 = "" x2 = "" y1 = "" y2 = "" leafpositions = [] for cp in cp_manager["ControlPointList"]: metersetweight.append(cp["Weight"]) if x1 == "": x1 = -cp["LeftJawPosition"] * 10 if x2 == "": x2 = cp["RightJawPosition"] * 10 if y2 == "": y2 = cp["TopJawPosition"] * 10 if y1 == "": y1 = -cp["BottomJawPosition"] * 10 points = cp["MLCLeafPositions"]["RawData"]["Points[]"].split(",") p_count = 0 leafpositions1 = [] leafpositions2 = [] for p in points: leafpoint = float(p.strip()) # logger.debug("leafpoints: ", leafpoints) if p_count % 2 == 0: leafpositions1.append(-leafpoint * 10) else: leafpositions2.append(leafpoint * 10) p_count += 1 if p_count == len(points): leafpositions1 = list(reversed(leafpositions1)) leafpositions2 = list(reversed(leafpositions2)) leafpositions = leafpositions1 + leafpositions2 gantryangle = cp["Gantry"] colangle = cp["Collimator"] psupportangle = cp["Couch"] numwedges = 0 if (cp["WedgeContext"]["WedgeName"] == "No Wedge" or cp["WedgeContext"]["WedgeName"] == ""): wedgeflag = False plan.logger.debug("Wedge is no name") numwedges = 0 elif ("edw" in cp["WedgeContext"]["WedgeName"] or "EDW" in cp["WedgeContext"]["WedgeName"]): plan.logger.debug("Wedge present") wedgetype = "DYNAMIC" wedgeflag = True numwedges = 1 wedgeangle = cp["WedgeContext"]["Angle"] wedgeinorout = "" wedgeinorout = cp["WedgeContext"]["Orientation"] if "WedgeBottomToTop" == wedgeinorout: wedgename = (cp["WedgeContext"]["WedgeName"].upper() + wedgeangle + "IN") wedgeorientation = ( "0") # temporary until I find out what to put here elif "WedgeTopToBottom" == wedgeinorout: wedgename = (cp["WedgeContext"]["WedgeName"].upper() + wedgeangle + "OUT") wedgeorientation = "180" plan.logger.debug("Wedge name = ", wedgename) elif "UP" in cp["WedgeContext"]["WedgeName"]: plan.logger.debug("Wedge present") wedgetype = "STANDARD" wedgeflag = True numwedges = 1 wedgeangle = cp["WedgeContext"]["Angle"] wedgeinorout = "" wedgeinorout = cp["WedgeContext"]["Orientation"] if int(wedgeangle) == 15: numberinname = "30" elif int(wedgeangle) == 45: numberinname = "20" elif int(wedgeangle) == 30: numberinname = "30" elif int(wedgeangle) == 60: numberinname = "15" if "WedgeRightToLeft" == wedgeinorout: wedgename = "W" + str( int(wedgeangle)) + "R" + numberinname # + "U" wedgeorientation = ( "90") # temporary until I find out what to put here elif "WedgeLeftToRight" == wedgeinorout: wedgename = "W" + str( int(wedgeangle)) + "L" + numberinname # + "U" wedgeorientation = "270" elif "WedgeTopToBottom" == wedgeinorout: wedgename = ("W" + str(int(wedgeangle)) + "OUT" + numberinname) # + "U" wedgeorientation = ( "180") # temporary until I find out what to put here elif "WedgeBottomToTop" == wedgeinorout: wedgename = ("W" + str(int(wedgeangle)) + "IN" + numberinname) # + "U" wedgeorientation = ( "0") # temporary until I find out what to put here plan.logger.debug("Wedge name = ", wedgename) # Get the prescription for this beam prescription = [ p for p in trial_info["PrescriptionList"] if p["Name"] == beam["PrescriptionName"] ][0] # Get the machine name and version and energy name for this beam machinenameandversion = beam["MachineNameAndVersion"].split(": ") machinename = machinenameandversion[0] machineversion = machinenameandversion[1] machineenergyname = beam["MachineEnergyName"] beam_energy = re.findall(r"[-+]?\d*\.\d+|\d+", beam["MachineEnergyName"])[0] # Find the DosePerMuAtCalibration parameter from the machine data dose_per_mu_at_cal = -1 if (machine_info["Name"] == machinename and machine_info["VersionTimestamp"] == machineversion): for energy in machine_info["PhotonEnergyList"]: if energy["Name"] == machineenergyname: dose_per_mu_at_cal = energy["PhysicsData"]["OutputFactor"][ "DosePerMuAtCalibration"] plan.logger.debug("Using DosePerMuAtCalibration of: " + str(dose_per_mu_at_cal)) prescripdose = beam["MonitorUnitInfo"]["PrescriptionDose"] normdose = beam["MonitorUnitInfo"]["NormalizedDose"] if normdose == 0: ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamMeterset = 0 else: ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamDose = (prescripdose / 100) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamMeterset = prescripdose / (normdose * dose_per_mu_at_cal) gantryrotdir = "NONE" if ( "GantryIsCCW" in cp_manager ): # This may be a problem here!!!! Not sure how to Pinnacle does this, could be 1 if CW, must be somewhere that states if gantry is rotating or not if cp_manager["GantryIsCCW"] == 1: gantryrotdir = "CC" if "GantryIsCW" in cp_manager: if cp_manager["GantryIsCW"] == 1: gantryrotdir = "CW" plan.logger.debug("Beam MU: " + str(ds.FractionGroupSequence[0]. ReferencedBeamSequence[beam_count - 1].BeamMeterset)) doserate = 0 if "DoseRate" in beam: # TODO What to do if DoseRate isn't available in Beam? doserate = beam["DoseRate"] if ("STEP" in beam["SetBeamType"].upper() and "SHOOT" in beam["SetBeamType"].upper()): plan.logger.debug("Using Step & Shoot") ds.BeamSequence[beam_count - 1].NumberOfControlPoints = numctrlpts * 2 ds.BeamSequence[beam_count - 1].SourceToSurfaceDistance = beam["SSD"] * 10 if numwedges > 0: ds.BeamSequence[beam_count - 1].WedgeSequence = Sequence() ds.BeamSequence[beam_count - 1].WedgeSequence.append( Dataset() ) # I am assuming only one wedge per beam (which makes sense because you can't change it during beam) ds.BeamSequence[beam_count - 1].WedgeSequence[ 0].WedgeNumber = 1 # might need to change this ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeType = wedgetype ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeAngle = wedgeangle ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeID = wedgename ds.BeamSequence[ beam_count - 1].WedgeSequence[0].WedgeOrientation = wedgeorientation ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeFactor = "" metercount = 1 for j in range(0, numctrlpts * 2): ds.BeamSequence[beam_count - 1].ControlPointSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ControlPointIndex = j ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence.append(Dataset()) if j % 2 == 1: # odd number control point currentmeterset = currentmeterset + float( metersetweight[metercount]) metercount = metercount + 1 ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].CumulativeMetersetWeight = currentmeterset ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = currentmeterset ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" if j == 0: # first control point beam meterset always zero ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].NominalBeamEnergy = beam_energy ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].DoseRateSet = doserate ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = "NONE" # logger.debug("Gantry angle list length: ", len(gantryangles)) # logger.debug("current controlpoint: ", j) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].GantryAngle = gantryangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceAngle = colangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) if numwedges > 0: ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence[0].WedgePosition = "IN" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].WedgePositionSequence[ 0].ReferencedWedgeNumber = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # This will be the x jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the y jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = [x1, x2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].LeafJawPositions = [y1, y2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the MLC ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].LeafJawPositions = leafpositions ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportAngle = psupportangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].IsocenterPosition = plan.iso_center ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = gantryrotdir else: ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset() ) # This will be the mlcs for control points other than the first ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = leafpositions ds.BeamSequence[beam_count - 1].NumberOfWedges = ( numwedges ) # this is temporary value, will read in from file later ds.BeamSequence[beam_count - 1].NumberOfCompensators = "0" # Also temporary ds.BeamSequence[beam_count - 1].NumberOfBoli = "0" ds.BeamSequence[beam_count - 1].NumberOfBlocks = "0" # Temp ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence = Sequence() ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[0].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[1].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[2].NumberOfLeafJawPairs = ( p_count / 2) bounds = [ "-200", "-190", "-180", "-170", "-160", "-150", "-140", "-130", "-120", "-110", "-100", "-95", "-90", "-85", "-80", "-75", "-70", "-65", "-60", "-55", "-50", "-45", "-40", "-35", "-30", "-25", "-20", "-15", "-10", "-5", "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200", ] ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].LeafPositionBoundaries = bounds else: plan.logger.debug("Not using Step & Shoot") ds.BeamSequence[beam_count - 1].NumberOfControlPoints = numctrlpts + 1 ds.BeamSequence[beam_count - 1].SourceToSurfaceDistance = beam["SSD"] * 10 if numwedges > 0: ds.BeamSequence[beam_count - 1].WedgeSequence = Sequence() ds.BeamSequence[beam_count - 1].WedgeSequence.append(Dataset()) # I am assuming only one wedge per beam (which makes sense # because you can't change it during beam) ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeNumber = 1 # might need to change this ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeType = wedgetype ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeAngle = wedgeangle ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeID = wedgename ds.BeamSequence[ beam_count - 1].WedgeSequence[0].WedgeOrientation = wedgeorientation ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeFactor = "" for j in range(0, numctrlpts + 1): ds.BeamSequence[beam_count - 1].ControlPointSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ControlPointIndex = j ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].CumulativeMetersetWeight = metersetweight[j] if j == 0: # first control point beam meterset always zero ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].NominalBeamEnergy = beam_energy ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].DoseRateSet = doserate ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = "NONE" # logger.debug("Gantry angle list length: ", len(gantryangles)) # logger.debug("current controlpoint: ", j) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].GantryAngle = gantryangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceAngle = colangle ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = "0" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" if numwedges > 0: WedgePosition1 = Dataset() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence.append(WedgePosition1) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence[0].WedgePosition = "IN" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].WedgePositionSequence[ 0].ReferencedWedgeNumber = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # This will be the x jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the y jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = [x1, x2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].LeafJawPositions = [y1, y2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the MLC ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].LeafJawPositions = leafpositions ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportAngle = psupportangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].IsocenterPosition = plan.iso_center ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = gantryrotdir ds.BeamSequence[beam_count - 1].NumberOfWedges = numwedges ds.BeamSequence[beam_count - 1].NumberOfCompensators = ( "0" ) # this is temporary value, will read in from file later ds.BeamSequence[beam_count - 1].NumberOfBoli = "0" # Also temporary ds.BeamSequence[beam_count - 1].NumberOfBlocks = "0" # Temp else: # This will be the mlcs for control points other than the first ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = leafpositions ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence = Sequence() ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[0].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[1].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[2].NumberOfLeafJawPairs = ( p_count / 2) bounds = [ "-200", "-190", "-180", "-170", "-160", "-150", "-140", "-130", "-120", "-110", "-100", "-95", "-90", "-85", "-80", "-75", "-70", "-65", "-60", "-55", "-50", "-45", "-40", "-35", "-30", "-25", "-20", "-15", "-10", "-5", "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200", ] ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].LeafPositionBoundaries = bounds ctrlptlist = False wedgeflag = False numwedges = 0 beginbeam = False # Get the prescription for this beam prescription = [ p for p in trial_info["PrescriptionList"] if p["Name"] == beam["PrescriptionName"] ][0] num_fractions = prescription["NumberOfFractions"] ds.FractionGroupSequence[0].FractionGroupNumber = 1 ds.FractionGroupSequence[0].NumberOfFractionsPlanned = num_fractions ds.FractionGroupSequence[0].NumberOfBeams = beam_count ds.FractionGroupSequence[0].NumberOfBrachyApplicationSetups = "0" # Save the RTPlan Dicom File output_file = os.path.join(export_path, RPfilename) plan.logger.info("Creating Plan file: %s \n" % (output_file)) ds.save_as(output_file)