Dicom Series From Array
Overview
This example illustrates how to write a DICOM series from a numeric array and create appropriate meta-data so it can be read by DICOM viewers.
Generating an array is done using a simple random number generator for this case but can come from other sources.
Writing the 3D image as a DICOM series is done by configuring the meta-data dictionary for each of the slices and then writing it in DICOM format. In our case we generate all of the meta-data to indicate that this series is derived. If the new image has float values we need to encode this via the rescale slope (0028|1053), rescale intercept (0028|1052), and several additional meta-data dictionary values specifying how the values are stored.
See also Read Image Meta-Data Dictionary and Print, Dicom Series Reader.
Code
/// <summary>
/// A SimpleITK example demonstrating how to write a DICOM series.
/// </summary>
using System;
using System.Collections.Generic;
using System.IO;
using itk.simple;
namespace itk.simple.examples
{
public class DicomSeriesFromArray
{
private static ImageFileWriter writer = new ImageFileWriter();
/// <summary>
/// Write slices to output directory
/// </summary>
static void WriteSlices(List<Tuple<string, string>> seriesTag, Image inImage, string outDir, int i)
{
Image imageSlice = inImage.Slice(0, (long)i, 2);
// Tags shared by the series.
foreach (var tagValue in seriesTag)
{
imageSlice.SetMetaData(tagValue.Item1, tagValue.Item2);
}
// Slice specific tags.
// Instance Creation Date
imageSlice.SetMetaData("0008|0012", DateTime.Now.ToString("yyyyMMdd"));
// Instance Creation Time
imageSlice.SetMetaData("0008|0013", DateTime.Now.ToString("HHmmss"));
// Setting the type to CT so that the slice location is preserved and
// the thickness is carried over.
imageSlice.SetMetaData("0008|0060", "CT");
// (0020, 0032) image position patient determines the 3D spacing between slices.
// Image Position (Patient)
double[] position = inImage.TransformIndexToPhysicalPoint(new long[] { 0, 0, i });
imageSlice.SetMetaData("0020|0032", string.Join("\\", position));
// Instance Number
imageSlice.SetMetaData("0020|0013", i.ToString());
// Write to the output directory and add the extension dcm, to force
// writing in DICOM format.
writer.SetFileName(Path.Combine(outDir, i.ToString() + ".dcm"));
writer.Execute(imageSlice);
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: DicomSeriesFromArray <output_directory>");
return;
}
// Set pixel type to sitkInt16 or sitkFloat64
var pixelType = PixelIDValueEnum.sitkInt16;
// Create image from random data array based on the pixel type
Random rand = new Random();
uint[] imageSize = { 512, 512, 256 };
int totalPixels = (int)(imageSize[0] * imageSize[1] * imageSize[2]);
Image newImg;
if (pixelType == PixelIDValueEnum.sitkInt16)
{
short[] randomData = new short[totalPixels];
for (int i = 0; i < totalPixels; i++)
{
randomData[i] = (short)(rand.Next(-1000, 1001));
}
newImg = SimpleITK.ImportAsInt16(randomData, new VectorUInt32(imageSize));
}
else if (pixelType == PixelIDValueEnum.sitkFloat64)
{
double[] randomData = new double[totalPixels];
for (int i = 0; i < totalPixels; i++)
{
randomData[i] = rand.NextDouble() * 2000.0 - 1000.0;
}
newImg = SimpleITK.ImportAsFloat64(randomData, new VectorUInt32(imageSize));
}
else
{
throw new ArgumentException("Unsupported pixel type");
}
newImg.SetSpacing(new VectorDouble(new double[] { 2.5, 3.5, 4.5 }));
// 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 operation 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
// If it is critical for your work to generate valid DICOM files,
// It is recommended to use David Clunie's Dicom3tools to validate
// the files:
// http://www.dclunie.com/dicom3tools.html
// Use the study/series/frame of reference information given in the meta-data
// dictionary and not the automatically generated information from the file IO
writer.KeepOriginalImageUIDOn();
string modificationTime = DateTime.Now.ToString("HHmmss");
string modificationDate = DateTime.Now.ToString("yyyyMMdd");
// 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.
VectorDouble direction = newImg.GetDirection();
List<Tuple<string, string>> seriesTagValues = new List<Tuple<string, string>>
{
new Tuple<string, string>("0008|0031", modificationTime), // Series Time
new Tuple<string, string>("0008|0021", modificationDate), // Series Date
new Tuple<string, string>("0008|0008", "DERIVED\\SECONDARY"), // Image Type
new Tuple<string, string>("0020|000e", "1.2.826.0.1.3680043.2.1125." + modificationDate + ".1" + modificationTime), // Series Instance UID
new Tuple<string, string>("0020|0037", string.Format("{0}\\{1}\\{2}\\{3}\\{4}\\{5}",
direction[0], direction[3], direction[6], direction[1], direction[4], direction[7])), // Image Orientation (Patient)
new Tuple<string, string>("0008|103e", "Created-SimpleITK") // Series Description
};
if (pixelType == PixelIDValueEnum.sitkFloat64)
{
// If we want to write floating point values, we need to use the rescale
// slope, "0028|1053", to select the number of digits we want to keep.
double rescaleSlope = 0.001; // keep three digits after the decimal point
seriesTagValues.AddRange(new List<Tuple<string, string>>
{
new Tuple<string, string>("0028|1053", rescaleSlope.ToString()), // rescale slope
new Tuple<string, string>("0028|1052", "0"), // rescale intercept
new Tuple<string, string>("0028|0100", "16"), // bits allocated
new Tuple<string, string>("0028|0101", "16"), // bits stored
new Tuple<string, string>("0028|0102", "15"), // high bit
new Tuple<string, string>("0028|0103", "1") // pixel representation
});
}
// Write slices to output directory
for (int i = 0; i < (int)newImg.GetDepth(); i++)
{
WriteSlices(seriesTagValues, newImg, args[0], i);
}
// Re-read the series
// Read the original series. First obtain the series file names using the
// image series reader.
string dataDirectory = args[0];
VectorString seriesIDs = ImageSeriesReader.GetGDCMSeriesIDs(dataDirectory);
if (seriesIDs.Count == 0)
{
Console.WriteLine("ERROR: given directory \"" + dataDirectory + "\" does not contain a DICOM series.");
return;
}
VectorString seriesFileNames = ImageSeriesReader.GetGDCMSeriesFileNames(dataDirectory, seriesIDs[0]);
ImageSeriesReader seriesReader = new ImageSeriesReader();
seriesReader.SetFileNames(seriesFileNames);
// 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.
seriesReader.LoadPrivateTagsOn();
Image image3D = seriesReader.Execute();
VectorDouble readSpacing = image3D.GetSpacing();
VectorDouble originalSpacing = newImg.GetSpacing();
Console.WriteLine("[{0}, {1}, {2}] vs [{3}, {4}, {5}]",
readSpacing[0], readSpacing[1], readSpacing[2],
originalSpacing[0], originalSpacing[1], originalSpacing[2]);
}
}
}
/**
* A SimpleITK example demonstrating how to write a DICOM series.
*/
#include <SimpleITK.h>
#include <iostream>
#include <vector>
#include <map>
#include <random>
#include <chrono>
#include <iomanip>
#include <sstream>
namespace sitk = itk::simple;
sitk::ImageFileWriter writer;
/**
* Write slices to output directory
*/
void
WriteSlices(const std::vector<std::pair<std::string, std::string>> & seriesTag,
const sitk::Image & inImage,
const std::string & outDir,
uint64_t i)
{
sitk::Image imageSlice = inImage.Slice(0, i, 2);
// Tags shared by the series.
for (const auto & tagValue : seriesTag)
{
imageSlice.SetMetaData(tagValue.first, tagValue.second);
}
// Slice specific tags.
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm = *std::localtime(&time_t);
std::ostringstream dateStream, timeStream;
dateStream << std::put_time(&tm, "%Y%m%d");
timeStream << std::put_time(&tm, "%H%M%S");
// Instance Creation Date
imageSlice.SetMetaData("0008|0012", dateStream.str());
// Instance Creation Time
imageSlice.SetMetaData("0008|0013", timeStream.str());
// Setting the type to CT so that the slice location is preserved and
// the thickness is carried over.
imageSlice.SetMetaData("0008|0060", "CT");
// (0020, 0032) image position patient determines the 3D spacing between slices.
// Image Position (Patient)
std::vector<double> position = inImage.TransformIndexToPhysicalPoint({ 0, 0, i });
std::ostringstream posStream;
for (size_t j = 0; j < position.size(); ++j)
{
if (j > 0)
posStream << "\\";
posStream << position[j];
}
imageSlice.SetMetaData("0020|0032", posStream.str());
// Instance Number
imageSlice.SetMetaData("0020|0013", std::to_string(i));
// Write to the output directory and add the extension dcm, to force
// writing in DICOM format.
std::string outPath = std::string(outDir) + "/" + std::to_string(i) + ".dcm";
writer.SetFileName(outPath);
writer.Execute(imageSlice);
}
int
main(int argc, char * argv[])
{
if (argc < 2)
{
std::cout << "Usage: DicomSeriesFromArray <output_directory>" << std::endl;
return 1;
}
// Set pixel type sitkInt16 or sitkFloat64
sitk::PixelIDValueEnum pixelType = sitk::sitkInt16;
// Create image from random data array based on the pixel type
std::vector<unsigned int> imageSize = { 512, 512, 256 };
size_t totalPixels = imageSize[0] * imageSize[1] * imageSize[2];
std::random_device rd;
std::mt19937 gen(rd());
sitk::Image newImg;
if (pixelType == sitk::sitkInt16)
{
std::uniform_int_distribution<short> dis(-1000, 1000);
std::vector<short> randomData(totalPixels);
for (size_t i = 0; i < totalPixels; ++i)
{
randomData[i] = dis(gen);
}
newImg = sitk::ImportAsInt16(randomData.data(), imageSize);
}
else if (pixelType == sitk::sitkFloat64)
{
std::uniform_real_distribution<> dis(-1000.0, 1000.0);
std::vector<double> randomData(totalPixels);
for (size_t i = 0; i < totalPixels; ++i)
{
randomData[i] = dis(gen);
}
newImg = sitk::ImportAsFloat64(randomData.data(), imageSize);
}
else
{
std::cerr << "Unsupported pixel type" << std::endl;
return 1;
}
newImg.SetSpacing({ 2.5, 3.5, 4.5 });
// 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 operation 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
// If it is critical for your work to generate valid DICOM files,
// It is recommended to use David Clunie's Dicom3tools to validate
// the files:
// http://www.dclunie.com/dicom3tools.html
// Use the study/series/frame of reference information given in the meta-data
// dictionary and not the automatically generated information from the file IO
writer.KeepOriginalImageUIDOn();
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm = *std::localtime(&time_t);
std::ostringstream modificationTimeStream, modificationDateStream;
modificationDateStream << std::put_time(&tm, "%Y%m%d");
modificationTimeStream << std::put_time(&tm, "%H%M%S");
std::string modificationTime = modificationTimeStream.str();
std::string modificationDate = modificationDateStream.str();
// 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.
std::vector<double> direction = newImg.GetDirection();
std::vector<std::pair<std::string, std::string>> seriesTagValues = {
{ "0008|0031", modificationTime }, // Series Time
{ "0008|0021", modificationDate }, // Series Date
{ "0008|0008", "DERIVED\\SECONDARY" }, // Image Type
{ "0020|000e", "1.2.826.0.1.3680043.2.1125." + modificationDate + ".1" + modificationTime }, // Series Instance UID
{ "0008|103e", "Created-SimpleITK" } // Series Description
};
// Image Orientation (Patient)
std::ostringstream orientationStream;
orientationStream << direction[0] << "\\" << direction[3] << "\\" << direction[6] << "\\" << direction[1] << "\\"
<< direction[4] << "\\" << direction[7];
seriesTagValues.push_back({ "0020|0037", orientationStream.str() });
if (pixelType == sitk::sitkFloat64)
{
// If we want to write floating point values, we need to use the rescale
// slope, "0028|1053", to select the number of digits we want to keep.
double rescaleSlope = 0.001; // keep three digits after the decimal point
seriesTagValues.insert(seriesTagValues.end(),
{
{ "0028|1053", std::to_string(rescaleSlope) }, // rescale slope
{ "0028|1052", "0" }, // rescale intercept
{ "0028|0100", "16" }, // bits allocated
{ "0028|0101", "16" }, // bits stored
{ "0028|0102", "15" }, // high bit
{ "0028|0103", "1" } // pixel representation
});
}
// Write slices to output directory
for (uint64_t i = 0; i < newImg.GetDepth(); ++i)
{
WriteSlices(seriesTagValues, newImg, argv[1], i);
}
// Re-read the series
// Read the original series. First obtain the series file names using the
// image series reader.
std::string dataDirectory = argv[1];
std::vector<std::string> seriesIDs = sitk::ImageSeriesReader::GetGDCMSeriesIDs(dataDirectory);
if (seriesIDs.empty())
{
std::cout << "ERROR: given directory \"" << dataDirectory << "\" does not contain a DICOM series." << std::endl;
return 1;
}
std::vector<std::string> seriesFileNames =
sitk::ImageSeriesReader::GetGDCMSeriesFileNames(dataDirectory, seriesIDs[0]);
sitk::ImageSeriesReader seriesReader;
seriesReader.SetFileNames(seriesFileNames);
// 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.
seriesReader.LoadPrivateTagsOn();
sitk::Image image3D = seriesReader.Execute();
std::vector<double> readSpacing = image3D.GetSpacing();
std::vector<double> originalSpacing = newImg.GetSpacing();
std::cout << "[" << readSpacing[0] << ", " << readSpacing[1] << ", " << readSpacing[2] << "] vs ["
<< originalSpacing[0] << ", " << originalSpacing[1] << ", " << originalSpacing[2] << "]" << std::endl;
return 0;
}
/**
* A SimpleITK example demonstrating how to write a DICOM series.
*/
import org.itk.simple.*;
import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;
import java.nio.*;
public class DicomSeriesFromArray {
private static ImageFileWriter writer = new ImageFileWriter();
/**
* Write slices to output directory
*/
static void writeSlices(List<String[]> seriesTag, Image inImage, String outDir, int i) throws Exception {
SliceImageFilter sliceFilter = new SliceImageFilter();
sliceFilter.setStart(new VectorInt32(new int[]{0,0,i}));
sliceFilter.setStep(1);
sliceFilter.setStop(new VectorInt32(new int[]{0,0,i+1}));
Image imageSlice = sliceFilter.execute(inImage);
// Tags shared by the series.
for (String[] tagValue : seriesTag) {
imageSlice.setMetaData(tagValue[0], tagValue[1]);
}
// Slice specific tags.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss");
Date now = new Date();
// Instance Creation Date
imageSlice.setMetaData("0008|0012", dateFormat.format(now));
// Instance Creation Time
imageSlice.setMetaData("0008|0013", timeFormat.format(now));
// Setting the type to CT so that the slice location is preserved and
// the thickness is carried over.
imageSlice.setMetaData("0008|0060", "CT");
// (0020, 0032) image position patient determines the 3D spacing between slices.
// Image Position (Patient)
VectorDouble position = inImage.transformIndexToPhysicalPoint(new VectorInt64(new long[]{0,0,i}));
StringJoiner posJoiner = new StringJoiner("\\");
for (int j = 0; j < position.size(); j++) {
posJoiner.add(String.valueOf(position.get(j)));
}
imageSlice.setMetaData("0020|0032", posJoiner.toString());
// Instance Number
imageSlice.setMetaData("0020|0013", String.valueOf(i));
// Write to the output directory and add the extension dcm, to force
// writing in DICOM format.
writer.setFileName(new File(outDir, i + ".dcm").getAbsolutePath());
writer.execute(imageSlice);
}
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("Usage: DicomSeriesFromArray <output_directory>");
System.exit(1);
}
// Set pixel type to sitkInt16 or sitkFloat64
PixelIDValueEnum pixelType = PixelIDValueEnum.sitkInt16;
// Create image from random data array based on the pixel type
VectorUInt32 imageSize = new VectorUInt32();
imageSize.add(512);
imageSize.add(512);
imageSize.add(256);
int totalPixels = 512 * 512 * 256;
Random rand = new Random();
Image newImg;
if (pixelType == PixelIDValueEnum.sitkInt16) {
short[] randomData = new short[totalPixels];
for (int i = 0; i < totalPixels; i++) {
randomData[i] = (short)(rand.nextInt(2001) - 1000); // Range -1000 to 1000
}
newImg = SimpleITK.importAsInt16(randomData, imageSize);
} else if (pixelType == PixelIDValueEnum.sitkFloat64) {
double[] randomData = new double[totalPixels];
for (int i = 0; i < totalPixels; i++) {
randomData[i] = (rand.nextDouble() * 2000.0) - 1000.0; // Range -1000 to 1000
}
newImg = SimpleITK.importAsFloat64(randomData, imageSize);
} else {
throw new IllegalArgumentException("Unsupported pixel type");
}
newImg.setSpacing(new VectorDouble(new double[]{2.5, 3.5, 4.5}));
// 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 operation 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
// If it is critical for your work to generate valid DICOM files,
// It is recommended to use David Clunie's Dicom3tools to validate
// the files:
// http://www.dclunie.com/dicom3tools.html
// Use the study/series/frame of reference information given in the meta-data
// dictionary and not the automatically generated information from the file IO
writer.keepOriginalImageUIDOn();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss");
Date now = new Date();
String modificationTime = timeFormat.format(now);
String modificationDate = dateFormat.format(now);
// 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.
VectorDouble direction = newImg.getDirection();
List<String[]> seriesTagValues = new ArrayList<>();
seriesTagValues.add(new String[]{"0008|0031", modificationTime}); // Series Time
seriesTagValues.add(new String[]{"0008|0021", modificationDate}); // Series Date
seriesTagValues.add(new String[]{"0008|0008", "DERIVED\\SECONDARY"}); // Image Type
seriesTagValues.add(new String[]{"0020|000e", "1.2.826.0.1.3680043.2.1125." + modificationDate + ".1" + modificationTime}); // Series Instance UID
// Image Orientation (Patient)
String orientation = String.format("%f\\%f\\%f\\%f\\%f\\%f",
direction.get(0), direction.get(3), direction.get(6),
direction.get(1), direction.get(4), direction.get(7));
seriesTagValues.add(new String[]{"0020|0037", orientation});
seriesTagValues.add(new String[]{"0008|103e", "Created-SimpleITK"}); // Series Description
if (pixelType == PixelIDValueEnum.sitkFloat64) {
// If we want to write floating point values, we need to use the rescale
// slope, "0028|1053", to select the number of digits we want to keep.
double rescaleSlope = 0.001; // keep three digits after the decimal point
seriesTagValues.add(new String[]{"0028|1053", String.valueOf(rescaleSlope)}); // rescale slope
seriesTagValues.add(new String[]{"0028|1052", "0"}); // rescale intercept
seriesTagValues.add(new String[]{"0028|0100", "16"}); // bits allocated
seriesTagValues.add(new String[]{"0028|0101", "16"}); // bits stored
seriesTagValues.add(new String[]{"0028|0102", "15"}); // high bit
seriesTagValues.add(new String[]{"0028|0103", "1"}); // pixel representation
}
// Write slices to output directory
for (int i = 0; i < newImg.getDepth(); i++) {
writeSlices(seriesTagValues, newImg, args[0], i);
}
// Re-read the series
// Read the original series. First obtain the series file names using the
// image series reader.
String dataDirectory = args[0];
VectorString seriesIDs = ImageSeriesReader.getGDCMSeriesIDs(dataDirectory);
if (seriesIDs.size() == 0) {
System.out.println("ERROR: given directory \"" + dataDirectory + "\" does not contain a DICOM series.");
System.exit(1);
}
VectorString seriesFileNames = ImageSeriesReader.getGDCMSeriesFileNames(dataDirectory, seriesIDs.get(0));
ImageSeriesReader seriesReader = new ImageSeriesReader();
seriesReader.setFileNames(seriesFileNames);
// 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.
seriesReader.loadPrivateTagsOn();
Image image3D = seriesReader.execute();
VectorDouble readSpacing = image3D.getSpacing();
VectorDouble originalSpacing = newImg.getSpacing();
System.out.printf("[%f, %f, %f] vs [%f, %f, %f]%n",
readSpacing.get(0), readSpacing.get(1), readSpacing.get(2),
originalSpacing.get(0), originalSpacing.get(1), originalSpacing.get(2));
}
}
""" A SimpleITK example demonstrating how to write a DICOM series. """
import sys
import time
import os
import numpy as np
import SimpleITK as sitk
pixel_dtypes = {"int16": np.int16, "float64": np.float64}
def writeSlices(series_tag, in_image, out_dir, i):
""" Write slices to output directory """
image_slice = in_image[:, :, i]
# Tags shared by the series.
list(
map(
lambda tag_value: image_slice.SetMetaData(tag_value[0], tag_value[1]),
series_tag,
)
)
# 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"))
# Setting the type to CT so that the slice location is preserved and
# the thickness is carried over.
image_slice.SetMetaData("0008|0060", "CT")
# (0020, 0032) image position patient determines the 3D spacing between
# slices.
# Image Position (Patient)
image_slice.SetMetaData(
"0020|0032",
"\\".join(map(str, in_image.TransformIndexToPhysicalPoint((0, 0, i)))),
)
# Instance 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(out_dir, str(i) + ".dcm"))
writer.Execute(image_slice)
if len(sys.argv) < 3:
print(
"Usage: python "
+ __file__
+ " <output_directory> ["
+ ", ".join(pixel_dtypes)
+ "]"
)
sys.exit(1)
# Create a new series from a numpy array
try:
pixel_dtype = pixel_dtypes[sys.argv[2]]
except KeyError:
pixel_dtype = pixel_dtypes["int16"]
new_arr = np.random.uniform(-10, 10, size=(3, 4, 5)).astype(pixel_dtype)
new_img = sitk.GetImageFromArray(new_arr)
new_img.SetSpacing([2.5, 3.5, 4.5])
# 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 operation 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
# If it is critical for your work to generate valid DICOM files,
# It is recommended to use David Clunie's Dicom3tools to validate
# the files:
# http://www.dclunie.com/dicom3tools.html
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 IO
writer.KeepOriginalImageUIDOn()
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 = new_img.GetDirection()
series_tag_values = [
("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],
direction[1],
direction[4],
direction[7],
),
)
),
), # Image Orientation
# (Patient)
("0008|103e", "Created-SimpleITK"), # Series Description
]
if pixel_dtype == np.float64:
# If we want to write floating point values, we need to use the rescale
# slope, "0028|1053", to select the number of digits we want to keep. We
# also need to specify additional pixel storage and representation
# information.
rescale_slope = 0.001 # keep three digits after the decimal point
series_tag_values = series_tag_values + [
("0028|1053", str(rescale_slope)), # rescale slope
("0028|1052", "0"), # rescale intercept
("0028|0100", "16"), # bits allocated
("0028|0101", "16"), # bits stored
("0028|0102", "15"), # high bit
("0028|0103", "1"),
] # pixel representation
# Write slices to output directory
list(
map(
lambda i: writeSlices(series_tag_values, new_img, sys.argv[1], i),
range(new_img.GetDepth()),
)
)
# Re-read the series
# Read the original series. First obtain the series file names using the
# image series reader.
data_directory = sys.argv[1]
series_IDs = sitk.ImageSeriesReader.GetGDCMSeriesIDs(data_directory)
if not series_IDs:
print(
'ERROR: given directory "'
+ data_directory
+ '" does not contain a DICOM series.'
)
sys.exit(1)
series_file_names = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(
data_directory, 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.LoadPrivateTagsOn()
image3D = series_reader.Execute()
print(image3D.GetSpacing(), "vs", new_img.GetSpacing())
sys.exit(0)
# Run with:
#
# Rscript --vanilla DicomSeriesFromArray.R output_directory int
#
library(SimpleITK)
writeSlices <- function(series_tag_values, new_img, out_dir, i) {
image_slice <- new_img[1:new_img$GetWidth(), 1:new_img$GetHeight(), i]
# Tags shared by the series.
lapply(1:nrow(series_tag_values),
function(tag_index){image_slice$SetMetaData(series_tag_values[tag_index, 1], series_tag_values[tag_index, 2])})
# Slice specific tags.
image_slice$SetMetaData("0008|0012", format(Sys.time(), "%Y%m%d")) # Instance Creation Date
image_slice$SetMetaData("0008|0013", format(Sys.time(), "%H%M%S")) # Instance Creation Time
# Setting the type to CT preserves the slice location.
image_slice$SetMetaData("0008|0060", "CT") # set the type to CT so the thickness is carried over
# (0020, 0032) image position patient determines the 3D spacing between slices.
image_slice$SetMetaData("0020|0032", paste(new_img$TransformIndexToPhysicalPoint(c(0,0,i)), collapse='\\')) # Image Position (Patient)
image_slice$SetMetaData("0020|0013", i-1) # Instance Number
# Write to the output directory and add the extension dcm, to force writing in DICOM format.
writer$SetFileName(file.path(out_dir, paste(i-1, '.dcm', sep="")))
writer$Execute(image_slice)
}
args <- commandArgs( TRUE )
if (length(args) < 2) {
stop("Two arguments expected - output_directory pixel_type [int, float]")
}
# Create a new series from an array
image_dim = c(5,4,3)
if( args[[2]] == "int" ) {
new_arr = array(as.integer(runif(60, min=-10, max=10)), dim=image_dim)
} else if( args[[2]] == "float" ) {
new_arr = array(runif(60, min=-10, max=10), dim=image_dim)
} else {
stop("Unexpected pixel type, valid values are [int, float].")
}
new_img <- as.image(new_arr)
new_img$SetSpacing(c(2.5,3.5,4.5))
# 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 operation and requires knowlege 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
# If it is critical for your work to generate valid DICOM files,
# It is recommended to use David Clunie's Dicom3tools to validate the files
# (http://www.dclunie.com/dicom3tools.html).
writer <- ImageFileWriter()
# Use the study/series/frame of reference information given in the meta-data
# dictionary and not the automatically generated information from the file IO
writer$KeepOriginalImageUIDOn()
modification_time <- format(Sys.time(), "%H%M%S")
modification_date <- format(Sys.time(), "%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 <- new_img$GetDirection()
series_tag_values <- c("0008|0031",modification_time, # Series Time
"0008|0021",modification_date, # Series Date
"0008|0008","DERIVED\\SECONDARY", # Image Type
"0020|000e", paste("1.2.826.0.1.3680043.2.1125.",modification_date,".1",modification_time, sep=''), # Series Instance UID
"0020|0037", paste(direction[[1]], direction[[4]], direction[[7]],# Image Orientation (Patient)
direction[[2]],direction[[5]],direction[[8]], sep='\\'),
"0008|103e", "Created-SimpleITK")
if(args[[2]] == "float") {
# If we want to write floating point values, we need to use the rescale slope, "0028|1053", to select the
# number of digits we want to keep. We also need to specify additional pixel storage and representation
# information.
rescale_slope <- 0.001 #keep three digits after the decimal point
series_tag_values <- c(series_tag_values,
c("0028|1053", paste(rescale_slope), #rescale slope
"0028|1052","0", #rescale intercept
"0028|0100", "16", #bits allocated
"0028|0101", "16", #bits stored
"0028|0102", "15", #high bit
"0028|0103", "1")) #pixel representation
}
series_tag_values <- matrix(series_tag_values, nrow=length(series_tag_values)/2, ncol=2, byrow=TRUE) # Series Description
# Write slices to output directory
invisible(lapply(1:(new_img$GetDepth()), function(i){writeSlices(series_tag_values, new_img, args[[1]], i)}))
# Re-read the series
# Read the original series. First obtain the series file names using the
# image series reader.
data_directory <- args[[1]]
series_IDs <- ImageSeriesReader_GetGDCMSeriesIDs(data_directory)
if (length(series_IDs)==0) {
stop("ERROR: given directory \"", data_directory, "\" does not contain a DICOM series.")
}
series_file_names <- ImageSeriesReader_GetGDCMSeriesFileNames(data_directory, series_IDs[[1]])
series_reader <- 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$LoadPrivateTagsOn()
image3D <- series_reader$Execute()
cat(image3D$GetSpacing(),'vs',new_img$GetSpacing(),'\n')