How to manipulate DICOM tags on individual slices from python code

I use the following code snippet for manipulating DICOM tags on individual slice:

...
    # insert tags per slice
    general_metadata = gdcmIO.GetMetaDataDictionary()

    metadata_array = []
    for slice_counter in range(1, num_slices):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.append(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)
...

The entire program can be seen in the bottom of the post if needed.

I however get the following error when executing my program:

Traceback (most recent call last):
  File "nifti2dicom.py", line 74, in <module>
    nifti_to_dicom(args.input_file, args.output_dir, args.output_prefix)
  File "nifti2dicom.py", line 61, in nifti_to_dicom
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)
TypeError: in method 'itkImageSeriesWriterIUS3IUS2_SetMetaDataDictionaryArray', argument 2 of type 'std::vector< itkMetaDataDictionary *,std::allocator< itkMetaDataDictionary * > > const *const'

I have tested many different ways of parsing the metadata_array to the SetMetaDataDictionaryArray function but cannot seem to find the correct way of doing this.

Any help or pointer to information would be of great value.

The full program code for convert a nifti file to a DICOM Series:

# inspired by: https://itk.org/Doxygen50/html/Examples_2IO_2ImageReadDicomSeriesWrite_8cxx-example.html

import argparse
import os
import itk


def nifti_to_dicom(input_filename, output_dir, prefix):
    PixelType = itk.US
    Dimension = 3
    ReaderType = itk.Image[PixelType, Dimension]
    reader = itk.ImageSeriesReader[ReaderType].New()
    dicomIO = itk.NiftiImageIO.New()
    reader.SetImageIO(dicomIO)
    reader.SetFileName(input_filename)
    reader.Update()
    image = reader.GetOutput()

    region = image.GetLargestPossibleRegion()
    start = region.GetIndex()
    size = region.GetSize()

    namesGenerator = itk.NumericSeriesFileNames.New()
    format = output_dir + "/" + prefix + "%04d.dcm"
    namesGenerator.SetSeriesFormat(format)
    start_index = start[2] + 1
    namesGenerator.SetStartIndex(start_index)
    end_index = start_index + size[2] - 1
    namesGenerator.SetEndIndex(end_index)
    namesGenerator.SetIncrementIndex(1)

    OutputPixelType = itk.US
    OutputDimension = 2
    SeriesWriterType = itk.Image[OutputPixelType, OutputDimension]

    seriesWriter = itk.ImageSeriesWriter[ReaderType, SeriesWriterType].New()

    seriesWriter.SetInput(image)
    seriesWriter.SetFileNames(namesGenerator.GetFileNames())

    gdcmIO = itk.GDCMImageIO.New()
    # inspired by: https://itk.org/ITKExamples/src/IO/GDCM/ReadAndPrintDICOMTags/Documentation.html
    metadata = gdcmIO.GetMetaDataDictionary()
    slice_spacing = image.GetSpacing()[2]
    # format, removes 0 from e.g. 1.0 if it is 0
    metadata['0018|0050'] = '{0:g}'.format(slice_spacing)
    metadata['0008|0060'] = "MR"
    seriesWriter.SetImageIO(gdcmIO)

    # insert tags per slice
    # inspired by: https://itk.org/ITKExamples/src/IO/GDCM/ResampleDICOMSeries/Documentation.html
    num_slices = size[2]
    general_metadata = gdcmIO.GetMetaDataDictionary()

    metadata_array = []
    for slice_counter in range(1, num_slices):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.append(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

    os.makedirs(output_dir, exist_ok=True)
    seriesWriter.Update()


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-i', '--input-file', required=True)
    parser.add_argument('-o', '--output-dir', required=True)
    parser.add_argument('-p', '--output-prefix', default="slice_")
    args = parser.parse_args()

    nifti_to_dicom(args.input_file, args.output_dir, args.output_prefix)

@matt.mccormick do we need any special handling of DictionaryArrayType? If not, what is the problem here?

@br_cpvc welcome to the ITK community! :sunny:

A quick note inspecting the code:

    PixelType = itk.US
    Dimension = 3
    ReaderType = itk.Image[PixelType, Dimension]
    reader = itk.ImageSeriesReader[ReaderType].New()
    dicomIO = itk.NiftiImageIO.New()
    reader.SetImageIO(dicomIO)
    reader.SetFileName(input_filename)
    reader.Update()
    image = reader.GetOutput()

could be just:

    image = itk.imread(input_filename)

@matt.mccormick do we need any special handling of DictionaryArrayType? If not, what is the problem here?

Yes, we need to use the requested type here,

argument 2 of type 'std::vector< itkMetaDataDictionary

so instead of:

    metadata_array = []
    for slice_counter in range(1, num_slices):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.append(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

we create the std::vector:

    metadata_array = itk.vector[itk.MetaDataDictionary]()
    for slice_counter in range(1, num_slices):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.push_back(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

The Array name here is understandably misleading!

1 Like

@dzenanz thank you for forwarding my question to the right person

@matt.mccormick thank you for taking the time to answer my question, and giving me hints to improve my implementation.

I have tried changing the code as you suggest to:

    metadata_array = itk.vector[itk.MetaDataDictionary]()
    for slice_counter in range(1, num_slices):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.push_back(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

with these code changes I however get a “Segmentation fault (core dumped)” error message when executing, I don’t know how to debug or proceed, do you have suggestions?

when removing the line:

    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

The “Segmentation fault (core dumped)” error goes away and output files are produced.

My setup: Popos 20.04 with itk installed using pip through conda. Have test with the following itk versions: 5.1.2, 5.2.0, 5.3rc1 but get the same results.

@br_cpvc we may need to run in a Debug build of ITK to find the backtrace related to the error.

If an empty metadata dictionary is created per slice, does the segfault still occur?

general_metadata = gdcmIO.GetMetaDataDictionary()

It is likely that there are tags other than 0020|0013 that need to be set per-slice.

Looking at the itk::ImageSeriesWrite does not do anything specific for DICOM files. It may be easier to write out the slices slice by slice yourself with the ImageFileWriter ( or procedural equivalent ).

Hello @br_cpvc,

Just adding to @blowekamp’s response, this SimpleITK Example illustrates how to do what you want. I believe it is straightforward to translate it to ITK Python.

@matt.mccormick I tried, as you suggested, to move the general_metadata inside the for loop so that it is created per slice, like so:

    metadata_array = itk.vector[itk.MetaDataDictionary]()
    for slice_counter in range(1, num_slices):
        general_metadata = gdcmIO.GetMetaDataDictionary()
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.push_back(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

this however do not change the output I get, it still reports “Segmentation fault (core dumped)” when I execute the code.

I have tried to build itk in debug mode to see how far I could get on my own. I have done the following until now:

sudo apt install -y git cmake python3.8-dev
git clone https://github.com/InsightSoftwareConsortium/ITK.git
cd ITK/
git checkout v5.2.1
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DITK_WRAP_PYTHON=ON ..
make -j 3

I think that I got ITK build correctly in debug mode, but are unsure how to proceed from here and execute my program so that I can inspect the debug output. If you have any pointers to some notes or documentation which could help me setup a debugging environment that would be of great help.

1 Like

Hi @blowekamp thank you for your input. I’m not entirely sure how the itk::ImageSeriesWriter works, but I’m trying to do the per slice python equivalent of what is implemented in: https://github.com/biolab-unige/nifti2dicom/blob/master/src/core/n2dOutputExporter.cxx

Hi @zivy thank you for you suggestion, I will take a look at the code and see if it is transferable.

@br_cpvc you got it :clap: :+1:

Create a symbolic link to the WrapITK.pth file in your build tree to a virtualenv site-packages to use the ITK Python build tree inside Python.

To find out where the issues is, you want to get a stack trace with gdb. Here is a general guide for debugging CPython with GDB.

@brad-t-moore also has some great tips for debugging ITK Python.

@matt.mccormick thank you for the pointers, they were the last pieces of the puzzle that I needed to figure things out.

I did the following to get the debug build setup and running in a gdb environment:

pip install itk
mv ~/.local/lib/python3.8/site-packages/itk ~/.local/lib/python3.8/site-packages/itk_old
ln -s ~/ITK/build/Wrapping/Generators/Python/itk ~/.local/lib/python3.8/site-packages/itk

sudo apt-get install gdb python3.8-dbg

wget http://www.neuromorphometrics.com/1103_3.tgz
tar --extract -f 1103_3.tgz
gdb -ex r --args /usr/bin/python3 Tools/nifti2dicom.py -i Data/1103/3/NIFTI/1103_3.nii -o test -p test_prefix_

After a lot of debugging and trail/error coding I finally managed to figure out what was happening. I needed to change the code to the following to get the it working:

    # list to store references to the itk.MetaDataDictionary objects
    # created inside the for loop, if the references are not stored
    # outside the for loop, then the objects' reference count returns
    # to zero when exiting the loop and the C++ objects are deleted,
    # if the objects are deleted before the function:
    # seriesWriter.Update() is called, then the entire application
    # exits with a segmentation fault.
    tmp_ref_list = []
    metadata_array = itk.vector[itk.MetaDataDictionary]()
    for slice_counter in range(1, num_slices + 1):
        slice_metadata = itk.MetaDataDictionary(general_metadata)
        # temporary work around, see comment above
        tmp_ref_list.append(slice_metadata)
        # instance number
        slice_metadata['0020|0013'] = slice_counter
        metadata_array.push_back(slice_metadata)
    seriesWriter.SetMetaDataDictionaryArray(metadata_array)

    os.makedirs(output_dir, exist_ok=True)
    seriesWriter.Update()

To avoid the segmentation fault (which was caused by deallocation of the itk.MetaDataDictionary objects stored inside the metadata_array), I had to add local references for all the slice_metadata objects created inside the for loop, and store these references until after the seriesWriter.Update() function has been called.

I’m unsure if this is an actually bug in the ITK python wrapper, or if this is how it is suppose to work. If it is an bug in ITK, I will gladly submit an bug issue upstream so that the problem can be addressed in a future version. What do you think, is this a bug?

I’m furthermore unsure if the work around that I have implemented is actually the correct/best way to do a work around, or if there are other/better ways of dealing with object references inside python?

1 Like