Skip to content

Commit

Permalink
ENH: Added policy for constant NeighborhoodRange values outside image
Browse files Browse the repository at this point in the history
Added ConstantBoundaryImageNeighborhoodPixelAccessPolicy - a policy
to get a specified constant value from ShapedImageNeighborhoodRange
when a pixel value outside the image is queried. Equivalent to
itk::ConstantBoundaryCondition with itk::ConstNeighborhoodIterator.

Adapted ShapedImageNeighborhoodRange to allow the user to specify an
optional pixel access parameter. This parameter allows specifying the
constant value for this policy.

Change-Id: I5a1ae9f918a31d03a4dd3d84ceedd4385ab4ec7d
  • Loading branch information
N-Dekker authored and hjmjohnson committed Nov 5, 2018
1 parent 0bfb0b5 commit def5d12
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*=========================================================================
*
* 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 <typename TImage>
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<ImageDimension>;
using OffsetType = Offset<ImageDimension>;
using ImageSizeType = Size<ImageDimension>;
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<ImageSizeValueType>(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:
// 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
71 changes: 52 additions & 19 deletions Modules/Core/Common/include/itkShapedImageNeighborhoodRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ namespace itk
namespace Experimental
{

/**
* \class EmptyPixelAccessParameter
* Empty struct, used by ShapedImageNeighborhoodRange to denote that
* there is no pixel access parameter specified.
*
* \ingroup ImageIterators
* \ingroup ITKCommon
*/
struct EmptyPixelAccessParameter {};

/**
* \class ShapedImageNeighborhoodRange
* Modern C++11 range to iterate over a neighborhood of pixels.
Expand Down Expand Up @@ -90,7 +100,8 @@ namespace Experimental
* \ingroup ITKCommon
*/
template<typename TImage,
typename TImageNeighborhoodPixelAccessPolicy = ZeroFluxNeumannImageNeighborhoodPixelAccessPolicy<TImage>>
typename TImageNeighborhoodPixelAccessPolicy = ZeroFluxNeumannImageNeighborhoodPixelAccessPolicy<TImage>,
typename TOptionalPixelAccessParameter = EmptyPixelAccessParameter>
class ShapedImageNeighborhoodRange final
{
private:
Expand Down Expand Up @@ -139,13 +150,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}
{
}

Expand Down Expand Up @@ -192,13 +200,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}
{
}

Expand Down Expand Up @@ -284,6 +289,8 @@ class ShapedImageNeighborhoodRange final
// The accessor of the image.
NeighborhoodAccessorFunctorType m_NeighborhoodAccessor;

TOptionalPixelAccessParameter m_OptionalPixelAccessParameter;

// The pixel coordinates of the location of the neighborhood.
// May be outside the image!
IndexType m_Location = { {} };
Expand All @@ -297,6 +304,7 @@ class ShapedImageNeighborhoodRange final
const ImageSizeType& imageSize,
const OffsetType& offsetTable,
const NeighborhoodAccessorFunctorType& neighborhoodAccessor,
const TOptionalPixelAccessParameter optionalPixelAccessParameter,
const IndexType& location,
const OffsetType* const offset) ITK_NOEXCEPT
:
Expand All @@ -306,12 +314,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 <typename TPixelAccessParameter>
TImageNeighborhoodPixelAccessPolicy CreatePixelAccessPolicy(const TPixelAccessParameter pixelAccessParameter) const
{
static_assert(std::is_same< TPixelAccessParameter, TOptionalPixelAccessParameter>::value,
"This helper function should only be used for TOptionalPixelAccessParameter!");
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;
Expand Down Expand Up @@ -347,7 +372,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));
}


Expand Down Expand Up @@ -535,6 +560,8 @@ class ShapedImageNeighborhoodRange final
// The number of neighborhood pixels.
const std::size_t m_NumberOfNeighborhoodPixels;

const TOptionalPixelAccessParameter m_OptionalPixelAccessParameter;

public:
using const_iterator = QualifiedIterator<true>;
using iterator = QualifiedIterator<false>;
Expand All @@ -549,7 +576,8 @@ class ShapedImageNeighborhoodRange final
ImageType& image,
const IndexType& location,
const OffsetType* const shapeOffsets,
const std::size_t numberOfNeigborhoodPixels)
const std::size_t numberOfNeigborhoodPixels,
const TOptionalPixelAccessParameter optionalPixelAccessParameter = {})
:
m_ImageBufferPointer{image.ImageType::GetBufferPointer()},
// Note: Use parentheses instead of curly braces to initialize data members,
Expand All @@ -559,7 +587,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();
Expand All @@ -580,13 +609,17 @@ class ShapedImageNeighborhoodRange final
ShapedImageNeighborhoodRange(
ImageType& image,
const IndexType& location,
TContainerOfOffsets&& shapeOffsets)
TContainerOfOffsets&& shapeOffsets,
const TOptionalPixelAccessParameter optionalPixelAccessParameter = {})
:
ShapedImageNeighborhoodRange{
ShapedImageNeighborhoodRange
{
image,
location,
shapeOffsets.data(),
shapeOffsets.size()}
shapeOffsets.size(),
optionalPixelAccessParameter
}
{
static_assert(!std::is_rvalue_reference<decltype(shapeOffsets)>::value,
"The container of offsets should not be a temporary (rvalue) object!");
Expand All @@ -596,14 +629,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.
Expand Down
1 change: 1 addition & 0 deletions Modules/Core/Common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ set(ITKCommonGTests
itkAggregateTypesTest.cxx
itkBuildInformationTest.cxx
itkConnectedImageNeighborhoodShapeGTest.cxx
itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx
itkImageNeighborhoodOffsetsGTest.cxx
itkShapedImageNeighborhoodRangeGTest.cxx
itkSmartPointerGTest.cxx
Expand Down
Loading

0 comments on commit def5d12

Please sign in to comment.