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 generate_rtplan_beam(field): beam = Dataset() beam.BeamNumber = 1 # TODO: Required, need to auto-set beam.BeamName = "3x3 J" # TODO: Optional, want to auto-set beam.BeamDescription = "3x3 J - generated by PyMedPhys" # Optional, want to auto-set beam.BeamType = "STATIC" beam.RadiationType = "PHOTON" # TODO: Handle electrons # Primary Fluence Mode Sequence & Fluence Mode (assume 1) primary_fluence_mode_sequence = Sequence() beam.PrimaryFluenceModeSequence = primary_fluence_mode_sequence primary_fluence_mode = Dataset() primary_fluence_mode.FluenceMode = 'STANDARD' primary_fluence_mode_sequence.append(primary_fluence_mode) # beam.HighDoseTechniqueType = "HDR" # TODO: Check this - might be needed for FFF? # Treatment Machine Required for ARIA/Eclipse (Must match existing machine in # database). Should we handle this in our TPS Dose Toolbox? beam.TreatmentMachineName = "TS2_TBHD" beam.PrimaryDosimeterUnit = 'MU' # Optional, but probably should set (user?) beam.SourceAxisDistance = "1000" # ----- Beam Limiting Device Sequence ----- beam_limiting_device_sequence = Sequence() beam.BeamLimitingDeviceSequence = beam_limiting_device_sequence # Beam Limiting Device Sequence: Beam Limiting Device 1 ( X Jaws) beam_limiting_device1 = Dataset() beam_limiting_device1.RTBeamLimitingDeviceType = 'X' # or "ASYMX" beam_limiting_device1.NumberOfLeafJawPairs = "1" beam_limiting_device_sequence.append(beam_limiting_device1) # Beam Limiting Device Sequence: Beam Limiting Device 2 ( Y Jaws) beam_limiting_device2 = Dataset() beam_limiting_device2.RTBeamLimitingDeviceType = 'Y' # or "ASYMY" beam_limiting_device2.NumberOfLeafJawPairs = "1" beam_limiting_device_sequence.append(beam_limiting_device2) if HAS_MLC: # Beam Limiting Device Sequence: Beam Limiting Device 3 ( X MLC) beam_limiting_device3 = Dataset() beam_limiting_device3.RTBeamLimitingDeviceType = 'MLCX' beam_limiting_device3.NumberOfLeafJawPairs = "1" beam_limiting_device_sequence.append(beam_limiting_device3) # Beam Limiting Device Sequence: Beam Limiting Device 4 ( Y MLC) beam_limiting_device4 = Dataset() beam_limiting_device4.RTBeamLimitingDeviceType = 'MLCY' beam_limiting_device4.NumberOfLeafJawPairs = "1" beam_limiting_device_sequence.append(beam_limiting_device4) # Optional but might be best to set. beam.TreatmentDeliveryType = 'TREATMENT' # ----- Wedge Sequence TODO: Handle wedges ----- # Assume no wedge or same wedge throughout beam (0 or 1) beam.NumberOfWedges = 0 if HAS_WEDGE: wedge_sequence = Sequence() beam.WedgeSequence = wedge_sequence # Wedge Sequence: Wedge wedge = Dataset() wedge.WedgeNumber = 1 # Unique within beam wedge.WedgeType = "DYNAMIC" # or "STANDARD" or "MOTORIZED" wedge.WedgeID = "" # Optional, TODO: check if needed wedge.AccessoryCode = "" # Optional TODO: check if needed wedge.WedgeAngle = 60 wedge.WedgeFactor = "" # Required but can leave empty # Degrees relative to Beam Limiting Device TODO: (always 0 for EDW?) wedge.WedgeOrientation = 0 wedge_sequence.append(wedge) beam.NumberOfCompensators = '0' # ASSUME NO COMPENSATORS beam.NumberOfBoli = '0' # ASSUME NO BOLI # ASSUME NO BLOCKS (TODO: check if used for electrons) beam.NumberOfBlocks = '0' # ----- Applicator Sequence (electrons & SRS) TODO: Handle cones ----- if HAS_APPLICATOR: applicator_sequence = Sequence() beam.ApplicatorSequence = applicator_sequence # Applicator Sequence: Applicator applicator = Dataset() applicator.ApplicatorID = "<machine supplied ID>" # TODO: check cone ID applicator.AccessoryCode = "" # Optional TODO: check if needed applicator.ApplicatorType = "ELECTRON_SQUARE" # many others possible applicator.ApplicatorGeometrySequence = [Dataset()] # RECTANGLE and CIRCULAR also possible applicator.ApplicatorGeometrySequence[ 0].ApplicatorApertureShape = "SYM_SQUARE" # Required for SQUARE and CIRCLE = length of side or diameter applicator.ApplicatorGeometrySequence[0].ApplicatorOpening = 10 # Required if RECTANGLE applicator.ApplicatorGeometrySequence[0].ApplicatorOpeningX # Required if RECTANGLE applicator.ApplicatorGeometrySequence[0].ApplicatorOpeningY # Optional TODO: decide whether to set cone desc applicator.ApplicatorDescription = "" applicator_sequence.append(applicator) # ASSUME NO ACCESSORIES? TODO: check if this includes cutouts beam.FinalCumulativeMetersetWeight = "1" beam.NumberOfControlPoints = "2" # ----- Control Point Sequence ----- cp_sequence = Sequence() beam.ControlPointSequence = cp_sequence # Control Point Sequence: Control Point 0 cp0 = Dataset() cp0.ControlPointIndex = "0" cp0.CumulativeMetersetWeight = "0" cp0.NominalBeamEnergy = "6" # TODO: User supplied cp0.DoseRateSet = "600" # TODO: User supplied # Wedge Position Sequence if HAS_WEDGE: wedge_position_sequence = Sequence() cp0.WedgePositionSequence = wedge_position_sequence # Wedge Position Sequence: Wedge wedge_position = Dataset() wedge_position.ReferencedWedgeNumber = 1 # Assume never more than 1 wedge wedge_position.WedgePosition = "IN" # Also "OUT" wedge_position_sequence.append(wedge_position) # Beam Limiting Device Position Sequence beam_limiting_device_position_sequence = Sequence() cp0.BeamLimitingDevicePositionSequence = beam_limiting_device_position_sequence # Beam Limiting Device Position Sequence: Beam Limiting Device Position 1 beam_limiting_device_position1 = Dataset() # or "ASYMX" TODO: User supplied beam_limiting_device_position1.RTBeamLimitingDeviceType = 'X' beam_limiting_device_position1.LeafJawPositions = ['-15', '15' ] # TODO: User supplied beam_limiting_device_position_sequence.append( beam_limiting_device_position1) # Beam Limiting Device Position Sequence: Beam Limiting Device Position 2 beam_limiting_device_position2 = Dataset() # or "ASYMY" TODO: User supplied beam_limiting_device_position2.RTBeamLimitingDeviceType = 'Y' beam_limiting_device_position2.LeafJawPositions = ['-15', '15' ] # TODO: User supplied beam_limiting_device_position_sequence.append( beam_limiting_device_position2) if HAS_MLC: # TODO: Handle MLCs # Beam Limiting Device Position Sequence: Beam Limiting Device Position 3 beam_limiting_device_position3 = Dataset() beam_limiting_device_position3.RTBeamLimitingDeviceType = 'MLCX' beam_limiting_device_position3.LeafJawPositions = [ ] # TODO: Tricksy MLC stuff beam_limiting_device_position_sequence.append( beam_limiting_device_position3) # Beam Limiting Device Position Sequence: Beam Limiting Device Position 4 beam_limiting_device_position4 = Dataset() beam_limiting_device_position4.RTBeamLimitingDeviceType = 'MLCY' beam_limiting_device_position4.LeafJawPositions = [ ] # TODO: Tricksy MLC stuff beam_limiting_device_position_sequence.append( beam_limiting_device_position4) cp0.GantryAngle = "0" # TODO: User supplied, default to 0 cp0.GantryRotationDirection = 'NONE' cp0.BeamLimitingDeviceAngle = "0" # TODO: User supplied, default to 0 cp0.BeamLimitingDeviceRotationDirection = 'NONE' cp0.PatientSupportAngle = "0" cp0.PatientSupportRotationDirection = 'NONE' cp0.TableTopEccentricAngle = "0" cp0.TableTopEccentricRotationDirection = 'NONE' cp0.TableTopPitchAngle = 0.0 cp0.TableTopPitchRotationDirection = 'NONE' cp0.TableTopRollAngle = 0.0 cp0.TableTopRollRotationDirection = 'NONE' cp0.TableTopVerticalPosition = '' # Required but can leave empty cp0.TableTopLongitudinalPosition = '' # Required but can leave empty cp0.TableTopLateralPosition = '' # Required but can leave empty # Required but can leave empty TODO: not according to RS! cp0.IsocenterPosition = ['0', '0', '0'] cp0.SourceToSurfaceDistance = 900 # TODO: User supplied cp_sequence.append(cp0) # Control Point Sequence: Control Point 1 cp1 = Dataset() cp1.ControlPointIndex = "1" cp1.CumulativeMetersetWeight = "1" cp_sequence.append(cp1) # Referenced Beam Sequence: Referenced Beam 1 refd_beam = Dataset() refd_beam.ReferencedBeamNumber = "1" refd_beam.BeamMeterset = "100" # TODO: Set upon plan setup return beam, refd_beam