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 to ExtractImageFilter(), but crops an image by an itk::Size at upper and lower bounds

  • Image 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());
        }
    }
}