def convert_struct(plan, export_path): # Check that the plan has a primary image, as we can't create a meaningful RTSTRUCT without it: if not plan.primary_image: plan.logger.error( "No primary image found for plan. Unable to generate RTSTRUCT.") return patient_info = plan.pinnacle.patient_info struct_sop_instuid = plan.struct_inst_uid # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = RTStructSOPClassUID file_meta.TransferSyntaxUID = GTransferSyntaxUID file_meta.MediaStorageSOPInstanceUID = struct_sop_instuid file_meta.ImplementationClassUID = GImplementationClassUID struct_filename = "RS." + struct_sop_instuid + ".dcm" ds = FileDataset(struct_filename, {}, file_meta=file_meta, preamble=b"\x00" * 128) ds = FileDataset(struct_filename, {}, file_meta=file_meta, preamble=b"\x00" * 128) struct_series_instuid = pydicom.uid.generate_uid() ds.ReferencedStudySequence = Sequence() # not sure what I want here, going off of template dicom file ds.SpecificCharacterSet = "ISO_IR 100" ds.InstanceCreationDate = time.strftime("%Y%m%d") ds.InstanceCreationTime = time.strftime("%H%M%S") ds.SOPClassUID = RTStructSOPClassUID ds.SOPInstanceUID = struct_sop_instuid ds.Modality = RTSTRUCTModality ds.AccessionNumber = "" ds.Manufacturer = Manufacturer # from sample dicom file, maybe should change? # not sure where to get information for this element can find this and # read in from ds.StationName = "adacp3u7" # ds.ManufacturersModelName = 'Pinnacle3' ReferencedStudy1 = Dataset() ds.ReferencedStudySequence.append(ReferencedStudy1) # Study Component Management SOP Class (chosen from template) ds.ReferencedStudySequence[ 0].ReferencedSOPClassUID = "1.2.840.10008.3.1.2.3.2" ds.ReferencedStudySequence[ 0].ReferencedSOPInstanceUID = plan.primary_image.image_info[0][ "StudyInstanceUID"] ds.StudyInstanceUID = plan.primary_image.image_info[0]["StudyInstanceUID"] ds.SeriesInstanceUID = struct_series_instuid ds.PatientID = patient_info["MedicalRecordNumber"] ds.ReferringPhysiciansName = patient_info["ReferringPhysician"] ds.PhysiciansOfRecord = patient_info["RadiationOncologist"] ds.StudyDescription = patient_info["Comment"] ds.PatientSex = patient_info["Gender"][0] ds.PatientBirthDate = patient_info["DOB"] ds.StructureSetLabel = plan.plan_info["PlanName"] ds.StudyID = plan.primary_image.image["StudyID"] datetimesplit = plan.plan_info["ObjectVersion"]["WriteTimeStamp"].split() # Read more accurate date from trial file if it is available trial_info = plan.trial_info if trial_info: datetimesplit = trial_info["ObjectVersion"]["WriteTimeStamp"].split() study_date = datetimesplit[0].replace("-", "") study_time = datetimesplit[1].replace(":", "") ds.StructureSetDate = study_date ds.StructureSetTime = study_time ds.StudyDate = study_date ds.StudyTime = study_time ds.ManufacturersModelName = plan.plan_info["ToolType"] ds.SoftwareVersions = plan.plan_info["PinnacleVersionDescription"] ds.StructureSetName = "POIandROI" ds.SeriesNumber = "1" ds.PatientName = patient_info["FullName"] ds.ReferencedFrameOfReferenceSequence = Sequence() ReferencedFrameofReference = Dataset() ds.ReferencedFrameOfReferenceSequence.append(ReferencedFrameofReference) ds.ReferencedFrameOfReferenceSequence[ 0].FrameOfReferenceUID = plan.primary_image.image_info[0]["FrameUID"] ds.ReferencedFrameOfReferenceSequence[ 0].RTReferencedStudySequence = Sequence() RTReferencedStudy = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence.append( RTReferencedStudy) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPClassUID = "1.2.840.10008.3.1.2.3.2" ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPInstanceUID = plan.primary_image.image_info[0][ "StudyInstanceUID"] ds.StudyInstanceUID = plan.primary_image.image_info[0]["StudyInstanceUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence = Sequence() RTReferencedSeries = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence.append(RTReferencedSeries) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[ 0].SeriesInstanceUID = plan.primary_image.image_info[0][ "SeriesUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence = Sequence() for info in plan.primary_image.image_info: contour_image = Dataset() contour_image.ReferencedSOPClassUID = "1.2.840.10008.5.1.4.1.1.2" contour_image.ReferencedSOPInstanceUID = info["InstanceUID"] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence.append( contour_image) ds.ROIContourSequence = Sequence() ds.StructureSetROISequence = Sequence() ds.RTROIObservationsSequence = Sequence() # Determine ISO Center find_iso_center(plan) ds = read_points(ds, plan) ds = read_roi(ds, plan) # find out where to get if its been approved or not # find out how to insert proper 'CodeString' here ds.ApprovalStatus = "UNAPPROVED" # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Save the RTDose Dicom File output_file = os.path.join(export_path, struct_filename) plan.logger.info("Creating Struct file: %s \n" % (output_file)) ds.save_as(output_file)
def create_dicom_base(self): if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") if self.header_set is False: raise InputError("Header not loaded") # TODO tags + code datatypes are described here: # https://www.dabsoft.ch/dicom/6/6/#(0020,0012) # datatype codes are described here: # ftp://dicom.nema.org/medical/DICOM/2013/output/chtml/part05/sect_6.2.html meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage # Media Storage SOP Instance UID tag 0x0002,0x0003 (type UI - Unique Identifier) meta.MediaStorageSOPInstanceUID = self._ct_sop_instance_uid meta.ImplementationClassUID = "1.2.3.4" meta.TransferSyntaxUID = uid.ImplicitVRLittleEndian # Implicit VR Little Endian - Default Transfer Syntax ds = FileDataset("file", {}, file_meta=meta, preamble=b"\0" * 128) ds.PatientName = self.patient_name if self.patient_id in (None, ''): ds.PatientID = datetime.datetime.today().strftime('%Y%m%d-%H%M%S') else: ds.PatientID = self.patient_id # Patient ID tag 0x0010,0x0020 (type LO - Long String) ds.PatientSex = '' # Patient's Sex tag 0x0010,0x0040 (type CS - Code String) # Enumerated Values: M = male F = female O = other. ds.PatientBirthDate = '19010101' ds.SpecificCharacterSet = 'ISO_IR 100' ds.AccessionNumber = '' ds.is_little_endian = True ds.is_implicit_VR = True ds.SOPClassUID = '1.2.3' # !!!!!!!! # SOP Instance UID tag 0x0008,0x0018 (type UI - Unique Identifier) ds.SOPInstanceUID = self._ct_sop_instance_uid # Study Instance UID tag 0x0020,0x000D (type UI - Unique Identifier) # self._dicom_study_instance_uid may be either set in __init__ when creating new object # or set when import a DICOM file # Study Instance UID for structures is the same as Study Instance UID for CTs ds.StudyInstanceUID = self._dicom_study_instance_uid # Series Instance UID tag 0x0020,0x000E (type UI - Unique Identifier) # self._ct_dicom_series_instance_uid may be either set in __init__ when creating new object # or set when import a DICOM file # Series Instance UID for structures might be different than Series Instance UID for CTs ds.SeriesInstanceUID = self._ct_dicom_series_instance_uid # Study Instance UID tag 0x0020,0x000D (type UI - Unique Identifier) ds.FrameofReferenceUID = '1.2.3' # !!!!!!!!! ds.StudyDate = datetime.datetime.today().strftime('%Y%m%d') ds.StudyTime = datetime.datetime.today().strftime('%H%M%S') ds.PhotometricInterpretation = 'MONOCHROME2' ds.SamplesPerPixel = 1 ds.ImageOrientationPatient = ['1', '0', '0', '0', '1', '0'] ds.Rows = self.dimx ds.Columns = self.dimy ds.SliceThickness = str(self.slice_distance) ds.PixelSpacing = [self.pixel_size, self.pixel_size] # Add eclipse friendly IDs ds.StudyID = '1' # Study ID tag 0x0020,0x0010 (type SH - Short String) ds.ReferringPhysiciansName = 'py^trip' # Referring Physician's Name tag 0x0008,0x0090 (type PN - Person Name) ds.PositionReferenceIndicator = '' # Position Reference Indicator tag 0x0020,0x1040 ds.SeriesNumber = '1' # SeriesNumber tag 0x0020,0x0011 (type IS - Integer String) return ds
def add_default_elements(self): filename = os.path.join( self.save_to_dir, "compressed_instance_" + str(self.instance_cnt) + ".dcm") ds = FileDataset(filename, {}, preamble=b"\0" * 128, is_implicit_VR=self.IS_IMPLICIT_VR, is_little_endian=self.IS_LITTLE_ENDIAN) ds.SpecificCharacterSet = 'ISO_IR 100' # ds.ImageType = ['ORIGINAL', 'PRIMARY', 'VOLUME', 'NONE'] ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.77.1.6' # VL Whole Slide Microscopy Image Storage # ds.SOPInstanceUID = '1.2.276.0.7230010.3.1.4.296485376.1.1484917438.721089' ds.StudyDate = '20170120' ds.SeriesDate = '20170120' ds.ContentDate = '20170120' ds.AcquisitionDateTime = '20170120130353.000000' ds.StudyTime = '130353.000000' ds.SeriesTime = '130353.000000' ds.ContentTime = '130353.000000' ds.AccessionNumber = '123456789' ds.Modality = 'SM' ds.Manufacturer = 'MyManufacturer' ds.ReferringPhysicianName = 'SOME^PHYSICIAN' ds.ManufacturerModelName = 'MyModel' ds.VolumetricProperties = 'VOLUME' if self.JPEG_COMPRESS: ds.PatientName = 'compressed_' + self._wsi_fn_ ds.PatientID = 'compressed_' + self._wsi_fn_ else: ds.PatientName = 'uncompressed_' + self._wsi_fn_ ds.PatientID = 'uncompressed_' + self._wsi_fn_ ds.PatientBirthDate = '19700101' ds.PatientSex = 'M' ds.DeviceSerialNumber = 'MySerialNumber' ds.SoftwareVersions = 'MyVersion' ds.AcquisitionDuration = 100 ds.StudyInstanceUID = '1.2.276.0.7230010.3.1.2.296485376.1.1484917433.721084' # ds.SeriesInstanceUID = '1.2.276.0.7230010.3.1.3.296485376.1.1484917433.721085' ds.StudyID = 'NONE' # ds.SeriesNumber = 1 ds.PatientOrientation = '' ds.ImageComments = 'http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/' ds_DimensionOrganization = Dataset() ds_DimensionOrganization.DimensionOrganizationUID = '1.2.276.0.7230010.3.1.4.296485376.1.1484917433.721087' ds.DimensionOrganizationSequence = Sequence([ds_DimensionOrganization]) ds_DimensionIndex0 = Dataset() ds_DimensionIndex0.DimensionOrganizationUID = '1.2.276.0.7230010.3.1.4.296485376.1.1484917433.721087' ds_DimensionIndex0.DimensionIndexPointer = Tag(0x0048021E) ds_DimensionIndex0.FunctionalGroupPointer = Tag(0x0048021A) ds_DimensionIndex1 = Dataset() ds_DimensionIndex1.DimensionOrganizationUID = '1.2.276.0.7230010.3.1.4.296485376.1.1484917433.721087' ds_DimensionIndex1.DimensionIndexPointer = Tag(0x0048021F) ds_DimensionIndex1.FunctionalGroupPointer = Tag(0x0048021A) ds.DimensionIndexSequence = Sequence( [ds_DimensionIndex0, ds_DimensionIndex1]) ds_WholeSlideMicroscopyImageFrameType = Dataset() ds_WholeSlideMicroscopyImageFrameType.FrameType = [ 'ORIGINAL', 'PRIMARY', 'VOLUME', 'NONE' ] ds.WholeSlideMicroscopyImageFrameTypeSequence = Sequence( [ds_WholeSlideMicroscopyImageFrameType]) ds.SamplesPerPixel = 3 # ds.PhotometricInterpretation = 'RGB' ds.PhotometricInterpretation = 'MONOCHROME2' ds.PlanarConfiguration = 0 ds.Rows = self.patch_size[1] ds.Columns = self.patch_size[0] ds.BitsAllocated = 8 ds.BitsStored = 8 ds.HighBit = 7 ds.PixelRepresentation = 0 ds.BurnedInAnnotation = 'NO' ds.LossyImageCompression = '01' ds.LossyImageCompressionRatio = 10 ds.LossyImageCompressionMethod = 'ISO_10918_1' # ds.LossyImageCompressionMethod = 'ISO_14495_1' ds.ContainerIdentifier = 'CI_12345' ds.IssuerOfTheContainerIdentifierSequence = [] ds.ContainerTypeCodeSequence = [] ds.AcquisitionContextSequence = [] ds.ColorSpace = 'sRGB' ds_SpecimenDescription = Dataset() ds_SpecimenDescription.SpecimenIdentifier = 'Specimen^Identifier' ds_SpecimenDescription.SpecimenUID = '1.2.276.0.7230010.3.1.4.3252829876.4112.1426166133.871' ds_SpecimenDescription.IssuerOfTheSpecimenIdentifierSequence = [] ds_SpecimenDescription.SpecimenPreparationSequence = [] ds.SpecimenDescriptionSequence = Sequence([ds_SpecimenDescription]) ds.ImagedVolumeWidth = 15 ds.ImagedVolumeHeight = 15 ds.ImagedVolumeDepth = 1 ds_TotalPixelMatrixOrigin = Dataset() ds_TotalPixelMatrixOrigin.XOffsetInSlideCoordinateSystem = 20 ds_TotalPixelMatrixOrigin.YOffsetInSlideCoordinateSystem = 40 ds.TotalPixelMatrixOriginSequence = Sequence( [ds_TotalPixelMatrixOrigin]) ds.SpecimenLabelInImage = 'NO' ds.FocusMethod = 'AUTO' ds.ExtendedDepthOfField = 'NO' ds.ImageOrientationSlide = ['0', '-1', '0', '-1', '0', '0'] ds_OpticalPath = Dataset() ds_IlluminationTypeCode = Dataset() ds_IlluminationTypeCode.CodeValue = '111744' ds_IlluminationTypeCode.CodingSchemeDesignator = 'DCM' ds_IlluminationTypeCode.CodeMeaning = 'Brightfield illumination' ds_OpticalPath.IlluminationTypeCodeSequence = Sequence( [ds_IlluminationTypeCode]) ds_OpticalPath.ICCProfile = b'RGB' ds_OpticalPath.OpticalPathIdentifier = '1' ds_OpticalPath.OpticalPathDescription = 'Brightfield' ds_IlluminationColorCode = Dataset() ds_IlluminationColorCode.CodeValue = 'R-102C0' ds_IlluminationColorCode.CodingSchemeDesignator = 'SRT' ds_IlluminationColorCode.CodeMeaning = 'Full Spectrum' ds_OpticalPath.IlluminationColorCodeSequence = Sequence( [ds_IlluminationColorCode]) ds.OpticalPathSequence = Sequence([ds_OpticalPath]) ds_PixelMeasures = Dataset() ds_PixelMeasures.SliceThickness = 1 ds_PixelMeasures.PixelSpacing = ['0.00025', '0.00025'] PixelMeasuresSequence = Sequence([ds_PixelMeasures]) ds_OpticalPathIdentification = Dataset() ds_OpticalPathIdentification.OpticalPathIdentifier = '1' OpticalPathIdentificationSequence = Sequence( [ds_OpticalPathIdentification]) SharedFunctionalGroupsSequence = Dataset() SharedFunctionalGroupsSequence.OpticalPathIdentificationSequence = OpticalPathIdentificationSequence SharedFunctionalGroupsSequence.PixelMeasuresSequence = PixelMeasuresSequence ds.SharedFunctionalGroupsSequence = Sequence( [SharedFunctionalGroupsSequence]) 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 write_dicom(ods, anon_values, out_dir, grouping): file_meta = Dataset() file_meta.MediaStorageSOPClassUID = 'Secondary Capture Image Storage' file_meta.MediaStorageSOPInstanceUID = str(anon_values['sopID']) file_meta.ImplementationClassUID = '0.0' file_meta.TransferSyntaxUID = ods.file_meta.TransferSyntaxUID if "TransferSyntaxUID" in ods.file_meta else "0.0" ds = FileDataset(anon_values['studyID'], {}, file_meta=file_meta, preamble=ods.preamble) ds.StudyInstanceUID = str(anon_values['studyID']) ds.SeriesInstanceUID = str(anon_values['seriesID']) ds.SOPInstanceUID = str(anon_values['sopID']) ds.SOPClassUID = 'Secondary Capture Image Storage' ds.AccessionNumber = str(anon_values['accession']) ds.PatientID = str(anon_values['mrn']) ds.StudyID = str(anon_values['studyID']) ds.PatientName = str(anon_values['mrn']) ds.ReferringPhysicianName = "" ds.StudyDate = "000000" ds.StudyTime = "000000" ds.PatientBirthTime = "000000.000000" ds.PatientBirthDate = "00000000" ds.PatientAge = utils.calculate_age( ods.StudyDate, ods.PatientBirthDate) if ( "StudyDate" in ods and "PatientBirthDate" in ods) else "" ds.PatientSex = ods.PatientSex if "PatientSex" in ods else "" ds.StudyDescription = ods.StudyDescription if "StudyDescription" in ods else "" ds.SeriesDescription = ods.SeriesDescription if "SeriesDescription" in ods else "" ds.Modality = ods.Modality if "Modality" in ods else "" ds.SeriesNumber = ods.SeriesNumber if "SeriesNumber" in ods else "" ds.InstanceNumber = ods.InstanceNumber if "InstanceNumber" in ods else "" ds.PlanarConfiguration = ods.PlanarConfiguration if "PlanarConfiguration" in ods else "" ds.ViewPosition = ods.ViewPosition if "ViewPosition" in ods else "" ds.PatientOrientation = ods.PatientOrientation if "PatientOrientation" in ods else "" ds.SamplesPerPixel = ods.SamplesPerPixel if "SamplesPerPixel" in ods else "" ds.PhotometricInterpretation = ods.PhotometricInterpretation if "PhotometricInterpretation" in ods else "" ds.PixelRepresentation = ods.PixelRepresentation if "PixelRepresentation" in ods else "" ds.ImagerPixelSpacing = ods.ImagerPixelSpacing if "ImagerPixelSpacing" in ods else "" ds.HighBit = ods.HighBit if "HighBit" in ods else "" ds.BitsStored = ods.BitsStored if "BitsStored" in ods else "" ds.BitsAllocated = ods.BitsAllocated if "BitsAllocated" in ods else "" ds.Columns = ods.Columns if "Columns" in ods else "" ds.Rows = ods.Rows if "Rows" in ods else "" ds.SpecificCharacterSet = ods.SpecificCharacterSet if "SpecificCharacterSet" in ods else "" ds.SecondaryCaptureDeviceManufctur = 'Python 3.X' ds.PresentationLUTShape = ods.PresentationLUTShape if "PresentationLUTShape" in ods else "" ds.KVP = ods.KVP if "KVP" in ods else "" ds.XRayTubeCurrent = ods.XRayTubeCurrent if "XRayTubeCurrent" in ods else "" ds.ExposureTime = ods.ExposureTime if "ExposureTime" in ods else "" ds.Exposure = ods.Exposure if "Exposure" in ods else "" ds.ExposureControlMode = ods.ExposureControlMode if "ExposureControlMode" in ods else "" ds.RelativeXRayExposure = ods.RelativeXRayExposure if "RelativeXRayExposure" in ods else "" ds.FocalSpots = ods.FocalSpots if "FocalSpots" in ods else "" ds.AnodeTargetMaterial = ods.AnodeTargetMaterial if "AnodeTargetMaterial" in ods else "" ds.BodyPartThickness = ods.BodyPartThickness if "BodyPartThickness" in ods else "" ds.CompressionForce = ods.CompressionForce if "CompressionForce" in ods else "" ds.PaddleDescription = ods.PaddleDescription if "PaddleDescription" in ods else "" ds.BurnedInAnnotation = "" ds.DistanceSourceToDetector = ods.DistanceSourceToDetector if "DistanceSourceToDetector" in ods else "" ds.DistanceSourceToPatient = ods.DistanceSourceToPatient if "DistanceSourceToPatient" in ods else "" ds.PositionerPrimaryAngle = ods.PositionerPrimaryAngle if "PositionerPrimaryAngle" in ods else "" ds.PositionerPrimaryAngleDirection = ods.PositionerPrimaryAngleDirection if "PositionerPrimaryAngleDirection" in ods else "" ds.PositionerSecondaryAngle = ods.PositionerSecondaryAngle if "PositionerSecondaryAngle" in ods else "" ds.ImageLaterality = ods.ImageLaterality if "ImageLaterality" in ods else "" ds.BreastImplantPresent = ods.BreastImplantPresent if "BreastImplantPresent" in ods else "" ds.Manufacturer = ods.Manufacturer if "Manufacturer" in ods else "" ds.ManufacturerModelName = ods.ManufacturerModelName if "ManufacturerModelName" in ods else "" ds.EstimatedRadiographicMagnificationFactor = ods.EstimatedRadiographicMagnificationFactor if "EstimatedRadiographicMagnificationFactor" in ods else "" ds.DateOfLastDetectorCalibration = ods.DateOfLastDetectorCalibration if "DateOfLastDetectorCalibration" in ods else "" filename = utils.clean_string('m' + str(anon_values['mrn']) + '_a' + str(anon_values['accession']) + '_st' + str(anon_values['studyID']) + "_se" + str(anon_values['seriesID']) + "_i" + str(anon_values['sopID']) + "_" + str(ds.SeriesNumber) + "_" + str(ds.InstanceNumber) + "_" + str(ds.Modality) + "_" + str(ds.ViewPosition) + ".dcm") # Create study directory, if it doesn't already exist. if grouping == 'a': utils.make_dirs(os.path.join(out_dir, str(anon_values['accession']))) out_path = os.path.join(out_dir, str(anon_values['accession']), filename) elif grouping == 's': utils.make_dirs(os.path.join(out_dir, str(anon_values['studyID']))) out_path = os.path.join(out_dir, str(anon_values['studyID']), filename) elif grouping == 'm': utils.make_dirs(os.path.join(out_dir, str(anon_values['mrn']))) out_path = os.path.join(out_dir, str(anon_values['mrn']), filename) else: out_path = os.path.join(out_dir, filename) if 'PixelData' in ods: ds.PixelData = '' pixel_array = ods.pixel_array f = h5py.File('{}.hdf5'.format(out_path[0:-4])) f.create_dataset("pixel_array", pixel_array.shape, data=pixel_array, dtype=str(pixel_array.dtype), compression="gzip", shuffle=True) ds.save_as(out_path, write_like_original=False)
def __write_dicom_series_file_nrrd(series_dataset, dst_one_case_file_path): print('start: write dicom series') if not os.path.exists(dst_one_case_file_path): os.makedirs(dst_one_case_file_path) dataset_list = [] sop_instance_info_list = __generate_sop_instance_info_nrrd(series_dataset) study_instance_uid = pydicom.uid.generate_uid() series_instance_uid = pydicom.uid.generate_uid() frame_of_reference_uid = pydicom.uid.generate_uid() volume_size = series_dataset[1]['sizes'] # [width height slices] for j in range(volume_size[2]): sop_instance_uid = sop_instance_info_list[j][1] filename = os.path.join(dst_one_case_file_path, 'CT.' + sop_instance_uid + '.dcm') file_meta = Dataset() file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' file_meta.MediaStorageSOPInstanceUID = sop_instance_uid file_meta.ImplementationClassUID = '1.2.246.352.70.2.1.7' ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) ds.InstanceNumber = sop_instance_info_list[j][0] ds.SliceLocation = sop_instance_info_list[j][2] ds.ImagePositionPatient = sop_instance_info_list[j][3] ds.fix_meta_info() ds.is_little_endian = True ds.is_implicit_VR = True ds.StudyInstanceUID = study_instance_uid ds.SeriesInstanceUID = series_instance_uid ds.FrameOfReferenceUID = frame_of_reference_uid ds.SpecificCharacterSet = 'ISO_IR 100' ds.ImageType = 'ORIGINAL\\PRIMARY\\AXIAL\\HELIX' dt = datetime.datetime.now() datestr = dt.strftime('%Y%m%d') timestr1 = dt.strftime('%H%M%S.%f') timestr2 = dt.strftime('%H%M%S') ds.InstanceCreationDate = datestr ds.InstanceCreationTime = timestr1 ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.2' ds.SOPInstanceUID = sop_instance_uid ds.StudyDate = datestr ds.SeriesDate = datestr ds.ContentDate = datestr ds.StudyTime = timestr2 ds.SeriesTime = timestr2 ds.ContentTime = timestr2 ds.AccessionNumber = '116939' ds.Modality = 'CT' ds.Manufacturer = 'Philips' ds.InstitutionName = 'Cancer Hosipital' ds.ReferringPhysicianName = '79309' ds.StationName = '-' ds.StudyDescription = '-' ds.PatientName = 'patientxxx' ds.PatientID = 'patientxxx' ds.PatientBirthDate = '19000101' ds.PatientBirthTime = '000000' ds.PatientAge = '119' ds.PatientSex = '-' ds.PatientSize = 0 ds.PatientWeight = 0 ds.PatientAddress = '-' ds.PatientComments = '-' ds.SliceThickness = series_dataset[1]['space directions'][2][2] ds.KVP = 1111111 patient_position = series_dataset[1]['space'] #if patient_position == 'left-posterior-superior': ds.PatientPosition = 'HFS' ds.StudyID = '-' ds.SeriesNumber = 1 ds.ImageOrientationPatient = '1\\0\\0\\0\\1\\0' ds.SamplesPerPixel = 1 ds.Rows = series_dataset[1]['sizes'][1] ds.Columns = series_dataset[1]['sizes'][0] spacing_x = series_dataset[1]['space directions'][0][0] spacing_y = series_dataset[1]['space directions'][1][1] ds.PixelSpacing = '%f\\%f' % (spacing_x, spacing_y) ds.BitsAllocated = 16 ds.BitsStored = 12 ds.HighBit = 11 ds.PixelRepresentation = 0 ds.WindowCenter = 60 ds.WindowWidth = 350 ds.RescaleIntercept = -1024 ds.RescaleSlope = 1 ds.PixelData = __create_pixel_data_array_nrrd(series_dataset, j) ds.save_as(filename, write_like_original=False) dataset_list.append(ds) print('end: write dicom series') return dataset_list
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
def convert_dose(plan, export_path): # Check that the plan has a primary image, as we can't create a meaningful RTDOSE without it: if not plan.primary_image: plan.logger.error( "No primary image found for plan. Unable to generate RTDOSE.") return patient_info = plan.pinnacle.patient_info plan_info = plan.plan_info trial_info = plan.trial_info machine_info = plan.machine_info image_info = plan.primary_image.image_info[0] patient_position = plan.patient_position # Get the UID for the Dose and the Plan doseInstanceUID = plan.dose_inst_uid planInstanceUID = plan.plan_inst_uid # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = RTDoseSOPClassUID file_meta.TransferSyntaxUID = GTransferSyntaxUID file_meta.MediaStorageSOPInstanceUID = doseInstanceUID file_meta.ImplementationClassUID = GImplementationClassUID # Create the FileDataset instance (initially no data elements, but # file_meta supplied) RDfilename = "RD." + file_meta.MediaStorageSOPInstanceUID + ".dcm" ds = FileDataset(RDfilename, {}, 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 = RTDoseSOPClassUID # RT Dose Storage ds.SOPInstanceUID = doseInstanceUID 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 = RTDOSEModality ds.Manufacturer = Manufacturer ds.OperatorsName = "" ds.ManufacturerModelName = 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.SliceThickness = trial_info["DoseGrid .VoxelSize .Z"] * 10 ds.SeriesInstanceUID = doseInstanceUID ds.StudyInstanceUID = image_info["StudyInstanceUID"] ds.FrameOfReferenceUID = image_info["FrameUID"] ds.StudyID = plan.primary_image.image["StudyID"] # Assume zero struct shift for now (may not the case for versions below Pinnacle 9) x_shift = 0 y_shift = 0 if patient_position == "HFP" or patient_position == "FFS": dose_origin_x = -trial_info["DoseGrid .Origin .X"] * 10 elif patient_position == "HFS" or patient_position == "FFP": dose_origin_x = trial_info["DoseGrid .Origin .X"] * 10 if patient_position == "HFS" or patient_position == "FFS": dose_origin_y = -trial_info["DoseGrid .Origin .Y"] * 10 elif patient_position == "HFP" or patient_position == "FFP": dose_origin_y = trial_info["DoseGrid .Origin .Y"] * 10 if patient_position == "HFS" or patient_position == "HFP": dose_origin_z = -trial_info["DoseGrid .Origin .Z"] * 10 elif patient_position == "FFS" or patient_position == "FFP": dose_origin_z = trial_info["DoseGrid .Origin .Z"] * 10 # Image Position (Patient) seems off, so going to calculate shift assuming # dose origin in center and I want outer edge ydoseshift = (trial_info["DoseGrid .VoxelSize .Y"] * 10 * trial_info["DoseGrid .Dimension .Y"] - trial_info["DoseGrid .VoxelSize .Y"] * 10) zdoseshift = (trial_info["DoseGrid .VoxelSize .Z"] * 10 * trial_info["DoseGrid .Dimension .Z"] - trial_info["DoseGrid .VoxelSize .Z"] * 10) if patient_position == "HFS": ds.ImagePositionPatient = [ dose_origin_x, dose_origin_y - ydoseshift, dose_origin_z - zdoseshift, ] elif patient_position == "HFP": ds.ImagePositionPatient = [ dose_origin_x, dose_origin_y + ydoseshift, dose_origin_z - zdoseshift, ] elif patient_position == "FFS": ds.ImagePositionPatient = [ dose_origin_x, dose_origin_y - ydoseshift, dose_origin_z + zdoseshift, ] elif patient_position == "FFP": ds.ImagePositionPatient = [ dose_origin_x, dose_origin_y + ydoseshift, dose_origin_z + zdoseshift, ] # Read this from CT DCM if available? if "HFS" in patient_position or "FFS" in patient_position: ds.ImageOrientationPatient = [1.0, 0.0, 0.0, 0.0, 1.0, -0.0] elif "HFP" in patient_position or "FFP" in patient_position: ds.ImageOrientationPatient = [-1.0, 0.0, 0.0, 0.0, -1.0, -0.0] # Read this from CT DCM if available ds.PositionReferenceIndicator = "" ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.NumberOfFrames = int( trial_info["DoseGrid .Dimension .Z"]) # is this Z dimension??? # Using y for Rows because that's what's in the exported dicom file for # test patient ds.Rows = int(trial_info["DoseGrid .Dimension .Y"]) ds.Columns = int(trial_info["DoseGrid .Dimension .X"]) ds.PixelSpacing = [ trial_info["DoseGrid .VoxelSize .X"] * 10, trial_info["DoseGrid .VoxelSize .Y"] * 10, ] ds.BitsAllocated = 16 # ???? ds.BitsStored = 16 # ??? ds.HighBit = 15 # ??? ds.PixelRepresentation = 0 ds.DoseUnits = "GY" # 'RELATIVE'#'GY' ds.DoseType = "PHYSICAL" ds.DoseSummationType = "PLAN" # TODO: need to look at what is required from this block ds.ReferencedRTPlanSequence = Sequence() ReferencedRTPlan1 = Dataset() ds.ReferencedRTPlanSequence.append(ReferencedRTPlan1) ds.ReferencedRTPlanSequence[0].ReferencedSOPClassUID = RTPlanSOPClassUID ds.ReferencedRTPlanSequence[0].ReferencedSOPInstanceUID = planInstanceUID ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence = Sequence() ReferencedFractionGroup1 = Dataset() ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence.append( ReferencedFractionGroup1) ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence[ 0].ReferencedBeamSequence = Sequence() ReferencedBeam1 = Dataset() ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence[ 0].ReferencedBeamSequence.append(ReferencedBeam1) ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence[ 0].ReferencedBeamSequence[0].ReferencedBeamNumber = 0 ds.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence[ 0].ReferencedFractionGroupNumber = "1" ds.TissueHeterogeneityCorrection = "IMAGE" frameoffsetvect = [] for p in range(0, int(trial_info["DoseGrid .Dimension .Z"])): frameoffsetvect.append( p * float(trial_info["DoseGrid .VoxelSize .X"] * 10)) ds.GridFrameOffsetVector = frameoffsetvect # Array in which to sum the dose values of all beams summed_pixel_values = [] # For each beam in the trial, convert the dose from the Pinnacle binary # file and sum together 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 RTDOSE.") return None for beam in beam_list: plan.logger.info("Exporting Dose for beam: " + beam["Name"]) # Get the binary file for this beam binary_id = re.findall("\\d+", beam["DoseVolume"])[0] binary_file = os.path.join( plan.path, "plan.Trial.binary." + str(binary_id).zfill(3)) # Get the prescription for this beam (need this for number of fractions) prescription = [ p for p in trial_info["PrescriptionList"] if p["Name"] == beam["PrescriptionName"] ][0] # Get the prescription point plan.logger.debug("PrescriptionPointName: {0}".format( beam["PrescriptionPointName"])) points = plan.points prescription_point = [] for p in points: if p["Name"] == beam["PrescriptionPointName"]: plan.logger.debug("Presc Point: {0} {1} {2} {3}".format( p["Name"], p["XCoord"], p["YCoord"], p["ZCoord"])) prescription_point = plan.convert_point(p) break if len(prescription_point) < 3: plan.logger.warning( "No valid prescription point found for beam! Beam will be ignored for Dose conversion. Dose will most likely be incorrect" ) continue plan.logger.debug("Presc Point Dicom: {0} {1}".format( p["Name"], prescription_point)) total_prescription = (beam["MonitorUnitInfo"]["PrescriptionDose"] * prescription["NumberOfFractions"]) plan.logger.debug("Total Prescription {0}".format(total_prescription)) # Read the dose into a grid, so that we can interpolate for the prescription point and determine the MU for the grid dose_grid = np.zeros(( trial_info["DoseGrid .Dimension .X"], trial_info["DoseGrid .Dimension .Y"], trial_info["DoseGrid .Dimension .Z"], )) spacing = [ trial_info["DoseGrid .VoxelSize .X"] * 10, trial_info["DoseGrid .VoxelSize .Y"] * 10, trial_info["DoseGrid .VoxelSize .Z"] * 10, ] origin = [ ds.ImagePositionPatient[0], ds.ImagePositionPatient[1], ds.ImagePositionPatient[2], ] if os.path.isfile(binary_file): with open(binary_file, "rb") as b: for z in range(trial_info["DoseGrid .Dimension .Z"] - 1, -1, -1): for y in range(0, trial_info["DoseGrid .Dimension .Y"]): for x in range(0, trial_info["DoseGrid .Dimension .X"]): data_element = b.read(4) value = struct.unpack(">f", data_element)[0] dose_grid[x, y, z] = value else: plan.logger.warning("Dose file not found") plan.logger.error("Skipping generating RTDOSE") return None # Get the index within that grid of the dose reference point idx = [0.0, 0.0, 0.0] for i in range(3): idx[i] = -(origin[i] - prescription_point[i]) / spacing[i] plan.logger.debug( "Index of prescription point within grid: {0}".format(idx)) # Trilinear interpolation of that point within the dose grid cgy_mu = trilinear_interpolation(idx, dose_grid) plan.logger.debug("cgy_mu: {0}".format(cgy_mu)) # Now that we have the cgy/mu value of the dose reference point, we can # extract an accurate value for MU beam_mu = (total_prescription / cgy_mu) / prescription["NumberOfFractions"] plan.logger.debug("Beam MU: {0}".format(beam_mu)) pixel_data_list = [] for z in range(trial_info["DoseGrid .Dimension .Z"] - 1, -1, -1): for y in range(0, trial_info["DoseGrid .Dimension .Y"]): for x in range(0, trial_info["DoseGrid .Dimension .X"]): value = (float(prescription["NumberOfFractions"]) * dose_grid[x, y, z] * beam_mu / 100) pixel_data_list.append(value) ds.FrameIncrementPointer = ds.data_element("GridFrameOffsetVector").tag main_pix_array = [] for h in range(0, trial_info["DoseGrid .Dimension .Z"]): pixelsforframe = [] for k in range( 0, trial_info["DoseGrid .Dimension .X"] * trial_info["DoseGrid .Dimension .Y"], ): pixelsforframe.append( float( pixel_data_list[h * trial_info["DoseGrid .Dimension .Y"] * trial_info["DoseGrid .Dimension .X"] + k])) main_pix_array = main_pix_array + list(reversed(pixelsforframe)) main_pix_array = list(reversed(main_pix_array)) # Add the values from this beam to the summed values if len(summed_pixel_values) == 0: summed_pixel_values = main_pix_array else: for i in range(0, len(summed_pixel_values)): summed_pixel_values[ i] = summed_pixel_values[i] + main_pix_array[i] # Compute the scaling factor scale = max(summed_pixel_values) / 16384 ds.DoseGridScaling = scale plan.logger.debug("Dose Grid Scaling: {0}".format(ds.DoseGridScaling)) pixel_binary_block = bytes() # Scale by the scaling factor pixelvaluelist = [] for pp, element in enumerate(summed_pixel_values, 0): if scale != 0: element = round(element / scale) else: element = 0 pixelvaluelist.append(int(element)) # Set the PixelData pixel_binary_block = struct.pack("%sh" % len(pixelvaluelist), *pixelvaluelist) ds.PixelData = pixel_binary_block # Save the RTDose Dicom File output_file = os.path.join(export_path, RDfilename) plan.logger.info("Creating Dose file: %s \n" % (output_file)) ds.save_as(output_file)
def convert_plan(plan, export_path): # Check that the plan has a primary image, as we can't create a meaningful RTPLAN without it: if not plan.primary_image: plan.logger.error( "No primary image found for plan. Unable to generate RTPLAN.") return # TODO Fix the RTPLAN export functionality and remove this warning plan.logger.warning( "RTPLAN export functionality is currently not validated and not stable. Use with caution." ) patient_info = plan.pinnacle.patient_info plan_info = plan.plan_info trial_info = plan.trial_info image_info = plan.primary_image.image_info[0] machine_info = plan.machine_info patient_position = plan.patient_position # Get the UID for the Plan planInstanceUID = plan.plan_inst_uid # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = RTPlanSOPClassUID file_meta.TransferSyntaxUID = GTransferSyntaxUID file_meta.MediaStorageSOPInstanceUID = planInstanceUID file_meta.ImplementationClassUID = GImplementationClassUID # Create the FileDataset instance (initially no data elements, but # file_meta supplied) RPfilename = "RP." + file_meta.MediaStorageSOPInstanceUID + ".dcm" ds = FileDataset(RPfilename, {}, file_meta=file_meta, preamble=b"\x00" * 128) ds.SpecificCharacterSet = "ISO_IR 100" ds.InstanceCreationDate = time.strftime("%Y%m%d") ds.InstanceCreationTime = time.strftime("%H%M%S") ds.SOPClassUID = RTPlanSOPClassUID # RT Plan Storage ds.SOPInstanceUID = planInstanceUID datetimesplit = plan_info["ObjectVersion"]["WriteTimeStamp"].split() datetimesplit = plan_info["ObjectVersion"]["WriteTimeStamp"].split() # Read more accurate date from trial file if it is available trial_info = plan.trial_info if trial_info: datetimesplit = trial_info["ObjectVersion"]["WriteTimeStamp"].split() ds.StudyDate = datetimesplit[0].replace("-", "") ds.StudyTime = datetimesplit[1].replace(":", "") ds.AccessionNumber = "" ds.Modality = RTPLANModality ds.Manufacturer = Manufacturer ds.OperatorsName = "" ds.ManufacturersModelName = plan_info["ToolType"] ds.SoftwareVersions = [plan_info["PinnacleVersionDescription"]] ds.PhysiciansOfRecord = patient_info["RadiationOncologist"] ds.PatientName = patient_info["FullName"] ds.PatientBirthDate = patient_info["DOB"] ds.PatientID = patient_info["MedicalRecordNumber"] ds.PatientSex = patient_info["Gender"][0] ds.StudyInstanceUID = image_info["StudyInstanceUID"] ds.SeriesInstanceUID = planInstanceUID ds.StudyID = plan.primary_image.image["StudyID"] ds.FrameOfReferenceUID = image_info["FrameUID"] ds.PositionReferenceIndicator = "" ds.RTPlanLabel = plan.plan_info["PlanName"] + ".0" ds.RTPlanName = plan.plan_info["PlanName"] ds.RTPlanDescription = plan.pinnacle.patient_info["Comment"] ds.RTPlanDate = ds.StudyDate ds.RTPlanTime = ds.StudyTime # ds.PlanIntent = "" #Not sure where to get this informationd, will likely # be 'CURATIVE' or 'PALIATIVE' ds.RTPlanGeometry = "PATIENT" # ds.DoseReferenceSequence = Sequence() #figure out what goes in DoseReferenceSequence... Should be like a target volume and reference point I think... # ds.ToleranceTableSequence = Sequence() #figure out where to get this # information ds.FractionGroupSequence = Sequence() ds.BeamSequence = Sequence() ds.PatientSetupSequence = Sequence() # need one per beam ds.ReferencedStructureSetSequence = Sequence() ReferencedStructureSet1 = Dataset() ds.ReferencedStructureSetSequence.append(ReferencedStructureSet1) ds.ReferencedStructureSetSequence[ 0].ReferencedSOPClassUID = RTStructSOPClassUID ds.ReferencedStructureSetSequence[ 0].ReferencedSOPInstanceUID = plan.struct_inst_uid ds.ApprovalStatus = "UNAPPROVED" # find out where to get this information ds.FractionGroupSequence.append(Dataset()) ds.FractionGroupSequence[0].ReferencedBeamSequence = Sequence() metersetweight = ["0"] num_fractions = 0 beam_count = 0 beam_list = trial_info["BeamList"] if trial_info["BeamList"] else [] if len(beam_list) == 0: plan.logger.warning( "No Beams found in Trial. Unable to generate RTPLAN.") return None for beam in beam_list: beam_count = beam_count + 1 plan.logger.info("Exporting Plan for beam: " + beam["Name"]) ds.PatientSetupSequence.append(Dataset()) ds.PatientSetupSequence[beam_count - 1].PatientPosition = patient_position ds.PatientSetupSequence[beam_count - 1].PatientSetupNumber = beam_count ds.FractionGroupSequence[0].ReferencedBeamSequence.append(Dataset()) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].ReferencedBeamNumber = beam_count ds.BeamSequence.append(Dataset()) # figure out what to put here ds.BeamSequence[beam_count - 1].Manufacturer = Manufacturer ds.BeamSequence[beam_count - 1].BeamNumber = beam_count ds.BeamSequence[beam_count - 1].TreatmentDeliveryType = "TREATMENT" ds.BeamSequence[beam_count - 1].ReferencedPatientSetupNumber = beam_count ds.BeamSequence[beam_count - 1].SourceAxisDistance = "1000" ds.BeamSequence[beam_count - 1].FinalCumulativeMetersetWeight = "1" ds.BeamSequence[beam_count - 1].PrimaryDosimeterUnit = "MU" ds.BeamSequence[beam_count - 1].PrimaryFluenceModeSequence = Sequence() ds.BeamSequence[beam_count - 1].PrimaryFluenceModeSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].PrimaryFluenceModeSequence[0].FluenceMode = "STANDARD" ds.BeamSequence[beam_count - 1].BeamName = beam["FieldID"] ds.BeamSequence[beam_count - 1].BeamDescription = beam["Name"] if "Photons" in beam["Modality"]: ds.BeamSequence[beam_count - 1].RadiationType = "PHOTON" elif "Electrons" in beam["Modality"]: ds.BeamSequence[beam_count - 1].RadiationType = "ELECTRON" else: ds.BeamSequence[beam_count - 1].RadiationType = "" if "STATIC" in beam["SetBeamType"].upper(): ds.BeamSequence[beam_count - 1].BeamType = beam["SetBeamType"].upper() else: ds.BeamSequence[beam_count - 1].BeamType = "DYNAMIC" ds.BeamSequence[beam_count - 1].TreatmentMachineName = beam[ "MachineNameAndVersion"].partition(":")[0] doserefpt = None for point in plan.points: if point["Name"] == beam["PrescriptionPointName"]: doserefpt = plan.convert_point(point) plan.logger.debug("Dose reference point found: " + point["Name"]) if not doserefpt: plan.logger.debug("No dose reference point, setting to isocenter") doserefpt = plan.iso_center plan.logger.debug("Dose reference point: " + str(doserefpt)) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamDoseSpecificationPoint = doserefpt ds.BeamSequence[beam_count - 1].ControlPointSequence = Sequence() cp_manager = {} if "CPManagerObject" in beam["CPManager"]: cp_manager = beam["CPManager"]["CPManagerObject"] else: cp_manager = beam["CPManager"] numctrlpts = cp_manager["NumberOfControlPoints"] currentmeterset = 0.0 plan.logger.debug("Number of control points: " + str(numctrlpts)) x1 = "" x2 = "" y1 = "" y2 = "" leafpositions = [] for cp in cp_manager["ControlPointList"]: metersetweight.append(cp["Weight"]) if x1 == "": x1 = -cp["LeftJawPosition"] * 10 if x2 == "": x2 = cp["RightJawPosition"] * 10 if y2 == "": y2 = cp["TopJawPosition"] * 10 if y1 == "": y1 = -cp["BottomJawPosition"] * 10 points = cp["MLCLeafPositions"]["RawData"]["Points[]"].split(",") p_count = 0 leafpositions1 = [] leafpositions2 = [] for p in points: leafpoint = float(p.strip()) # logger.debug("leafpoints: ", leafpoints) if p_count % 2 == 0: leafpositions1.append(-leafpoint * 10) else: leafpositions2.append(leafpoint * 10) p_count += 1 if p_count == len(points): leafpositions1 = list(reversed(leafpositions1)) leafpositions2 = list(reversed(leafpositions2)) leafpositions = leafpositions1 + leafpositions2 gantryangle = cp["Gantry"] colangle = cp["Collimator"] psupportangle = cp["Couch"] numwedges = 0 if (cp["WedgeContext"]["WedgeName"] == "No Wedge" or cp["WedgeContext"]["WedgeName"] == ""): wedgeflag = False plan.logger.debug("Wedge is no name") numwedges = 0 elif ("edw" in cp["WedgeContext"]["WedgeName"] or "EDW" in cp["WedgeContext"]["WedgeName"]): plan.logger.debug("Wedge present") wedgetype = "DYNAMIC" wedgeflag = True numwedges = 1 wedgeangle = cp["WedgeContext"]["Angle"] wedgeinorout = "" wedgeinorout = cp["WedgeContext"]["Orientation"] if "WedgeBottomToTop" == wedgeinorout: wedgename = (cp["WedgeContext"]["WedgeName"].upper() + wedgeangle + "IN") wedgeorientation = ( "0") # temporary until I find out what to put here elif "WedgeTopToBottom" == wedgeinorout: wedgename = (cp["WedgeContext"]["WedgeName"].upper() + wedgeangle + "OUT") wedgeorientation = "180" plan.logger.debug("Wedge name = ", wedgename) elif "UP" in cp["WedgeContext"]["WedgeName"]: plan.logger.debug("Wedge present") wedgetype = "STANDARD" wedgeflag = True numwedges = 1 wedgeangle = cp["WedgeContext"]["Angle"] wedgeinorout = "" wedgeinorout = cp["WedgeContext"]["Orientation"] if int(wedgeangle) == 15: numberinname = "30" elif int(wedgeangle) == 45: numberinname = "20" elif int(wedgeangle) == 30: numberinname = "30" elif int(wedgeangle) == 60: numberinname = "15" if "WedgeRightToLeft" == wedgeinorout: wedgename = "W" + str( int(wedgeangle)) + "R" + numberinname # + "U" wedgeorientation = ( "90") # temporary until I find out what to put here elif "WedgeLeftToRight" == wedgeinorout: wedgename = "W" + str( int(wedgeangle)) + "L" + numberinname # + "U" wedgeorientation = "270" elif "WedgeTopToBottom" == wedgeinorout: wedgename = ("W" + str(int(wedgeangle)) + "OUT" + numberinname) # + "U" wedgeorientation = ( "180") # temporary until I find out what to put here elif "WedgeBottomToTop" == wedgeinorout: wedgename = ("W" + str(int(wedgeangle)) + "IN" + numberinname) # + "U" wedgeorientation = ( "0") # temporary until I find out what to put here plan.logger.debug("Wedge name = ", wedgename) # Get the prescription for this beam prescription = [ p for p in trial_info["PrescriptionList"] if p["Name"] == beam["PrescriptionName"] ][0] # Get the machine name and version and energy name for this beam machinenameandversion = beam["MachineNameAndVersion"].split(": ") machinename = machinenameandversion[0] machineversion = machinenameandversion[1] machineenergyname = beam["MachineEnergyName"] beam_energy = re.findall(r"[-+]?\d*\.\d+|\d+", beam["MachineEnergyName"])[0] # Find the DosePerMuAtCalibration parameter from the machine data dose_per_mu_at_cal = -1 if (machine_info["Name"] == machinename and machine_info["VersionTimestamp"] == machineversion): for energy in machine_info["PhotonEnergyList"]: if energy["Name"] == machineenergyname: dose_per_mu_at_cal = energy["PhysicsData"]["OutputFactor"][ "DosePerMuAtCalibration"] plan.logger.debug("Using DosePerMuAtCalibration of: " + str(dose_per_mu_at_cal)) prescripdose = beam["MonitorUnitInfo"]["PrescriptionDose"] normdose = beam["MonitorUnitInfo"]["NormalizedDose"] if normdose == 0: ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamMeterset = 0 else: ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamDose = (prescripdose / 100) ds.FractionGroupSequence[0].ReferencedBeamSequence[ beam_count - 1].BeamMeterset = prescripdose / (normdose * dose_per_mu_at_cal) gantryrotdir = "NONE" if ( "GantryIsCCW" in cp_manager ): # This may be a problem here!!!! Not sure how to Pinnacle does this, could be 1 if CW, must be somewhere that states if gantry is rotating or not if cp_manager["GantryIsCCW"] == 1: gantryrotdir = "CC" if "GantryIsCW" in cp_manager: if cp_manager["GantryIsCW"] == 1: gantryrotdir = "CW" plan.logger.debug("Beam MU: " + str(ds.FractionGroupSequence[0]. ReferencedBeamSequence[beam_count - 1].BeamMeterset)) doserate = 0 if "DoseRate" in beam: # TODO What to do if DoseRate isn't available in Beam? doserate = beam["DoseRate"] if ("STEP" in beam["SetBeamType"].upper() and "SHOOT" in beam["SetBeamType"].upper()): plan.logger.debug("Using Step & Shoot") ds.BeamSequence[beam_count - 1].NumberOfControlPoints = numctrlpts * 2 ds.BeamSequence[beam_count - 1].SourceToSurfaceDistance = beam["SSD"] * 10 if numwedges > 0: ds.BeamSequence[beam_count - 1].WedgeSequence = Sequence() ds.BeamSequence[beam_count - 1].WedgeSequence.append( Dataset() ) # I am assuming only one wedge per beam (which makes sense because you can't change it during beam) ds.BeamSequence[beam_count - 1].WedgeSequence[ 0].WedgeNumber = 1 # might need to change this ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeType = wedgetype ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeAngle = wedgeangle ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeID = wedgename ds.BeamSequence[ beam_count - 1].WedgeSequence[0].WedgeOrientation = wedgeorientation ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeFactor = "" metercount = 1 for j in range(0, numctrlpts * 2): ds.BeamSequence[beam_count - 1].ControlPointSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ControlPointIndex = j ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence.append(Dataset()) if j % 2 == 1: # odd number control point currentmeterset = currentmeterset + float( metersetweight[metercount]) metercount = metercount + 1 ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].CumulativeMetersetWeight = currentmeterset ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = currentmeterset ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" if j == 0: # first control point beam meterset always zero ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].NominalBeamEnergy = beam_energy ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].DoseRateSet = doserate ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = "NONE" # logger.debug("Gantry angle list length: ", len(gantryangles)) # logger.debug("current controlpoint: ", j) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].GantryAngle = gantryangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceAngle = colangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) if numwedges > 0: ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence[0].WedgePosition = "IN" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].WedgePositionSequence[ 0].ReferencedWedgeNumber = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # This will be the x jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the y jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = [x1, x2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].LeafJawPositions = [y1, y2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the MLC ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].LeafJawPositions = leafpositions ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportAngle = psupportangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].IsocenterPosition = plan.iso_center ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = gantryrotdir else: ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset() ) # This will be the mlcs for control points other than the first ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = leafpositions ds.BeamSequence[beam_count - 1].NumberOfWedges = ( numwedges ) # this is temporary value, will read in from file later ds.BeamSequence[beam_count - 1].NumberOfCompensators = "0" # Also temporary ds.BeamSequence[beam_count - 1].NumberOfBoli = "0" ds.BeamSequence[beam_count - 1].NumberOfBlocks = "0" # Temp ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence = Sequence() ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[0].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[1].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[2].NumberOfLeafJawPairs = ( p_count / 2) bounds = [ "-200", "-190", "-180", "-170", "-160", "-150", "-140", "-130", "-120", "-110", "-100", "-95", "-90", "-85", "-80", "-75", "-70", "-65", "-60", "-55", "-50", "-45", "-40", "-35", "-30", "-25", "-20", "-15", "-10", "-5", "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200", ] ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].LeafPositionBoundaries = bounds else: plan.logger.debug("Not using Step & Shoot") ds.BeamSequence[beam_count - 1].NumberOfControlPoints = numctrlpts + 1 ds.BeamSequence[beam_count - 1].SourceToSurfaceDistance = beam["SSD"] * 10 if numwedges > 0: ds.BeamSequence[beam_count - 1].WedgeSequence = Sequence() ds.BeamSequence[beam_count - 1].WedgeSequence.append(Dataset()) # I am assuming only one wedge per beam (which makes sense # because you can't change it during beam) ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeNumber = 1 # might need to change this ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeType = wedgetype ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeAngle = wedgeangle ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeID = wedgename ds.BeamSequence[ beam_count - 1].WedgeSequence[0].WedgeOrientation = wedgeorientation ds.BeamSequence[beam_count - 1].WedgeSequence[0].WedgeFactor = "" for j in range(0, numctrlpts + 1): ds.BeamSequence[beam_count - 1].ControlPointSequence.append( Dataset()) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].ControlPointIndex = j ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].CumulativeMetersetWeight = metersetweight[j] if j == 0: # first control point beam meterset always zero ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].NominalBeamEnergy = beam_energy ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].DoseRateSet = doserate ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = "NONE" # logger.debug("Gantry angle list length: ", len(gantryangles)) # logger.debug("current controlpoint: ", j) ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].GantryAngle = gantryangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceAngle = colangle ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = "0" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" if numwedges > 0: WedgePosition1 = Dataset() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence = Sequence() ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence.append(WedgePosition1) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].WedgePositionSequence[0].WedgePosition = "IN" ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].WedgePositionSequence[ 0].ReferencedWedgeNumber = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # This will be the x jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the y jaws ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = [x1, x2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 1].LeafJawPositions = [y1, y2] ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append( Dataset()) # this will be the MLC ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 2].LeafJawPositions = leafpositions ds.BeamSequence[ beam_count - 1].ControlPointSequence[j].SourceToSurfaceDistance = ( beam["SSD"] * 10) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDeviceRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportAngle = psupportangle ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].PatientSupportRotationDirection = "NONE" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].IsocenterPosition = plan.iso_center ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].GantryRotationDirection = gantryrotdir ds.BeamSequence[beam_count - 1].NumberOfWedges = numwedges ds.BeamSequence[beam_count - 1].NumberOfCompensators = ( "0" ) # this is temporary value, will read in from file later ds.BeamSequence[beam_count - 1].NumberOfBoli = "0" # Also temporary ds.BeamSequence[beam_count - 1].NumberOfBlocks = "0" # Temp else: # This will be the mlcs for control points other than the first ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].BeamLimitingDevicePositionSequence[ 0].LeafJawPositions = leafpositions ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].CumulativeDoseReferenceCoefficient = "1" ds.BeamSequence[beam_count - 1].ControlPointSequence[ j].ReferencedDoseReferenceSequence[ 0].ReferencedDoseReferenceNumber = "1" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence = Sequence() ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence.append(Dataset()) ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 0].RTBeamLimitingDeviceType = "ASYMX" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 1].RTBeamLimitingDeviceType = "ASYMY" ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].RTBeamLimitingDeviceType = "MLCX" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[0].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[1].NumberOfLeafJawPairs = "1" ds.BeamSequence[ beam_count - 1].BeamLimitingDeviceSequence[2].NumberOfLeafJawPairs = ( p_count / 2) bounds = [ "-200", "-190", "-180", "-170", "-160", "-150", "-140", "-130", "-120", "-110", "-100", "-95", "-90", "-85", "-80", "-75", "-70", "-65", "-60", "-55", "-50", "-45", "-40", "-35", "-30", "-25", "-20", "-15", "-10", "-5", "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200", ] ds.BeamSequence[beam_count - 1].BeamLimitingDeviceSequence[ 2].LeafPositionBoundaries = bounds ctrlptlist = False wedgeflag = False numwedges = 0 beginbeam = False # Get the prescription for this beam prescription = [ p for p in trial_info["PrescriptionList"] if p["Name"] == beam["PrescriptionName"] ][0] num_fractions = prescription["NumberOfFractions"] ds.FractionGroupSequence[0].FractionGroupNumber = 1 ds.FractionGroupSequence[0].NumberOfFractionsPlanned = num_fractions ds.FractionGroupSequence[0].NumberOfBeams = beam_count ds.FractionGroupSequence[0].NumberOfBrachyApplicationSetups = "0" # Save the RTPlan Dicom File output_file = os.path.join(export_path, RPfilename) plan.logger.info("Creating Plan file: %s \n" % (output_file)) ds.save_as(output_file)
info_mask.PatientBirthDate= info.PatientBirthDate info_mask.PatientSex= info.PatientSex if 'PatientAge' in info: info_mask.PatientAge= info.PatientAge if 'PatientWeight' in info: info_mask.PatientWeight= info.PatientWeight info_mask.StudyID=info.StudyID info_mask.ImageType='DERIVED\PRIMARY' info_mask.InstanceCreatorUID='1.2.276.0.7230010.3' info_mask.SOPClassUID='1.2.840.10008.5.1.4.1.1.66.4' 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')
def create_dicom_base(self): if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") if self.header_set is False: raise InputError("Header not loaded") # TODO tags + code datatypes are described here: # https://www.dabsoft.ch/dicom/6/6/#(0020,0012) # datatype codes are described here: # ftp://dicom.nema.org/medical/DICOM/2013/output/chtml/part05/sect_6.2.html meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage # Media Storage SOP Instance UID tag 0x0002,0x0003 (type UI - Unique Identifier) meta.MediaStorageSOPInstanceUID = self._ct_sop_instance_uid meta.ImplementationClassUID = "1.2.3.4" meta.TransferSyntaxUID = uid.ImplicitVRLittleEndian # Implicit VR Little Endian - Default Transfer Syntax ds = FileDataset("file", {}, file_meta=meta, preamble=b"\0" * 128) ds.PatientName = self.patient_name if self.patient_id in (None, ''): ds.PatientID = datetime.datetime.today().strftime('%Y%m%d-%H%M%S') else: ds.PatientID = self.patient_id # Patient ID tag 0x0010,0x0020 (type LO - Long String) ds.PatientSex = '' # Patient's Sex tag 0x0010,0x0040 (type CS - Code String) # Enumerated Values: M = male F = female O = other. ds.PatientBirthDate = '19010101' ds.SpecificCharacterSet = 'ISO_IR 100' ds.AccessionNumber = '' ds.is_little_endian = True ds.is_implicit_VR = True ds.SOPClassUID = '1.2.3' # !!!!!!!! # SOP Instance UID tag 0x0008,0x0018 (type UI - Unique Identifier) ds.SOPInstanceUID = self._ct_sop_instance_uid # Study Instance UID tag 0x0020,0x000D (type UI - Unique Identifier) # self._dicom_study_instance_uid may be either set in __init__ when creating new object # or set when import a DICOM file # Study Instance UID for structures is the same as Study Instance UID for CTs ds.StudyInstanceUID = self._dicom_study_instance_uid # Series Instance UID tag 0x0020,0x000E (type UI - Unique Identifier) # self._ct_dicom_series_instance_uid may be either set in __init__ when creating new object # or set when import a DICOM file # Series Instance UID for structures might be different than Series Instance UID for CTs ds.SeriesInstanceUID = self._ct_dicom_series_instance_uid # Study Instance UID tag 0x0020,0x000D (type UI - Unique Identifier) ds.FrameofReferenceUID = '1.2.3' # !!!!!!!!! ds.StudyDate = datetime.datetime.today().strftime('%Y%m%d') ds.StudyTime = datetime.datetime.today().strftime('%H%M%S') ds.PhotometricInterpretation = 'MONOCHROME2' ds.SamplesPerPixel = 1 ds.ImageOrientationPatient = ['1', '0', '0', '0', '1', '0'] ds.Rows = self.dimx ds.Columns = self.dimy ds.SliceThickness = str(self.slice_distance) ds.PixelSpacing = [self.pixel_size, self.pixel_size] # Add eclipse friendly IDs ds.StudyID = '1' # Study ID tag 0x0020,0x0010 (type SH - Short String) ds.ReferringPhysiciansName = 'py^trip' # Referring Physician's Name tag 0x0008,0x0090 (type PN - Person Name) ds.PositionReferenceIndicator = '' # Position Reference Indicator tag 0x0020,0x1040 ds.SeriesNumber = '1' # SeriesNumber tag 0x0020,0x0011 (type IS - Integer String) return ds
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
def convert_npy_to_dicom(fname, npy_array, slice_thickness=None, pixel_spacing=None): """ convert npy array to dicom :param fname: file name :param npy_array: npy array :param slice_thickness: slice thickness :param pixel_spacing: pixel spacing :return: dcm """ uint16_img = np.array(npy_array) uint16_img = ((uint16_img - uint16_img.min()) / (uint16_img.max() - uint16_img.min()) * (2**16 - 1)).astype( np.uint16) dim = len(uint16_img.shape) if dim == 1: raise Exception('Cannot convert 1D array to dicom') elif dim == 2: uint16_img = uint16_img[np.newaxis, :, :] elif dim > 3: raise Exception('{}D array is not supported.'.format(dim)) x_min = npy_array.min() x_max = npy_array.max() x_max_min = x_max - x_min t_max = (2**16) - 1 slope = x_max_min / t_max intercept = x_min file_meta = Dataset() file_meta.MediaStorageSOPClassUID = '0.0.000.000000.0.0.0.0.0.00' file_meta.MediaStorageSOPInstanceUID = \ '333.333.0.0.0.333.333333333.{}'.format( datetime.now().timestamp()) file_meta.ImplementationClassUID = '0.0.0.0' dcm = FileDataset(fname, {}, file_meta=file_meta, preamble=b'\0' * 128) dcm.Modality = 'OT' dcm.ImageType = ['ORIGINAL', 'PRIMARY'] dcm.ContentDate = datetime.now().strftime('%Y%m%d') dcm.ContentTime = datetime.now().strftime('%H%M%S') dcm.InstanceCreationDate = datetime.now().strftime('%Y%m%d') dcm.InstanceCreationTime = datetime.now().strftime('%H%M%S') dcm.SeriesDate = datetime.now().strftime('%Y%m%d') dcm.SeriesTime = datetime.now().strftime('%H%M%S') dcm.AcquisitionTime = datetime.now().strftime('%H%M%S') dcm.PatientName = os.path.basename(fname) dcm.PatientBirthDate = datetime.now().strftime('%Y%m%d') dcm.PatientAge = '000Y' dcm.PatientSize = 1 dcm.PatientWeight = 1 dcm.PatientID = os.path.basename(fname) dcm.PatientSex = 'O' dcm.StudyDescription = os.path.basename(fname) dcm.StudyDate = datetime.now().strftime('%Y%m%d') dcm.StudyTime = datetime.now().strftime('%H%M%S') dcm.StudyID = os.path.basename(fname) dcm.SeriesDescription = os.path.basename(fname) dcm.SamplesPerPixel = 1 dcm.PhotometricInterpretation = 'MONOCHROME1' dcm.PixelRepresentation = 0 # unsigned 0, signed 1 dcm.HighBit = 16 dcm.BitsStored = 16 dcm.BitsAllocated = 16 dcm.SmallestImagePixelValue = uint16_img.min() dcm.LargestImagePixelValue = uint16_img.max() dcm.Columns = uint16_img.shape[2] dcm.Rows = uint16_img.shape[1] dcm.NumberOfFrames = uint16_img.shape[0] dcm.NumberOfSlices = uint16_img.shape[0] dcm.ImagesInAquisition = uint16_img.shape[0] dcm.RescaleIntercept = intercept dcm.RescaleSlope = slope dcm.SliceVector = (np.arange(uint16_img.shape[0]) + 1).tolist() dcm.FrameIncrementPointer = [(0x0054, 0x0080)] dcm.PixelData = uint16_img.tostring() dcm.SliceThickness = 1 if slice_thickness is None else slice_thickness ps = 1 if pixel_spacing is None else pixel_spacing if isinstance(ps, list) or isinstance(ps, np.ndarray): dcm.PixelSpacing = [ps[0], ps[1]] else: dcm.PixelSpacing = [ps, ps] dcm.InstanceCreatorUID = '333.333.0.0.0' dcm.SOPClassUID = '0.0.000.00000.0.0.0.0.0.00' dcm.SOPInstanceUID = '333.333.0.0.0.{}'.format(datetime.now().timestamp()) dcm.StudyInstanceUID = '333.333.0.0.0.{}'.format( datetime.now().timestamp()) dcm.SeriesInstanceUID = '333.333.0.0.0.{}.3333'.format( datetime.now().timestamp()) dcm.FrameOfReferenceUID = dcm.StudyInstanceUID dcm.SeriesNumber = 0 dcm.InstanceNumber = 0 dcm.BodyPartExamined = 'UNKNOWN' dcm.Manufacturer = 'DicomConversionUtils' dcm.DeviceSerialNumber = '' dcm.AcquisitionTerminationCondition = 'MANU' dcm.SoftwareVersions = 'UNKNOWN' dcm.AccessionNumber = '{:13d}'.format(random.randint(0, 1e13)) dcm.InstitutionName = 'DicomConversionUtils' dcm.save_as(fname) return dcm
def Edm(input, empty_Name): """ Read single DICOM file, write the meta data to a new dicom file as template for m2d_lossless(). If input is a DICOM folder, then only use the first DICOM file. :param input: a single dicom slice or a folder :param empty_Name: the empty dicom name """ start = time.time() if (os.path.isfile(input)): file = input else: files = sorted(glob.glob(os.path.join(input, "*.dcm"))) file = sortDCM(files)[0] dataSet = pydicom.dcmread(file) fileName = "Edm.dcm" file_meta = Dataset() # add file meta data file_meta.MediaStorageSOPClassUID = dataSet.file_meta.MediaStorageSOPClassUID file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian file_meta.FileMetaInformationVersion = dataSet.file_meta.FileMetaInformationVersion file_meta.MediaStorageSOPInstanceUID = dataSet.file_meta.MediaStorageSOPInstanceUID file_meta.ImplementationClassUID = dataSet.file_meta.ImplementationClassUID file_meta.ImplementationVersionName = dataSet.file_meta.ImplementationVersionName ds = FileDataset(fileName, {}, file_meta=file_meta, preamble=b"\0" * 128) # add meta data ds.ImageType = dataSet.ImageType ds.SOPClassUID = dataSet.SOPClassUID ds.SOPInstanceUID = dataSet.SOPInstanceUID ds.StudyDate = dataSet.StudyDate ds.StudyTime = dataSet.StudyTime ds.AccessionNumber = dataSet.AccessionNumber ds.Manufacturer = dataSet.Manufacturer ds.StudyDescription = dataSet.StudyDescription ds.SeriesDescription = dataSet.SeriesDescription ds.ManufacturerModelName = dataSet.ManufacturerModelName ds.PatientName = dataSet.PatientName ds.PatientID = dataSet.PatientID ds.PatientBirthDate = dataSet.PatientBirthDate ds.PatientSex = dataSet.PatientSex ds.SliceThickness = dataSet.SliceThickness ds.PatientPosition = dataSet.PatientPosition ds.StudyInstanceUID = dataSet.StudyInstanceUID ds.SeriesInstanceUID = dataSet.SeriesInstanceUID ds.StudyID = dataSet.StudyID ds.SeriesNumber = dataSet.SeriesNumber ds.InstanceNumber = "1" ds.ImagePositionPatient = dataSet.ImagePositionPatient ds.ImageOrientationPatient = dataSet.ImageOrientationPatient ds.FrameOfReferenceUID = dataSet.FrameOfReferenceUID ds.PositionReferenceIndicator = dataSet.PositionReferenceIndicator ds.PositionReferenceIndicator = "SN" ds.SamplesPerPixel = dataSet.SamplesPerPixel ds.PhotometricInterpretation = "MONOCHROME2" ds.Rows = 0 ds.Columns = 0 ds.PixelSpacing = dataSet.PixelSpacing ds.BitsAllocated = dataSet.BitsAllocated ds.BitsStored = dataSet.BitsStored ds.HighBit = dataSet.HighBit ds.PixelRepresentation = dataSet.PixelRepresentation ds.PixelData = bytes(0) ds.save_as(empty_Name) print("Cost time (seconds): ", (time.time() - start))
def writeDCMheader(filename, dcmStruct): logging.info("Setting file meta information...") # Populate required values for file meta information file_meta = Dataset() file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' # IOP of RT STORAGE file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() file_meta.ImplementationClassUID = "1.3.6.1.4.1.9590.100.1.3.100.9.4" file_meta.TransferSyntaxUID = '1.2.840.10008.1.2.1' file_meta.FileMetaInformationVersion = b'\x00\x01' file_meta.FileMetaInformationGroupLength = 1 logging.info("Setting dataset values...") # Create the FileDataset instance (initially no data elements, but file_meta supplied) ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) if hasattr(dcmStruct, 'PatientName'): ds.PatientName = dcmStruct.PatientName if hasattr(dcmStruct, 'PatientID'): ds.PatientID = dcmStruct.PatientID # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = False # Set creation date/time dt = datetime.datetime.now() ds.StructureSetDate = dt.strftime('%Y%m%d') ds.StudyDate = dt.strftime('%Y%m%d') timeStr = dt.strftime('%H%M%S.%f') # long format with micro seconds ds.StructureSetTime = timeStr ds.StudyTime = timeStr ds.SOPClassUID = file_meta.MediaStorageSOPClassUID ds.SOPInstanceUID = pydicom.uid.generate_uid() ds.SeriesInstanceUID = pydicom.uid.generate_uid() if hasattr(dcmStruct, 'StudyInstanceUID'): ds.StudyInstanceUID = dcmStruct.StudyInstanceUID if hasattr(dcmStruct, 'PatientSex'): ds.PatientSex = dcmStruct.PatientSex if hasattr(dcmStruct, 'PatientBirthDate'): ds.PatientBirthDate = dcmStruct.PatientBirthDate if hasattr(dcmStruct, 'StudyID'): ds.StudyID = dcmStruct.StudyID if hasattr(dcmStruct, 'ReferringPhysicianName'): ds.ReferringPhysicianName = dcmStruct.ReferringPhysicianName ds.StudyDescription = 'DLROI' ds.SeriesDescription = 'DLROI' if hasattr(dcmStruct, 'AccessionNumber'): ds.AccessionNumber = dcmStruct.AccessionNumber if hasattr(dcmStruct, 'InstitutionName'): ds.InstitutionName = dcmStruct.InstitutionName if hasattr(dcmStruct, 'StationName'): ds.StationName = dcmStruct.StationName ds.ColorType = 'grayscale' ds.Manufacturer = 'CourtLab - MD Anderson' ds.Modality = 'RTSTRUCT' ds.SoftwareVersion = 'test_v1' ds.StructureSetName = 'ROI' ds.StructureSetLabel = 'AutoPlan ROI' ds.SeriesNumber = 1 ds.InstanceNumber = 1 ds.Rows = 0 ds.Columns = 0 ds.BitsAllocated = 16 ds.BitsStored = 16 ds.Width = 0 ds.Height = 0 ds.BitDepth = 16 ds.HighBit = 15 ds.PixelRepresentation = 0 return ds
def newDCM(meta_file, shape): """ Create a new dicom file as template for m2d. This dicom file has no pixel data. Pixel data will be filled in m2d method. :param meta_file: provide meta data :param shape: mgz data shape :return: the dataset of dicom file, which could be filled pixel data from mgz file """ fileName = "template.dcm" prefix = "1.2.826.0.1.3680043.10.271." suffix = str(datetime.datetime.today())[:10].replace('-', '') + str( time.time()).replace('.', '') file_meta = Dataset() file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.4" # Standard SOP CLasses: MR Image Storage ds = FileDataset(fileName, {}, file_meta=file_meta, preamble=b"\0" * 128) ds.SeriesInstanceUID = prefix + suffix # change Series Instance UID ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.4" # Standard SOP CLasses: MR Image Storage ds.ImageType = ['ORIGINAL', 'PRIMARY', 'OTHER'] ds.PatientPosition = "HFS" ds.Manufacturer = "GE MEDICAL SYSTEMS" ds.ManufacturerModelName = "SIGNA EXCITE" ds.PositionReferenceIndicator = "SN" ds.SliceThickness = 1 # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True ds.PixelData = bytes(0) ds.Rows = shape[0] ds.Columns = shape[1] ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.SeriesDescription = "_m2d" ds.PixelSpacing = [1, 1] ds.BitsAllocated = 16 ds.BitsStored = 16 ds.HighBit = 15 ds.PixelRepresentation = 1 ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian with open(meta_file) as f: for line in f: index = line.rindex(":") key = line[:index].replace(' ', '').lower() value = line[index + 1:].strip() if (key == "studydate"): ds.StudyDate = value if (key == "seriesdate"): ds.SeriesDate = value if (key == "patientbirthdate"): ds.PatientBirthDate = value if (key == "studytime"): ds.StudyTime = value if (key == "accessionnumber"): ds.AccessionNumber = value if (key == "studydescription"): ds.StudyDescription = value if (key == "seriesdescription"): ds.SeriesDescription = value if (key == "patientname"): ds.PatientName = value if (key == "patientid"): ds.PatientID = value if (key == "seriesnumber"): ds.SeriesNumber = value if (key == "patientsex"): ds.PatientSex = value return ds
def write_dicom_slice( pixel_array, # 2D array in LP orientation filename=None, outputdir='mydcmdir', suffix='.dcm', modality='PT', SecondaryCaptureDeviceManufctur='KUL', uid_base='1.2.826.0.1.3680043.9.7147.', # UID root for Georg Schramm PatientName='Test^Patient', PatientID='08150815', AccessionNumber='08150815', StudyDescription='test study', SeriesDescription='test series', PixelSpacing=['1', '1'], SliceThickness='1', ImagePositionPatient=['0', '0', '0'], ImageOrientationPatient=['1', '0', '0', '0', '1', '0'], CorrectedImage=['DECY', 'ATTN', 'SCAT', 'DTIM', 'LIN', 'CLN'], ImageType='STATIC', RescaleSlope=None, RescaleIntercept=None, StudyInstanceUID=None, SeriesInstanceUID=None, SOPInstanceUID=None, FrameOfReferenceUID=None, RadiopharmaceuticalInformationSequence=None, PatientGantryRelationshipCodeSequence=None, sl=None, frm=None, verbose=False, **kwargs): """write a 2D PET dicom slice Parameters --------- pixel_array : 2d numpy array array that contains the image values filename : str, optional name of the output dicom file (default: None -> automatically generated) outputdir : string, optional output directory fir dicom file (default: mydcmdir) suffix : string, optional suffix for dicom file (default '.dcm') sl, frm : int, optional slice and frame numbers that are appended to the file name prefix if given SecondaryCaptureDeviceManufctur --| uid_base | PatientName | PatientID | AccessionNumber | StudyDescription | SeriesDescription | PixelSpacing | SliceThickness | ImagePositionPatient | ImageOrientationPatient | CorrectedImage | ... dicom tags that should be present in a minimal ImageType | dicom header RescaleSlope | see function definition for default values RescaleIntercept | default None means that they are creacted automatically StudyInstanceUID | SeriesInstanceUID | SOPInstanceUID | FrameOfReferenceUID | RadiopharmaceuticalInformationSequence | PatientGantryRelationshipCodeSequence --| **kwargs : additional tags from the standard dicom dictionary to write the following tags could be useful: StudyDate StudyTime SeriesDate SeriesTime AcquisitionDate AcquisitionTime PatientBirthDate PatientSex PatientAge PatientSize PatientWeight ActualFrameDuration PatientPosition DecayCorrectionDateTime ImagesInAcquisition SliceLocation NumberOfSlices Units DecayCorrection ReconstructionMethod FrameReferenceTime DecayFactor DoseCalibrationFactor ImageIndex Returns ------- str containing the ouput file name """ # create output dir if it does not exist if not os.path.exists(outputdir): os.mkdir(outputdir) # Populate required values for file meta information file_meta = Dataset() if modality == 'PT': file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.128' elif modality == 'NM': file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.20' elif modality == 'CT': file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' elif modality == 'MR': file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.4' else: file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.4' # the MediaStorageSOPInstanceUID sould be the same as SOPInstanceUID # however it is stored in the meta information header to have faster access if SOPInstanceUID is None: SOPInstanceUID = dicom.uid.generate_uid(uid_base) file_meta.MediaStorageSOPInstanceUID = SOPInstanceUID file_meta.ImplementationClassUID = uid_base + '1.1.1' prefix = modality # append the frame and slice number to the file name prefix if given # not needed for the dicom standard but can be usefule for less dicom conform "2D" readers if frm is not None: prefix += f'.{frm:03}' if sl is not None: prefix += f'.{sl:05}' filename = f'{prefix}.{SOPInstanceUID}{suffix}' # Create the FileDataset instance (initially no data elements, but file_meta # supplied) ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) # Add the data elements -- not trying to set all required here. Check DICOM # standard ds.PatientName = PatientName ds.PatientID = PatientID ds.AccessionNumber = AccessionNumber ds.Modality = modality ds.StudyDescription = StudyDescription ds.SeriesDescription = SeriesDescription if StudyInstanceUID is None: StudyInstanceUID = dicom.uid.generate_uid(uid_base) ds.StudyInstanceUID = StudyInstanceUID if SeriesInstanceUID is None: SeriesInstanceUID = dicom.uid.generate_uid(uid_base) ds.SeriesInstanceUID = SeriesInstanceUID if FrameOfReferenceUID is None: FrameOfReferenceUID = dicom.uid.generate_uid(uid_base) ds.FrameOfReferenceUID = FrameOfReferenceUID ds.SOPInstanceUID = SOPInstanceUID ds.SOPClassUID = file_meta.MediaStorageSOPClassUID ds.SecondaryCaptureDeviceManufctur = SecondaryCaptureDeviceManufctur ## These are the necessary imaging components of the FileDataset object. ds.SamplesPerPixel = 1 if modality == 'PT' or modality == 'NM': ds.PhotometricInterpretation = "MONOCHROME1" else: ds.PhotometricInterpretation = "MONOCHROME2" ds.HighBit = 15 ds.BitsStored = 16 ds.BitsAllocated = 16 # PixelRepresentation is 0 for uint16, 1 for int16 if pixel_array.dtype == np.uint16: ds.PixelRepresentation = 0 ds.RescaleIntercept = 0 ds.RescaleSlope = 1 elif pixel_array.dtype == np.int16: ds.PixelRepresentation = 1 ds.RescaleIntercept = 0 ds.RescaleSlope = 1 else: ds.PixelRepresentation = 0 # rescale the input pixel array to uint16 if needed if RescaleIntercept is None: RescaleIntercept = pixel_array.min() if RescaleIntercept != 0: pixel_array = 1.0 * pixel_array - RescaleIntercept if RescaleSlope is None: if pixel_array.max() != 0: RescaleSlope = 1.0 * pixel_array.max() / (2**16 - 1) else: RescaleSlope = 1.0 if RescaleSlope != 1: pixel_array = 1.0 * pixel_array / RescaleSlope pixel_array = pixel_array.astype(np.uint16) ds.RescaleIntercept = RescaleIntercept ds.RescaleSlope = RescaleSlope # we have to transpose the column and row direction in the dicoms ds.PixelData = pixel_array.transpose().tobytes() ds.Columns = pixel_array.shape[0] ds.Rows = pixel_array.shape[1] # the pixel spacing also has to be inverted (transposed array saved!) ds.PixelSpacing = PixelSpacing[::-1] ds.SliceThickness = SliceThickness # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Set creation date/time dt = datetime.datetime.now() timeStr = dt.strftime('%H%M%S.%f') # long format with micro seconds ds.ContentDate = dt.strftime('%Y%m%d') ds.ContentTime = timeStr # voxel coordinate tags ds.ImagePositionPatient = ImagePositionPatient ds.ImageOrientationPatient = ImageOrientationPatient # special NM tags if modality == 'PT' or modality == 'NM': ds.CorrectedImage = CorrectedImage ds.ImageType = ImageType if RadiopharmaceuticalInformationSequence != None: rpi = RadiopharmaceuticalInformationSequence # this is needed otherwise the dicoms cannot be read rpi.is_undefined_length = True rpi[0].RadionuclideCodeSequence.is_undefined_length = True ds.RadiopharmaceuticalInformationSequence = rpi # add all key word arguments to dicom structure for key, value in kwargs.items(): if dicom.datadict.tag_for_keyword(key) != None: setattr(ds, key, value) else: warnings.warn( key + ' not in standard dicom dictionary -> will not be written') if verbose: print("Writing file", os.path.join(outputdir, filename)) dicom.filewriter.write_file(os.path.join(outputdir, filename), ds, write_like_original=False) return os.path.join(outputdir, 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 writeDCM(root_Path,doc,pat,data): # HUaPath = 'Data/dcmHua/liping(0703)/0014712255/RTSTRUCT_2.16.840.1.113669.2.931128.121126138.20190626165800.28009' # RTHuaData = pydicom.read_file(HUaPath, force=True) # Create some temporary filenames # 获取对应的CT信息 CTpath = getCTName(root_Path + '/' + doc + '/' + pat, 'CT_') CTrefds = pydicom.read_file(CTpath, force=True) # 新建RTSTRUCT文件 # filename = root_Path + '/' + doc + '/' + pat + '/'+'RTSTRUCT_2.16.840.1.113669.2.931128.121126138.20190703172017.764011' filename = root_Path + '/' + doc + '/' + pat + '/RTSTRUCT_' + pat + '.dcm' RTName = pat + '.dcm' # RTName = filename dirName = getDirName(root_Path + '/' + doc + '/' + pat)#切片的目录文件.dir的名字 dirPath = root_Path + '/' + doc + '/' + pat + '/'+dirName # print(dirName) # dirPath = 'Data\dcmHua\doctor1/0002348871/CT_1.3.12.2.1107.5.1.4.66045.30000014051200211018700037438.dir' ds = FileDataset(filename, {},preamble=b"\0" * 128) # Specific Character Set # ds.SpecificCharacterSet = RTHuaData.SpecificCharacterSet#'ISO_IR 100'#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! try: ds.SpecificCharacterSet = CTrefds.SpecificCharacterSet except BaseException: print('对应CT没有 Specific Character Set属性') #SOPInstanceUID ds.SOPInstanceUID = RTName # InstanceCreationDate year = datetime.datetime.now().year month = datetime.datetime.now().month day = datetime.datetime.now().day ds.InstanceCreationDate = str(year) + str(month) + str(day) # Instance Creation Time ??????????????????????????????????????????????? time = datetime.datetime.now() ds.InstanceCreationTime = time # SOP Class UID ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' #RTHuaData.SOPClassUID# "RT Structure Set Storage" # Study Date ds.StudyDate = CTrefds.StudyDate # Study Time ds.StudyTime = CTrefds.StudyTime # Accession Number ds.AccessionNumber = CTrefds.AccessionNumber # Modality ds.Modality = "RTSTRUCT" # Manufacturer????????????????????!!!!!!!!!!!!!!!!!!!!!!!!!!!! ds.Manufacturer ="UESTC"#'ADAC' # Referring Physician's Name ds.ReferringPhysicianName = "Huaxi" # Station Name ????????????????? ds.StationName = 'pinnsx86c-6' #"UESTC"!!!!!!!!!!!!!!!!!!!!!!! # Physician(s) of Record ds.PhysiciansOfRecord = "UESTC" # Manufacturer's Model Name ds.ManufacturerModelName = "UESTC" # Referenced Study Sequence 写sequence beam = Dataset() ds.ReferencedStudySequence = Sequence([beam]) ds.ReferencedStudySequence[0].ReferencedSOPClassUID = "Study Component Management SOP Class" ds.ReferencedStudySequence[0].ReferencedSOPInstanceUID = CTrefds.StudyInstanceUID # ds.ReferencedStudySequence = RTHuaData.ReferencedStudySequence # Patient's Name??????????!!!!!!!!!!!!!! ds.PatientName = CTrefds.PatientName#RTHuaData.PatientName#'du qi wen restored^13684p1204224^pelvis^'#RTHuaData.PatientName#'du qi wen restored^13684p1204224^^'# # Patient ID ds.PatientID = CTrefds.PatientID # Patient's Birth Date ds.PatientBirthDate = CTrefds.PatientBirthDate # Patient's Sex ds.PatientSex = CTrefds.PatientSex # Software Version ds.SoftwareVersions = ['9.2', '9.2'] # Study Instance UID ds.StudyInstanceUID = CTrefds.StudyInstanceUID # Series Instance UID ????????????? ds.SeriesInstanceUID = RTName#RTHuaData.SeriesInstanceUID#'2.16.840.1.113669.2.931128.121126138.20190703172017.513499'#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Study ID ds.StudyID = CTrefds.StudyID # Series Number ds.SeriesNumber = "1" # Structure Set Label ds.StructureSetLabel = "Plan_1" # Structure Set Name ds.StructureSetName ="POIandROIandBOLUS" # Structure Set Date ds.StructureSetDate = ds.InstanceCreationDate # Structure Set Time ds.StructureSetTime = datetime.datetime.now() # Referenced Frame of Reference Sequence beam = Dataset() ds.ReferencedFrameOfReferenceSequence = Sequence([beam]) ds.ReferencedFrameOfReferenceSequence[0].FrameOfReferenceUID = CTrefds.FrameOfReferenceUID beam2 = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence = Sequence([beam2]) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPClassUID = "Study Component Management SOP Class" ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPInstanceUID = CTrefds.StudyInstanceUID beam3 = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence = Sequence( [beam3]) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[ 0].SeriesInstanceUID = dirName # 写dir文件 # 这个sequence包含的是dir文件中的每一个文件名遍历,思路是先读取dir文件,对每一行进行遍历,然后将每一行放入文件的lengthi处 print(dirPath) print('^^^^^^^^^^^^^') dirList = open(dirPath, 'r') count_i = 0 for dirname in dirList: # print(count_i) block_i = Dataset() if count_i == 0: beam4 = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[ 0].ContourImageSequence = Sequence([beam4]) else: ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[ 0].ContourImageSequence.append(block_i) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[ 0].ContourImageSequence[count_i].ReferencedSOPClassUID = "CT Image Storage" ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[ 0].ContourImageSequence[count_i].ReferencedSOPInstanceUID = dirname count_i += 1 # 写标注部位 # 思路:输入标注部位列表,StructureSetROISequenceList,然后对每一个部位进行填充 # StructureSetROISequenceList = ['locref', 'Spinal Cord N', 'Liver N', 'Kidney L N', 'Kidney R N', 'Bladder','Femoral Head R', 'Femoral Head L', 'Rectum', 'CTV', 'PTV'] StructureSetROISequenceList = getLablePos(data) print(StructureSetROISequenceList) print('@@@@@@@@@@@@') count_j = 0 for ROISequence in StructureSetROISequenceList: block_j = Dataset() if count_j == 0: beam5 = Dataset() ds.StructureSetROISequence = Sequence([beam5]) else: ds.StructureSetROISequence.append(block_j) ds.StructureSetROISequence[count_j].ROINumber = count_j ds.StructureSetROISequence[count_j].ReferencedFrameOfReferenceUID = CTrefds.FrameOfReferenceUID ds.StructureSetROISequence[count_j].ROIName = ROISequence count_j += 1 # 对每一个标注部位开始写contour # 思路:先建立部位的列表,每个部位包含有该部位的切片列表,然后每个切片包含contour count_ROI = 0 colorList = [['127', '255', '212'], ['0', '255', '0'], ['0', '0', '255'], ['0', '255', '255'], ['255', '150', '0'], ['128', '0', '255'], ['34', '139', '34'], ['255', '0', '0'], ['127', '255', '212'], ['128', '0', '255'], ['165', '161', '55']] # print('*******************%%%%%%%%%%%%%%%%%%%') for ROISequence in StructureSetROISequenceList: # 对部位进行循环 block_ROI = Dataset() if count_ROI == 0: beam_ROI = Dataset() ds.ROIContourSequence = Sequence([beam_ROI]) else: ds.ROIContourSequence.append((block_ROI)) ds.ROIContourSequence[count_ROI].ROIDisplayColor = colorList[count_ROI] count_Slice = 0 SliceList = getSliceList(data, doc, pat, ROISequence) # print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') # print(len(SliceList[0][4])) # SliceList = np.array(SliceList) # print(SliceList) # print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') # SliceList = ['1.3.12.2.1107.5.1.4.66045.30000014051200211018700037557','1.3.12.2.1107.5.1.4.66045.30000014051200211018700037563'] # # 对应的contour for slice in SliceList: # 对切片进行循环,这里需要写一个筛选哪些切片有标注,形成一个list # print(slice) # print('(((((((((((((()))))))))))))))') block_Slice = Dataset() if count_Slice == 0: beam_Slice = Dataset() ds.ROIContourSequence[count_ROI].ContourSequence = Sequence([beam_Slice]) else: ds.ROIContourSequence[count_ROI].ContourSequence.append((block_Slice)) # # # 每张切片单独成一个slice的item,不需要对slice内部进行循环 # ''' Png文件存储要求: # 1.png文件名是"部位_切片名" # 2.写一个文件,读入png及其名称,转换其像素点为dicom坐标contour,判断连通区域,每个联通区域存储成一项, # # --RTSTRUCT # --ROI # --slice # # ''' beam_contour = Dataset() ds.ROIContourSequence[count_ROI].ContourSequence[count_Slice].ContourImageSequence = Sequence( [beam_contour]) ds.ROIContourSequence[count_ROI].ContourSequence[count_Slice].ContourImageSequence[ 0].ReferencedSOPClassUID = "CT Image Storage" ds.ROIContourSequence[count_ROI].ContourSequence[count_Slice].ContourImageSequence[ 0].ReferencedSOPInstanceUID = slice[3] ds.ROIContourSequence[count_ROI].ContourSequence[count_Slice].ContourGeometricType = 'CLOSED_PLANAR' # contourData = slice[4].split(',') ds.ROIContourSequence[count_ROI].ContourSequence[count_Slice].NumberOfContourPoints = len(slice[4]) / 3 ds.ROIContourSequence[count_ROI].ContourSequence[ count_Slice].ContourData = slice[4]#mv(DSfloat, contourData) # ['-39.4297', '-227.672','-1689.79', '-37.346','-228.714', '-1689.79'] count_Slice += 1 ds.ROIContourSequence[count_ROI].ReferencedROINumber = count_ROI count_ROI += 1 # ds.ReferencedFrameOfReferenceSequence = RTHuaData.ReferencedFrameOfReferenceSequence # ds.StructureSetROISequence = RTHuaData.StructureSetROISequence # ds.ROIContourSequence = RTHuaData.ROIContourSequence print('&&&&&&&&&&&&&') print("Writing test file", filename) ds.save_as(filename) print("File saved.") print('**************') # path = pat+'.dcm' p = pydicom.read_file(filename) print(p)
def convert_npy_to_dicom(npy_array, fname=None, slice_thickness=None, pixel_spacing=None, spacing_between_slices=None, single_file_mode=True ): """ convert npy array to dicom :param npy_array: npy array :param fname: file name :param slice_thickness: slice thickness :param spacing_between_slices: spacing between slices :param pixel_spacing: pixel spacing :param single_file_mode: if False, slice by slice dicom files are generated :return: dcm """ uint16_img = np.array(npy_array).astype(float) uint16_img = ( (uint16_img - uint16_img.min()) / (uint16_img.max() - uint16_img.min()) * (2 ** 16 - 1) ).astype(np.uint16) dim = len(uint16_img.shape) if dim == 1: raise Exception('Cannot convert 1D array to dicom') elif dim == 2: uint16_img = uint16_img[np.newaxis, :, :] elif dim > 3: raise Exception('{}D array is not supported.'.format(dim)) x_min = float(npy_array.min()) x_max = float(npy_array.max()) x_max_min = x_max - x_min t_max = (2 ** 16) - 1 slope = x_max_min / t_max intercept = x_min now = datetime.now().timestamp() file_meta = Dataset() file_meta.MediaStorageSOPClassUID = '1.2.840.100008.5.1.4.1.1.20' file_meta.MediaStorageSOPInstanceUID = f'333.333.0.0.0.333.333333333.{now}' file_meta.ImplementationClassUID = '0.0.0.0' file_meta.FileMetaInformationGroupLength = 140 dcm = FileDataset(fname, {}, file_meta=file_meta, preamble=b'\0' * 128) dcm.Modality = 'OT' if single_file_mode: dcm.ImageType = ["DERIVED", "PRIMARY", "RECON TOMO", "EMISSION"] else: dcm.ImageType = ["DERIVED", "SECONDARY"] dcm.ContentDate = datetime.now().strftime('%Y%m%d') dcm.ContentTime = datetime.now().strftime('%H%M%S') dcm.InstanceCreationDate = datetime.now().strftime('%Y%m%d') dcm.InstanceCreationTime = datetime.now().strftime('%H%M%S') dcm.SeriesDate = datetime.now().strftime('%Y%m%d') dcm.SeriesTime = datetime.now().strftime('%H%M%S') dcm.AcquisitionTime = datetime.now().strftime('%H%M%S') dcm.PatientName = os.path.basename(fname) dcm.PatientBirthDate = datetime.now().strftime('%Y%m%d') dcm.PatientAge = '000Y' dcm.PatientSize = 1 dcm.PatientWeight = 1 dcm.PatientID = os.path.basename(fname) dcm.PatientSex = 'O' dcm.StudyDescription = os.path.basename(fname) dcm.StudyDate = datetime.now().strftime('%Y%m%d') dcm.StudyTime = datetime.now().strftime('%H%M%S') dcm.StudyID = os.path.basename(fname) dcm.SeriesDescription = os.path.basename(fname) dcm.SamplesPerPixel = 1 dcm.PhotometricInterpretation = 'MONOCHROME2' dcm.PixelRepresentation = 0 # unsigned 0, signed 1 dcm.HighBit = 16 dcm.BitsStored = 16 dcm.BitsAllocated = 16 dcm.Columns = uint16_img.shape[2] dcm.Rows = uint16_img.shape[1] if single_file_mode: dcm.NumberOfFrames = uint16_img.shape[0] dcm.ImagesInAquisition = uint16_img.shape[0] dcm.SliceVector = (np.arange(uint16_img.shape[0]) + 1).tolist() dcm.FrameIncrementPointer = [(0x0054, 0x0080)] else: dcm.NumberOfTimeSlices = 1 dcm.FrameReferenceTime = 0. dcm.ImageOrientationPatient = [1., 0., 0., 0., -1., 0.] dcm.SeriesNumber = 0 dcm.NumberOfSlices = uint16_img.shape[0] dcm.RescaleIntercept = intercept dcm.RescaleSlope = slope dcm.Units = "NONE" dcm.DecayCorrection = "NONE" dcm.InstanceCreatorUID = '333.333.0.0.0' dcm.SOPClassUID = '1.2.840.10008.5.1.4.1.1.20' dcm.SliceThickness = 1 if slice_thickness is None else slice_thickness if spacing_between_slices is None: dcm.SpacingBetweenSlices = 1 if slice_thickness is None else slice_thickness else: dcm.SpacingBetweenSlices = spacing_between_slices ps = 1 if pixel_spacing is None else pixel_spacing if isinstance(ps, list) or isinstance(ps, np.ndarray): dcm.PixelSpacing = [ps[0], ps[1]] else: dcm.PixelSpacing = [ps, ps] if single_file_mode: dcm.PixelData = uint16_img.tostring() dcm.StudyInstanceUID = f'333.333.0.0.0.{now}' dcm.SeriesInstanceUID = f'333.333.0.0.0.{now}.3333' dcm.FrameOfReferenceUID = dcm.StudyInstanceUID dcm.SeriesNumber = 0 dcm.InstanceNumber = 0 dcm.BodyPartExamined = 'UNKNOWN' dcm.Manufacturer = 'DicomConversionUtils' dcm.DeviceSerialNumber = '' dcm.AcquisitionTerminationCondition = 'MANU' dcm.SoftwareVersions = f'{pydicom_ext_version}' dcm.AccessionNumber = '{:13d}'.format(random.randint(0, 1e13)) dcm.InstitutionName = 'DicomConversionUtils' dcm.ImagePositionPatient = [0, 0, 0] if fname is not None: dcm.save_as(fname, write_like_original=False) return dcm else: dcms = [] for slice_idx in range(uint16_img.shape[0]): dcm.SOPInstanceUID = f'333.333.0.0.0.{now}.{slice_idx:06d}' dcm.PixelData = uint16_img[slice_idx].tostring() dcm.StudyInstanceUID = f'333.333.0.0.0.{now}' dcm.SeriesInstanceUID = f'333.333.0.0.0.{now}.3333' dcm.FrameOfReferenceUID = dcm.StudyInstanceUID dcm.InstanceNumber = slice_idx dcm.BodyPartExamined = 'UNKNOWN' dcm.Manufacturer = 'DicomConversionUtils' dcm.DeviceSerialNumber = '' dcm.AcquisitionTerminationCondition = 'MANU' dcm.SoftwareVersions = f'{pydicom_ext_version}' dcm.AccessionNumber = '{:13d}'.format(random.randint(0, 1e13)) dcm.InstitutionName = 'DicomConversionUtils' dcm.ImageIndex = slice_idx if spacing_between_slices is None: if slice_thickness is None: dcm.ImagePositionPatient = [0, 0, slice_idx] dcm.SliceLocation = slice_idx else: dcm.ImagePositionPatient = [0, 0, slice_idx * slice_thickness] dcm.SliceLocation = slice_idx * slice_thickness else: dcm.ImagePositionPatient = [0, 0, slice_idx * spacing_between_slices] dcm.SliceLocation = slice_idx * spacing_between_slices dcms.append(dcm) if fname is not None: f = copy.copy(fname) if ".dcm" in f: f = f.replace(".dcm", f"_{slice_idx:06d}.dcm") else: f += f"_{slice_idx:06d}.dcm" dcm.save_as(f, write_like_original=False) return dcms
def __write_dicom_series_file_nii(series_dataset, dst_one_case_file_path): print('start: write dicom series') if not os.path.exists(dst_one_case_file_path): os.makedirs(dst_one_case_file_path) dataset_list = [] hdr = series_dataset.header print(hdr) sop_instance_info_list = __generate_sop_instance_info_nii(series_dataset) study_instance_uid = pydicom.uid.generate_uid() series_instance_uid = pydicom.uid.generate_uid() frame_of_reference_uid = pydicom.uid.generate_uid() volume_size = series_dataset.shape for j in range(volume_size[2]): sop_instance_uid = sop_instance_info_list[j][1] filename = os.path.join(dst_one_case_file_path, 'CT.' + sop_instance_uid + '.dcm') file_meta = Dataset() file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' file_meta.MediaStorageSOPInstanceUID = sop_instance_uid file_meta.ImplementationClassUID = '1.2.246.352.70.2.1.7' ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) ds.InstanceNumber = sop_instance_info_list[j][0] ds.SliceLocation = sop_instance_info_list[j][2] ds.ImagePositionPatient = sop_instance_info_list[j][3] ds.fix_meta_info() ds.is_little_endian = True ds.is_implicit_VR = True ds.StudyInstanceUID = study_instance_uid ds.SeriesInstanceUID = series_instance_uid ds.FrameOfReferenceUID = frame_of_reference_uid ds.SpecificCharacterSet = 'ISO_IR 100' ds.ImageType = 'ORIGINAL\\PRIMARY\\AXIAL\\HELIX' dt = datetime.datetime.now() datestr = dt.strftime('%Y%m%d') timestr1 = dt.strftime('%H%M%S.%f') timestr2 = dt.strftime('%H%M%S') ds.InstanceCreationDate = datestr ds.InstanceCreationTime = timestr1 ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.2' ds.SOPInstanceUID = sop_instance_uid ds.StudyDate = datestr ds.SeriesDate = datestr ds.ContentDate = datestr ds.StudyTime = timestr2 ds.SeriesTime = timestr2 ds.ContentTime = timestr2 ds.AccessionNumber = '116939' ds.Modality = 'CT' ds.Manufacturer = 'HYHY' #'Philips' ds.InstitutionName = 'Cancer Hosipital' ds.ReferringPhysicianName = '79309' ds.StationName = '-' ds.StudyDescription = '-' ds.PatientName = 'patientxxx' ds.PatientID = 'patientxxx' ds.PatientBirthDate = '19000101' ds.PatientBirthTime = '000000' ds.PatientAge = '119' ds.PatientSex = '1' #'-' ds.PatientSize = 120 ds.PatientWeight = 100 ds.PatientAddress = 'some where' ds.PatientComments = 'some' ds.SliceThickness = series_dataset.header['pixdim'][3] ds.KVP = 1111111 ds.PatientPosition = 'HFS' ds.StudyID = '20200423' ds.SeriesNumber = 1 ds.ImageOrientationPatient = '1\\0\\0\\0\\1\\0' ds.SamplesPerPixel = 1 ds.Rows = volume_size[1] ds.Columns = volume_size[0] spacing_x = series_dataset.header['pixdim'][1] spacing_y = series_dataset.header['pixdim'][2] ds.PixelSpacing = '%f\\%f' % (spacing_x, spacing_y) niiPixelDataType = series_dataset.header['datatype'].dtype if (niiPixelDataType == 'uint16' or niiPixelDataType == 'uint8' ): #判断有符号数 无符号数 if(niiPixelDataType.find('uint') == 1 ) ds.PixelRepresentation = 0 elif (niiPixelDataType == 'int16' or niiPixelDataType == 'int8'): ds.PixelRepresentation = 1 niiBitPixel = series_dataset.header['bitpix'] ds.BitsAllocated = niiBitPixel #16 ds.BitsStored = niiBitPixel #12 ds.HighBit = niiBitPixel - 1 #11 #ds.WindowCenter = 756#60 #ds.WindowWidth = 1500#350 rescale_intercept = series_dataset.header['scl_inter'] if np.isnan(rescale_intercept): ds.RescaleIntercept = 0 #-1024 else: ds.RescaleIntercept = rescale_intercept rescale_slope = series_dataset.header['scl_slope'] if np.isnan(rescale_slope): ds.RescaleSlope = 1 else: ds.RescaleSlope = rescale_slope ds.PixelData = __create_pixel_data_array_nii(series_dataset, j) ds.save_as(filename, write_like_original=False) dataset_list.append(ds) print('end: write dicom series') return dataset_list