Resample and Convert DICOM to Common Image Formats
Overview
This example illustrates the use of SimpleITK for converting a set of DICOM images to other file formats (tif, jpg, png,…). The output file format is specified by the user, and the output image width can also be specified by the user (height is determined from the width as resulting pixel sizes are required to be isotropic). Grayscale images with high dynamic range are rescaled to [0,255] before conversion to the new format. Output is written in the same location as the input image, or to a user specified output directory. An additional csv file mapping between original and converted file names is also written, either to the specified output directory or to the current working directory.
Code
// A SimpleITK example demonstrating how to convert and resize DICOM files to common image types.
using System;
using System.Collections.Generic;
using System.IO;
using itk.simple;
namespace itk.simple.examples
{
public class DicomConvert
{
public static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: DicomConvert <root_of_data_directory> <output_file_extension> [--w width] [--od output_directory]");
return;
}
string rootDir = args[0];
string outputExt = args[1];
int? width = null;
string outDir = null;
for (int i = 2; i < args.Length; ++i)
{
if (args[i] == "--w" && i + 1 < args.Length)
{
width = int.Parse(args[++i]);
}
else if (args[i] == "--od" && i + 1 < args.Length)
{
outDir = args[++i];
}
}
var inputFiles = new List<string>();
WalkDir(new DirectoryInfo(rootDir), inputFiles);
var fileNames = new List<string>();
if (outDir != null)
{
for (int i = 0; i < inputFiles.Count; ++i)
{
fileNames.Add(Path.Combine(outDir, i.ToString()));
}
}
else
{
fileNames.AddRange(inputFiles);
}
var outputFiles = new List<string>();
foreach (var f in fileNames)
{
outputFiles.Add(f + "." + outputExt);
}
var res = new List<bool>();
for (int i = 0; i < inputFiles.Count; ++i)
{
res.Add(ConvertImage(inputFiles[i], outputFiles[i], width));
}
var filteredInput = new List<string>();
var filteredOutput = new List<string>();
for (int i = 0; i < res.Count; ++i)
{
if (res[i])
{
filteredInput.Add(inputFiles[i]);
filteredOutput.Add(outputFiles[i]);
}
}
string csvDir = outDir ?? Directory.GetCurrentDirectory();
using (var writer = new StreamWriter(Path.Combine(csvDir, "file_names.csv")))
{
writer.WriteLine("input file name,output file name");
for (int i = 0; i < filteredInput.Count; ++i)
{
writer.WriteLine(filteredInput[i] + ","+ filteredOutput[i]);
}
}
}
static void WalkDir(DirectoryInfo dir, List<string> files)
{
foreach (var f in dir.GetFileSystemInfos())
{
if ((f.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
WalkDir((DirectoryInfo)f, files);
}
else
{
files.Add(f.FullName);
}
}
}
static bool ConvertImage(string inputFile, string outputFile, int? newWidth)
{
try
{
var reader = new ImageFileReader();
// Only read DICOM images
reader.SetImageIO("GDCMImageIO");
reader.SetFileName(inputFile);
var image = reader.Execute();
var imageSize = image.GetSize();
// If 3D image with single slice, treat as 2D
if (imageSize.Count == 3 && imageSize[2] == 1) {
ExtractImageFilter extractFilter = new ExtractImageFilter();
extractFilter.SetIndex(new VectorInt32( new int[] { 0, 0, 0 }));
imageSize[2] = 0;
extractFilter.SetSize(imageSize);
image = extractFilter.Execute(image);
}
// Resample if new width is specified
if (newWidth.HasValue)
{
var originalSize = image.GetSize();
var originalSpacing = image.GetSpacing();
double newSpacing = ((originalSize[0] - 1) * originalSpacing[0]) / (newWidth.Value - 1);
long newHeight = (long)(((originalSize[1] - 1) * originalSpacing[1]) / newSpacing);
image = SimpleITK.Resample(image, new VectorUInt32(new long[] { newWidth.Value, newHeight }), new Transform(), InterpolatorEnum.sitkLinear,
image.GetOrigin(), new VectorDouble(new double[] { newSpacing, newSpacing }), image.GetDirection(), 0, image.GetPixelID());
}
// If a single channel image, rescale to [0,255] and cast to UInt8.
if (image.GetNumberOfComponentsPerPixel() == 1)
{
image = SimpleITK.RescaleIntensity(image, 0, 255);
image = SimpleITK.Cast(image, PixelIDValueEnum.sitkUInt8);
}
SimpleITK.WriteImage(image, outputFile);
return true;
}
catch
{
return false;
}
}
}
}
/**
* A SimpleITK example demonstrating how to convert and resize DICOM files
* to common image types.
*/
#include <SimpleITK.h>
#include <iostream>
#include <filesystem>
namespace sitk = itk::simple;
namespace fs = std::filesystem;
bool
ConvertImage(const std::string & inputFile, const std::string & outputFile, unsigned int newWidth = 0)
{
try
{
sitk::ImageFileReader reader;
// Only read DICOM images
reader.SetImageIO("GDCMImageIO");
reader.SetFileName(inputFile);
auto image = reader.Execute();
auto imageSize = image.GetSize();
// If 3D image with single slice, treat as 2D
if (imageSize.size() == 3 && imageSize[2] == 1)
{
sitk::ExtractImageFilter extractFilter;
extractFilter.SetIndex({ 0, 0, 0 });
imageSize[2] = 0;
extractFilter.SetSize(imageSize);
image = extractFilter.Execute(image);
}
// Resample if new width is specified
if (newWidth > 0)
{
auto originalSize = image.GetSize();
auto originalSpacing = image.GetSpacing();
double newSpacing = ((originalSize[0] - 1) * originalSpacing[0]) / (newWidth - 1);
unsigned int newHeight = static_cast<unsigned int>(((originalSize[1] - 1) * originalSpacing[1]) / newSpacing);
image = sitk::Resample(image,
{ newWidth, newHeight },
sitk::Transform(),
sitk::sitkLinear,
image.GetOrigin(),
{ newSpacing, newSpacing },
image.GetDirection(),
0,
image.GetPixelID());
}
// If a single channel image, rescale to [0,255] and cast to UInt8.
if (image.GetNumberOfComponentsPerPixel() == 1)
{
image = sitk::RescaleIntensity(image, 0, 255);
image = sitk::Cast(image, sitk::sitkUInt8);
}
sitk::WriteImage(image, outputFile);
return true;
}
catch (...)
{
return false;
}
}
int
main(int argc, char * argv[])
{
if (argc < 3)
{
std::cout << "Usage: DicomConvert <input_file> <output_file> [--w width]\n";
return 1;
}
std::string inputFile = argv[1];
std::string outputFile = argv[2];
int width = 0;
for (int i = 3; i < argc; ++i)
{
if (std::string(argv[i]) == "--w" && i + 1 < argc)
{
width = std::stoi(argv[++i]);
}
}
if (ConvertImage(inputFile, outputFile, width))
{
return 0;
}
return 1;
}
/**
* A SimpleITK example demonstrating how to convert and resize DICOM files
* to common image types.
*/
import org.itk.simple.*;
import java.io.*;
import java.util.*;
public class DicomConvert {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: DicomConvert <root_of_data_directory> <output_file_extension> [--w width] [--od output_directory]");
System.exit(1);
}
String rootDir = args[0];
String outputExt = args[1];
Integer width = null;
String outDir = null;
for (int i = 2; i < args.length; ++i) {
if (args[i].equals("--w") && i + 1 < args.length) {
width = Integer.parseInt(args[++i]);
} else if (args[i].equals("--od") && i + 1 < args.length) {
outDir = args[++i];
}
}
List<String> inputFiles = new ArrayList<>();
walkDir(new File(rootDir), inputFiles);
List<String> fileNames = new ArrayList<>();
if (outDir != null) {
for (int i = 0; i < inputFiles.size(); ++i) {
fileNames.add(new File(outDir, Integer.toString(i)).getAbsolutePath());
}
} else {
for (String f : inputFiles) {
fileNames.add(f);
}
}
List<String> outputFiles = new ArrayList<>();
for (String f : fileNames) {
outputFiles.add(f + "." + outputExt);
}
List<Boolean> res = new ArrayList<>();
for (int i = 0; i < inputFiles.size(); ++i) {
res.add(convertImage(inputFiles.get(i), outputFiles.get(i), width));
}
List<String> filteredInput = new ArrayList<>();
List<String> filteredOutput = new ArrayList<>();
for (int i = 0; i < res.size(); ++i) {
if (res.get(i)) {
filteredInput.add(inputFiles.get(i));
filteredOutput.add(outputFiles.get(i));
}
}
String csvDir = (outDir != null) ? outDir : System.getProperty("user.dir");
try (PrintWriter writer = new PrintWriter(new File(csvDir, "file_names.csv"), "UTF-8")) {
writer.println("input file name,output file name");
for (int i = 0; i < filteredInput.size(); ++i) {
writer.println(filteredInput.get(i) + "," + filteredOutput.get(i));
}
}
}
static void walkDir(File dir, List<String> files) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
walkDir(f, files);
} else {
files.add(f.getAbsolutePath());
}
}
}
static boolean convertImage(String inputFile, String outputFile, Integer newWidth) {
try {
ImageFileReader reader = new ImageFileReader();
// Only read DICOM images
reader.setImageIO("GDCMImageIO");
reader.setFileName(inputFile);
Image image = reader.execute();
// If 3D image with single slice, treat as 2D
VectorUInt32 imageSize = image.getSize();
if (imageSize.size() == 3 && imageSize.get(2).longValue() == 1) {
ExtractImageFilter extractFilter = new ExtractImageFilter();
extractFilter.setIndex(new VectorInt32(new int[]{0, 0, 0}));
imageSize.set(2, 0L);
extractFilter.setSize(imageSize);
image = extractFilter.execute(image);
}
// Resample if new width is specified
if (newWidth != null) {
VectorUInt32 originalSize = image.getSize();
VectorDouble originalSpacing = image.getSpacing();
double newSpacing = ((originalSize.get(0) - 1) * originalSpacing.get(0)) / (newWidth - 1);
long newHeight = (long)(((originalSize.get(1) - 1) * originalSpacing.get(1)) / newSpacing);
image = SimpleITK.resample(image, new VectorUInt32(new long[]{newWidth, newHeight}), new Transform(), InterpolatorEnum.sitkLinear,
image.getOrigin(), new VectorDouble(new double[]{newSpacing, newSpacing}), image.getDirection(), 0, image.getPixelID());
}
// If a single channel image, rescale to [0,255] and cast to UInt8.
if (image.getNumberOfComponentsPerPixel() == 1) {
image = SimpleITK.rescaleIntensity(image, 0, 255);
image = SimpleITK.cast(image, PixelIDValueEnum.sitkUInt8);
}
SimpleITK.writeImage(image, outputFile);
return true;
} catch (Exception e) {
return false;
}
}
}
""" A SimpleITK example demonstrating how to convert and resize DICOM files
to common image types. """
import argparse
import csv
import functools
import itertools
import multiprocessing
import os
import sys
import SimpleITK as sitk
def convert_image(input_file_name, output_file_name, new_width=None):
""" Convert a single DICOM image to a common image type. """
try:
image_file_reader = sitk.ImageFileReader()
# only read DICOM images
image_file_reader.SetImageIO("GDCMImageIO")
image_file_reader.SetFileName(input_file_name)
image_file_reader.ReadImageInformation()
image_size = list(image_file_reader.GetSize())
if len(image_size) == 3 and image_size[2] == 1:
image_size[2] = 0
image_file_reader.SetExtractSize(image_size)
image = image_file_reader.Execute()
if new_width:
original_size = image.GetSize()
original_spacing = image.GetSpacing()
new_spacing = [
(original_size[0] - 1) * original_spacing[0] / (new_width - 1)
] * 2
new_size = [
new_width,
int((original_size[1] - 1) * original_spacing[1] / new_spacing[1]),
]
image = sitk.Resample(
image1=image,
size=new_size,
transform=sitk.Transform(),
interpolator=sitk.sitkLinear,
outputOrigin=image.GetOrigin(),
outputSpacing=new_spacing,
outputDirection=image.GetDirection(),
defaultPixelValue=0,
outputPixelType=image.GetPixelID(),
)
# If a single channel image, rescale to [0,255] and cast to UInt8.
if image.GetNumberOfComponentsPerPixel() == 1:
image = sitk.RescaleIntensity(image, 0, 255)
image = sitk.Cast(image, sitk.sitkUInt8)
sitk.WriteImage(image, output_file_name)
return True
except RuntimeError:
return False
def convert_images(input_file_names, output_file_names, new_width):
""" Convert multiple DICOM images in parallel to a common image type. """
MAX_PROCESSES = 15
with multiprocessing.Pool(processes=MAX_PROCESSES) as pool:
return pool.starmap(
functools.partial(convert_image, new_width=new_width),
zip(input_file_names, output_file_names),
)
def positive_int(int_str):
""" Custom argparse type for positive integers. """
value = int(int_str)
if value <= 0:
raise argparse.ArgumentTypeError(int_str + " is not a positive integer value")
return value
def directory(dir_name):
""" Custom argparse type for directory. """
if not os.path.isdir(dir_name):
raise argparse.ArgumentTypeError(dir_name + " is not a valid directory name")
return dir_name
def main(argv=None):
""" Main function. """
parser = argparse.ArgumentParser(
description="Convert and resize DICOM files to common image types."
)
parser.add_argument(
"root_of_data_directory",
type=directory,
help="Path to the topmost directory containing data.",
)
parser.add_argument(
"output_file_extension",
help="Image file extension, this determines output file type " "(e.g. png) .",
)
parser.add_argument("--w", type=positive_int, help="Width of converted images.")
parser.add_argument("--od", type=directory, help="Output directory.")
args = parser.parse_args(argv)
input_file_names = []
for dir_name, _, file_names in os.walk(args.root_of_data_directory):
input_file_names += [
os.path.join(os.path.abspath(dir_name), fname) for fname in file_names
]
if args.od:
# if all output files are written to the same directory we need them
# to have a unique name, so use an index.
file_names = [
os.path.join(os.path.abspath(args.od), str(i))
for i in range(len(input_file_names))
]
else:
file_names = input_file_names
output_file_names = [
file_name + "." + args.output_file_extension for file_name in file_names
]
res = convert_images(input_file_names, output_file_names, args.w)
input_file_names = list(itertools.compress(input_file_names, res))
output_file_names = list(itertools.compress(output_file_names, res))
# save csv file mapping input and output file names.
# using csv module and not pandas so as not to create more dependencies
# for the examples. pandas based code is more elegant/shorter.
dir_name = args.od if args.od else os.getcwd()
with open(os.path.join(dir_name, "file_names.csv"), mode="w", encoding='utf-8') as fp:
fp_writer = csv.writer(
fp, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
fp_writer.writerow(["input file name", "output file name"])
for data in zip(input_file_names, output_file_names):
fp_writer.writerow(data)
if __name__ == "__main__":
sys.exit(main())
library(SimpleITK)
library(argparser)
convert_image <- function(input_file_name, output_file_name, new_width = NA)
{
image_file_reader = ImageFileReader()
# only read DICOM images
image_file_reader$SetImageIO('GDCMImageIO')
image_file_reader$SetFileName(input_file_name)
try(image_file_reader$ReadImageInformation(), silent = TRUE)
image_size <- image_file_reader$GetSize()
#if this isn't a DICOM image return FALSE
if(length(image_size) == 0) {
return(FALSE)
}
if(length(image_size) == 3 & image_size[3] == 1) {
image_size[3] <- 0
image_file_reader$SetExtractSize(image_size)
}
image <- NULL
try(image <- image_file_reader$Execute(), silent = TRUE)
if(is.null(image))
{
return(FALSE)
}
if(!is.na(new_width))
{
original_size <- image$GetSize()
original_spacing <- image$GetSpacing()
new_spacing <- c((original_size[1]-1)*original_spacing[1]/(new_width-1),
(original_size[1]-1)*original_spacing[1]/(new_width-1))
new_size <- c(new_width, as.integer((original_size[2]-1)*original_spacing[2]/new_spacing[2]))
image <- Resample(image, new_size, Transform(), 'sitkLinear',
image$GetOrigin(), new_spacing, image$GetDirection(),0,
image$GetPixelID())
}
# If a single channel image, rescale to [0,255] and cast to UInt8.
if(image$GetNumberOfComponentsPerPixel() == 1)
{
image <- RescaleIntensity(image, 0, 255)
image <- Cast(image, 'sitkUInt8')
}
WriteImage(image, output_file_name)
return(TRUE)
}
parser<-arg_parser('Convert and resize DICOM files to common image types.')
parser<-add_argument(parser, 'root_of_data_directory', help='Path to the topmost directory containing data.')
parser<-add_argument(parser, 'output_file_extension', help='Image file extension, this determines output file type (e.g. png) .')
parser<-add_argument(parser, '--w', help='Width of converted images.')
parser<-add_argument(parser, '--od', help='Output directory.')
argv<-parse_args(parser)
input_file_names <- list.files(argv$root_of_data_directory, recursive=TRUE, full.names=TRUE)
if(!is.na(argv$od)) {
# if all output files are written to the same directory we need them to have a unique name, so use an index.
file_names <- lapply(seq_len(length(input_file_names)), function(new_file_name) return(file.path(argv$od, toString(new_file_name))))
} else {
file_names <- input_file_names
}
#append the file extension to the output file names
output_file_names <- lapply(file_names, function(file_name) return(paste0(file_name,'.',argv$output_file_extension)))
res <- mapply(convert_image, input_file_names, output_file_names, new_width=as.integer(argv$w), SIMPLIFY=FALSE)
df <- data.frame(input_file_name = unlist(input_file_names[unlist(res)]),
output_file_name = unlist(output_file_names[unlist(res)]))
dir_name <- if(!is.na(argv$od)) argv$od else getwd()
write.csv(df,file.path(dir_name, 'file_names.csv'), row.names=FALSE)