New for ITK 5.0: IndexRange, ZeroBasedIndexRange, ImageRegionIndexRange

FYI, Last weekend, @matt.mccormick merged the itk::Experimental::IndexRange template class that I proposed, making it available for ITK 5.0 :drum: :drum: :drum:

IndexRange is a modern C++ range of iterators. It should ease iterating over a sequence of consecutive N-dimensional indices, as it can be used as a range-expression of a C++11 range-based for-loop:

for (const Index<Dimension>& index : indexRange)
   // Use the 'index' here...! 

The order in which the indices are iterated corresponds to the order in which pixels that are stored in a regular itk::Image.

There are two IndexRange type aliases:

  1. ZeroBasedIndexRange<Dimension> has its begin iterator at index zero (that’s [0, 0] in 2-D, or [0, 0, 0] in 3-D). The range is specified by itk::Size<Dimension>, which is typically the N-dimensional size of an image.

  2. ImageRegionIndexRange<Dimension> can begin at any arbitrary index, as it can be specified by an itk::ImageRegion<Dimension>. (itk::ImageRegion<Dimension>consists of an N-dimensional index and an N-dimensional size.)

The iterators of IndexRange are bidirectional, as they support both forward iteration (++it) and backward iteration (--it). When using ZeroBasedIndexRange, it appears fastest to iterate backward, from the end to the begin of the range:

const ZeroBasedIndexRange<Dimension> range{ imageSize };
const auto begin = range.begin();
auto it = range.end();

while (it != begin)
{
  --it;
  const auto& index = *it;
  // Use the 'index' here...! 
  
}

However, I think that in most cases, the range-based for-loop is fast enough :slight_smile:

3 Likes

Would for (auto index : indexRange) work? How about for (const auto& index : indexRange)?

Sure!

for (auto index : indexRange)
    // 'index' is of type 'itk::Index<Dimension>'

And

for (const auto& index : indexRange)
    // 'index' is of type 'const itk::Index<Dimension> &'

Kind regards, Niels

2 Likes

This looks really great!

Do we have any filters that could converted to use this new iterator?

It would be good to come to some kind of recommendation for best practice for performance and style for this type of loop.

I also feel compelled to give a strong word of caution about the ZeroBasedIndexRange this strategy is not suitable for using in most ITK filters. Yes, it will work most of the time but it won’t work all of the time. Filters are expected to work with images or regions that don’t have a zero starting index unless they override the VerifyInputInformation with a check. Non-zero based images or regions can occur with streaming, or as a result of filters like the ExtractImageFilter. These complicated features and situations are not well tested in ITK, but I have encountered and fixed them many times.

3 Likes

Actually my first use case was to ease comparing the “old-school” ITK neighborhood iterators with the new ShapedImageNeighborhoodRange, when iterating on an image region.

Old-school ITK neighborhood iteration:

neighborhoodIterator.GoToBegin();

while (!neighborhoodIterator.IsAtEnd())
{
  for (itk::SizeValueType i = 0; i < numberOfNeigbors; ++i)
  {
    auto neighbor = neighborhoodIterator.GetPixel(i);
    // Process neighbor pixel here... 
  }
  ++neighborhoodIterator;
}

And the corresponding iteration when using ShapedImageNeighborhoodRange + IndexRange:

for (const auto& index : indexRange)
{
  shapedImageNeighborhoodRange.SetLocation(index);

  for (PixelType neighbor : shapedImageNeighborhoodRange)
  {
    // Process neighbor pixel here... 
  }
}

So IndexRange makes it easier to interchange between “old-school” and range-based neighborhood iteration, and it also eases comparing both the performance and the result, for testing purposes.

This is awesome. :fireworks:

How do you think a class would be designed to works on an itk::Image, but does not need to iterate over the neighborhood, i.e. *it is the pixel value?

itk::Experimental::IndexRange does already support iterating on an itk::Image to retrieve and modify its pixel values, for example:

for (const auto& index : indexRange)
{
  auto pixelValue = image1.GetPixel(index);
  image2.SetPixel(index, pixelValue);
}

Unfortunately itk::Image::GetPixel and itk::Image::SetPixel may not be superfast, because they have to compute the offset value into the image buffer.

Still very much Work In Progress: This itk::Experimental::ImageRange should support range-based iteration on the pixels on an itk::Image: https://github.com/N-Dekker/ITK/tree/ImageRange Please tell me what you think!

Initial commit: WIP: Added ImageRange for itk::Image iteration. · N-Dekker/ITK@e1dd5ab · GitHub

1 Like

Beautiful @Niels_Dekker!

   ImageRange<ImageType> range{ *image };

   for (auto&& pixel : range)
   {
     pixel = pixel + 42;
   }

for_each, std::inner_product tested – extremely exciting!!