Exemple #1
0
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'
Exemple #2
0
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"
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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)