Skip to content

Commit

Permalink
ENH: Convenience overloads for ImageBase Transform member functions
Browse files Browse the repository at this point in the history
Added convenience overloads to the six `ImageBase` Transform member
functions, returning the result by value, instead of having an output
parameter:

    TransformPhysicalPointToIndex
    TransformPhysicalPointToContinuousIndex
    TransformContinuousIndexToPhysicalPoint
    TransformIndexToPhysicalPoint
    TransformLocalVectorToPhysicalVector
    TransformPhysicalVectorToLocalVector

These overloads allow users to have a more "functional" programming
style, or at least, place the transformation result in a `const` variable.

So instead of:

    Bar bar;
    image->TransformFooToBar(foo, bar);

It typically allows users to write:

    const auto bar = image->TransformFooToBar(foo);

Added a unit test to itkImageBaseGTest.
  • Loading branch information
N-Dekker authored and dzenanz committed May 16, 2019
1 parent 3f9e22e commit 65233e3
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 13 deletions.
133 changes: 120 additions & 13 deletions Modules/Core/Common/include/itkImageBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,15 +398,27 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
virtual void SetSpacing(const double spacing[VImageDimension]);
virtual void SetSpacing(const float spacing[VImageDimension]);

/** Get the index (discrete) of a voxel from a physical point.
/** Returns the index (discrete) of a voxel from a physical point.
* Floating point index results are rounded to integers
* Returns true if the resulting index is within the image, false otherwise
* \note This specific overload does not figure out whether or not
* the returned index is inside the image. Of course, the user can
* still test this afterwards by calling ImageRegion::IsInside(index):
\code
auto index = image->TransformPhysicalPointToIndex(point);
if (image->GetLargestPossibleRegion().IsInside(index)) // Et cetera...
\endcode
* Which is equivalent to the following code, which calls the other overload:
\code
IndexType index;
if (image->TransformPhysicalPointToIndex(point, index)) // Et cetera...
\endcode
* \sa Transform */
template< typename TCoordRep >
bool TransformPhysicalPointToIndex(
const Point< TCoordRep, VImageDimension > & point,
IndexType & index) const
IndexType TransformPhysicalPointToIndex(
const Point< TCoordRep, VImageDimension > & point) const
{
IndexType index;

for ( unsigned int i = 0; i < VImageDimension; i++ )
{
TCoordRep sum = NumericTraits< TCoordRep >::ZeroValue();
Expand All @@ -416,20 +428,45 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
}
index[i] = Math::RoundHalfIntegerUp< IndexValueType >(sum);
}
return index;
}

/** Get the index (discrete) of a voxel from a physical point.
* Floating point index results are rounded to integers
* Returns true if the resulting index is within the image, false otherwise
* \sa Transform */
template< typename TCoordRep >
bool TransformPhysicalPointToIndex(
const Point< TCoordRep, VImageDimension > & point,
IndexType & index) const
{
index = TransformPhysicalPointToIndex(point);

// Now, check to see if the index is within allowed bounds
const bool isInside = this->GetLargestPossibleRegion().IsInside(index);
return isInside;
}

/** \brief Get the continuous index from a physical point
*
* Returns true if the resulting index is within the image, false otherwise.

/** \brief Returns the continuous index from a physical point
* \note This specific overload does not figure out whether or not
* the returned index is inside the image. Of course, the user can
* still test this afterwards by calling ImageRegion::IsInside(index):
\code
auto index = image->TransformPhysicalPointToContinuousIndex<double>(point);
if (image->GetLargestPossibleRegion().IsInside(index)) // Et cetera...
\endcode
* Which is equivalent to the following code, which calls the other overload:
\code
itk::ContinuousIndex<double, ImageDimension> index;
if (image->TransformPhysicalPointToContinuousIndex(point, index)) // Et cetera...
\endcode
* \sa Transform */
template< typename TCoordRep, typename TIndexRep >
bool TransformPhysicalPointToContinuousIndex(
const Point< TCoordRep, VImageDimension > & point,
ContinuousIndex< TIndexRep, VImageDimension > & index) const
template< typename TIndexRep, typename TCoordRep >
ContinuousIndex< TIndexRep, VImageDimension > TransformPhysicalPointToContinuousIndex(
const Point< TCoordRep, VImageDimension > & point) const
{
ContinuousIndex< TIndexRep, VImageDimension > index;
Vector< SpacePrecisionType, VImageDimension > cvector;

for ( unsigned int k = 0; k < VImageDimension; ++k )
Expand All @@ -441,10 +478,22 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
{
index[i] = static_cast< TIndexRep >( cvector[i] );
}
return index;
}

/** \brief Get the continuous index from a physical point
*
* Returns true if the resulting index is within the image, false otherwise.
* \sa Transform */
template< typename TCoordRep, typename TIndexRep >
bool TransformPhysicalPointToContinuousIndex(
const Point< TCoordRep, VImageDimension > & point,
ContinuousIndex< TIndexRep, VImageDimension > & index) const
{
index = TransformPhysicalPointToContinuousIndex<TIndexRep>(point);

// Now, check to see if the index is within allowed bounds
const bool isInside = this->GetLargestPossibleRegion().IsInside(index);

return isInside;
}

Expand All @@ -468,6 +517,19 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
}
}

/** Returns a physical point (in the space which
* the origin and spacing information comes from)
* from a continuous index (in the index space)
* \sa Transform */
template< typename TCoordRep, typename TIndexRep >
Point< TCoordRep, VImageDimension > TransformContinuousIndexToPhysicalPoint(
const ContinuousIndex< TIndexRep, VImageDimension > & index) const
{
Point< TCoordRep, VImageDimension > point;
TransformContinuousIndexToPhysicalPoint(index, point);
return point;
}

/** Get a physical point (in the space which
* the origin and spacing information comes from)
* from a discrete index (in the index space)
Expand All @@ -488,6 +550,20 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
}
}

/** Returns a physical point (in the space which
* the origin and spacing information comes from)
* from a discrete index (in the index space)
*
* \sa Transform */
template< typename TCoordRep >
Point< TCoordRep, VImageDimension > TransformIndexToPhysicalPoint(
const IndexType & index) const
{
Point< TCoordRep, VImageDimension > point;
TransformIndexToPhysicalPoint(index, point);
return point;
}

/** Take a vector or covariant vector that has been computed in the
* coordinate system parallel to the image grid and rotate it by the
* direction cosines in order to get it in terms of the coordinate system of
Expand Down Expand Up @@ -523,6 +599,22 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
}
}

/** Take a vector or covariant vector that has been computed in the
* coordinate system parallel to the image grid and rotate it by the
* direction cosines in order to get it in terms of the coordinate system of
* the image acquisition device. Returns the resulting gradient.
* \sa Image
*/
template< typename TCoordRep >
FixedArray< TCoordRep, VImageDimension > TransformLocalVectorToPhysicalVector(
const FixedArray< TCoordRep, VImageDimension > & inputGradient) const
{
FixedArray< TCoordRep, VImageDimension > outputGradient;
TransformLocalVectorToPhysicalVector(inputGradient, outputGradient);
return outputGradient;
}


/** Take a vector or covariant vector that has been computed in terms of the
* coordinate system of the image acquisition device, and rotate it by the
* inverse direction cosines in order to get it in the coordinate system
Expand Down Expand Up @@ -556,6 +648,21 @@ class ITK_TEMPLATE_EXPORT ImageBase:public DataObject
}
}

/** Take a vector or covariant vector that has been computed in terms of the
* coordinate system of the image acquisition device, and rotate it by the
* inverse direction cosines in order to get it in the coordinate system
* parallel to the image grid. Returns the result.
*
*/
template< typename TCoordRep >
FixedArray< TCoordRep, VImageDimension > TransformPhysicalVectorToLocalVector(
const FixedArray< TCoordRep, VImageDimension > & inputGradient) const
{
FixedArray< TCoordRep, VImageDimension > outputGradient;
TransformPhysicalVectorToLocalVector(inputGradient, outputGradient);
return outputGradient;
}

/** Copy information from the specified data set. This method is
* part of the pipeline execution model. By default, a ProcessObject
* will copy meta-data from the first input to all of its
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 @@ -615,6 +615,7 @@ set(ITKCommonGTests
itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx
itkFixedArrayGTest.cxx
itkImageNeighborhoodOffsetsGTest.cxx
itkImageBaseGTest.cxx
itkImageBufferRangeGTest.cxx
itkIndexRangeGTest.cxx
itkPointGTest.cxx
Expand Down
114 changes: 114 additions & 0 deletions Modules/Core/Common/test/itkImageBaseGTest.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*=========================================================================
*
* 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 "itkImageBase.h"
#include "itkImage.h"

#include <gtest/gtest.h>
#include <type_traits> // For is_same.


namespace
{
template <typename T1, typename T2>
void Expect_same_type_and_equal_value(T1&& value1, T2&& value2)
{
static_assert(std::is_same<T1, T2>::value, "Expect the same type!");
EXPECT_EQ(value1, value2);
}


template <typename TImage>
void Expect_by_default_Transform_result_equals_default_constructed_value(
const typename TImage::SizeType& imageSize)
{
const auto ImageDimension = TImage::ImageDimension;
const auto image = TImage::New();
image->SetRegions(imageSize);
image->Allocate();

using ImageBaseType = itk::ImageBase<ImageDimension>;
using IndexType = typename ImageBaseType::IndexType;

using PointType = itk::Point<double, ImageDimension>;
using ContinuousIndexType = itk::ContinuousIndex<double, ImageDimension>;

using FloatArrayType = itk::FixedArray<float, ImageDimension>;
using DoubleArrayType = itk::FixedArray<double, ImageDimension>;

const ImageBaseType& imageBase = *image;

Expect_same_type_and_equal_value(
imageBase.TransformPhysicalPointToIndex(PointType()),
IndexType());

// For the three member functions TransformPhysicalPointToContinuousIndex,
// TransformContinuousIndexToPhysicalPoint, and TransformIndexToPhysicalPoint,
// the first template argument of the return type is expected to
// correspond with the first template argument of the member function.
Expect_same_type_and_equal_value(
imageBase.template TransformPhysicalPointToContinuousIndex<float>(PointType()),
itk::ContinuousIndex<float, ImageDimension>());
Expect_same_type_and_equal_value(
imageBase.template TransformPhysicalPointToContinuousIndex<double>(PointType()),
itk::ContinuousIndex<double, ImageDimension>());
Expect_same_type_and_equal_value(
imageBase.template TransformContinuousIndexToPhysicalPoint<float>(ContinuousIndexType()),
itk::Point<float, ImageDimension>());
Expect_same_type_and_equal_value(
imageBase.template TransformContinuousIndexToPhysicalPoint<double>(ContinuousIndexType()),
itk::Point<double, ImageDimension>());
Expect_same_type_and_equal_value(
imageBase.template TransformIndexToPhysicalPoint<float>(IndexType()),
itk::Point<float, ImageDimension>());
Expect_same_type_and_equal_value(
imageBase.template TransformIndexToPhysicalPoint<double>(IndexType()),
itk::Point<double, ImageDimension>());

// The two member functions TransformLocalVectorToPhysicalVector and
// TransformPhysicalVectorToLocalVector are expected to return an
// array of the same type as their first function argument.
Expect_same_type_and_equal_value(
imageBase.TransformLocalVectorToPhysicalVector(FloatArrayType()),
FloatArrayType());
Expect_same_type_and_equal_value(
imageBase.TransformLocalVectorToPhysicalVector(DoubleArrayType()),
DoubleArrayType());
Expect_same_type_and_equal_value(
imageBase.TransformPhysicalVectorToLocalVector(FloatArrayType()),
FloatArrayType());
Expect_same_type_and_equal_value(
imageBase.TransformPhysicalVectorToLocalVector(DoubleArrayType()),
DoubleArrayType());
}

} // end namespace


// Tests that "by default" (when the image has a default origin and a default
// direction matrix, and the function argument is just default-constructed), the
// return value of a single-parameter Transform member function is equal to a
// default-constructed value. This is tested for all six single-parameter
// Transform member functions of ImageBase.
TEST(ImageBase, ByDefaultTransformResultEqualsDefaultConstructedValue)
{
// Test both 2D and 3D, for different pixel types and sizes:
Expect_by_default_Transform_result_equals_default_constructed_value<itk::Image<double>>({ {2, 2} });
Expect_by_default_Transform_result_equals_default_constructed_value<itk::Image<unsigned char, 3>>({ {2, 3, 4} });
}

0 comments on commit 65233e3

Please sign in to comment.