Exemple #1
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
 def set_dose(self, dose):
     try:
         dose = float(dose)
         if dose < 0:
             raise Exception()
         self.dose = dose
     except Exception:
         raise InputError("Dose should be a number larger or equal to 0")
Exemple #3
0
 def set_zsteps(self, zsteps):
     try:
         zsteps = float(zsteps)
         if zsteps < 0:
             raise Exception()
         self.zsteps = zsteps
     except Exception:
         raise InputError("ZSteps should be larger 0")
Exemple #4
0
 def set_doseextension(self, doseextension):
     try:
         doseextension = float(doseextension)
         if doseextension < 0:
             raise Exception()
         self.doseextension = doseextension
     except Exception:
         raise InputError("Doseextension should be larger 0")
Exemple #5
0
 def set_contourextension(self, contourextension):
     try:
         contourextension = float(contourextension)
         if contourextension < 0:
             raise Exception()
         self.contourextension = contourextension
     except Exception:
         raise InputError("Contourextension should be larger 0")
Exemple #6
0
    def _read_trip_data_file(
            self,
            datafile_path,
            header_path,
            multiply_by_2=False):  # TODO: could be made private? #126
        """Read TRiP98 formatted data.

        If header file was not previously loaded, it will be attempted first.

        Due to an issue in VIRTUOS, sometimes DosCube data have been reduced with a factor of 2.
        Setting multiply_by_2 to True, will restore the true values, in this case.

        :param datafile_path: Path to TRiP formatted data.
        :param multiply_by_2: The data read will automatically be multiplied with a factor of 2.
        """

        # fill header data if self.header is empty
        if not self.header_set:
            self._read_trip_header_file(header_path)

        # raise exception if reading header failed
        if not self.header_set:
            raise InputError("Header file not loaded")

        # preparation
        data_dtype = np.dtype(self.format_str)
        data_count = self.dimx * self.dimy * self.dimz

        # load data from data file (gzipped or not)
        logger.info("Opening file: " + datafile_path)
        if datafile_path.endswith('.gz'):
            import gzip
            with gzip.open(datafile_path, "rb") as f:
                s = f.read(data_dtype.itemsize * data_count)
                cube = np.frombuffer(s, dtype=data_dtype, count=data_count)
        else:
            cube = np.fromfile(datafile_path, dtype=data_dtype)

        if self.byte_order == "aix":
            logger.info("AIX big-endian data.")
            # byteswapping is not needed anymore, handled by "<" ">" in dtype

        # sanity check
        logger.info("Cube data points : {:d}".format(len(cube)))
        if len(cube) != self.dimx * self.dimy * self.dimz:
            logger.error("Header size and cube size dont match.")
            logger.error("Cube data points : {:d}".format(len(cube)))
            logger.error("Header says      : {:d} = {:d} * {:d} * {:d}".format(
                self.dimx * self.dimy * self.dimz, self.dimx, self.dimy,
                self.dimz))
            raise IOError("Header data and dose cube size are not consistent.")

        cube = np.reshape(cube, (self.dimz, self.dimy, self.dimx))
        if multiply_by_2:
            logger.warning(
                "Cube was previously rescaled to 50%. Now multiplying with 2.")
            cube *= 2
        self.cube = cube
 def set_hu_value(self, value):
     if len(value) is 0:
         self.hu_value = None
         return
     try:
         value = float(value)
         self.hu_value = value
     except Exception:
         raise InputError("HU Value should be a number")
 def set_max_dose_fraction(self, max_dose_fraction):
     try:
         max_dose_fraction = float(max_dose_fraction)
         if max_dose_fraction < 0:
             raise Exception()
         self.max_dose_fraction = max_dose_fraction
     except Exception:
         raise InputError("Max dose fraction should be "
                          "a number between 0 and 1")
Exemple #9
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
Exemple #10
0
    def set_fwhm(self, fwhm):
        try:
            fwhm = float(fwhm)
            if fwhm <= 0:
                raise Exception()
            self.fwhm = fwhm

        except Exception:
            raise InputError("Fwhm shoud be a number and larger than 0")
Exemple #11
0
 def set_rasterstep(self, a, b):
     try:
         a = float(a)
         b = float(b)
         if a < 0 or b < 0:
             raise Exception()
         self.rasterstep = [a, b]
     except Exception:
         raise InputError("Rastersteps should be " "larger than 0 and numbers")
Exemple #12
0
    def get_voi_by_name(self, name):
        """ Returns a Voi object by its name.

        :param str name: Name of voi to be returned.
        :returns: the Voi which has exactly this name, else raise an Error.
        """
        for voi in self.vois:
            if voi.name.lower() == name.lower():
                return voi
        raise InputError("Voi doesn't exist")
Exemple #13
0
    def set_couch(self, angle):
        if type(angle) is str and not len(angle):
            angle = 0
        try:
            angle = (float(angle) + 360) % 360
            if angle < 0 or angle > 360:
                raise Exception()
            self.couch = angle

        except Exception:
            raise InputError("Couch angle shoud be " "a number between 0 and 360")
Exemple #14
0
    def set_gantry(self, angle):
        if type(angle) is str and not len(angle):
            angle = 0
        try:
            angle = (float(angle) + 360) % 360
            if angle < 0 or angle > 360:
                raise Exception()
            self.gantry = angle

        except Exception:
            raise InputError("Gantry angle shoud be a " "number between 0 and 360")
Exemple #15
0
    def read_dicom(self, dcm):
        """ Imports the dose distribution from DICOM object.

        :param DICOM dcm: a DICOM object
        """
        if "rtdose" not in dcm:
            raise InputError("Data doesn't contain dose information")
        if self.header_set is False:
            self._set_header_from_dicom(dcm)
        self.cube = np.zeros((self.dimz, self.dimy, self.dimx))
        for i, item in enumerate(dcm["rtdose"].pixel_array):
            self.cube[i][:][:] = item
Exemple #16
0
 def set_target(self, target):
     if len(target) is 0:
         self.target = []
         return
     target = target.split(",")
     if len(target) is 3:
         try:
             self.target = [float(target[0]), float(target[1]), float(target[2])]
             return
         except Exception:
             # TODO fix that !
             pass
     raise InputError("Target should be empty " "or in the format x,y,z")
Exemple #17
0
    def read_dicom(self, data, structure_ids=None):
        """
        Reads structures from a Dicom RTSS Object.

        :param Dicom data: A Dicom RTSS object.
        :param structure_ids: (TODO: undocumented)
        """
        if "rtss" not in data:
            raise InputError("Input is not a valid rtss structure")
        dcm = data["rtss"]
        self.version = "2.0"
        for i, item in enumerate(dcm.ROIContours):
            if structure_ids is None or item.RefdROINumber in structure_ids:
                v = Voi(dcm.RTROIObservations[i].ROIObservationLabel.decode('iso-8859-1'), self.cube)
                v.read_dicom(dcm.RTROIObservations[i], item)
                self.add_voi(v)
        self.cube.xoffset = 0
        self.cube.yoffset = 0
        self.cube.zoffset = 0
        """shift = min(self.cube.slice_pos)
Exemple #18
0
    def read_dicom(self, dcm):
        """ Imports CT-images from Dicom object.

        :param Dicom dcm: a Dicom object
        """
        if "images" not in dcm:
            raise InputError("Data doesn't contain ct data")
        if not self.header_set:
            self.read_dicom_header(dcm)

        self.cube = np.zeros((self.dimz, self.dimy, self.dimx), dtype=np.int16)
        intersect = float(dcm["images"][0].RescaleIntercept)
        slope = float(dcm["images"][0].RescaleSlope)

        for i in range(len(dcm["images"])):
            data = np.array(dcm["images"][i].pixel_array) * slope + intersect
            self.cube[i][:][:] = data
        if self.slice_pos[1] < self.slice_pos[0]:
            self.slice_pos.reverse()
            self.zoffset = self.slice_pos[0]
            self.cube = self.cube[::-1]
Exemple #19
0
    def set_projectile(self, projectile):

        if projectile not in ['H', 'C', 'O', 'Ne']:
            raise InputError("Projectile not allowed")
        self.projectile = projectile
Exemple #20
0
    def split_plan(self, plan=None):
        self.targets = []
        self.oar_list = []

        dose = 0
        for voi in self.plan.get_vois():
            if voi.is_oar():
                self.oar_list.append(voi)
            if voi.is_target():
                self.targets.append(voi)
                if voi.get_dose() > dose:
                    dose = voi.get_dose()
        if not len(self.targets):
            raise InputError("No targets")
        if not len(self.plan.get_fields()):
            raise InputError("No fields")
        self.target_dose = dose
        if plan is None:
            plan = self.plan
        proj = []
        self.projectile_dose_level = {}
        for field in plan.fields:
            if field.get_projectile() not in proj:
                proj.append(field.get_projectile())
                self.projectile_dose_level[field.get_projectile()] = 0

        if len(proj) > 1:
            self.mult_proj = True
        else:
            self.mult_proj = False

        if self.mult_proj:
            self.projectiles = {}
            for field in plan.fields:
                if field.get_projectile() not in self.projectiles.keys():
                    self.projectiles[field.get_projectile()] = {
                        "target_dos": DosCube(self.images),
                        "fields": [field],
                        "name": field.get_projectile(),
                        "projectile": field.get_projectile()
                    }
                else:
                    self.projectiles[field.get_projectile()]["fields"].append(
                        field)

            self.target_dos = DosCube(self.images)

            for i, voi in enumerate(self.targets):
                temp = DosCube(self.images)
                voi_dose_level = int(voi.get_dose() / dose * 1000)
                temp.load_from_structure(voi.get_voi().get_voi_data(), 1)
                for projectile, data in self.projectiles.items():
                    dose_percent = self.plan.get_dose_percent(projectile)
                    if not voi.get_dose_percent(projectile) is None:
                        dose_percent = voi.get_dose_percent(projectile)
                    proj_dose_lvl = int(voi.get_dose() / self.target_dose *
                                        dose_percent * 10)
                    if self.projectile_dose_level[projectile] < proj_dose_lvl:
                        self.projectile_dose_level[projectile] = proj_dose_lvl
                    if proj_dose_lvl == 0:
                        proj_dose_lvl = -1
                    if i == 0:
                        data["target_dos"] = temp * proj_dose_lvl
                    else:
                        data["target_dos"].merge_zero(temp * proj_dose_lvl)
                if i == 0:
                    self.target_dos = temp * voi_dose_level
                else:
                    self.target_dos.merge_zero(temp * voi_dose_level)
            for projectile, data in self.projectiles.items():
                data["target_dos"].cube[data["target_dos"].cube == -1] = int(0)
                self.plan.add_dose(data["target_dos"],
                                   "target_%s" % projectile)
            self.rest_dose = copy.deepcopy(self.target_dos)
Exemple #21
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
Exemple #22
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