/*========================================================================= * * Copyright NumFOCUS * * 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 * * https://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 itkConnectedImageNeighborhoodShape_h #define itkConnectedImageNeighborhoodShape_h #include "itkIntTypes.h" // For uintmax_t #include "itkMath.h" #include "itkOffset.h" #include #include #include namespace itk { /** * \class ConnectedImageNeighborhoodShape * Connected image-neighborhood shape, based on the topological property of * pixel connectivity. Eases creating a sequence of offsets to construct a * ShapedImageNeighborhoodRange object. Can also be used to specify the shape * of a ShapedNeighborhoodIterator, using its ActivateOffset member function. * * This shape class supports generating offsets in colexicographic order. Which * means that, for example, a sequence of generated offsets for a 2-dimensional * shape will have offset {1, 0} before offset {0, 1}. This order was chosen * because it is usually in agreement with the order of the corresponding * neighbor pixels, as stored in the internal image buffer. * * The following example generates the offsets for a 3-dimensional 18-connected * neighborhood shape, including the center pixel, and asserts that the result * is as expected: \code size_t maximumCityblockDistance = 2; bool includeCenterPixel = true; ConnectedImageNeighborhoodShape<3> shape{ maximumCityblockDistance, includeCenterPixel }; std::vector> offsets = GenerateImageNeighborhoodOffsets(shape); assert(offsets.size() == 19); assert(offsets == std::vector>( { {{0, -1, -1}}, {{-1, 0, -1}}, {{0, 0, -1}}, {{1, 0, -1}}, {{0, 1, -1}}, {{-1, -1, 0}}, {{0, -1, 0}}, {{1, -1, 0}}, {{-1, 0, 0}}, {{0, 0, 0}}, {{1, 0, 0}}, {{-1, 1, 0}}, {{0, 1, 0}}, {{1, 1, 0}}, {{0, -1, 1}}, {{-1, 0, 1}}, {{0, 0, 1}}, {{1, 0, 1}}, {{0, 1, 1}} })); \endcode * * The following code shows how to create 4-connected, 8-connected, * 6-connected, 18-connected, and 26-connected neighborhood shapes: \code // 2-dimensional: ConnectedImageNeighborhoodShape<2> _4_connectedShape{ 1, includeCenterPixel }; ConnectedImageNeighborhoodShape<2> _8_connectedShape{ 2, includeCenterPixel }; // 3-dimensional: ConnectedImageNeighborhoodShape<3> _6_connectedShape{ 1, includeCenterPixel }; ConnectedImageNeighborhoodShape<3> _18_connectedShape{ 2, includeCenterPixel }; ConnectedImageNeighborhoodShape<3> _26_connectedShape{ 3, includeCenterPixel }; \endcode * * \author Niels Dekker, LKEB, Leiden University Medical Center * * \see ShapedImageNeighborhoodRange * \see ShapedNeighborhoodIterator * \ingroup ImageIterators * \ingroup ITKCommon */ template class ConnectedImageNeighborhoodShape { public: static constexpr unsigned int ImageDimension = VImageDimension; /** Constructs a connected image-neighborhood shape. Its offsets contain only * the offset values -1, 0, and 1. * The parameter 'maximumCityblockDistance' specifies the maximum city-block * distance (Manhattan distance) between the center pixel and the connected * neighbor pixel. This distance measure corresponds to the number of * non-zero values of an offset. For example, in a 3-dimensional neighborhood, * offset {1,0,0}, {0,1,0}, and {0,0,1} have distance = 1 to the center, * while offset {1,1,1} has distance = 3. * The parameter 'includeCenterPixel' specifies whether or not the center * pixel (offset zero) should be included with the offsets for this shape. */ constexpr explicit ConnectedImageNeighborhoodShape(const size_t maximumCityblockDistance, const bool includeCenterPixel) noexcept : m_MaximumCityblockDistance{ maximumCityblockDistance } , m_IncludeCenterPixel{ includeCenterPixel } , m_NumberOfOffsets{ CalculateNumberOfOffsets(maximumCityblockDistance, includeCenterPixel) } {} /** Returns the number of offsets needed for this shape. */ constexpr size_t GetNumberOfOffsets() const noexcept { return m_NumberOfOffsets; } /** Fills the specified buffer with the offsets for a neighborhood of this shape. */ void FillOffsets(Offset * const offsets) const noexcept { if (m_NumberOfOffsets > 0) { assert(offsets != nullptr); Offset offset; std::fill_n(offset.begin(), ImageDimension, -1); size_t i = 0; while (i < m_NumberOfOffsets) { const size_t numberOfNonZeroOffsetValues = ImageDimension - static_cast(std::count(offset.begin(), offset.end(), 0)); if ((m_IncludeCenterPixel || (numberOfNonZeroOffsetValues > 0)) && (numberOfNonZeroOffsetValues <= m_MaximumCityblockDistance)) { offsets[i] = offset; ++i; } // Go to the next offset: for (unsigned int direction = 0; direction < ImageDimension; ++direction) { auto & offsetValue = offset[direction]; ++offsetValue; if (offsetValue <= 1) { break; } offsetValue = -1; } } } } private: // The maximum city-block distance (Manhattan distance) between the center // pixel and each connected neighbor pixel. size_t m_MaximumCityblockDistance; // Specifies whether or not the center pixel (offset zero) should be included // with the offsets for this shape. bool m_IncludeCenterPixel; // The number of offsets needed to represent this shape. size_t m_NumberOfOffsets; // Calculates a + b. Numeric overflow triggers a compilation error in // "constexpr context" and a debug assert failure at run-time. static constexpr uintmax_t CalculateSum(const uintmax_t a, const uintmax_t b) noexcept { return ((a + b) >= a) && ((a + b) >= b) ? (a + b) : (assert(!"CalculateSum overflow!"), 0); } // Calculates 2 ^ n. Numeric overflow triggers a compilation error in // "constexpr context" and a debug assert failure at run-time. static constexpr uintmax_t CalculatePowerOfTwo(const size_t n) noexcept { return (n < std::numeric_limits::digits) ? (uintmax_t{ 1 } << n) : (assert(!"CalculatePowerOfTwo overflow!"), 0); } // Calculates the binomial coefficient, 'n' over 'k'. // Inspired by the 'binom' function from Walter, June 23, 2017: // https://stackoverflow.com/questions/44718971/calculate-binomial-coffeficient-very-reliable/44719165#44719165 // Optimized for small values of 'k' (k <= n/2). static constexpr uintmax_t CalculateBinomialCoefficient(const uintmax_t n, const uintmax_t k) noexcept { return (k > n) ? (assert(!"Out of range!"), 0) : (k == 0) ? 1 : Math::UnsignedProduct(n, CalculateBinomialCoefficient(n - 1, k - 1)) / k; } // Calculates the number of m-dimensional hypercubes on the boundary of an // n-cube, as described at https://en.wikipedia.org/wiki/Hypercube#Elements // (Which refers to H.S.M. Coxeter, Regular polytopes, 3rd ed., 1973, p.120.) static constexpr size_t CalculateNumberOfHypercubesOnBoundaryOfCube(const size_t m, const size_t n) noexcept { // Calculate 2^(n-m) * BinomialCoefficient(n, m) return Math::UnsignedProduct(CalculatePowerOfTwo(n - m), (((2 * m) < n) ? // Calculate either the binomial coefficient of (n, m) or (n, n - m). // Mathematically, both should yield the same number, but the // calculation is optimized for a smaller second argument. CalculateBinomialCoefficient(n, m) : CalculateBinomialCoefficient(n, n - m))); } // Iterates recursively from i = ImageDimension-1 down to m (inclusive). static constexpr size_t CalculateSumOfNumberOfHypercubesOnBoundaryOfCube(const size_t i, const size_t m) noexcept { return assert(i >= m), CalculateSum(CalculateNumberOfHypercubesOnBoundaryOfCube(i, ImageDimension), ((i <= m) ? 0 : CalculateSumOfNumberOfHypercubesOnBoundaryOfCube(i - 1, m))); } /** Calculates the number of neighbors connected to the center pixel. */ static constexpr size_t CalculateNumberOfConnectedNeighbors(const size_t maximumCityblockDistance) noexcept { return (((maximumCityblockDistance == 0) || (ImageDimension == 0)) ? 0 : ((maximumCityblockDistance >= ImageDimension) ? (Math::UnsignedPower(3, ImageDimension) - 1) : CalculateSumOfNumberOfHypercubesOnBoundaryOfCube(ImageDimension - 1, (ImageDimension - maximumCityblockDistance)))); } /** Calculates the number of offsets needed for this shape. */ static constexpr size_t CalculateNumberOfOffsets(const size_t maximumCityblockDistance, const bool includeCenterPixel) noexcept { return (includeCenterPixel ? 1 : 0) + CalculateNumberOfConnectedNeighbors(maximumCityblockDistance); } }; /** Generates the offsets for a connected image neighborhood shape. */ template auto GenerateConnectedImageNeighborhoodShapeOffsets() noexcept { constexpr ConnectedImageNeighborhoodShape shape{ VMaximumCityblockDistance, VIncludeCenterPixel }; std::array, shape.GetNumberOfOffsets()> offsets; shape.FillOffsets(offsets.data()); return offsets; } } // namespace itk #endif