def mk_dcm(dcm_path, slice_data, meta):
        file_meta = pydicom.Dataset()
        file_meta.TransferSyntaxUID = '1.2.840.10008.1.2'
        file_meta.MediaStorageSOPClassUID = 'Secondary Capture Image Storage'
        file_meta.MediaStorageSOPInstanceUID = '1.3.6.1.4.1.9590.100.1.1.111165684411017669021768385720736873780'
        file_meta.ImplementationClassUID = '1.3.6.1.4.1.9590.100.1.0.100.4.0'
        ds = pydicom.FileDataset(dcm_path, {},
                                 file_meta=file_meta,
                                 preamble=b'\0' * 128)
        ds.Modality = 'WSD'
        ds.ContentDate = str(datetime.date.today()).replace('-', '')
        ds.ContentTime = str(time.time())  #milliseconds since the epoch
        ds.StudyInstanceUID = '1.3.6.1.4.1.9590.100.1.1.124313977412360175234271287472804872093'
        ds.SeriesInstanceUID = '1.3.6.1.4.1.9590.100.1.1.369231118011061003403421859172643143649'
        ds.SOPInstanceUID = '1.3.6.1.4.1.9590.100.1.1.111165684411017669021768385720736873780'
        ds.SOPClassUID = 'Secondary Capture Image Storage'
        ds.SecondaryCaptureDeviceManufctur = 'Python 3.6'

        # TAGS NECESSARY TO CONTAIN IMAGE DATA:
        ds.SamplesPerPixel = 1
        ds.PhotometricInterpretation = "MONOCHROME2"
        ds.PixelRepresentation = 1
        ds.HighBit = 15
        ds.BitsStored = 16
        ds.BitsAllocated = 16
        ds.RescaleIntercept = 0
        ds.RescaleSlope = 1
        ds.WindowCenter = 80
        ds.WindowWidth = 600
        ds.Columns = slice_data.shape[0]
        ds.Rows = slice_data.shape[1]
        ds.PixelData = slice_data.tobytes()
        [ds.add_new(k, 'LO', v) for k, v in meta.items()]

        ds.save_as(dcm_path)
Пример #2
0
    def gen_file(self, fn=None):

        # print("Setting file meta information...")
        # Populate required values for file meta information
        file_meta = pydicom.Dataset()
        file_meta.FileMetaInformationGroupLength = 60  # Will be rewritten but must exist
        file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'  # CT
        file_meta.MediaStorageSOPInstanceUID = "1.2.3"
        file_meta.ImplementationClassUID = "1.2.3.4"

        # Wants all of these to be legit:
        #   * (0002,0000) FileMetaInformationGroupLength, UL, 4
        #   * (0002,0001) FileMetaInformationVersion, OB, 2
        # X * (0002,0002) MediaStorageSOPClassUID, UI, N
        # X * (0002,0003) MediaStorageSOPInstanceUID, UI, N
        #   * (0002,0010) TransferSyntaxUID, UI, N
        # X * (0002,0012) ImplementationClassUID, UI, N

        ds = pydicom.FileDataset(fn,
                                 self.as_pydicom_ds(),
                                 file_meta=file_meta,
                                 preamble=b"\0" * 128)

        # print(ds)
        with NamedTemporaryFile() as f:
            ds.save_as(filename=f.name, write_like_original=True)
            self.file = f.read()
    def save(self) -> None:
        """
        Save input data into DICOM file.
        :return: None
        """
        if not self.parseInput():
            return
        else:
            filename = QtWidgets.QFileDialog.getSaveFileName(self, "Save DICOM", "../../..", "*.dcm")[0]
            if filename == '':
                return
            else:
                fm = pd.Dataset()

                # CT Image Storage
                fm.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.2"
                fm.MediaStorageSOPInstanceUID = pd.uid.generate_uid()
                fm.TransferSyntaxUID = pd.uid.ExplicitVRLittleEndian
                fm.ImplementationClassUID = pd.uid.generate_uid()

                ds = pd.FileDataset(None, {})

                ds.file_meta = fm

                # CT Image Storage
                ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.2"
                ds.SOPInstanceUID = pd.uid.generate_uid()

                ds.ContentDate = f"{self.date_time.year:04}{self.date_time.month:02}{self.date_time.day:02}"
                ds.ContentTime = f"{self.date_time.hour:02}{self.date_time.minute:02}{self.date_time.second:02}"

                ds.StudyInstanceID = pd.uid.generate_uid()
                ds.SeriesInstanceID = pd.uid.generate_uid()

                ds.Modality = "CT"
                ds.ConversionType = 'WSD'  # workstation
                ds.ImageType = r"ORIGINAL\PRIMARY\AXIAL"

                ds.PatientName = self.input_data["PatientName"]
                ds.PatientID = self.input_data["PatientID"]
                ds.PatientSex = self.input_data["PatientSex"]
                ds.ImageComments = self.input_data["ImageComments"]

                ds.PixelData = self.img.astype(np.uint8).tobytes()
                ds.Rows, ds.Columns = self.img.shape
                ds.SamplesPerPixel = 1
                ds.PixelRepresentation = 0
                ds.PhotometricInterpretation = "MONOCHROME2"
                ds.BitsAllocated, ds.BitsStored = 8, 8
                ds.HighBit = 7

                ds.is_little_endian = True
                ds.is_implicit_VR = False

                ds.save_as(f"{filename}.dcm", write_like_original=False)
                self.close()
def write_dicom(output_name, data, image_number=0):
    # Write the data to a dicom file.
    # Dicom files are difficult to set up correctly, this file will likely
    # crash when trying to open it using dcm2nii. However, if it is loaded in
    # python (e.g., dicom.dcmread) then pixel_array contains the relevant
    # voxel data

    # Convert data from float to in
    dataInts = data.astype(np.int16)

    # Populate required values for file meta information
    file_meta = dicom.Dataset()
    file_meta.MediaStorageSOPClassUID = '1.2'  # '1.2.840.10008.5.1.4.1.1.2'
    file_meta.MediaStorageSOPInstanceUID = "1.2.3"
    file_meta.ImplementationClassUID = "1.2.3.4"
    file_meta.TransferSyntaxUID = '1.2.840.10008.1.2'

    # Create the FileDataset
    ds = dicom.FileDataset(output_name, {},
                           file_meta=file_meta,
                           preamble=b"\0" * 128)

    # Set image dimensions
    frames, rows, cols = dataInts.shape
    ds.Rows = rows
    ds.Columns = cols
    ds.NumberOfFrames = frames
    ds.SamplesPerPixel = 1
    ds.BitsAllocated = 16
    ds.BitsStored = 16
    ds.PixelRepresentation = 0
    ds.InstanceNumber = image_number
    ds.ImagePositionPatient = [0, 0, 0]
    ds.ImageOrientationPatient = [.01, 0, 0, 0, 0, 0]

    # Add the data elements -- not trying to set all required here. Check DICOM
    # standard
    ds.PatientName = "sim"
    ds.PatientID = "sim"

    # Set the transfer syntax
    ds.is_little_endian = True
    ds.is_implicit_VR = True

    # Set creation date/time
    image_datetime = script_datetime + datetime.timedelta(seconds=image_number)
    timeStr = image_datetime.strftime('%H%M%S')
    ds.ContentDate = image_datetime.strftime('%Y%m%d')
    ds.ContentTime = timeStr

    # Add the data
    ds.PixelData = dataInts.tobytes()

    ds.save_as(output_name)
Пример #5
0
def validate_transfer_syntax_uid(data_set):
    meta = pydicom.Dataset()
    meta.ImplementationClassUID = pydicom.uid.generate_uid()
    meta.TransferSyntaxUID = ImplicitVRLittleEndian
    new_data_set = pydicom.FileDataset(filename_or_obj=None,
                                       dataset=data_set,
                                       is_little_endian=True,
                                       file_meta=meta)
    new_data_set.is_little_endian = True
    new_data_set.is_implicit_VR = True

    return new_data_set
Пример #6
0
    def build(kwargs: dict = None) -> pydicom.FileDataset:
        if kwargs is None:
            kwargs = {}
        validate_kwargs(kwargs)

        file_meta = pydicom.Dataset()
        file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
        file_meta.MediaStorageSOPInstanceUID = '1.2.3'
        file_meta.ImplementationClassUID = '1.2.3.4'

        ds = pydicom.FileDataset('', {},
                                 file_meta=file_meta,
                                 preamble=b"\0" * 128)

        # Add the data elements
        ds.PatientName = 'Fake Name'
        ds.PatientID = '123456'

        # Set transfer syntax
        ds.is_little_endian = True
        ds.is_implicit_VR = True

        # Set creation time
        dt = datetime.datetime.now()
        ds.ContentDate = dt.strftime('%Y%m%d')
        ds.ContentTime = dt.strftime('%H%M%S.%f')

        # Set transfer syntax
        ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian

        ds.BitsAllocated = 8
        ds.Rows = kwargs.get('Rows', 64)
        ds.Columns = kwargs.get('Columns', 64)
        ds.PixelRepresentation = 0
        ds.SamplesPerPixel = 1
        ds.PhotometricInterpretation = 'MONOCHROME1'
        ds.SeriesDescription = kwargs.get('SeriesDescription', '')
        ds.ViewPosition = ''

        np.random.seed(kwargs.get('seed', np.random.randint(0, 2**32 - 1)))
        ds.PixelData = np.random.randint(0,
                                         256,
                                         size=(ds.Rows, ds.Columns),
                                         dtype=np.uint8).reshape(-1)

        return ds
Пример #7
0
def save_dicom(orgpath, npy, config, idx):
    """
    Original code can be found in : https://github.com/jryoungw/DicomWrite/blob/master/write_dicom.py
    Supported by Mingyu Kim
    """

    dcm = pydicom.read_file(orgpath)
    savepath = config.result_path + '/' + '/'.join(orgpath.split('/')[:-1])
    os.makedirs(config.result_path + '/' + '/'.join(orgpath.split('/')[:-1]),
                exist_ok=True)
    ds = pydicom.FileDataset(config.result_path + '/' + '/'.join(orgpath.split('/')[:-1])+\
                             '/'+'{:05d}.dcm'.format(idx), {}, file_meta=pydicom.Dataset(), \
                             preamble=("\0"*128).encode())
    ds.ImageType = dcm.ImageType
    ds.SOPClassUID = dcm.SOPClassUID
    ds.SOPInstanceUID = dcm.SOPInstanceUID

    methods = [i for i in dcm.__dir__() if '_' not in i]

    for m in methods:
        try:
            exec('ds.' + m + '=dcm.' + m)
        except:
            print('[*] ' + m + ' is not implemented')
    try:
        ds.RescaleIntercept = '-1024'
    except:
        pass
    try:
        ds.RescaleSlope = '1'
    except:
        pass
    try:
        ds.WindowCenterWidthExplanation = dcm.WindowCenterWidthExplanation
    except:
        pass

    ds.is_little_endian = True

    ##################### If pixels are not written correctly, change this value to True #####################
    ds.is_implicit_VR = False
    ##########################################################################################################
    savepath = os.path.join(savepath, '{:05d}.dcm'.format(idx + 1))
    ds.PixelData = npy.tostring()
    ds.save_as(savepath)
def labelvol_to_rtstruct(roi_vol,
                         aff,
                         refdcm_file,
                         filename,
                         ordered_slices,
                         uid_base='1.2.826.0.1.3680043.9.7147.',
                         seriesDescription='test rois',
                         structureSetLabel='RTstruct',
                         structureSetName='my rois',
                         connect_holes=True,
                         roinames=None,
                         roidescriptions=None,
                         roigenerationalgs=None,
                         roi_colors=[['255', '0', '0'], ['0', '0', '255'],
                                     ['0', '255', '0'], ['255', '0', '255'],
                                     ['255', '255', '0'], ['0', '255', '255']],
                         tags_to_copy=[
                             'PatientName', 'PatientID', 'AccessionNumber',
                             'StudyID', 'StudyDescription', 'StudyDate',
                             'StudyTime', 'SeriesDate', 'SeriesTime'
                         ],
                         tags_to_add=None):
    """Convert a 3D array with integer ROI label to RTstruct

  Parameters
  ---------

  roi_vol : 3d numpy integer array 
    in LPS orientation containing the ROI labels
    0 is considered background
    1  ...          ROI-1
    n  ...          ROI-n


  aff : 2d 4x4 numpy array
      affine matrix that maps from voxel to (RAS) world coordinates
  
  refdcm_file : string or list
    A single reference dicom file or a list of reference files (multiple CT slices)
    From this file several dicom tags are copied (e.g. the FrameOfReferenceUID).
    In case a list of files is given, the dicom tags are copied from the first file,
    however all SOPInstanceUIDs are added to the ContourImageSequence (needed for some
    RT systems)

  filename : string
    name of the output rtstruct file

  uid_base : string, optional
    uid base used to generate some dicom UID

  seriesDescription : string, optional
    dicom series description for the rtstruct series

  structureSetLabel, structureSetName : string, optional
    Label and Name of the structSet

  connect_holes : bool, optional
    whether to connect inner holes to their outer parents contour - default: True
    this connection is needed to show holes correctly in MIM

  roinames, roidescriptions, roigenerationalgs : lists, optional
    containing strings for ROIName, ROIDescription and ROIGenerationAlgorithm
  
  roi_colors: list of lists containing 3 integer strings (0 - 255), optional
    used as ROI display colors

  tags_to_copy: list of strings, list optional
    extra dicom tags to copy from the refereced dicom file

  tags_to_add: dictionary
    with valid dicom tags to add in the header
  """

    roinumbers = np.unique(roi_vol)
    roinumbers = roinumbers[roinumbers > 0]
    nrois = len(roinumbers)

    if isinstance(refdcm_file, list):
        refdcm = pydicom.read_file(refdcm_file[0])
    else:
        refdcm = pydicom.read_file(refdcm_file)

    file_meta = pydicom.Dataset()

    file_meta.ImplementationClassUID = uid_base + '1.1.1'
    file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3'
    file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid(
        '1.2.840.10008.5.1.4.1.1.481.3.')

    ds = pydicom.FileDataset(filename, {},
                             file_meta=file_meta,
                             preamble=b"\0" * 128)

    ds.Modality = 'RTSTRUCT'
    ds.SeriesDescription = seriesDescription

    #--- copy dicom tags from reference dicom file
    for tag in tags_to_copy:
        if tag in refdcm:
            setattr(ds, tag, refdcm.data_element(tag).value)
        else:
            warnings.warn(
                tag + ' not in reference dicom file -> will not be written')

    ds.StudyInstanceUID = refdcm.StudyInstanceUID
    ds.SeriesInstanceUID = pydicom.uid.generate_uid(uid_base)

    ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3'
    ds.SOPInstanceUID = pydicom.uid.generate_uid(uid_base)

    ds.StructureSetLabel = structureSetLabel
    ds.StructureSetName = structureSetName

    dfr = pydicom.Dataset()
    dfr.FrameOfReferenceUID = refdcm.FrameOfReferenceUID

    ds.ReferencedFrameOfReferenceSequence = pydicom.Sequence([dfr])

    if tags_to_add is not None:
        for tag, value in tags_to_add.items():
            setattr(ds, tag, value)

    #######################################################################
    #######################################################################
    # write the ReferencedFrameOfReferenceSequence

    contourImageSeq = pydicom.Sequence()

    if isinstance(refdcm_file, list):
        # in case we got all reference dicom files we add all SOPInstanceUIDs
        # otherwise some RT planning systems refuse to read the RTstructs
        for fname in refdcm_file:
            with pydicom.read_file(fname) as tmpdcm:
                tmp = pydicom.Dataset()
                tmp.ReferencedSOPClassUID = tmpdcm.SOPClassUID
                tmp.ReferencedSOPInstanceUID = tmpdcm.SOPInstanceUID
                contourImageSeq.append(tmp)
    else:
        tmp = pydicom.Dataset()
        tmp.ReferencedSOPClassUID = refdcm.SOPClassUID
        tmp.ReferencedSOPInstanceUID = refdcm.SOPInstanceUID
        contourImageSeq.append(tmp)

    tmp2 = pydicom.Dataset()
    tmp2.SeriesInstanceUID = refdcm.SeriesInstanceUID
    tmp2.ContourImageSequence = contourImageSeq

    tmp3 = pydicom.Dataset()
    tmp3.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.1'
    # TODO SOP just copied from MIM rtstructs
    tmp3.ReferencedSOPInstanceUID = refdcm.StudyInstanceUID
    tmp3.RTReferencedSeriesSequence = pydicom.Sequence([tmp2])

    tmp4 = pydicom.Dataset()
    tmp4.FrameOfReferenceUID = refdcm.FrameOfReferenceUID
    tmp4.RTReferencedStudySequence = pydicom.Sequence([tmp3])

    ds.ReferencedFrameOfReferenceSequence = pydicom.Sequence([tmp4])

    #######################################################################
    #######################################################################

    ds.StructureSetROISequence = pydicom.Sequence()
    ds.ROIContourSequence = pydicom.Sequence()

    if roinames is None: roinames = ['ROI-' + str(x) for x in roinumbers]
    if roidescriptions is None:
        roidescriptions = ['ROI-' + str(x) for x in roinumbers]
    if roigenerationalgs is None:
        roigenerationalgs = len(roinumbers) * ['MANUAL']

    # loop over the ROIs
    for iroi, roinumber in enumerate(roinumbers):
        dssr = pydicom.Dataset()
        dssr.ROINumber = roinumber
        dssr.ROIName = roinames[iroi]
        dssr.ROIDescription = roidescriptions[iroi]
        dssr.ROIGenerationAlgorithm = roigenerationalgs[iroi]
        dssr.ReferencedFrameOfReferenceUID = dfr.FrameOfReferenceUID

        ds.StructureSetROISequence.append(dssr)

        #######################################################################
        #######################################################################
        # write ROIContourSequence containing the actual 2D polygon points of the ROI

        ds_contour = pydicom.Dataset()
        ds_contour.ReferencedSOPClassUID = refdcm.SOPClassUID  ###

        # generate binary volume for the current ROI
        bin_vol = (roi_vol == dssr.ROINumber).astype(int)

        # find the bounding box in the last direction
        ob_sls = find_objects(bin_vol)
        z_start = min([x[2].start for x in ob_sls])
        z_end = max([x[2].stop for x in ob_sls])

        ds_roi_contour = pydicom.Dataset()
        ds_roi_contour.ROIDisplayColor = roi_colors[iroi % len(roi_colors)]
        ds_roi_contour.ReferencedROINumber = dssr.ROINumber
        ds_roi_contour.ContourSequence = pydicom.Sequence()

        # loop over the slices in the 2 direction to create 2D polygons
        for sl in np.arange(z_start, z_end):
            bin_slice = bin_vol[:, :, sl]

            if bin_slice.max() > 0:
                contours = binary_2d_image_to_contours(
                    bin_slice, connect_holes=connect_holes)

                for ic in range(len(contours)):
                    npoints = contours[ic].shape[0]

                    contour = np.zeros((npoints, 3))

                    for ipoint in range(npoints):
                        contour[ipoint, :] = (aff @ np.concatenate(
                            (contours[ic][ipoint, :], [sl, 1])))[:-1]

                    dsci = pydicom.Dataset()
                    dsci.ReferencedSOPInstanceUID = ordered_slices[sl][0]
                    dsci.ContourGeometricType = 'CLOSED_PLANAR'
                    dsci.NumberOfContourPoints = contour.shape[0]
                    dsci.ContourImageSequence = pydicom.Sequence([ds_contour])
                    dsci.ContourData = contour.flatten().tolist()

                    # ContourImageSequence contains 1 element per 2D contour
                    ds_roi_contour.ContourSequence.append(dsci)

        # has to contain one element per ROI
        ds.ROIContourSequence.append(ds_roi_contour)

    #######################################################################
    #######################################################################

    pydicom.filewriter.write_file(os.path.join('.', filename),
                                  ds,
                                  write_like_original=False)

    return z_start, z_end
Пример #9
0
    def __init__(self, fname, spotspecs=None, uid=None, verbose=False):
        """
        Create a fake treatment plan DICOM file for unit testing.  The spot
        specs are a list of dictionaries, one dictionary per beam.  Each beam
        dictionary contains optional entries for gantry angle, patient support
        angle and isocenter position and an obligatory list of "controlpoints".

        A file with filename `fname` should not yet exist. A DICOM file with
        name `fname` will be written by _test_plan_creator, and also be deleted
        when the creator goes out of scope.
        """
        assert (sys.version_info.major == 3)
        self.verbose = verbose
        assert (not os.path.exists(fname))
        self.dcm_filename = fname
        classUID = '1.2.840.10008.5.1.4.1.1.481.8' if uid is None else uid
        instanceUID = pydicom.uid.generate_uid()

        # File meta info data elements
        file_meta = pydicom.Dataset()
        file_meta.MediaStorageSOPClassUID = str(classUID)
        file_meta.MediaStorageSOPInstanceUID = str(instanceUID)
        file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
        #FIXME: we probably need to apply for an official UID here
        file_meta.ImplementationClassUID = '1.2.826.0.1.3680043.1.2.100.6.40.0.76'
        file_meta.ImplementationVersionName = 'DicomObjects.NET'

        self.ds = pydicom.FileDataset(self.dcm_filename, {},
                                      file_meta=file_meta,
                                      preamble=b"\0" * 128)
        self.ds.SOPClassUID = classUID
        self.ds.SOPInstanceUID = instanceUID
        self.ds.PatientName = "unit test"
        self.ds.PatientID = "42"
        #print("trying to fix 'is_little_endian'")

        if spotspecs:
            self.ds.IonBeamSequence = pydicom.Sequence()
            self.ds.NumberOfBeams = len(spotspecs)
            for beamdata in spotspecs:
                beam = pydicom.Dataset()
                beam.BeamName = beamdata.get('Nm', 'noname')
                beam.BeamNumber = beamdata.get('Nr', -1)
                beam.NumberOfIonControlPoints = len(beamdata["controlpoints"])
                beam.IonControlPointSequence = pydicom.Sequence()
                for i, icpdata in enumerate(beamdata["controlpoints"]):
                    icp = pydicom.Dataset()
                    if i == 0:
                        if 'G' in beamdata:
                            icp.GantryAngle = beamdata['G']
                        if 'P' in beamdata:
                            icp.PatientSupportAngle = beamdata['P']
                        if "I" in beamdata:
                            icp.IsocenterPosition = list(
                                [str(v) for v in beamdata.get('I')])
                        else:
                            icp.IsocenterPosition = ""
                    icp.NominalBeamEnergy = icpdata.get('E', 100.)
                    if 'T' in icpdata:
                        icp.ScanSpotTuneID = icpdata['T']
                    #unfortunately, this does not work
                    icp.ScanSpotMetersetWeights = [
                        float(v) for v in icpdata['SSW']
                    ]
                    icp.ScanSpotPositionMap = [
                        float(v) for v in np.array(icpdata['SSP']).flatten()
                    ]
                    icp.NumberOfScanSpotPositions = int(
                        icpdata.get('NSSP',
                                    len(icp.ScanSpotPositionMap) / 2))
                    beam.IonControlPointSequence.append(icp)
                self.ds.IonBeamSequence.append(beam)
        else:
            self.ds.NumberOfBeams = 0
        if verbose:
            logger.info("number of beams is {}".format(self.ds.NumberOfBeams))
        dt = datetime.datetime.now()
        self.ds.ContentDate = dt.strftime('%Y%m%d')
        timeStr = dt.strftime('%H%M%S.%f')  # long format with micro seconds
        self.ds.ContentTime = timeStr
        self.ds.is_little_endian = True
        self.ds.is_implicit_VR = True
        self.ds.fix_meta_info()
        self.ds.save_as(self.dcm_filename)
        if self.verbose:
            logger.info("wrote dicom file {}".format(self.dcm_filename))