Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions Modules/Core/Transform/include/itkTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "vnl/vnl_vector_fixed.h"
#include "vnl/vnl_matrix_fixed.h"
#include "itkMatrix.h"
#include "itkImageBase.h"

namespace itk
{
Expand Down Expand Up @@ -542,22 +543,43 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate<TParametersVa
*
* Updates image metadata (origin, spacing, direction cosines matrix) in place.
*
* Only available when input and output space are of the same dimension.
* In C++, only available when input and output space are of the same dimension.
* Only works properly for linear transforms.
*
* The image parameter may be either a SmartPointer or a raw pointer.
* */
/** @ITKStartGrouping */
#if defined(ITK_WRAPPING_PARSER) || defined(SWIG_VERSION)
// This variant is only available in the wrapped languages.
// Make it unavailable in C++ to avoid being called when the versions above do not match.
void
ApplyToImageMetadata(ImageBase<VInputDimension> * image) const
Comment thread
dzenanz marked this conversation as resolved.
{
if (VInputDimension != VOutputDimension)
{
itkExceptionMacro("ApplyToImageMetadata is only usable with transforms with equal input and output dimensions."
" This class is: "
<< this->GetNameOfClass());
}
this->ApplyToImageMetadataInternal(image);
}
#else
// These two variants are only available in C++ (assuming same in/out dimensions).
template <typename TImage>
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
ApplyToImageMetadata(TImage * image) const;
ApplyToImageMetadata(TImage * image) const
{
this->ApplyToImageMetadataInternal(image);
}
template <typename TImage>
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
ApplyToImageMetadata(SmartPointer<TImage> image) const
{
this->ApplyToImageMetadata(image.GetPointer()); // Delegate to the raw pointer signature
this->ApplyToImageMetadataInternal(image.GetPointer());
}
#endif
/** @ITKEndGrouping */

protected:
/**
* Clone the current transform.
Expand Down Expand Up @@ -591,6 +613,10 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate<TParametersVa
}

private:
/** Implementation of ApplyToImageMetadata. */
void
ApplyToImageMetadataInternal(ImageBase<VInputDimension> * image) const;

template <typename TType>
static std::string
GetTransformTypeAsString(TType *)
Expand Down
78 changes: 43 additions & 35 deletions Modules/Core/Transform/include/itkTransform.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -456,52 +456,60 @@ Transform<TParametersValueType, VInputDimension, VOutputDimension>::TransformSym
}

template <typename TParametersValueType, unsigned int VInputDimension, unsigned int VOutputDimension>
template <typename TImage>
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
Transform<TParametersValueType, VInputDimension, VOutputDimension>::ApplyToImageMetadata(TImage * image) const
void
Transform<TParametersValueType, VInputDimension, VOutputDimension>::ApplyToImageMetadataInternal(
ImageBase<VInputDimension> * image) const
{
using ImageType = TImage;

if (!this->IsLinear())
if constexpr (VInputDimension == VOutputDimension)
{
itkWarningMacro("ApplyToImageMetadata was invoked with non-linear transform of type: "
<< this->GetNameOfClass() << ". This might produce unexpected results.");
}
using ImageType = ImageBase<VInputDimension>;

const typename Self::Pointer inverse = this->GetInverseTransform();
if (inverse.IsNull())
{
itkExceptionMacro(
"ApplyToImageMetadata was invoked with non-invertible transform of type: " << this->GetNameOfClass());
}

// transform origin
typename ImageType::PointType origin = image->GetOrigin();
origin = inverse->TransformPoint(origin);
image->SetOrigin(origin);
if (!this->IsLinear())
{
itkWarningMacro("ApplyToImageMetadata was invoked with non-linear transform of type: "
<< this->GetNameOfClass() << ". This might produce unexpected results.");
}

typename ImageType::SpacingType spacing = image->GetSpacing();
typename ImageType::DirectionType direction = image->GetDirection();
// transform direction cosines and compute new spacing
for (unsigned int i = 0; i < ImageType::ImageDimension; ++i)
{
Vector<typename Self::ParametersValueType, ImageType::ImageDimension> dirVector;
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
const typename Self::Pointer inverse = this->GetInverseTransform();
if (inverse.IsNull())
{
dirVector[k] = direction[k][i];
itkExceptionMacro(
"ApplyToImageMetadata was invoked with non-invertible transform of type: " << this->GetNameOfClass());
}

dirVector *= spacing[i];
dirVector = inverse->TransformVector(dirVector);
spacing[i] = dirVector.Normalize();
// transform origin
typename ImageType::PointType origin = image->GetOrigin();
origin = inverse->TransformPoint(origin);
image->SetOrigin(origin);

for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
typename ImageType::SpacingType spacing = image->GetSpacing();
typename ImageType::DirectionType direction = image->GetDirection();
// transform direction cosines and compute new spacing
for (unsigned int i = 0; i < ImageType::ImageDimension; ++i)
{
direction[k][i] = dirVector[k];
Vector<typename Self::ParametersValueType, ImageType::ImageDimension> dirVector;
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
{
dirVector[k] = direction[k][i];
}

dirVector *= spacing[i];
dirVector = inverse->TransformVector(dirVector);
spacing[i] = dirVector.Normalize();

for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
{
direction[k][i] = dirVector[k];
}
}
image->SetDirection(direction);
image->SetSpacing(spacing);
}
else
{
itkExceptionMacro("ApplyToImageMetadata was invoked with transform of type: "
<< this->GetNameOfClass() << " that has different input and output dimensions.");
}
image->SetDirection(direction);
image->SetSpacing(spacing);
}


Expand Down
9 changes: 9 additions & 0 deletions Modules/Core/Transform/wrapping/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ if(ITK_WRAP_PYTHON)
${ITK_EXAMPLE_DATA_ROOT}/DiagonalLines.png
)

list(FIND ITK_WRAP_IMAGE_DIMS 3 wrap_3_index)
if(wrap_3_index GREATER -1)
itk_python_add_test(
NAME itkApplyToImageMetadataTest
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/itkApplyToImageMetadataTest.py
)
endif()

itk_python_expression_add_test(
NAME PythonInstantiateBSplineTransform2D2D
EXPRESSION "t = itk.BSplineTransform[itk.D, 2, 2].New()"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ==========================================================================
#
# 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.
#
# ==========================================================================

import itk
import numpy as np

Dimension = 3
PixelType = itk.F

# Create a simple image with known origin, spacing and direction.
ImageType = itk.Image[PixelType, Dimension]
image = ImageType.New()
size = itk.Size[Dimension]()
size.Fill(4)
region = itk.ImageRegion[Dimension]()
region.SetSize(size)
image.SetRegions(region)
image.SetOrigin([1.0, 2.0, 3.0])
image.SetSpacing([1.0, 1.0, 1.0])
image.Allocate()

# Build a translation transform, which is linear and invertible.
TransformType = itk.TranslationTransform[itk.D, Dimension]
transform = TransformType.New()
translation = itk.Vector[itk.D, Dimension]()
translation[0] = 10.0
translation[1] = 20.0
translation[2] = 30.0
transform.Translate(translation)

# Apply the transform to the image metadata, updating origin/spacing/direction
# in place, without resampling the pixel data.
transform.ApplyToImageMetadata(image)

expected_origin = transform.GetInverseTransform().TransformPoint([1.0, 2.0, 3.0])

for i in range(Dimension):
assert np.isclose(image.GetOrigin()[i], expected_origin[i])

print("ApplyToImageMetadata Test Done")
Loading