From 8195abb3e12501ceb4c44b5f79bd9564b7111852 Mon Sep 17 00:00:00 2001 From: Niels Dekker Date: Thu, 1 Nov 2018 16:27:19 +0100 Subject: [PATCH] ENH: Added IndexRange for efficient region and grid space iteration Added Experimental::IndexRange, to allow efficient iteration over the indices of an image region or grid space by means of a C++11 range-based for-loop or Standard C++ algorithm. Added type aliases, ImageRegionIndexRange and ZeroBasedIndexRange. This class appears useful in combination with ShapedImageNeighborhoodRange, e.g.: ImageRegionIndexRange indexRange{ imageRegion }; for (IndexType index : indexRange) { shapedImageNeighborhoodRange.SetLocation(index); for (PixelType neighborPixel : shapedImageNeighborhoodRange) // Process neighbor pixel... } Change-Id: Ia1f777ba251a9c617f55c16bf37878eaee632f43 --- Modules/Core/Common/include/itkIndexRange.h | 429 ++++++++++++++++++ Modules/Core/Common/test/CMakeLists.txt | 1 + .../Core/Common/test/itkIndexRangeGTest.cxx | 195 ++++++++ 3 files changed, 625 insertions(+) create mode 100644 Modules/Core/Common/include/itkIndexRange.h create mode 100644 Modules/Core/Common/test/itkIndexRangeGTest.cxx diff --git a/Modules/Core/Common/include/itkIndexRange.h b/Modules/Core/Common/include/itkIndexRange.h new file mode 100644 index 00000000000..cab30acb746 --- /dev/null +++ b/Modules/Core/Common/include/itkIndexRange.h @@ -0,0 +1,429 @@ +/*========================================================================= +* +* Copyright Insight Software Consortium +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0.txt +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*=========================================================================*/ + +#ifndef itkIndexRange_h +#define itkIndexRange_h + +#include +#include // For ptrdiff_t. +#include // For bidirectional_iterator_tag and reverse_iterator. +#include // For conditional and enable_if. + +#include "itkImageRegion.h" +#include "itkIndex.h" +#include "itkSize.h" + +namespace itk +{ +namespace Experimental +{ + +/** + * \class IndexRange + * + * Modern C++11 range, supporting efficient iteration over the indices of + * an image grid space. + * + * The following example prints all indices of an 2-D grid space of size 2x3. + \code + constexpr unsigned Dimension = 2; + const Size size = { {2, 3} }; + const ZeroBasedIndexRange indexRange{ size }; + + for (const Index index : indexRange) + { + std::cout << index; + } + // Output: "[0, 0][1, 0][0, 1][1, 1][0, 2][1, 2]" + \endcode + * + * The indices from IndexRange can also be used as consecutive locations + * of a ShapedImageNeighborhoodRange, for example: + \code + for (const auto index : indexRange) + { + shapedImageNeighborhoodRange.SetLocation(index); + + for (const PixelType neighborPixel : shapedImageNeighborhoodRange) + { + // Process neighbor pixel... + } + } + \endcode + * + * IndexRange is designed to conform to Standard C++ Iterator requirements, + * so that it can be used in range-based for loop, and its iterators can be + * passed to Standard C++ algorithms. + * + * \author Niels Dekker, LKEB, Leiden University Medical Center + * + * \see ShapedImageNeighborhoodRange + * \ingroup ImageIterators + * \ingroup ITKCommon + */ +template +class IndexRange final +{ +public: + constexpr static unsigned Dimension = VDimension; + using SizeType = Size; + using IndexType = Index; + + class const_iterator final + { + public: + // Types conforming the iterator requirements of the C++ standard library: + using difference_type = std::ptrdiff_t; + using value_type = IndexType; + using reference = const IndexType&; + using pointer = const IndexType*; + using iterator_category = std::bidirectional_iterator_tag; + + /** Default-constructor, as required for any C++11 Forward Iterator. + */ + const_iterator() = default; + + + /** Returns a reference to the current index. */ + reference operator*() const ITK_NOEXCEPT + { + return m_Index; + } + + + /** Returns a pointer to the current index. */ + pointer operator->() const ITK_NOEXCEPT + { + return &(**this); + } + + + /** Prefix increment ('++it'). */ + const_iterator& operator++() ITK_NOEXCEPT + { + for (unsigned i = 0; i < (VDimension - 1); ++i) + { + auto& indexValue = m_Index[i]; + + ++indexValue; + + if (indexValue <= m_MaxIndex[i]) + { + return *this; + } + indexValue = m_MinIndex[i]; + } + ++m_Index.back(); + + return *this; + } + + + /** Postfix increment ('it++'). + * \note Usually prefix increment ('++it') is preferable. */ + const_iterator operator++(int) ITK_NOEXCEPT + { + auto result = *this; + ++(*this); + return result; + } + + + /** Prefix decrement ('--it'). */ + const_iterator& operator--() ITK_NOEXCEPT + { + for (unsigned i = 0; i < (VDimension - 1); ++i) + { + auto& indexValue = m_Index[i]; + + --indexValue; + + if (indexValue >= m_MinIndex[i]) + { + return *this; + } + indexValue = m_MaxIndex[i]; + } + --m_Index.back(); + return *this; + } + + + /** Postfix increment ('it--'). + * \note Usually prefix increment ('--it') is preferable. */ + const_iterator operator--(int) ITK_NOEXCEPT + { + auto result = *this; + --(*this); + return result; + } + + + /** Returns (it1 == it2) for iterators it1 and it2. Note that these iterators + * should be from the same range. This operator does not support comparing iterators + * from different ranges. */ + friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + assert(lhs.m_MaxIndex == rhs.m_MaxIndex); + + return lhs.m_Index == rhs.m_Index; + } + + + /** Returns (it1 != it2) for iterators it1 and it2. */ + friend bool operator!=(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + // Implemented just like the corresponding std::rel_ops operator. + return !(lhs == rhs); + } + + + /** Returns (it1 < it2) for iterators it1 and it2. */ + friend bool operator<(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + for (unsigned i = VDimension; i > 0; --i) + { + const auto difference = lhs.m_Index[i - 1] - rhs.m_Index[i - 1]; + + if (difference < 0) + { + return true; + } + if (difference > 0) + { + break; + } + } + return false; + } + + + /** Returns (it1 > it2) for iterators it1 and it2. */ + friend bool operator>(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + // Implemented just like the corresponding std::rel_ops operator. + return rhs < lhs; + } + + + /** Returns (it1 <= it2) for iterators it1 and it2. */ + friend bool operator<=(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + // Implemented just like the corresponding std::rel_ops operator. + return !(rhs < lhs); + } + + + /** Returns (it1 >= it2) for iterators it1 and it2. */ + friend bool operator>=(const const_iterator& lhs, const const_iterator& rhs) ITK_NOEXCEPT + { + // Implemented just like the corresponding std::rel_ops operator. + return !(lhs < rhs); + } + + + private: + friend class IndexRange; + + // Represents an N-dimensional index that is always zero + // Aims towards zero runtime overhead. + struct ZeroIndex + { + // The "index" operator. + constexpr IndexValueType operator[](unsigned) const + { + return 0; + } + + // Implicitly converts to a default-initialized itk::Index. + constexpr operator IndexType() const + { + return IndexType(); + } + }; + + + // When BeginAtZero is true, use zero as minimum index, otherwise use itk::Index. + using MinIndexType = typename std::conditional::type; + + // Private constructor, only used by friend class IndexRange. + const_iterator( + const IndexType& index, + const MinIndexType& minIndex, + const IndexType& maxIndex) ITK_NOEXCEPT + : + // Note: Use parentheses instead of curly braces to initialize data members, + // to avoid AppleClang 6.0.0.6000056 compilation error, "no viable conversion..." + m_Index(index), + m_MinIndex(minIndex), + m_MaxIndex(maxIndex) + { + } + + + // IndexRange::const_iterator data members: + + // Current (N-dimensional) index. + IndexType m_Index; + + // Minimum (N-dimensional) index. + MinIndexType m_MinIndex; + + // Maximum (N-dimensional) index. + IndexType m_MaxIndex; + }; + + using iterator = const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + /** Constructs a range of indices for the specified grid size. + */ + explicit IndexRange(const SizeType& size) + : + // Note: Use parentheses instead of curly braces to initialize data members, + // to avoid AppleClang 6.0.0.6000056 compile errors, "no viable conversion..." + m_MinIndex(), + m_MaxIndex(CalculateMaxIndex({}, size)) + { + } + + + /** Constructs a range of indices for the specified image region. + * \note This function is unavailable when VBeginAtZero is true (in + * case there is a substitution failure, and C++ "SFINAE" kicks in). + */ + template ::type> + explicit IndexRange(const ImageRegion& imageRegion) + : + // Note: Use parentheses instead of curly braces to initialize data members, + // to avoid AppleClang 6.0.0.6000056 compile errors, "no viable conversion..." + m_MinIndex(imageRegion.GetIndex()), + m_MaxIndex(CalculateMaxIndex(imageRegion.GetIndex(), imageRegion.GetSize())) + { + // Three compile-time asserts, just to check if SFINAE worked properly: + static_assert(!VIsSubstitutionFailure, + "This template should (of course) be instantiated without substitution failure."); + static_assert(std::is_same::value, + "std::enable_if should yield void, by definition."); + static_assert(!VBeginAtZero, + "This constructor should only be is available when VBeginAtZero is false."); + } + + + /** Returns an iterator to the first index. */ + iterator begin() const ITK_NOEXCEPT + { + return iterator(m_MinIndex, m_MinIndex, m_MaxIndex); + } + + /** Returns an 'end iterator' for this range. */ + iterator end() const ITK_NOEXCEPT + { + IndexType index = m_MinIndex; + index.back() = m_MaxIndex.back() + 1; + return iterator(index, m_MinIndex, m_MaxIndex); + } + + /** Returns a const iterator to the first index. + * Provides only read-only access to the index data. */ + const_iterator cbegin() const ITK_NOEXCEPT + { + return this->begin(); + } + + /** Returns a const 'end iterator' for this range. */ + const_iterator cend() const ITK_NOEXCEPT + { + return this->end(); + } + + /** Returns a reverse 'begin iterator' for this range. */ + reverse_iterator rbegin() const ITK_NOEXCEPT + { + return reverse_iterator(this->end()); + } + + /** Returns a reverse 'end iterator' for this range. */ + reverse_iterator rend() const ITK_NOEXCEPT + { + return reverse_iterator(this->begin()); + } + + /** Returns a const reverse 'begin iterator' for this range. */ + const_reverse_iterator crbegin() const ITK_NOEXCEPT + { + return this->rbegin(); + } + + /** Returns a const reverse 'end iterator' for this range. */ + const_reverse_iterator crend() const ITK_NOEXCEPT + { + return this->rend(); + } + + + /** Returns the size of the range, that is the number of indices. */ + std::size_t size() const ITK_NOEXCEPT + { + std::size_t result = 1; + + for (unsigned i = 0; i < VDimension; ++i) + { + result *= ((m_MaxIndex[i] + 1) - m_MinIndex[i]); + } + return result; + } + +private: + + using MinIndexType = typename iterator::MinIndexType; + + static IndexType CalculateMaxIndex(const MinIndexType& minIndex, const SizeType& size) + { + IndexType index; + + for (unsigned i = 0; i < VDimension; ++i) + { + index[i] = minIndex[i] + static_cast(size[i]) - 1; + } + + return index; + } + + // IndexRange data members: + + // Minimum (N-dimensional) index. + MinIndexType m_MinIndex; + + // Maximum (N-dimensional) index. + IndexType m_MaxIndex; + +}; + +template +using ImageRegionIndexRange = IndexRange; + +template +using ZeroBasedIndexRange = IndexRange; + +} // namespace Experimental +} // namespace itk + +#endif diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 7a430310f86..90748c43a00 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -628,6 +628,7 @@ set(ITKCommonGTests itkBuildInformationTest.cxx itkConnectedImageNeighborhoodShapeGTest.cxx itkImageNeighborhoodOffsetsGTest.cxx + itkIndexRangeGTest.cxx itkShapedImageNeighborhoodRangeGTest.cxx itkSmartPointerGTest.cxx itkCommonTypeTraitsGTest.cxx diff --git a/Modules/Core/Common/test/itkIndexRangeGTest.cxx b/Modules/Core/Common/test/itkIndexRangeGTest.cxx new file mode 100644 index 00000000000..a0ae0028850 --- /dev/null +++ b/Modules/Core/Common/test/itkIndexRangeGTest.cxx @@ -0,0 +1,195 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + + // First include the header file to be tested: +#include "itkIndexRange.h" + +#include + +// Test template instantiations for various template arguments: +template class itk::Experimental::IndexRange<1, true>; +template class itk::Experimental::IndexRange<1, false>; +template class itk::Experimental::IndexRange<2, true>; +template class itk::Experimental::IndexRange<2, false>; + + +using itk::Experimental::IndexRange; +using itk::Experimental::ImageRegionIndexRange; +using itk::Experimental::ZeroBasedIndexRange; + + +static_assert(sizeof(ZeroBasedIndexRange<3>) < sizeof(ImageRegionIndexRange<3>), + "ZeroBasedIndexRange does not need to store the index of a region, so it should take less memory."); + + +// Tests that a begin iterator compares equal to another begin iterator of the +// same range. Also does this test for end iterators. +TEST(IndexRange, EquivalentBeginOrEndIteratorsCompareEqual) +{ + using RangeType = IndexRange<2, true>; + + RangeType range(RangeType::SizeType{ {1, 2} }); + + const RangeType::iterator begin = range.begin(); + const RangeType::iterator end = range.end(); + const RangeType::const_iterator cbegin = range.cbegin(); + const RangeType::const_iterator cend = range.cend(); + + // An iterator object compares equal to itself: + EXPECT_EQ(begin, begin); + EXPECT_EQ(end, end); + EXPECT_EQ(cbegin, cbegin); + EXPECT_EQ(cend, cend); + + // Multiple calls of the same function yield equivalent objects: + EXPECT_EQ(range.begin(), range.begin()); + EXPECT_EQ(range.end(), range.end()); + EXPECT_EQ(range.cbegin(), range.cbegin()); + EXPECT_EQ(range.cend(), range.cend()); + + // Corresponding const_iterator and non-const iterator compare equal: + EXPECT_EQ(begin, cbegin); + EXPECT_EQ(end, cend); + EXPECT_EQ(cbegin, begin); + EXPECT_EQ(cend, end); +} + + +// Tests that a 'begin' iterator and an 'end' iterator do not compare +// equal, when the size of the range is greater than zero. +TEST(IndexRange, BeginAndEndDoNotCompareEqualWhenSizeIsGreaterThanZero) +{ + using RangeType = IndexRange<2, true>; + RangeType range(RangeType::SizeType{ {1, 2} }); + + EXPECT_TRUE(range.size() > 0); + + EXPECT_FALSE(range.begin() == range.end()); + EXPECT_NE(range.begin(), range.end()); +} + + +// Tests that the iterators of an IndexRange can be used as first and +// second argument of an std::vector constructor. +TEST(IndexRange, IteratorsCanBePassedToStdVectorConstructor) +{ + using RangeType = IndexRange<2, true>; + RangeType range(RangeType::SizeType{ {1, 2} }); + + // Easily store all indices of an IndexRange in an std::vector: + const std::vector stdVector(range.begin(), range.end()); + EXPECT_EQ(stdVector, std::vector(range.cbegin(), range.cend())); + ASSERT_EQ(stdVector.size(), range.size()); + EXPECT_TRUE(std::equal(stdVector.cbegin(), stdVector.cend(), range.cbegin())); +} + + +// Tests that the iterators of an IndexRange can be used as first and +// second argument of std::reverse (which requires bidirectional iterators). +TEST(IndexRange, IteratorsCanBePassedToStdReverseCopy) +{ + using RangeType = IndexRange<2, true>; + using IndexType = RangeType::IndexType; + RangeType range(RangeType::SizeType{ {2, 3} }); + + const unsigned numberOfIndices = range.size(); + + const std::vector stdVector(range.begin(), range.end()); + std::vector reversedStdVector1(numberOfIndices); + std::vector reversedStdVector2(numberOfIndices); + std::vector reversedStdVector3(numberOfIndices); + + // Checks bidirectionality of the range iterators. + std::reverse_copy(stdVector.cbegin(), stdVector.cend(), reversedStdVector1.begin()); + std::reverse_copy(range.begin(), range.end(), reversedStdVector2.begin()); + std::reverse_copy(range.cbegin(), range.cend(), reversedStdVector3.begin()); + + // Sanity check + EXPECT_NE(reversedStdVector1, stdVector); + EXPECT_NE(reversedStdVector2, stdVector); + EXPECT_NE(reversedStdVector3, stdVector); + + // The real tests: + EXPECT_EQ(reversedStdVector1, reversedStdVector2); + EXPECT_EQ(reversedStdVector1, reversedStdVector3); +} + + +// Tests that the iterators of an IndexRange can be used as first and +// second argument of std::for_each. +TEST(IndexRange, IteratorsCanBePassedToStdForEach) +{ + using RangeType = IndexRange<2, true>; + using IndexType = RangeType::IndexType; + RangeType range(RangeType::SizeType{ {2, 3} }); + + std::for_each(range.begin(), range.end(), [](const IndexType index) + { + EXPECT_TRUE(index >= IndexType{}); + }); +} + + +// Tests that an IndexRange can be used as the "range expression" of a +// C++11 range-based for loop. +TEST(IndexRange, CanBeUsedAsExpressionOfRangeBasedForLoop) +{ + using RangeType = IndexRange<2, true>; + using IndexType = RangeType::IndexType; + RangeType range(RangeType::SizeType{ {2, 3} }); + + for (auto&& index : range) + { + EXPECT_TRUE(index >= IndexType{}); + } +} + + +TEST(IndexRange, SupportsImageRegion) +{ + constexpr unsigned Dimension = 2; + + using ImageRegionIndexRangeType = ImageRegionIndexRange; + using IndexType = ImageRegionIndexRangeType::IndexType; + using RegionType = itk::ImageRegion; + + const RegionType::SizeType size = { {2, 3} }; + const RegionType::IndexType regionIndex = { {4, 5} }; + const RegionType imageRegion{ regionIndex, size }; + + // Default index range, beginning at [0, 0]. + const ZeroBasedIndexRange indexRange(size); + const auto beginOfIndexRange = indexRange.cbegin(); + const auto endOfIndexRange = indexRange.cend(); + + const ImageRegionIndexRangeType imageRegionIndexRange(imageRegion); + + EXPECT_EQ(imageRegionIndexRange.size(), indexRange.size()); + + const auto offset = regionIndex - IndexType(); + auto indexIterator = beginOfIndexRange; + + for (auto&& imageRegionIndex : imageRegionIndexRange) + { + // Emergency stop if 'indexIterator' would already be at the end. + ASSERT_NE(indexIterator, endOfIndexRange); + + EXPECT_EQ(imageRegionIndex, *indexIterator + offset); + ++indexIterator; + } +}