Dicom Series Read Modify Write
Overview
This example illustrates how to read a DICOM series, modify the 3D image, and then write the result as a DICOM series.
Reading the DICOM series is a three step process: first obtain the series ID, then obtain the file names associated with the series ID, and finally use the series reader to read the images. By default the DICOM meta-data dictionary for each of the slices is not read. In this example we configure the series reader to load the meta-data dictionary including all of the private tags.
Modifying the 3D image can involve changes to its physical characteristics (spacing, direction cosines) and its intensities. In our case we only modify the intensities by blurring the image.
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 copy some of the meta-data from the original dictionaries which are available from the series reader. We then set some additional meta-data values to indicate that this series is derived from the original acquired data. Note that we write the intensity values as is and thus do not set the rescale slope (0028|1053), rescale intercept (0028|1052) meta-data dictionary values.
Always represent DICOM tags using lower case hexadecimals. DICOM tags represent hexadecimal numbers, so 0020|000D and 0020|000d are equivalent. The ITK/SimpleITK dictionary is string based, so these are two different keys, case sensitive. When read from a DICOM file the hexadecimal string representations are in lower case. To ensure consistency, always use lower case for the tags.
See also Read Image Meta-Data Dictionary and Print, Dicom Series Reader.
Code
using System;
using System.Collections.Generic;
using System.IO;
using itk.simple;
namespace itk.simple.examples
{
public class DicomSeriesReadModifySeriesWrite
{
public static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: DicomSeriesReadModifySeriesWrite <input_directory_with_DICOM_series> <output_directory>");
return;
}
// 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.MetaDataDictionaryArrayUpdateOn();
seriesReader.LoadPrivateTagsOn();
Image image3D = seriesReader.Execute();
// Modify the image (blurring)
Image filteredImage = SimpleITK.DiscreteGaussian(image3D);
// 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
ImageFileWriter writer = new 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();
// Copy relevant tags from the original meta-data dictionary (private tags are
// also accessible).
string[] tagsToCopy = {
"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
};
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.
// NOTE: Always represent DICOM tags using lower case hexadecimals.
VectorDouble direction = filteredImage.GetDirection();
List<KeyValuePair<string, string>> seriesTagValues = new List<KeyValuePair<string, string>>();
// Copy existing tags
foreach (string tag in tagsToCopy)
{
if (seriesReader.HasMetaDataKey(0, tag))
{
seriesTagValues.Add(new KeyValuePair<string, string>(tag, seriesReader.GetMetaData(0, tag)));
}
}
// Add modification tags
seriesTagValues.AddRange(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("0008|0031", modificationTime), // Series Time
new KeyValuePair<string, string>("0008|0021", modificationDate), // Series Date
new KeyValuePair<string, string>("0008|0008", "DERIVED\\SECONDARY"), // Image Type
new KeyValuePair<string, string>("0020|000e", "1.2.826.0.1.3680043.2.1125." + modificationDate + ".1" + modificationTime), // Series Instance UID
});
// Image Orientation (Patient)
string orientation = string.Format("{0}\\{1}\\{2}\\{3}\\{4}\\{5}",
direction[0], direction[3], direction[6], direction[1], direction[4], direction[7]);
seriesTagValues.Add(new KeyValuePair<string, string>("0020|0037", orientation));
// Series Description
string seriesDescription = "";
if (seriesReader.HasMetaDataKey(0, "0008|103e"))
{
seriesDescription = seriesReader.GetMetaData(0, "0008|103e");
}
seriesDescription += " Processed-SimpleITK";
seriesTagValues.Add(new KeyValuePair<string, string>("0008|103e", seriesDescription));
ExtractImageFilter extractFilter = new ExtractImageFilter();
VectorUInt32 size = filteredImage.GetSize();
size[2] = 1; // Extract single slice
extractFilter.SetSize(size);
for (int i = 0; i < (int)filteredImage.GetDepth(); i++)
{
extractFilter.SetIndex(new VectorInt32(new int[] { 0, 0, i }));
Image imageSlice = extractFilter.Execute(filteredImage);
// Tags shared by the series.
foreach (var tagValue in seriesTagValues)
{
imageSlice.SetMetaData(tagValue.Key, tagValue.Value);
}
// 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"));
// Image Position (Patient)
VectorDouble position = filteredImage.TransformIndexToPhysicalPoint(new VectorInt64(new long[] { 0, 0, i }));
imageSlice.SetMetaData("0020|0032", position[0].ToString() + "\\" + position[1].ToString() + "\\" + position[2].ToString());
// 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(args[1], i.ToString() + ".dcm"));
writer.Execute(imageSlice);
}
}
}
}
#include <SimpleITK.h>
#include <iostream>
#include <vector>
#include <string>
#include <chrono>
#include <iomanip>
#include <sstream>
namespace sitk = itk::simple;
int
main(int argc, char * argv[])
{
if (argc < 3)
{
std::cout << "Usage: " << argv[0] << " <input_directory_with_DICOM_series> <output_directory>" << std::endl;
return 1;
}
// 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.MetaDataDictionaryArrayUpdateOn();
seriesReader.LoadPrivateTagsOn();
sitk::Image image3D = seriesReader.Execute();
// Modify the image (blurring)
sitk::Image filteredImage = sitk::DiscreteGaussian(image3D);
// 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
sitk::ImageFileWriter writer;
// 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();
// Copy relevant tags from the original meta-data dictionary (private tags are
// also accessible).
std::vector<std::string> tagsToCopy = {
"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
};
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.
// NOTE: Always represent DICOM tags using lower case hexadecimals.
std::vector<double> direction = filteredImage.GetDirection();
std::vector<std::pair<std::string, std::string>> seriesTagValues;
// Copy existing tags
for (const auto & tag : tagsToCopy)
{
if (seriesReader.HasMetaDataKey(0, tag))
{
seriesTagValues.push_back({ tag, seriesReader.GetMetaData(0, tag) });
}
}
// Add modification tags
seriesTagValues.insert(
seriesTagValues.end(),
{
{ "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
});
// 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() });
// Series Description
std::string seriesDescription;
if (seriesReader.HasMetaDataKey(0, "0008|103e"))
{
seriesDescription = seriesReader.GetMetaData(0, "0008|103e");
}
seriesDescription += " Processed-SimpleITK";
seriesTagValues.push_back({ "0008|103e", seriesDescription });
sitk::ExtractImageFilter extractFilter;
std::vector<unsigned int> size = filteredImage.GetSize();
size[2] = 1; // Extract single slice
extractFilter.SetSize(size);
for (int i = 0; i < (int)filteredImage.GetDepth(); ++i)
{
extractFilter.SetIndex({ 0, 0, i });
auto imageSlice = extractFilter.Execute(filteredImage);
// Tags shared by the series.
for (const auto & tagValue : seriesTagValues)
{
imageSlice.SetMetaData(tagValue.first, tagValue.second);
}
// Slice specific tags.
auto sliceNow = std::chrono::system_clock::now();
auto sliceTime_t = std::chrono::system_clock::to_time_t(sliceNow);
std::tm sliceTm = *std::localtime(&sliceTime_t);
std::ostringstream instanceDateStream, instanceTimeStream;
instanceDateStream << std::put_time(&sliceTm, "%Y%m%d");
instanceTimeStream << std::put_time(&sliceTm, "%H%M%S");
// Instance Creation Date
imageSlice.SetMetaData("0008|0012", instanceDateStream.str());
// Instance Creation Time
imageSlice.SetMetaData("0008|0013", instanceTimeStream.str());
// Image Position (Patient)
std::vector<double> position = filteredImage.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(argv[2]) + "/" + std::to_string(i) + ".dcm";
writer.SetFileName(outPath);
writer.Execute(imageSlice);
}
return 0;
}
import org.itk.simple.*;
import java.io.*;
import java.util.*;
import java.text.SimpleDateFormat;
public class DicomSeriesReadModifySeriesWrite {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: DicomSeriesReadModifySeriesWrite <input_directory_with_DICOM_series> <output_directory>");
System.exit(1);
}
// 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.metaDataDictionaryArrayUpdateOn();
seriesReader.loadPrivateTagsOn();
Image image3D = seriesReader.execute();
// Modify the image (blurring)
Image filteredImage = SimpleITK.discreteGaussian(image3D);
// 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
ImageFileWriter writer = new 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();
// Copy relevant tags from the original meta-data dictionary (private tags are
// also accessible).
String[] tagsToCopy = {
"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
};
SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
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.
// NOTE: Always represent DICOM tags using lower case hexadecimals.
VectorDouble direction = filteredImage.getDirection();
List<String[]> seriesTagValues = new ArrayList<>();
// Copy existing tags
for (String tag : tagsToCopy) {
if (seriesReader.hasMetaDataKey(0, tag)) {
seriesTagValues.add(new String[]{tag, seriesReader.getMetaData(0, tag)});
}
}
// Add modification tags
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});
// Series Description
String seriesDescription = "";
if (seriesReader.hasMetaDataKey(0, "0008|103e")) {
seriesDescription = seriesReader.getMetaData(0, "0008|103e");
}
seriesDescription += " Processed-SimpleITK";
seriesTagValues.add(new String[]{"0008|103e", seriesDescription});
ExtractImageFilter extractFilter = new ExtractImageFilter();
VectorUInt32 size = filteredImage.getSize();
size.set(2, 1L);
extractFilter.setSize(size);
for (int i = 0; i < filteredImage.getDepth(); i++) {
extractFilter.setIndex(new VectorInt32(new int[]{0, 0, i}));
Image imageSlice = extractFilter.execute(filteredImage);
// Tags shared by the series.
for (String[] tagValue : seriesTagValues) {
imageSlice.setMetaData(tagValue[0], tagValue[1]);
}
// Slice specific tags.
SimpleDateFormat instanceDateFormat = new SimpleDateFormat("yyyyMMdd");
SimpleDateFormat instanceTimeFormat = new SimpleDateFormat("HHmmss");
Date instanceNow = new Date();
// Instance Creation Date
imageSlice.setMetaData("0008|0012", instanceDateFormat.format(instanceNow));
// Instance Creation Time
imageSlice.setMetaData("0008|0013", instanceTimeFormat.format(instanceNow));
// Image Position (Patient)
VectorDouble position = filteredImage.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(args[1], i + ".dcm").getAbsolutePath());
writer.execute(imageSlice);
}
}
}
import sys
import time
import os
import SimpleITK as sitk
if len(sys.argv) < 3:
print(
"Usage: python "
+ __file__
+ " <input_directory_with_DICOM_series> <output_directory>"
)
sys.exit(1)
# 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.MetaDataDictionaryArrayUpdateOn()
series_reader.LoadPrivateTagsOn()
image3D = series_reader.Execute()
# Modify the image (blurring)
filtered_image = sitk.DiscreteGaussian(image3D)
# 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
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()
# 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
]
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.
# NOTE: Always represent DICOM tags using lower case hexadecimals.
# DICOM tags represent hexadecimal numbers, so 0020|000D and 0020|000d
# are equivalent. The ITK/SimpleITK dictionary is string based, so these
# are two different keys, case sensitive. When read from a DICOM file the
# hexadecimal string representations are in lower case. To ensure consistency,
# always use lower case for the tags.
# Tags of interest:
direction = filtered_image.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],
direction[1],
direction[4],
direction[7],
), # Image Orientation (Patient)
)
),
),
(
"0008|103e",
(
series_reader.GetMetaData(0, "0008|103e")
if series_reader.HasMetaDataKey(0, "0008|103e")
else "" + " Processed-SimpleITK"
),
), # Series Description is an optional tag, so may not exist
]
for i in range(filtered_image.GetDepth()):
image_slice = filtered_image[:, :, i]
# Tags shared by the series.
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, filtered_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(sys.argv[2], str(i) + ".dcm"))
writer.Execute(image_slice)
sys.exit(0)
library(SimpleITK)
args <- commandArgs( TRUE )
if (length(args) < 2) {
write("Usage arguments: <input_directory_with_DICOM_series> <output_directory>", stderr())
quit(save="no", status=1)
}
# 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) {
write(paste0("ERROR: given directory \"",data_directory,"\" does not contain a DICOM series."))
quit(save="no", status=1)
}
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$MetaDataDictionaryArrayUpdateOn()
series_reader$LoadPrivateTagsOn()
image3D <- series_reader$Execute()
# Modify the image (blurring)
filtered_image <- DiscreteGaussian(image3D)
# 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 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
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()
# Copy relevant tags from the original meta-data dictionary (private tags are also
# accessible).
tags_to_copy <- c("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
)
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.
# When attempting to copy we are not ensured that the tags exist, in which case
# we get a NULL as an entry in the list returned by lapply, these are removed
# by the Filter.
# 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.
# NOTE: Always represent DICOM tags using lower case hexadecimals.
# DICOM tags represent hexadecimal numbers, so 0020|000D and 0020|000d
# are equivalent. The ITK/SimpleITK dictionary is string based, so these
# are two different keys, case sensitive. When read from a DICOM file the
# hexadecimal string representations are in lower case. To ensure consistency,
# always use lower case for the tags.
# Tags of interest:
direction <- filtered_image$GetDirection()
series_tag_values <- c(Filter(Negate(is.null),
lapply(tags_to_copy,
function(k) {
if(series_reader$HasMetaDataKey(0,k)) {
list(k, series_reader$GetMetaData(0,k))
}
})),
list(list("0008|0031",modification_time), # Series Time
list("0008|0021",modification_date), # Series Date
list("0008|0008","DERIVED\\SECONDARY"), # Image Type
list("0020|000e", paste0("1.2.826.0.1.3680043.2.1125.",modification_date,".1",modification_time)), # Series Instance UID
list("0020|0037", paste(c(direction[1], direction[4], direction[7],
direction[2],direction[5],direction[8]), collapse="\\")), # Image Orientation (Patient)
list("0008|103e", paste0(if(series_reader$HasMetaDataKey(0,"0008|103e")) series_reader$GetMetaData(0,"0008|103e") else "", " Processed-SimpleITK")))) # Series Description is an optional tag, so may not exist
for(i in 1:filtered_image$GetDepth()) {
image_slice <- filtered_image[,,i]
# Tags shared by the series.
invisible(lapply(series_tag_values,
function(tag_value) {image_slice$SetMetaData(tag_value[1], tag_value[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
image_slice$SetMetaData("0020|0032", paste(filtered_image$TransformIndexToPhysicalPoint(c(0,0,i-1)), collapse="\\")) # Image Position (Patient)
image_slice$SetMetaData("0020|0013", as.character(i)) # Instance Number
# Write to the output directory and add the extension dcm, to force writing in DICOM format.
writer$SetFileName(file.path(args[[2]], sprintf("%d.dcm",i)))
writer$Execute(image_slice)
}
quit(save="no", status=0)