def create_dicom(x, filename, sp, sz=None, f=1, study_uid=None, series_uid=None, time=datetime.datetime.now(), storage_directory=None): """ Create DICOM format output file from data create_dicom(x, filename, sp) creates a new DICOM file with a name `filename_0001.dcm' and containing data from x. The pixel scale is given by sp which is in mm. create_dicom(x, filename, sp, sz, f) creates a new DICOM file with a name formed from the given filename and the frame number f, and containing data from x. The pixel scale is given by sp, and the frame spacing is given by sz, both of which are in mm. create_dicom(x, filename, sp, sz, f, study_uid, series_uid, time) uses the DICOM UIDs study_uid and series_uid, and also the datetime, for the file. This is useful if you want to write several frames in the same DICOM series. The UIDs can be generated using the DICOMUID function. The time can be generated using datetime.datetime.now(). optional storage_directory parameter can set the file's storage directory path """ # check for inputs if sz is None: sz = sp if study_uid is None: study_uid = pydicom.uid.generate_uid() if series_uid is None: series_uid = pydicom.uid.generate_uid() # get data with the appropriate limits x = x + 1024 x = np.clip(x, 0, None) x = np.clip(x, None, 4096) file_meta = Dataset() # Initial write to create DICOM file with default settings full_filename = filename + '_' + str(f).zfill(4) + '.dcm' full_file = full_filename #add storage directory if needed if storage_directory is not None: full_filename = os.path.join(storage_directory, full_filename) series_date = time.strftime('%Y%m%d') series_time = time.strftime('%H%M%S') ds = FileDataset(full_file, {}, file_meta=file_meta, preamble=b"\0" * 128) ds.Modality = 'CT' ds.ContentDate = str(datetime.date.today()).replace('-', '') ds.ContentTime = str(time) #milliseconds since the epoch ds.StudyInstanceUID = study_uid ds.SeriesInstanceUID = series_uid ds.SOPClassUID = 'CT Image Storage' ds.StudyInstanceUID = study_uid ds.SeriesInstanceUID = series_uid ds.StudyDescription = full_file + ' Study' ds.SeriesDescription = full_file + ' Series' ds.StudyID = '1' ds.SeriesNumber = 1 ds.StudyDate = series_date ds.SeriesDate = series_date ds.AcquisitionDate = series_date ds.ContentDate = series_date ds.StudyTime = series_time ds.SeriesTime = series_time ds.AcquisitionTime = series_time ds.ContentTime = series_time ds.PatientName = full_file ds.Modality = 'CT' ds.RescaleIntercept = '-1024' ds.RescaleSlope = '1' ds.RescaleType = 'HU' ds.WindowWidth = '2000' ds.WindowCenter = '0' ds.ImagePositionPatient = [0.000, 0.000, float(f * sz)] ds.ImageOrientationPatient = [1.000, 0.000, 0.000, 0.000, 1.000, 0.000] ds.SpacingBetweenSlices = str(sz) ds.SliceThickness = str(sz) ds.GantryDetectorTilt = '0' ds.SliceLocation = str(f * sz) ds.PixelSpacing = [sp, sp] ## These are the necessary imaging components of the FileDataset object. ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.PixelRepresentation = 0 ds.HighBit = 15 ds.BitsStored = 16 ds.BitsAllocated = 16 ds.Columns = x.shape[1] ds.Rows = x.shape[0] if x.dtype != np.uint16: x = x.astype(np.uint16) ds.PixelData = x.tostring() # write final file with this metadata ds.save_as(full_filename)
def generate_dicom_from_image(image_file, **kwargs): suffix = '.dcm' filename_little_endian = tempfile.NamedTemporaryFile(suffix=suffix).name image_name = image_file img = Image.open(image_name) # required data elements # File meta info data elements file_meta = Dataset() file_meta.FileMetaInformationGroupLength = 190 file_meta.FileMetaInformationVersion = b'\x00\x01' # secondary capture image storage file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.7' file_meta.MediaStorageSOPInstanceUID = kwargs['sop_instance_uid'] file_meta.TransferSyntaxUID = '1.2.840.10008.1.2.1' # explicit VR Little-endian file_meta.ImplementationClassUID = '1.2.840.000000.1.1' # old - '1.2.840.114202.5.2' file_meta.ImplementationVersionName = 'PF.1.0.0' ds = FileDataset(filename_little_endian, {}, file_meta=file_meta, preamble=b"\0" * 128) # kwargs so far = accession, modality, procedure, tech initia, patient # name, patient id # Main data elements ds.ImageType = ['ORIGINAL', 'SECONDARY'] ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.7' ds.SOPInstanceUID = kwargs['sop_instance_uid'] ds.StudyDate = kwargs['date_of_capture'] ds.SeriesDate = kwargs['date_of_capture'] ds.AcquisitionDate = kwargs['date_of_capture'] ds.StudyTime = kwargs['time_of_capture'] ds.SeriesTime = kwargs['time_of_capture'] ds.AcquisitionTime = kwargs['time_of_capture'] ds.AccessionNumber = kwargs['accession'] ds.Modality = kwargs['modality'] ds.ConversionType = 'WSD' ds.Manufacturer = 'PACSFORM' ds.InstitutionName = 'LXA' ds.ReferringPhysicianName = '' ds.StationName = 'PACS-FORM-PC01' ds.StudyDescription = kwargs['procedure'] ds.SeriesDescription = 'PACS FORM' ds.InstitutionalDepartmentName = 'US' ds.OperatorsName = kwargs['tech_initials'] ds.ManufacturerModelName = '' ds.PatientName = kwargs['patient_name'] ds.PatientID = kwargs['patient_id'] ds.PatientBirthDate = '19850112' ds.PatientSex = '' ds.BodyPartExamined = '' ds.DeviceSerialNumber = '' ds.DateOfSecondaryCapture = kwargs['date_of_capture'] ds.TimeOfSecondaryCapture = kwargs['time_of_capture'] ds.SecondaryCaptureDeviceManufacturer = 'Shihab' ds.SecondaryCaptureDeviceManufacturerModelName = 'PACSFORM' ds.SecondaryCaptureDeviceSoftwareVersions = '1.0.0' ds.SoftwareVersions = 'V1.0.0' ds.DigitalImageFormatAcquired = '' ds.StudyInstanceUID = kwargs['study_instance_uid'] ds.SeriesInstanceUID = kwargs['series_instance_uid'] ds.StudyID = '' ds.SeriesNumber = "999" ds.InstanceNumber = "1" ds.PatientOrientation = '' ds.SamplesPerPixel = 3 # 1 for grayscale #3 for color ds.PhotometricInterpretation = 'RGB' ds.Rows = img.size[1] ds.Columns = img.size[0] ds.BitsAllocated = 8 ds.BitsStored = 7 # NEED TO FIX THIS ds.HighBit = 7 ds.PixelRepresentation = 0 ds.PixelData = img.tobytes() ds.is_implicit_VR = False ds.is_little_endian = True return ds
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 create_image_files(image, export_path): # TODO: Fix this function, output not working image.logger.warn( "Creating image files: The output of these are not correct!") patient_info = image.pinnacle.patient_info image_header = image.image_header image_info = image.image_info image_set = image.image_set currentpatientposition = image_header["patient_position"] modality = "CT" try: # Also should come from header file, but not always present modality = image_header["modality"] except: pass # Incase it is not present in header img_file = os.path.join(image.path, "ImageSet_%s.img" % (image.image["ImageSetID"])) if os.path.isfile(img_file): allframeslist = [] pixel_array = np.fromfile(img_file, dtype=np.short) # will loop over every frame for i in range(0, int(image_header["z_dim"])): frame_array = pixel_array[i * int(image_header["x_dim"]) * int(image_header["y_dim"]):(i + 1) * int(image_header["x_dim"]) * int(image_header["y_dim"])] allframeslist.append(frame_array) image.logger.debug("Length of frames list: " + str(len(allframeslist))) image.logger.debug(image_info[0]) curframe = 0 for info in image_info: sliceloc = -info["TablePosition"] * 10 instuid = info["InstanceUID"] seriesuid = info["SeriesUID"] classuid = info["ClassUID"] frameuid = info["FrameUID"] studyinstuid = info["StudyInstanceUID"] slicenum = info["SliceNumber"] dateofscan = image_set["scan_date"] timeofscan = image_set["scan_time"] file_meta = Dataset() file_meta.MediaStorageSOPClassUID = classuid file_meta.MediaStorageSOPInstanceUID = instuid file_meta.TransferSyntaxUID = GTransferSyntaxUID # this value remains static since implementation for creating # file is the same file_meta.ImplementationClassUID = GImplementationClassUID image_file_name = modality + "." + instuid + ".dcm" ds = FileDataset(image_file_name, {}, file_meta=file_meta, preamble=b"\x00" * 128) ds.SpecificCharacterSet = "ISO_IR 100" ds.ImageType = ["ORIGINAL", "PRIMARY", "AXIAL"] ds.AccessionNumber = "" ds.SOPClassUID = classuid ds.SOPInstanceUID = instuid ds.StudyDate = dateofscan ds.SeriesDate = dateofscan ds.AcquisitionDate = dateofscan ds.ContentDate = dateofscan ds.AcquisitionTime = timeofscan ds.Modality = modality # This should come from Manufacturer in header, but for some # patients it isn't set?? ds.Manufacturer = "" ds.StationName = modality ds.PatientsName = patient_info["FullName"] ds.PatientID = patient_info["MedicalRecordNumber"] ds.PatientsBirthDate = patient_info["DOB"] ds.BitsAllocated = 16 ds.BitsStored = 16 ds.HighBit = 15 ds.PixelRepresentation = 1 ds.RescaleIntercept = -1024 ds.RescaleSlope = 1.0 # ds.kvp = ?? This should be peak kilovoltage output of x ray # generator used ds.PatientPosition = currentpatientposition # this is probably x_pixdim * xdim = y_pixdim * ydim ds.DataCollectionDiameter = (float(image_header["x_pixdim"]) * 10 * float(image_header["x_dim"])) # ds.SpatialResolution = 0.35 # ??????? # # ds.DistanceSourceToDetector = #??? # # ds.DistanceSourceToPatient = #???? # ds.GantryDetectorTilt = 0.0 # ?? # ds.TableHeight = -158.0 # ?? # ds.RotationDirection = "CW" # ??? # ds.ExposureTime = 1000 # ?? # ds.XRayTubeCurrent = 398 # ?? # ds.GeneratorPower = 48 # ?? # ds.FocalSpots = 1.2 # ?? # ds.ConvolutionKernel = "STND" # ???? ds.SliceThickness = float(image_header["z_pixdim"]) * 10 ds.NumberOfSlices = int(image_header["z_dim"]) # ds.StudyInstanceUID = studyinstuid # ds.SeriesInstanceUID = seriesuid ds.FrameOfReferenceUID = info["FrameUID"] ds.StudyInstanceUID = info["StudyInstanceUID"] ds.SeriesInstanceUID = info["SeriesUID"] # problem, some of these are repeated in image file so not sure # what to do with that ds.InstanceNumber = slicenum ds.ImagePositionPatient = [ -float(image_header["x_pixdim"]) * 10 * float(image_header["x_dim"]) / 2, -float(image_header["y_pixdim"]) * 10 * float(image_header["y_dim"]) / 2, sliceloc, ] if "HFS" in currentpatientposition or "FFS" in currentpatientposition: ds.ImageOrientationPatient = [1.0, 0.0, 0.0, 0.0, 1.0, -0.0] elif "HFP" in currentpatientposition or "FFP" in currentpatientposition: ds.ImageOrientationPatient = [-1.0, 0.0, 0.0, 0.0, -1.0, -0.0] ds.PositionReferenceIndicator = "LM" # ??? ds.SliceLocation = sliceloc ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.Rows = int(image_header["x_dim"]) ds.Columns = int(image_header["y_dim"]) ds.PixelSpacing = [ float(image_header["x_pixdim"]) * 10, float(image_header["y_pixdim"]) * 10, ] ds.PixelData = allframeslist[curframe].tostring() output_file = os.path.join(export_path, image_file_name) image.logger.info("Creating image: " + output_file) ds.save_as(output_file) curframe = curframe + 1
info_mask.SOPInstanceUID= instanceuid info_mask.AccessionNumber=info.AccessionNumber info_mask.Modality='SEG' info_mask.Manufacturer='Stanford University' info_mask.ManufacturerModelName= 'ePAD Matlab' info_mask.DeviceSerialNumber='SN123456' info_mask.SoftwareVersion='1.0' info_mask.StudyInstanceUID=info.StudyInstanceUID info_mask.SeriesInstanceUID= dicomuid info_mask.SeriesNumber= 1000 info_mask.ContentDate=datetime.today().strftime('%Y%m%d') info_mask.StudyDate=info.StudyDate info_mask.SeriesDate=datetime.today().strftime('%Y%m%d') info_mask.AcquisitionDate=datetime.today().strftime('%Y%m%d') currentTime=datetime.today().strftime('%H%M%S.') f = datetime.today().strftime('%f') currentTime += str(f[0:3]) info_mask.ContentTime=currentTime info_mask.StudyTime=info.StudyTime info_mask.SeriesTime=currentTime info_mask.AcquisitionTime=currentTime info_mask.InstanceNumber= 1 info_mask.FrameOfReferenceUID= info.FrameOfReferenceUID info_mask.PositionReferenceIndicator= '' da1 = Dataset() da2 = Dataset() da3 = Dataset() da4 = Dataset()
def saveDICOM_sliced_CT(image_np, voxel_size, image_position_patient, output_path, output_name, orig_Z_pos=0, patient_ID_in='SynthAttCorr_Test', StudyInstanceUID='', SeriesInstanceUID='', TYPE_INT=np.int16, vervose=True): '''Saves a 3D numpy array as a CT DICOM file. ''' # Get info type info_bits = np.iinfo(TYPE_INT) Vol_CT_max_val = np.max(image_np) # Create output folder OUTPUT_DCM_FOLDER = os.path.join(output_path, output_name) File_mng.check_create_path('OUTPUT_DCM_FOLDER', OUTPUT_DCM_FOLDER, clear_folder=True) # get current time now = datetime.datetime.now() # Number of slices to save num_CT_slices = image_np.shape[Z] # Save each slice for idx_slice_CT in range(num_CT_slices): print('Slice: %d/%d ' % (idx_slice_CT + 1, num_CT_slices), end='') print('', end='\r') OUTPUT_DCM = os.path.join(OUTPUT_DCM_FOLDER, '%04d.dcm' % idx_slice_CT) slice_aux = image_np[:, :, idx_slice_CT] # This code was taken from the output of a valid CT file # I do not know what the long dotted UIDs mean, but this code works... file_meta = Dataset() # (0008, 0016) SOP Class UID UI: CT Image Storage file_meta.MediaStorageSOPClassUID = 'CT Image Storage' # (0008, 0018) SOP Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.3320.3273.139372384049551777032016175435 file_meta.MediaStorageSOPInstanceUID = '1.3.6.1.4.1.14519.5.2.1.3320.3273.139372384049551777032016175435' # file_meta.ImplementationClassUID = '1.2.840.10008.5.1.4.1.1.20' file_meta.TransferSyntaxUID = dicom.uid.ImplicitVRLittleEndian ds = FileDataset(OUTPUT_DCM, {}, file_meta=file_meta, preamble=b'\x00' * 128) ds.Modality = 'CT' ds.ContentDate = str(datetime.date.today()).replace('-', '') ds.ContentTime = str(time.time()) #milliseconds since the epoch # (0020, 000d) Study Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.3320.3273.231445815234682119538863169983 if StudyInstanceUID == '': StudyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.3320.3273.231445815234682119538863169983' ds.StudyInstanceUID = StudyInstanceUID # (0020, 000e) Series Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.3320.3273.330516103388011054891344582212 if SeriesInstanceUID == '': SeriesInstanceUID = '1.3.6.1.4.1.14519.5.2.1.3320.3273.330516103388011054891344582212' ds.SeriesInstanceUID = SeriesInstanceUID # (0008, 1155) Referenced SOP Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.3320.3273.275604822259752169794323488440 ds.SOPInstanceUID = '1.3.6.1.4.1.14519.5.2.1.3320.3273.2756048222597%d' % np.random.randint( low=52169794323488440, high=92169794323488440) # ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.20' # These are the necessary imaging components of the FileDataset object. # (0028, 0002) Samples per Pixel US: 1 ds.SamplesPerPixel = 1 # (0028, 0004) Photometric Interpretation CS: 'MONOCHROME2' ds.PhotometricInterpretation = "MONOCHROME2" # (0028, 0103) Pixel Representation US: 1 ds.PixelRepresentation = 1 # (0028, 0100) Bits Allocated US: 16 ds.BitsAllocated = 16 # (0028, 0101) Bits Stored US: 16 ds.BitsStored = 16 # (0028, 0102) High Bit US: 15 ds.HighBit = 15 # (0028, 0010) Rows US: 512 ds.Rows = slice_aux.shape[X] # (0028, 0011) Columns US: 512 ds.Columns = slice_aux.shape[Y] # (0028, 0030) Pixel Spacing DS: ['0.976562', '0.976562'] ds.PixelSpacing = [str(voxel_size[X]), str(voxel_size[Y])] # (0018, 0088) Spacing Between Slices DS: "3.260000" ds.SpacingBetweenSlices = b'00000' # (0018, 0050) Slice Thickness DS: "1.250000" ds.SliceThickness = str(voxel_size[Z]) ds.ImageOrientationPatient = [ '1.000000', '0.000000', '0.000000', '0.000000', '1.000000', '0.000000' ] ds.ImagePositionPatient = [ str(image_position_patient[X]).encode(), str(image_position_patient[Y]).encode(), str(orig_Z_pos + (idx_slice_CT * voxel_size[Z])).encode() ] # Otros agregados # (0008, 0050) Accession Number SH: '' ds.AccessionNumber = '' # (0008, 0022) Acquisition Date DA: '20001007' ds.AcquisitionDate = now.strftime("%Y%m%d") # (0008, 0032) Acquisition Time TM: '110409.543667' ds.AcquisitionTime = now.strftime("%H%M%S") # (0008, 0008) Image Type CS: ['ORIGINAL', 'PRIMARY', 'AXIAL'] ds.ImageType = ['ORIGINAL', 'PRIMARY', 'AXIAL'] # (0008, 0012) Instance Creation Date DA: '20001007' ds.InstanceCreationDate = now.strftime("%Y%m%d") # (0008, 0013) Instance Creation Time TM: '110451' ds.InstanceCreationTime = now.strftime("%H%M%S") # (0020, 0013) Instance Number IS: "207" ds.InstanceNumber = str(idx_slice_CT) ds.Manufacturer = 'UTN-UTT-CNEA-CONICET' # (0010, 0020) Patient ID LO: 'C3N-02285' ds.PatientID = patient_ID_in # (0010, 0010) Patient's Name PN: 'C3N-02285' ds.PatientName = output_name # (0018, 5100) Patient Position CS: 'HFS' ds.PatientPosition = 'HFS' # (0018, 1030) Protocol Name LO: '4.1 PET_WB LOW BMI' ds.ProtocolName = 'Synth. CT' # (0018, 1100) Reconstruction Diameter DS: "500.000000" ds.ReconstructionDiameter = '500.000000' # (0008, 0090) Referring Physician's Name PN: '' ds.ReferringPhysicianName = '' # (0008, 0021) Series Date DA: '20001007' ds.SeriesDate = now.strftime("%Y%m%d") ds.SeriesNumber = '' # (0008, 0031) Series Time TM: '110352' ds.SeriesTime = now.strftime("%H%M%S") # (0020, 1041) Slice Location DS: "-216.500" ds.SliceLocation = str(orig_Z_pos + (idx_slice_CT * voxel_size[Z])) # (0008, 0020) Study Date DA: '20001007' ds.StudyDate = now.strftime("%Y%m%d") # (0008, 1030) Study Description LO: 'PET WB LOW BMI' ds.StudyDescription = 'synthetic CT created from NAC PET' ds.StudyID = '' # (0008, 0030) Study Time TM: '110246' ds.StudyTime = now.strftime("%H%M%S") # (0028, 1052) Rescale Intercept DS: "-1024" ds.RescaleIntercept = "-1024" # (0028, 1053) Rescale Slope DS: "1" ds.RescaleSlope = "1" #str(Vol_CT_max_val/info_bits.max) # (0028, 1054) Rescale Type LO: 'HU' ds.RescaleType = 'HU' # slice_aux = ((slice_aux/Vol_CT_max_val)*info_bits.max)+1024 # slice_aux = (((slice_aux+1024)/Vol_CT_max_val)*info_bits.max) slice_aux = (slice_aux + 1024).astype(np.int16) # print(slice_aux.max(),slice_aux.min()) # volume_aux = np.swapaxes(volume_aux,0,1) # volume_aux = np.swapaxes(volume_aux,0,2) ds.PixelData = slice_aux.tostring() ds.save_as(OUTPUT_DCM) print('done.') return