Image Grid Manipulation
Overview
There are numerous SimpleITK filters that have similar functions, but very important differences. The filters that will be compared are:
JoinSeriesImageFilter()- Joins multiple N-D images into an (N+1)-D image
ComposeImageFilter()- Combines several scalar images into a multi-component vector image
VectorIndexSelectionCastImageFilter()- Extracts the selected index of the input pixel type vector (the input image pixel type must be a vector and the output a scalar). Additionally, this filter can cast the output pixel type (SetOutputPixelType method).
ExtractImageFilter()- Crops an image to the selected region bounds using vectors; Collapses dimensions unless dimension is two
CropImageFilter()- Similar toExtractImageFilter(), but crops an image by anitk::Sizeat upper and lower boundsImage Slicing Operator - Uses slicing (
img[i:j, k:l]) to extract a subregion of an image
All of these operations will maintain the physical location of the pixels, instead modifying the image’s metadata.
Comparisons
Composition Filters
While JoinSeriesImageFilter() merges multiple images of the same pixel
type in N dimensions into an image in N+1 dimensions, ComposeImageFilter()
will combine scalar images into a vector image of the same dimension. The
former is useful for connecting a series of contiguous images while the latter
is more useful for merging multiple channels of the same object into one image
(such as RGB).
Extraction Filters
VectorIndexSelectionCastImageFilter() will isolate a single channel in a
vector image and return a scalar image. On the other hand,
ExtractImageFilter() and CropImageFilter() will extract and return a
subregion of an image, using an ExtractionRegion size and index and
itk::Size’s respectively. However, note that only the
ExtractImageFilter() collapses dimensions. The image slicing operator
can also serve the same purpose.
Code
using System;
using itk.simple;
namespace itk.simple.examples
{
class ImageGridManipulation
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: {0} <input-1> <input-2>",
System.AppDomain.CurrentDomain.FriendlyName);
return;
}
// Two vector images of same pixel type and dimension expected
Image image1 = SimpleITK.ReadImage(args[0]);
Image image2 = SimpleITK.ReadImage(args[1]);
// Join two N-D Vector images to form an (N+1)-D image
JoinSeriesImageFilter join = new JoinSeriesImageFilter();
Image joinedImage = join.Execute(image1, image2);
// Extract first three channels of joined image (assuming RGB)
VectorIndexSelectionCastImageFilter select = new VectorIndexSelectionCastImageFilter();
select.SetOutputPixelType(PixelIDValueEnum.sitkUInt8);
select.SetIndex(0);
Image channel1Image = select.Execute(joinedImage);
select.SetIndex(1);
Image channel2Image = select.Execute(joinedImage);
select.SetIndex(2);
Image channel3Image = select.Execute(joinedImage);
// Recompose image (should be same as joined_image)
ComposeImageFilter compose = new ComposeImageFilter();
Image composedImage = compose.Execute(channel1Image, channel2Image, channel3Image);
// Select same subregion using image slicing operator
VectorInt32 sliceStart = new VectorInt32(new int[] { 10, 10, 0 });
VectorInt32 sliceStop = new VectorInt32(new int[] { 40, 40, 1 });
Image slicedImage = SimpleITK.Slice(composedImage, sliceStart, sliceStop);
// Select same subregion using ExtractImageFilter
ExtractImageFilter extract = new ExtractImageFilter();
VectorUInt32 size = new VectorUInt32(new uint[] { 30, 30, 0 });
VectorInt32 index = new VectorInt32(new int[] { 10, 10, 0 });
extract.SetSize(size);
extract.SetIndex(index);
Image extractedImage = extract.Execute(composedImage);
// Select same sub-region using CropImageFilter (NOTE: CropImageFilter cannot
// reduce dimensions unlike ExtractImageFilter, so cropped_image is a three
// dimensional image with depth of 1)
CropImageFilter crop = new CropImageFilter();
VectorUInt32 lowerBoundary = new VectorUInt32(new uint[] { 10, 10, 0 });
VectorUInt32 upperBoundary = new VectorUInt32(new uint[] {
composedImage.GetWidth() - 40,
composedImage.GetHeight() - 40,
1
});
crop.SetLowerBoundaryCropSize(lowerBoundary);
crop.SetUpperBoundaryCropSize(upperBoundary);
Image croppedImage = crop.Execute(composedImage);
Console.WriteLine("Sliced image size: {0}x{1}x{2}",
slicedImage.GetWidth(), slicedImage.GetHeight(), slicedImage.GetDepth());
Console.WriteLine("Extracted image size: {0}x{1}x{2}",
extractedImage.GetWidth(), extractedImage.GetHeight(), extractedImage.GetDepth());
Console.WriteLine("Cropped image size: {0}x{1}x{2}",
croppedImage.GetWidth(), croppedImage.GetHeight(), croppedImage.GetDepth());
}
}
}
#include <SimpleITK.h>
#include <iostream>
#include <stdlib.h>
namespace sitk = itk::simple;
int
main(int argc, char * argv[])
{
if (argc < 3)
{
std::cerr << "Usage: " << argv[0] << " <input-1> <input-2>" << std::endl;
return 1;
}
// Two vector images of same pixel type and dimension expected
sitk::Image image1 = sitk::ReadImage(argv[1]);
sitk::Image image2 = sitk::ReadImage(argv[2]);
// Join two N-D Vector images to form an (N+1)-D image
sitk::JoinSeriesImageFilter join;
sitk::Image joinedImage = join.Execute(image1, image2);
// Extract first three channels of joined image (assuming RGB)
sitk::VectorIndexSelectionCastImageFilter select;
select.SetOutputPixelType(sitk::sitkUInt8);
select.SetIndex(0);
sitk::Image channel1Image = select.Execute(joinedImage);
select.SetIndex(1);
sitk::Image channel2Image = select.Execute(joinedImage);
select.SetIndex(2);
sitk::Image channel3Image = select.Execute(joinedImage);
// Recompose image (should be same as joined_image)
sitk::ComposeImageFilter compose;
sitk::Image composedImage = compose.Execute(channel1Image, channel2Image, channel3Image);
// Select same subregion using ExtractImageFilter
sitk::ExtractImageFilter extract;
extract.SetSize({ 30, 30, 0 });
extract.SetIndex({ 10, 10, 0 });
sitk::Image extractedImage = extract.Execute(composedImage);
// Select same sub-region using CropImageFilter (NOTE: CropImageFilter cannot
// reduce dimensions unlike ExtractImageFilter, so cropped_image is a three
// dimensional image with depth of 1)
sitk::CropImageFilter crop;
crop.SetLowerBoundaryCropSize({ 10, 10, 0 });
crop.SetUpperBoundaryCropSize({ composedImage.GetWidth() - 40, composedImage.GetHeight() - 40, 1 });
sitk::Image croppedImage = crop.Execute(composedImage);
// Print image information to demonstrate variable usage
std::cout << "Extracted image size: " << extractedImage.GetWidth() << "x" << extractedImage.GetHeight() << "x"
<< extractedImage.GetDepth() << std::endl;
std::cout << "Cropped image size: " << croppedImage.GetWidth() << "x" << croppedImage.GetHeight() << "x"
<< croppedImage.GetDepth() << std::endl;
return 0;
}
import org.itk.simple.*;
class ImageGridManipulation {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: ImageGridManipulation <input-1> <input-2>");
return;
}
// Two vector images of same pixel type and dimension expected
Image image1 = SimpleITK.readImage(args[0]);
Image image2 = SimpleITK.readImage(args[1]);
// Join two N-D Vector images to form an (N+1)-D image
JoinSeriesImageFilter join = new JoinSeriesImageFilter();
Image joinedImage = join.execute(image1, image2);
// Extract first three channels of joined image (assuming RGB)
VectorIndexSelectionCastImageFilter select = new VectorIndexSelectionCastImageFilter();
select.setOutputPixelType(PixelIDValueEnum.sitkUInt8);
select.setIndex(0);
Image channel1Image = select.execute(joinedImage);
select.setIndex(1);
Image channel2Image = select.execute(joinedImage);
select.setIndex(2);
Image channel3Image = select.execute(joinedImage);
// Recompose image (should be same as joined_image)
ComposeImageFilter compose = new ComposeImageFilter();
Image composedImage = compose.execute(channel1Image, channel2Image, channel3Image);
// Select same subregion using ExtractImageFilter
ExtractImageFilter extract = new ExtractImageFilter();
VectorUInt32 size = new VectorUInt32();
size.add(30L); size.add(30L); size.add(0L);
VectorInt32 index = new VectorInt32();
index.add(10); index.add(10); index.add(0);
extract.setSize(size);
extract.setIndex(index);
Image extractedImage = extract.execute(composedImage);
// Select same sub-region using CropImageFilter (NOTE: CropImageFilter cannot
// reduce dimensions unlike ExtractImageFilter, so cropped_image is a three
// dimensional image with depth of 1)
CropImageFilter crop = new CropImageFilter();
VectorUInt32 lowerBoundary = new VectorUInt32();
lowerBoundary.add(10L); lowerBoundary.add(10L); lowerBoundary.add(0L);
VectorUInt32 upperBoundary = new VectorUInt32();
upperBoundary.add((long)(composedImage.getWidth() - 40));
upperBoundary.add((long)(composedImage.getHeight() - 40));
upperBoundary.add(1L);
crop.setLowerBoundaryCropSize(lowerBoundary);
crop.setUpperBoundaryCropSize(upperBoundary);
Image croppedImage = crop.execute(composedImage);
// Print image information to demonstrate variable usage
System.out.println("Extracted image size: " + extractedImage.getWidth() + "x" +
extractedImage.getHeight() + "x" + extractedImage.getDepth());
System.out.println("Cropped image size: " + croppedImage.getWidth() + "x" +
croppedImage.getHeight() + "x" + croppedImage.getDepth());
}
}
#!/usr/bin/env python
import sys
import SimpleITK as sitk
if len(sys.argv) < 3:
print("Usage: " + sys.argv[0] + " <input-1> <input-2>")
sys.exit(1)
image_1 = sitk.ReadImage(sys.argv[1])
image_2 = sitk.ReadImage(sys.argv[2])
# Join two N-D Vector images to form an (N+1)-D image
join = sitk.JoinSeriesImageFilter()
joined_image = join.Execute(image_1, image_2)
# Extract first three channels of joined image (assuming RGB)
select = sitk.VectorIndexSelectionCastImageFilter()
select.SetOutputPixelType(sitk.sitkUInt8)
select.SetIndex(0)
channel1_image = select.Execute(joined_image)
select.SetIndex(1)
channel2_image = select.Execute(joined_image)
select.SetIndex(2)
channel3_image = select.Execute(joined_image)
# Recompose image (should be same as joined_image)
compose = sitk.ComposeImageFilter()
composed_image = compose.Execute(channel1_image, channel2_image, channel3_image)
# Select same subregion using image slicing operator
sliced_image = composed_image[10:40, 10:40, 0]
# Select same subregion using ExtractImageFilter
extract = sitk.ExtractImageFilter()
extract.SetSize([30, 30, 0])
extract.SetIndex([10, 10, 0])
extracted_image = extract.Execute(composed_image)
# Select same sub-region using CropImageFilter (NOTE: CropImageFilter cannot
# reduce dimensions unlike ExtractImageFilter, so cropped_image is a three
# dimensional image with depth of 1)
crop = sitk.CropImageFilter()
crop.SetLowerBoundaryCropSize([10, 10, 0])
crop.SetUpperBoundaryCropSize(
[composed_image.GetWidth() - 40, composed_image.GetHeight() - 40, 1]
)
cropped_image = crop.Execute(composed_image)
# Print image information to demonstrate variable usage
print(f"Sliced image size: {sliced_image.GetWidth()}x{sliced_image.GetHeight()}x{sliced_image.GetDepth()}")
print(f"Extracted image size: {extracted_image.GetWidth()}x{extracted_image.GetHeight()}x{extracted_image.GetDepth()}")
print(f"Cropped image size: {cropped_image.GetWidth()}x{cropped_image.GetHeight()}x{cropped_image.GetDepth()}")
# Run with:
#
# Rscript --vanilla ImageGridManipulation.R input-1 input-2
#
library(SimpleITK)
args <- commandArgs( TRUE )
if (length(args) < 2){
write("Usage arguments: <input-1> <input-2>", stderr())
quit(save="no", status=1)
}
# Two vector images of same pixel type and dimension expected
reader <- ImageFileReader()
reader$SetFileName(args[[1]])
image_1 <- reader$Execute()
reader$SetFileName(args[[2]])
image_2 <- reader$Execute()
# Join two N-D Vector images to form an (N+1)-D image
join <- JoinSeriesImageFilter()
joined_image <- join$Execute( image_1, image_2 )
# Extract first three channels of joined image (assuming RGB)
select <- VectorIndexSelectionCastImageFilter()
select$SetOutputPixelType("sitkUInt8")
select$SetIndex(0)
channel1_image <- select$Execute(joined_image)
select$SetIndex(1)
channel2_image <- select$Execute(joined_image)
select$SetIndex(2)
channel3_image <- select$Execute(joined_image)
# Recompose image (should be same as joined_image)
compose <- ComposeImageFilter()
composed_image <- compose$Execute(channel1_image, channel2_image, channel3_image)
# Select same subregion using image slicing operator
sliced_image = composed_image[11:40, 11:40, 1]
# Select same subregion using ExtractImageFilter
extract <- ExtractImageFilter()
extract$SetSize( list(30, 30, 0) )
extract$SetIndex( list(10, 10, 0) )
extracted_image <- extract$Execute(composed_image)
# Select same subregion using CropImageFilter (NOTE: CropImageFilter cannot reduce dimensions
# unlike ExtractImageFilter, so cropped_image is a three dimensional image with depth of 1)
crop <- CropImageFilter()
crop$SetLowerBoundaryCropSize( list(10, 10, 0) )
crop$SetUpperBoundaryCropSize( list(composed_image$GetWidth()-40, composed_image$GetHeight()-40, 1) )
cropped_image <- crop$Execute(composed_image)
# Print image information to demonstrate variable usage
cat("Sliced image size:", sliced_image$GetWidth(), "x", sliced_image$GetHeight(), "x", sliced_image$GetDepth(), "\n")
cat("Extracted image size:", extracted_image$GetWidth(), "x", extracted_image$GetHeight(), "x", extracted_image$GetDepth(), "\n")
cat("Cropped image size:", cropped_image$GetWidth(), "x", cropped_image$GetHeight(), "x", cropped_image$GetDepth(), "\n")