示例#1
0
    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
示例#2
0
    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
示例#3
0
    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
示例#4
0
    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
示例#5
0
    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
示例#6
0
    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
示例#7
0
    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
示例#8
0
    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