def _set_header_from_dicom(self, dcm): """ Creates the header metadata for this Cube class, based on a given Dicom object. :param DICOM dcm: Dicom object which will be used for generating the header data. """ if not _dicom_loaded: raise ModuleNotLoadedError("Dicom") ds = dcm["images"][0] self.version = "1.4" self.created_by = "pytrip" self.creation_info = "Created by PyTRiP98;" self.primary_view = "transversal" self.set_data_type(type(ds.pixel_array[0][0])) self.patient_name = ds.PatientsName self.slice_dimension = int(ds.Rows) # should be changed ? self.pixel_size = float( ds.PixelSpacing[0]) # (0028, 0030) Pixel Spacing (DS) self.slice_thickness = ds.SliceThickness # (0018, 0050) Slice Thickness (DS) # slice_distance != SliceThickness. One may have overlapping slices. See #342 self.slice_number = len(dcm["images"]) self.xoffset = float(ds.ImagePositionPatient[0]) self.dimx = int(ds.Rows) # (0028, 0010) Rows (US) self.yoffset = float(ds.ImagePositionPatient[1]) self.dimy = int(ds.Columns) # (0028, 0011) Columns (US) self.zoffset = float(ds.ImagePositionPatient[2] ) # note that zoffset should not be used. self.dimz = len(dcm["images"]) self.z_table = True self._set_z_table_from_dicom(dcm) # Fix for bug #342 # TODO: slice_distance should probably be a list of distances, # but for now we will just use the distance between the first two slices. if len(self.slice_pos ) > 1: # _set_z_table_from_dicom() must be called before self.slice_distance = abs(self.slice_pos[1] - self.slice_pos[0]) logger.debug("Slice distance set to {:.2f}".format( self.slice_distance)) else: logger.warning( "Only a single slice found. Setting slice_distance to slice_thickness." ) self.slice_distance = self.slice_thickness if self.slice_thickness > self.slice_distance: # TODO: this is probably valid dicom format, however let's print a warning for now # as it may indicate some problem with the input dicom, as it is rather unusual. logger.warning( "Overlapping slices found: slice thickness is larger than the slice distance." ) self.set_byteorder() self.data_type = "integer" self.num_bytes = 2 self._set_format_str() self.header_set = True # unique for whole structure set self._dicom_study_instance_uid = ds.StudyInstanceUID self._ct_dicom_series_instance_uid = ds.SeriesInstanceUID
def read_dicom_header(self, dcm): """ Creates the header metadata for this Cube class, based on a given Dicom object. :param Dicom dcm: Dicom object which will be used for generating the header data. """ if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") ds = dcm["images"][0] self.version = "1.4" self.created_by = "pytrip" self.creation_info = "Created by PyTRiP98;" self.primary_view = "transversal" self.set_data_type(type(ds.pixel_array[0][0])) self.patient_name = ds.PatientsName self.slice_dimension = int(ds.Rows) # should be changed ? self.pixel_size = float(ds.PixelSpacing[0]) self.slice_distance = abs( float(dcm["images"][0].ImagePositionPatient[2]) - float(dcm["images"][1].ImagePositionPatient[2])) self.slice_number = len(dcm["images"]) self.xoffset = float(ds.ImagePositionPatient[0]) self.dimx = int(ds.Rows) self.yoffset = float(ds.ImagePositionPatient[1]) self.dimy = int(ds.Columns) self.zoffset = float(ds.ImagePositionPatient[2]) self.dimz = len(dcm["images"]) self.z_table = True self.set_z_table(dcm) self.set_byteorder() self._set_format_str() self.header_set = True
def create_dicom_base(self): if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") if self.header_set is False: raise InputError("Header not loaded") meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage meta.MediaStorageSOPInstanceUID = "1.2.3" 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.PatientsName = self.patient_name ds.PatientID = "123456" ds.PatientsSex = '0' ds.PatientsBirthDate = '19010101' ds.SpecificCharacterSet = 'ISO_IR 100' ds.AccessionNumber = '' ds.is_little_endian = True ds.is_implicit_VR = True ds.SOPClassUID = '1.2.3' # !!!!!!!! ds.SOPInstanceUID = '1.2.3' # !!!!!!!!!! ds.StudyInstanceUID = '1.2.3' # !!!!!!!!!! ds.FrameofReferenceUID = '1.2.3' # !!!!!!!!! ds.StudyDate = '19010101' # !!!!!!! ds.StudyTime = '000000' # !!!!!!!!!! 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] return ds
def read_dicom_header(self, dcm): """ Creates the header metadata for this Cube class, based on a given Dicom object. :param Dicom dcm: Dicom object which will be used for generating the header data. """ if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") ds = dcm["images"][0] self.version = "1.4" self.created_by = "pytrip" self.creation_info = "Created by PyTRiP98;" self.primary_view = "transversal" self.set_data_type(type(ds.pixel_array[0][0])) self.patient_name = ds.PatientsName self.slice_dimension = int(ds.Rows) # should be changed ? self.pixel_size = float(ds.PixelSpacing[0]) # (0028, 0030) Pixel Spacing (DS) self.slice_distance = ds.SliceThickness # (0018, 0050) Slice Thickness (DS) self.slice_number = len(dcm["images"]) self.xoffset = float(ds.ImagePositionPatient[0]) self.dimx = int(ds.Rows) # (0028, 0010) Rows (US) self.yoffset = float(ds.ImagePositionPatient[1]) self.dimy = int(ds.Columns) # (0028, 0011) Columns (US) self.zoffset = float(ds.ImagePositionPatient[2]) # note that zoffset should not be used. self.dimz = len(dcm["images"]) self.z_table = True self.set_z_table(dcm) self.set_byteorder() self._set_format_str() self.header_set = True # unique for whole structure set self._dicom_study_instance_uid = ds.StudyInstanceUID self._ct_dicom_series_instance_uid = ds.SeriesInstanceUID
def create_dicom(self): """ Creates a Dicom RT-Dose object from self. This function can be used to convert a TRiP98 Dose file to Dicom format. :returns: a Dicom RT-Dose object. """ if not _dicom_loaded: raise ModuleNotLoadedError("Dicom") if not self.header_set: raise InputError("Header not loaded") ds = self.create_dicom_base() ds.Modality = 'RTDOSE' ds.SamplesperPixel = 1 ds.BitsAllocated = self.num_bytes * 8 ds.BitsStored = self.num_bytes * 8 ds.AccessionNumber = '' ds.SeriesDescription = 'RT Dose' ds.DoseUnits = 'GY' ds.DoseType = 'PHYSICAL' ds.DoseGridScaling = self.target_dose / 10**5 ds.DoseSummationType = 'PLAN' ds.SliceThickness = '' ds.InstanceCreationDate = '19010101' ds.InstanceCreationTime = '000000' ds.NumberOfFrames = len(self.cube) ds.PixelRepresentation = 0 ds.StudyID = '1' ds.SeriesNumber = 14 ds.GridFrameOffsetVector = [ x * self.slice_distance for x in range(self.dimz) ] ds.InstanceNumber = '' ds.NumberofFrames = len(self.cube) ds.PositionReferenceIndicator = "RF" ds.TissueHeterogeneityCorrection = ['IMAGE', 'ROI_OVERRIDE'] ds.ImagePositionPatient = [ "%.3f" % (self.xoffset * self.pixel_size), "%.3f" % (self.yoffset * self.pixel_size), "%.3f" % (self.slice_pos[0]) ] ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.2' ds.SOPInstanceUID = '1.2.246.352.71.7.320687012.47206.20090603085223' ds.SeriesInstanceUID = '1.2.246.352.71.2.320687012.28240.20090603082420' # Bind to rtplan rt_set = Dataset() rt_set.RefdSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.5' rt_set.RefdSOPInstanceUID = '1.2.3' ds.ReferencedRTPlans = Sequence([rt_set]) pixel_array = np.zeros((len(self.cube), ds.Rows, ds.Columns), dtype=self.pydata_type) pixel_array[:][:][:] = self.cube[:][:][:] ds.PixelData = pixel_array.tostring() return ds
def create_dicom(self): """ Generates and returns Dicom RTSTRUCT object, which holds all VOIs. :returns: a Dicom RTSTRUCT object holding any VOIs. """ if _dicom_loaded is False: raise ModuleNotLoadedError("Dicom") meta = Dataset() meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' # RT Structure Set Storage SOP Class # see https://github.com/darcymason/pydicom/blob/master/pydicom/_uid_dict.py meta.MediaStorageSOPInstanceUID = "1.2.3" 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) if self.cube is not None: ds.PatientsName = self.cube.patient_name else: ds.PatientsName = "" ds.PatientID = "123456" ds.PatientsSex = '0' ds.PatientsBirthDate = '19010101' ds.SpecificCharacterSet = 'ISO_IR 100' ds.AccessionNumber = '' ds.is_little_endian = True ds.is_implicit_VR = True ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' # RT Structure Set Storage SOP Class ds.SOPInstanceUID = '1.2.3' # !!!!!!!!!! ds.StudyInstanceUID = '1.2.3' # !!!!!!!!!! ds.SeriesInstanceUID = '1.2.3' # !!!!!!!!!! ds.FrameofReferenceUID = '1.2.3' # !!!!!!!!! ds.SeriesDate = '19010101' # !!!!!!!! ds.ContentDate = '19010101' # !!!!!! ds.StudyDate = '19010101' # !!!!!!! ds.SeriesTime = '000000' # !!!!!!!!! ds.StudyTime = '000000' # !!!!!!!!!! ds.ContentTime = '000000' # !!!!!!!!! ds.StructureSetLabel = 'pyTRiP plan' ds.StructureSetDate = '19010101' ds.StructureSetTime = '000000' ds.StructureSetName = 'ROI' ds.Modality = 'RTSTRUCT' roi_label_list = [] roi_data_list = [] roi_structure_roi_list = [] # to get DICOM which can be loaded in Eclipse we need to store information about UIDs of all slices in CT # first we check if DICOM cube is loaded if self.cube is not None: rt_ref_series_data = Dataset() rt_ref_series_data.SeriesInstanceUID = '1.2.3.4.5' rt_ref_series_data.ContourImageSequence = Sequence([]) # each CT slice corresponds to one DICOM file for slice_dicom in self.cube.create_dicom(): slice_dataset = Dataset() slice_dataset.ReferencedSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage SOP Class slice_dataset.ReferencedSOPInstanceUID = slice_dicom.SOPInstanceUID # most important - slice UID rt_ref_series_data.ContourImageSequence.append(slice_dataset) rt_ref_study_seq_data = Dataset() rt_ref_study_seq_data.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.2' # Study Component Management Class rt_ref_study_seq_data.ReferencedSOPInstanceUID = '1.2.3.4.5' rt_ref_study_seq_data.RTReferencedSeriesSequence = Sequence([rt_ref_series_data]) rt_ref_frame_study_data = Dataset() rt_ref_frame_study_data.RTReferencedStudySequence = Sequence([rt_ref_study_seq_data]) rt_ref_frame_study_data.FrameOfReferenceUID = '1.2.3.4.5' ds.ReferencedFrameOfReferenceSequence = Sequence([rt_ref_frame_study_data]) for i in range(self.number_of_vois()): roi_label = self.vois[i].create_dicom_label() roi_label.ObservationNumber = str(i + 1) roi_label.ReferencedROINumber = str(i + 1) roi_label.RefdROINumber = str(i + 1) roi_contours = self.vois[i].create_dicom_contour_data(i) roi_contours.RefdROINumber = str(i + 1) roi_contours.ReferencedROINumber = str(i + 1) roi_structure_roi = self.vois[i].create_dicom_structure_roi() roi_structure_roi.ROINumber = str(i + 1) roi_structure_roi_list.append(roi_structure_roi) roi_label_list.append(roi_label) roi_data_list.append(roi_contours) ds.RTROIObservations = Sequence(roi_label_list) ds.ROIContours = Sequence(roi_data_list) ds.StructureSetROIs = Sequence(roi_structure_roi_list) return ds
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.PatientsName = 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.PatientsSex = '' # Patient's Sex tag 0x0010,0x0040 (type CS - Code String) # Enumerated Values: M = male F = female O = other. ds.PatientsBirthDate = '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 create_dicom(self): """ Creates a DICOM RT-Dose object from self. This function can be used to convert a TRiP98 Dose file to DICOM format. :returns: a DICOM RT-Dose object. """ if not _dicom_loaded: raise ModuleNotLoadedError("DICOM") if not self.header_set: raise InputError("Header not loaded") ds = self.create_dicom_base() ds.Modality = 'RTDOSE' ds.SamplesPerPixel = 1 ds.BitsAllocated = self.num_bytes * 8 ds.BitsStored = self.num_bytes * 8 ds.AccessionNumber = '' ds.SeriesDescription = 'RT Dose' ds.DoseUnits = 'GY' ds.DoseType = 'PHYSICAL' ds.DoseGridScaling = self.target_dose / 10**5 ds.DoseSummationType = 'PLAN' ds.SliceThickness = '' ds.InstanceCreationDate = '19010101' ds.InstanceCreationTime = '000000' ds.NumberOfFrames = len(self.cube) ds.PixelRepresentation = 0 ds.StudyID = '1' ds.SeriesNumber = '14' # SeriesNumber tag 0x0020,0x0011 (type IS - Integer String) ds.GridFrameOffsetVector = [ x * self.slice_distance for x in range(self.dimz) ] ds.InstanceNumber = '' ds.PositionReferenceIndicator = "RF" ds.TissueHeterogeneityCorrection = ['IMAGE', 'ROI_OVERRIDE'] ds.ImagePositionPatient = [ "%.3f" % (self.xoffset * self.pixel_size), "%.3f" % (self.yoffset * self.pixel_size), "%.3f" % (self.slice_pos[0]) ] ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.2' ds.SOPInstanceUID = '1.2.246.352.71.7.320687012.47206.20090603085223' # 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._dose_dicom_series_instance_uid may be either set in __init__ when creating new object # Series Instance UID might be different than Series Instance UID for CTs ds.SeriesInstanceUID = self._dose_dicom_series_instance_uid # Bind to rtplan rt_set = Dataset() rt_set.RefdSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.5' rt_set.RefdSOPInstanceUID = '1.2.3' ds.ReferencedRTPlanSequence = Sequence([rt_set]) pixel_array = np.zeros((len(self.cube), ds.Rows, ds.Columns), dtype=self.pydata_type) pixel_array[:][:][:] = self.cube[:][:][:] ds.PixelData = pixel_array.tostring() return ds