Saving DICOM Series

Hello! I am reading in a DICOM series with SimpleITK. If I save this as a nii.gz file, it shows up perfectly in Slicer, however if I try to save it as a DICOM series, based on this example code, the DICOM series shows up weirdly. Series are shown as separate series per frame. (The frames themselves look good). Is there any suggestion how to fix this problem?

Hello @Franciska,

Hard to guess what the issue is without seeing your code or a better description of “series shows up weirdly”. Do you mean that each slice has its own series ID? This will happen if you forget to configure the writer using writer.KeepOriginalImageUIDOn() as shown in that example. Or is it that the written images have the same study and series ID as the original and when you read the written images you have two slices per location (original and modified). In this case you forgot to modify the series ID so that the new set of images belongs to the same study but a different series.

If these aren’t the issues, please provide additional details about the problem. What are the study (0020|000D) and series (0020|0037) IDs for the original and written slices?

1 Like

Hi!

This is the script:

import SimpleITK as sitk
import sys
import time
import os
import numpy as np



class Prediction():

    def __init__(self, original_dicom_folder, nifti_prediction_path, target_path):
        self.original_dicom_folder = original_dicom_folder
        self.nifti_prediction_path = nifti_prediction_path
        self.target_path = target_path



    def writeDICOMFromMeta(self):

        if not os.path.exists(self.target_path):
            os.makedirs(self.target_path)


        series_IDs = sitk.ImageSeriesReader.GetGDCMSeriesIDs(self.original_dicom_folder)

        # Read the original series. First obtain the series file names using the
        # image series reader.

        series_file_names = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(self.original_dicom_folder, series_IDs[0])
        series_reader = sitk.ImageSeriesReader()
        series_reader.SetFileNames(series_file_names)

        # Configure the reader to load all of the DICOM tags (public+private):
        # By default tags are not loaded (saves time).
        # By default if tags are loaded, the private tags are not loaded.
        # We explicitly configure the reader to load tags, including the
        # private ones.
        series_reader.MetaDataDictionaryArrayUpdateOn()
        series_reader.LoadPrivateTagsOn()
        series_reader.Execute()

        # Load prediction
        prediction_volume = sitk.ReadImage(self.nifti_prediction_path)

        # Write the 3D image as a series
        # IMPORTANT: There are many DICOM tags that need to be updated when you modify
        #            an original image. This is a delicate opration and requires
        #            knowledge of the DICOM standard. This example only modifies some.
        #            For a more complete list of tags that need to be modified see:
        #                http://gdcm.sourceforge.net/wiki/index.php/Writing_DICOM

        writer = sitk.ImageFileWriter()
        # Use the study/series/frame of reference information given in the meta-data
        # dictionary and not the automatically generated information from the file IOSE000002
        # Copy relevant tags from the original meta-data dictionary (private tags are
        # also accessible).
        tags_to_copy = [
            "0010|0010",  # Patient Name
            "0010|0020",  # Patient ID
            "0010|0030",  # Patient Birth Date
            "0020|000D",  # Study Instance UID, for machine consumption
            "0020|0010",  # Study ID, for human consumption
            "0008|0020",  # Study Date
            "0008|0030",  # Study Time
            "0008|0050",  # Accession Number
            "0008|0060",  # Modality
            "0008|1030",  # Study Description
        ]

        modification_time = time.strftime("%H%M%S")
        modification_date = time.strftime("%Y%m%d")

        # Copy some of the tags and add the relevant tags indicating the change.
        # For the series instance UID (0020|000e), each of the components is a number,
        # cannot start with zero, and separated by a '.' We create a unique series ID
        # using the date and time.
        # Tags of interest:
        direction = prediction_volume.GetDirection()
        series_tag_values = [
            (k, series_reader.GetMetaData(0, k))
            for k in tags_to_copy
            if series_reader.HasMetaDataKey(0, k)
        ] + [
            ("0008|0031", modification_time),  # Series Time
            ("0008|0021", modification_date),  # Series Date
            ("0008|0008", "DERIVED\\SECONDARY"),  # Image Type
            (
                "0020|000e",
                "1.2.826.0.1.3680043.2.1125."
                + modification_date
                + ".1"
                + modification_time,
            ),
            # Series Instance UID
            (
                "0020|0037",
                "\\".join(
                    map(
                        str,
                        (
                            direction[0],
                            direction[3],
                            direction[6],
                            # Image Orientation (Patient)
                            direction[1],
                            direction[4],
                            direction[7],
                        ),
                    )
                ),
            ),
            (
                "0008|103e",
                series_reader.GetMetaData(0, "0008|103e") + "_ULY_prediction",
            ),
        ]  # Series Description

        for i in range(prediction_volume.GetDepth()):
            _image_slice = prediction_volume[:, :, i]
            # Tags shared by the series.

            castFilter = sitk.CastImageFilter()
            castFilter.SetOutputPixelType(sitk.sitkInt16)
            image_slice = castFilter.Execute(_image_slice)


            for tag, value in series_tag_values:
                image_slice.SetMetaData(tag, value)
                
            # Slice specific tags.
            #   Instance Creation Date
            image_slice.SetMetaData("0008|0012", time.strftime("%Y%m%d"))
            #   Instance Creation Time
            image_slice.SetMetaData("0008|0013", time.strftime("%H%M%S"))
            #   Image Position (Patient)
            image_slice.SetMetaData(
                "0020|0032",
                "\\".join(
                    map(str, prediction_volume.TransformIndexToPhysicalPoint((0, 0, i)))
                ),
            )
            #   Instace Number
            image_slice.SetMetaData("0020|0013", str(i))

            # Write to the output directory and add the extension dcm, to force writing
            # in DICOM format.
            writer.SetFileName(os.path.join(self.target_path, str(i) + ".dcm"))
            writer.Execute(image_slice)


# 0028,0004 (PhotometricInterpretation): RGB
# prediction pdf to DICOM


if __name__ == "__main__":


    ctpath=r'/original/SE000002'
    savepath=r'/outline/SE000002'
    predpath=r'/lungsegm/SE000002.nii.gz'


    prediction = Prediction(original_dicom_folder=ctpath,
                            nifti_prediction_path= predpath,
                            target_path=savepath)



    prediction.writeDICOMFromMeta()


When I load the original into Slicer it shows one DICOM series. When I load the saved one, it shows all the slices as different series, with the same size (512x512x1), patient ID, Series # , Series Description, Study ID and Study Date

Hello @Franciska,

As suspected, your code is missing the call to writer.KeepOriginalImageUIDOn(). You only copied the documentation from the example (i.e. “Use the study/series/frame of reference information…”) but missed the row that this is referring to. Just insert this line right after you create the writer and it should solve the issue.

When you don’t configure the writer, any series/study information found in the image’s meta-data dictionary is ignored and the writer “automagically” assigns a study/series to each file, which is why they no longer belong to the same volume.

2 Likes

Ohh my… I have no idea how i managed to delete only that line from the script. Sorry for wasting your time, and thanks for the solution!

1 Like

No worries, happens to everyone.

2 Likes