Read DICOM Series and Write 3D Image

Synopsis

This example reads all the DICOM series in a given folder argv[1] and writes them in the same folder with following file pattern: seriesIdentifier.nrrd

if output file name argv[2] and series name argv[3] are given, then it behaves like DicomSeriesReadImageWrite2.cxx (writing just the requested series with the specified name).

Based on DicomSeriesReadImageWrite2.cxx

Author: Dženan Zukić <dzenan.zukic@kitware.com>

Code

C++

#include "itkImage.h"
#include "itkGDCMImageIO.h"
#include "itkGDCMSeriesFileNames.h"
#include "itkImageSeriesReader.h"
#include "itkImageFileWriter.h"

int
main(int argc, char * argv[])
{
  if (argc < 2)
  {
    std::cerr << "Usage: " << std::endl;
    std::cerr << argv[0] << " [DicomDirectory  [outputFileName  [seriesName]]]";
    std::cerr << "\nIf DicomDirectory is not specified, current directory is used\n";
  }
  std::string dirName = "."; // current directory by default
  if (argc > 1)
  {
    dirName = argv[1];
  }

  using PixelType = signed short;
  constexpr unsigned int Dimension = 3;
  using ImageType = itk::Image<PixelType, Dimension>;

  using NamesGeneratorType = itk::GDCMSeriesFileNames;
  NamesGeneratorType::Pointer nameGenerator = NamesGeneratorType::New();

  nameGenerator->SetUseSeriesDetails(true);
  nameGenerator->AddSeriesRestriction("0008|0021");
  nameGenerator->SetGlobalWarningDisplay(false);
  nameGenerator->SetDirectory(dirName);

  try
  {
    using SeriesIdContainer = std::vector<std::string>;
    const SeriesIdContainer & seriesUID = nameGenerator->GetSeriesUIDs();
    auto                      seriesItr = seriesUID.begin();
    auto                      seriesEnd = seriesUID.end();

    if (seriesItr != seriesEnd)
    {
      std::cout << "The directory: ";
      std::cout << dirName << std::endl;
      std::cout << "Contains the following DICOM Series: ";
      std::cout << std::endl;
    }
    else
    {
      std::cout << "No DICOMs in: " << dirName << std::endl;
      return EXIT_SUCCESS;
    }

    while (seriesItr != seriesEnd)
    {
      std::cout << seriesItr->c_str() << std::endl;
      ++seriesItr;
    }

    seriesItr = seriesUID.begin();
    while (seriesItr != seriesUID.end())
    {
      std::string seriesIdentifier;
      if (argc > 3) // If seriesIdentifier given convert only that
      {
        seriesIdentifier = argv[3];
        seriesItr = seriesUID.end();
      }
      else // otherwise convert everything
      {
        seriesIdentifier = seriesItr->c_str();
        seriesItr++;
      }
      std::cout << "\nReading: ";
      std::cout << seriesIdentifier << std::endl;
      using FileNamesContainer = std::vector<std::string>;
      FileNamesContainer fileNames = nameGenerator->GetFileNames(seriesIdentifier);

      using ReaderType = itk::ImageSeriesReader<ImageType>;
      ReaderType::Pointer reader = ReaderType::New();
      using ImageIOType = itk::GDCMImageIO;
      ImageIOType::Pointer dicomIO = ImageIOType::New();
      reader->SetImageIO(dicomIO);
      reader->SetFileNames(fileNames);
      reader->ForceOrthogonalDirectionOff(); // properly read CTs with gantry tilt

      using WriterType = itk::ImageFileWriter<ImageType>;
      WriterType::Pointer writer = WriterType::New();
      std::string         outFileName;
      if (argc > 2)
      {
        outFileName = argv[2];
      }
      else
      {
        outFileName = dirName + std::string("/") + seriesIdentifier + ".nrrd";
      }
      writer->SetFileName(outFileName);
      writer->UseCompressionOn();
      writer->SetInput(reader->GetOutput());
      std::cout << "Writing: " << outFileName << std::endl;
      try
      {
        writer->Update();
      }
      catch (itk::ExceptionObject & ex)
      {
        std::cout << ex << std::endl;
        continue;
      }
    }
  }
  catch (itk::ExceptionObject & ex)
  {
    std::cout << ex << std::endl;
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

Python

#!/usr/bin/env python
import os

import itk

if len(sys.argv) < 2:
    print("Usage: " + sys.argv[0] +
          " [DicomDirectory [outputFileName [seriesName]]]")
    print("If DicomDirectory is not specified, current directory is used\n")

# current directory by default
dirName = '.'
if len(sys.argv) > 1:
    dirName = sys.argv[1]

PixelType = itk.ctype('signed short')
Dimension = 3

ImageType = itk.Image[PixelType, Dimension]

namesGenerator = itk.GDCMSeriesFileNames.New()
namesGenerator.SetUseSeriesDetails(True)
namesGenerator.AddSeriesRestriction("0008|0021")
namesGenerator.SetGlobalWarningDisplay(False)
namesGenerator.SetDirectory(dirName)

seriesUID = namesGenerator.GetSeriesUIDs()

if len(seriesUID) < 1:
    print('No DICOMs in: ' + dirName)
    sys.exit(1)

print('The directory: ' + dirName)
print('Contains the following DICOM Series: ')
for uid in seriesUID:
    print(uid)

seriesFound = False
for uid in seriesUID:
    seriesIdentifier = uid
    if len(sys.argv) > 3:
        seriesIdentifier = sys.argv[3]
        seriesFound = True
    print('Reading: ' + seriesIdentifier)
    fileNames = namesGenerator.GetFileNames(seriesIdentifier)

    reader = itk.ImageSeriesReader[ImageType].New()
    dicomIO = itk.GDCMImageIO.New()
    reader.SetImageIO(dicomIO)
    reader.SetFileNames(fileNames)
    reader.ForceOrthogonalDirectionOff()

    writer = itk.ImageFileWriter[ImageType].New()
    outFileName = os.path.join(dirName, seriesIdentifier + '.nrrd')
    if len(sys.argv) > 2:
        outFileName = sys.argv[2]
    writer.SetFileName(outFileName)
    writer.UseCompressionOn()
    writer.SetInput(reader.GetOutput())
    print('Writing: ' + outFileName)
    writer.Update()

    if seriesFound:
        break

Classes demonstrated

class GDCMSeriesFileNames : public itk::ProcessObject

Generate a sequence of filenames from a DICOM series.

This class generates a sequence of files whose filenames point to a DICOM file. The ordering is based on the following strategy: Read all images in the directory (assuming there is only one study/series)

  1. Extract Image Orientation & Image Position from DICOM images, and then calculate the ordering based on the 3D coordinate of the slice.

  2. If for some reason this information is not found or failed, another strategy is used: the ordering is based on ‘Instance Number’.

  3. If this strategy also failed, then the filenames are ordered by lexicographical order.

If multiple volumes are being grouped as a single series for your DICOM objects, you may want to try calling SetUseSeriesDetails(true) prior to calling SetDirectory().

See itk::GDCMSeriesFileNames for additional documentation.
class GDCMImageIO : public itk::ImageIOBase

ImageIO class for reading and writing DICOM V3.0 and ACR/NEMA 1&2 uncompressed images. This class is only an adaptor to the GDCM library.

GDCM can be found at: http://sourceforge.net/projects/gdcm

To learn more about the revision shipped with ITK, call

git log Modules/ThirdParty/GDCM/src/

From an ITK Git checkout.

GDCM build, instead of the one included within ITK itself.

The compressors supported include “JPEG2000” (default), and “JPEG”. The compression level parameter is not supported.

Warning

There are several restrictions to this current writer:

  • Even though during the writing process you pass in a DICOM file as input The output file may not contains ALL DICOM field from the input file. In particular:

    • The SeQuence DICOM field (SQ).

    • Fields from Private Dictionary.

  • Some very long (>0xfff) binary fields are not loaded (typically 0029|0010), you need to explicitly set the maximum length of elements to load to be bigger (see Get/SetMaxSizeLoadEntry).

  • In DICOM some fields are stored directly using their binary representation. When loaded into the MetaDataDictionary some fields are converted to ASCII (only VR: OB/OW/OF and UN are encoded as mime64).

ITK Sphinx Examples:

See itk::GDCMImageIO for additional documentation.
template<typename TOutputImage>
class ImageSeriesReader : public itk::ImageSource<TOutputImage>

Data source that reads image data from a series of disk files.

This class builds an n-dimension image from multiple n-1 dimension image files. The files stored in a vector of strings are read using the ImageFileReader. File format may vary between the files, but the image data must have the same Size for all dimensions.

See

GDCMSeriesFileNames

See

NumericSeriesFileNames

See itk::ImageSeriesReader for additional documentation.