diff --git a/Modules/Core/Common/include/itkConstantBoundaryImageNeighborhoodPixelAccessPolicy.h b/Modules/Core/Common/include/itkConstantBoundaryImageNeighborhoodPixelAccessPolicy.h new file mode 100644 index 00000000000..620492b6f38 --- /dev/null +++ b/Modules/Core/Common/include/itkConstantBoundaryImageNeighborhoodPixelAccessPolicy.h @@ -0,0 +1,163 @@ +/*========================================================================= +* +* 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 itkConstantBoundaryImageNeighborhoodPixelAccessPolicy_h +#define itkConstantBoundaryImageNeighborhoodPixelAccessPolicy_h + +#include "itkIndex.h" +#include "itkOffset.h" +#include "itkSize.h" + +namespace itk +{ +namespace Experimental +{ + +/** + * \class ConstantBoundaryImageNeighborhoodPixelAccessPolicy + * ImageNeighborhoodPixelAccessPolicy class for ShapedImageNeighborhoodRange. + * Allows getting and setting the value of a pixel, located in a specified + * neighborhood location, at a specified offset. Uses a constant as value + * for pixels outside the image border. + * + * \see ShapedNeighborhoodIterator + * \see ConstantBoundaryCondition + * \ingroup ImageIterators + * \ingroup ITKCommon + */ +template +class ConstantBoundaryImageNeighborhoodPixelAccessPolicy final +{ +private: + using NeighborhoodAccessorFunctorType = typename TImage::NeighborhoodAccessorFunctorType; + using PixelType = typename TImage::PixelType; + using InternalPixelType = typename TImage::InternalPixelType; + + using ImageDimensionType = typename TImage::ImageDimensionType; + static constexpr ImageDimensionType ImageDimension = TImage::ImageDimension; + + using IndexType = Index; + using OffsetType = Offset; + using ImageSizeType = Size; + using ImageSizeValueType = SizeValueType; + + // Index value to the image buffer, indexing the current pixel. -1 is used to indicate out-of-bounds. + const IndexValueType m_PixelIndexValue; + + // A reference to the accessor of the image. + const NeighborhoodAccessorFunctorType& m_NeighborhoodAccessor; + + // The constant whose value is returned a pixel value outside the image is queried. + const PixelType m_Constant; + + + // Private helper function. Tells whether the pixel at 'pixelIndex' is inside the image. + static bool IsInside( + const IndexType& pixelIndex, + const ImageSizeType& imageSize) ITK_NOEXCEPT + { + bool result = true; + + for (ImageDimensionType i = 0; i < ImageDimension; ++i) + { + const IndexValueType indexValue = pixelIndex[i]; + + // Note: Do not 'quickly' break or return out of the for-loop when the + // result is false! For performance reasons (loop unrolling, etc.) it + // appears preferable to complete the for-loop iteration in this case! + result = result && + (indexValue >= 0) && + (static_cast(indexValue) < imageSize[i]); + } + return result; + } + + + // Private helper function. Calculates and returns the index value of the + // current pixel within the image buffer. + static IndexValueType CalculatePixelIndexValue( + const OffsetType& offsetTable, + const IndexType& pixelIndex) ITK_NOEXCEPT + { + IndexValueType result = 0; + + for (ImageDimensionType i = 0; i < ImageDimension; ++i) + { + result += pixelIndex[i] * offsetTable[i]; + } + return result; + } + +public: + /** This type is necessary to tell the ShapedImageNeighborhoodRange that the + * constructor accepts a pixel value as (optional) extra parameter. */ + using PixelAccessParameterType = PixelType; + + // Deleted member functions: + ConstantBoundaryImageNeighborhoodPixelAccessPolicy() = delete; + ConstantBoundaryImageNeighborhoodPixelAccessPolicy& operator=(const ConstantBoundaryImageNeighborhoodPixelAccessPolicy&) = delete; + + // Explicitly-defaulted functions: + ~ConstantBoundaryImageNeighborhoodPixelAccessPolicy() = default; + ConstantBoundaryImageNeighborhoodPixelAccessPolicy( + const ConstantBoundaryImageNeighborhoodPixelAccessPolicy&) = default; + + /** Constructor called directly by the pixel proxy of + * ShapedImageNeighborhoodRange. */ + ConstantBoundaryImageNeighborhoodPixelAccessPolicy( + const ImageSizeType& imageSize, + const OffsetType& offsetTable, + const NeighborhoodAccessorFunctorType& neighborhoodAccessor, + const IndexType& pixelIndex, + const PixelType constant = {}) ITK_NOEXCEPT + : + m_PixelIndexValue + { + IsInside(pixelIndex, imageSize) ? CalculatePixelIndexValue(offsetTable, pixelIndex) : -1 + }, + m_NeighborhoodAccessor(neighborhoodAccessor), + m_Constant{constant} + { + } + + + /** Retrieves the pixel value from the image buffer, at the current + * index. When the index is out of bounds, it returns the constant + * value specified during construction. */ + PixelType GetPixelValue(const InternalPixelType* const imageBufferPointer) const ITK_NOEXCEPT + { + return (m_PixelIndexValue < 0) ? + m_Constant : + m_NeighborhoodAccessor.Get(imageBufferPointer + m_PixelIndexValue); + } + + /** Sets the value of the image buffer at the current index value to the + * specified value. */ + void SetPixelValue(InternalPixelType* const imageBufferPointer, const PixelType& pixelValue) const ITK_NOEXCEPT + { + if (m_PixelIndexValue >= 0) + { + m_NeighborhoodAccessor.Set(imageBufferPointer + m_PixelIndexValue, pixelValue); + } + } +}; + +} // namespace Experimental +} // namespace itk + +#endif diff --git a/Modules/Core/Common/include/itkShapedImageNeighborhoodRange.h b/Modules/Core/Common/include/itkShapedImageNeighborhoodRange.h index 76f2d4b49d0..50f07758f2b 100644 --- a/Modules/Core/Common/include/itkShapedImageNeighborhoodRange.h +++ b/Modules/Core/Common/include/itkShapedImageNeighborhoodRange.h @@ -94,6 +94,47 @@ template + static int Test(typename T::PixelAccessParameterType*); + + template + static void Test(...); + + public: + // This constant tells whether the policy has a PixelAccessParameterType: + static constexpr bool HasPixelAccessParameterType = ! std::is_same< + decltype(Test(nullptr)), + decltype(Test())>::value; + }; + + + template + struct OptionalPixelAccessParameter + { + using Type = typename TPolicy::PixelAccessParameterType; + }; + + // Specialization for when the policy does not have PixelAccessParameterType. + template + struct OptionalPixelAccessParameter + { + using Type = EmptyPixelAccessParameter; + }; + + using ImageType = TImage; using ImageDimensionType = typename TImage::ImageDimensionType; using ImageSizeType = typename TImage::SizeType; @@ -105,6 +146,9 @@ class ShapedImageNeighborhoodRange final using IndexType = typename TImage::IndexType; using IndexValueType = typename TImage::IndexValueType; using OffsetType = Offset; + using OptionalPixelAccessParameterType = + typename OptionalPixelAccessParameter::Type; + // PixelProxy: internal class that aims to act like a reference to a pixel: // It acts either like 'PixelType &' or like 'const PixelType &', depending @@ -139,13 +183,10 @@ class ShapedImageNeighborhoodRange final // Constructor, called directly by operator*() of the iterator class. PixelProxy( const InternalPixelType* const imageBufferPointer, - const ImageSizeType& imageSize, - const OffsetType& offsetTable, - const NeighborhoodAccessorFunctorType& neighborhoodAccessor, - const IndexType& pixelIndex) ITK_NOEXCEPT + const TImageNeighborhoodPixelAccessPolicy& pixelAccessPolicy) ITK_NOEXCEPT : m_ImageBufferPointer{imageBufferPointer}, - m_PixelAccessPolicy{ imageSize, offsetTable, neighborhoodAccessor, pixelIndex } + m_PixelAccessPolicy{pixelAccessPolicy} { } @@ -192,13 +233,10 @@ class ShapedImageNeighborhoodRange final // Constructor, called directly by operator*() of the iterator class. PixelProxy( InternalPixelType* const imageBufferPointer, - const ImageSizeType& imageSize, - const OffsetType& offsetTable, - const NeighborhoodAccessorFunctorType& neighborhoodAccessor, - const IndexType& pixelIndex) ITK_NOEXCEPT + const TImageNeighborhoodPixelAccessPolicy& pixelAccessPolicy) ITK_NOEXCEPT : m_ImageBufferPointer{imageBufferPointer}, - m_PixelAccessPolicy{ imageSize, offsetTable, neighborhoodAccessor, pixelIndex } + m_PixelAccessPolicy{pixelAccessPolicy} { } @@ -284,6 +322,8 @@ class ShapedImageNeighborhoodRange final // The accessor of the image. NeighborhoodAccessorFunctorType m_NeighborhoodAccessor; + OptionalPixelAccessParameterType m_OptionalPixelAccessParameter; + // The pixel coordinates of the location of the neighborhood. // May be outside the image! IndexType m_Location = { {} }; @@ -297,6 +337,7 @@ class ShapedImageNeighborhoodRange final const ImageSizeType& imageSize, const OffsetType& offsetTable, const NeighborhoodAccessorFunctorType& neighborhoodAccessor, + const OptionalPixelAccessParameterType optionalPixelAccessParameter, const IndexType& location, const OffsetType* const offset) ITK_NOEXCEPT : @@ -306,12 +347,29 @@ class ShapedImageNeighborhoodRange final m_ImageSize(imageSize), m_OffsetTable(offsetTable), m_NeighborhoodAccessor(neighborhoodAccessor), + m_OptionalPixelAccessParameter(optionalPixelAccessParameter), m_Location(location), m_CurrentOffset{offset} { assert(m_ImageBufferPointer != nullptr); } + + TImageNeighborhoodPixelAccessPolicy CreatePixelAccessPolicy(EmptyPixelAccessParameter) const + { + return TImageNeighborhoodPixelAccessPolicy{ m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_Location + *m_CurrentOffset }; + } + + template + TImageNeighborhoodPixelAccessPolicy CreatePixelAccessPolicy(const TPixelAccessParameter pixelAccessParameter) const + { + static_assert(std::is_same< TPixelAccessParameter, OptionalPixelAccessParameterType>::value, + "This helper function should only be used for OptionalPixelAccessParameterType!"); + static_assert(!std::is_same< TPixelAccessParameter, EmptyPixelAccessParameter>::value, + "EmptyPixelAccessParameter indicates that there is no pixel access parameter specified!"); + return TImageNeighborhoodPixelAccessPolicy{ m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_Location + *m_CurrentOffset, pixelAccessParameter }; + } + public: // Types conforming the iterator requirements of the C++ standard library: using difference_type = std::ptrdiff_t; @@ -347,7 +405,7 @@ class ShapedImageNeighborhoodRange final /** Returns a reference to the current pixel. */ reference operator*() const ITK_NOEXCEPT { - return reference{m_ImageBufferPointer, m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_Location + *m_CurrentOffset}; + return reference{m_ImageBufferPointer, CreatePixelAccessPolicy(m_OptionalPixelAccessParameter)}; } @@ -535,6 +593,8 @@ class ShapedImageNeighborhoodRange final // The number of neighborhood pixels. const std::size_t m_NumberOfNeighborhoodPixels; + const OptionalPixelAccessParameterType m_OptionalPixelAccessParameter; + public: using const_iterator = QualifiedIterator; using iterator = QualifiedIterator; @@ -552,7 +612,8 @@ class ShapedImageNeighborhoodRange final ImageType& image, const IndexType& location, const OffsetType* const shapeOffsets, - const std::size_t numberOfNeigborhoodPixels) + const std::size_t numberOfNeigborhoodPixels, + const OptionalPixelAccessParameterType optionalPixelAccessParameter = {}) : m_ImageBufferPointer{image.ImageType::GetBufferPointer()}, // Note: Use parentheses instead of curly braces to initialize data members, @@ -562,7 +623,8 @@ class ShapedImageNeighborhoodRange final m_NeighborhoodAccessor(image.GetNeighborhoodAccessor()), m_Location(location), m_ShapeOffsets{ shapeOffsets }, - m_NumberOfNeighborhoodPixels{ numberOfNeigborhoodPixels } + m_NumberOfNeighborhoodPixels{ numberOfNeigborhoodPixels }, + m_OptionalPixelAccessParameter(optionalPixelAccessParameter) { assert(m_ImageBufferPointer != nullptr); const OffsetValueType* const offsetTable = image.GetOffsetTable(); @@ -586,13 +648,17 @@ class ShapedImageNeighborhoodRange final ShapedImageNeighborhoodRange( ImageType& image, const IndexType& location, - const TContainerOfOffsets& shapeOffsets) + const TContainerOfOffsets& shapeOffsets, + const OptionalPixelAccessParameterType optionalPixelAccessParameter = {}) : - ShapedImageNeighborhoodRange{ + ShapedImageNeighborhoodRange + { image, location, shapeOffsets.data(), - shapeOffsets.size()} + shapeOffsets.size(), + optionalPixelAccessParameter + } { } @@ -600,14 +666,14 @@ class ShapedImageNeighborhoodRange final iterator begin() const ITK_NOEXCEPT { assert(m_ImageBufferPointer != nullptr); - return iterator(m_ImageBufferPointer, m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_Location, m_ShapeOffsets); + return iterator(m_ImageBufferPointer, m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_OptionalPixelAccessParameter, m_Location, m_ShapeOffsets); } /** Returns an 'end iterator' for this range. */ iterator end() const ITK_NOEXCEPT { assert(m_ImageBufferPointer != nullptr); - return iterator(m_ImageBufferPointer, m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_Location, m_ShapeOffsets + m_NumberOfNeighborhoodPixels); + return iterator(m_ImageBufferPointer, m_ImageSize, m_OffsetTable, m_NeighborhoodAccessor, m_OptionalPixelAccessParameter, m_Location, m_ShapeOffsets + m_NumberOfNeighborhoodPixels); } /** Returns a const iterator to the first neighborhood pixel. diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 90748c43a00..5e1d0059f1d 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -627,6 +627,7 @@ set(ITKCommonGTests itkAggregateTypesTest.cxx itkBuildInformationTest.cxx itkConnectedImageNeighborhoodShapeGTest.cxx + itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx itkImageNeighborhoodOffsetsGTest.cxx itkIndexRangeGTest.cxx itkShapedImageNeighborhoodRangeGTest.cxx diff --git a/Modules/Core/Common/test/itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx b/Modules/Core/Common/test/itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx new file mode 100644 index 00000000000..d2370461f15 --- /dev/null +++ b/Modules/Core/Common/test/itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx @@ -0,0 +1,164 @@ +/*========================================================================= + * + * 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 "itkConstantBoundaryImageNeighborhoodPixelAccessPolicy.h" + +#include "itkShapedImageNeighborhoodRange.h" +#include "itkConstantBoundaryCondition.h" +#include "itkConstNeighborhoodIterator.h" +#include "itkImage.h" +#include "itkImageNeighborhoodOffsets.h" +#include "itkVectorImage.h" + +#include + +// Test template instantiations for various template arguments: +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; +template class itk::Experimental::ConstantBoundaryImageNeighborhoodPixelAccessPolicy>; + +namespace +{ + template + typename TImage::Pointer CreateImage(const unsigned sizeX, const unsigned sizeY) + { + const auto image = TImage::New(); + const typename TImage::SizeType imageSize = { { sizeX , sizeY } }; + image->SetRegions(imageSize); + image->Allocate(); + return image; + } + + + // Creates a test image, filled with a sequence of natural numbers, 1, 2, 3, ..., N. + template + typename TImage::Pointer CreateImageFilledWithSequenceOfNaturalNumbers(const unsigned sizeX, const unsigned sizeY) + { + using PixelType = typename TImage::PixelType; + const auto image = CreateImage(sizeX, sizeY); + + const unsigned numberOfPixels = sizeX * sizeY; + + PixelType* const bufferPointer = image->GetBufferPointer(); + + for (unsigned i = 0; i < numberOfPixels; ++i) + { + bufferPointer[i] = static_cast(i + 1); + } + return image; + } +} + + +// When no constant is specified (which is the default), an attempt to retrieve +// a pixel outside the image boundaries should yield zero, for a scalar pixel type. +TEST(ConstantBoundaryImageNeighborhoodPixelAccessPolicy, YieldsZeroOutsideImageByDefault) +{ + using PixelType = int; + using ImageType = itk::Image; + using RangeType = itk::Experimental::ShapedImageNeighborhoodRange >; + + enum { sizeX = 9, sizeY = 11 }; + const auto image = CreateImage(sizeX, sizeY); + image->FillBuffer(42); + + const ImageType::IndexType locationOutsideImage{ {-1, -1} }; + const itk::Size radius = { {} }; + const std::vector> offsets = + itk::Experimental::GenerateRectangularImageNeighborhoodOffsets(radius); + const RangeType range{ *image, locationOutsideImage, offsets }; + + for (const PixelType pixel : range) + { + EXPECT_EQ(pixel, 0); + } +} + + +// When the constant is added as extra argument of a neighborhood range, an attempt to retrieve +// a pixel outside the image boundaries should yield this specific constant value. +TEST(ConstantBoundaryImageNeighborhoodPixelAccessPolicy, YieldsSpecifiedConstantOutsideImage) +{ + using PixelType = int; + using ImageType = itk::Image; + using RangeType = itk::Experimental::ShapedImageNeighborhoodRange>; + + enum { sizeX = 9, sizeY = 11 }; + const auto image = CreateImage(sizeX, sizeY); + image->FillBuffer(42); + + const ImageType::IndexType locationOutsideImage{ {-1, -1} }; + const itk::Size radius = { {} }; + const std::vector> offsets = + itk::Experimental::GenerateRectangularImageNeighborhoodOffsets(radius); + + for (PixelType constantValue = -1; constantValue <= 2; ++constantValue) + { + const RangeType range{ *image, locationOutsideImage, offsets, constantValue }; + + for (const PixelType pixel : range) + { + EXPECT_EQ(pixel, constantValue); + } + } +} + +// With this policy, ShapedImageNeighborhoodRange should yield the same pixel +// values as itk::ConstNeighborhoodIterator with itk::ConstantBoundaryCondition. +TEST(ConstantBoundaryImageNeighborhoodPixelAccessPolicy, YieldsSameValuesAsConstantBoundaryCondition) +{ + using PixelType = int; + using ImageType = itk::Image; + using RangeType = itk::Experimental::ShapedImageNeighborhoodRange>; + + enum { sizeX = 3, sizeY = 4 }; + const auto image = CreateImageFilledWithSequenceOfNaturalNumbers(sizeX, sizeY); + + const ImageType::IndexType location{ {} }; + const itk::Size radius = { { 1, 2 } }; + const std::vector> offsets = + itk::Experimental::GenerateRectangularImageNeighborhoodOffsets(radius); + + for (PixelType constantValue = -1; constantValue <= 2; ++constantValue) + { + const RangeType range{ *image, location, offsets, constantValue }; + + itk::ConstantBoundaryCondition boundaryCondition; + boundaryCondition.SetConstant(constantValue); + itk::ConstNeighborhoodIterator> + constNeighborhoodIterator(radius, image, image->GetRequestedRegion()); + constNeighborhoodIterator.SetLocation(location); + constNeighborhoodIterator.SetBoundaryCondition(boundaryCondition); + + itk::SizeValueType i = 0; + + for (const PixelType pixel : range) + { + EXPECT_EQ(pixel, constNeighborhoodIterator.GetPixel(i)); + ++i; + } + } +}