Skip to content

Commit 32b44f6

Browse files
committed
ENH: make ApplyToImageMetadata available in Python wrapping
1 parent 4275a06 commit 32b44f6

4 files changed

Lines changed: 128 additions & 38 deletions

File tree

Modules/Core/Transform/include/itkTransform.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "vnl/vnl_vector_fixed.h"
2828
#include "vnl/vnl_matrix_fixed.h"
2929
#include "itkMatrix.h"
30+
#include "itkImageBase.h"
3031

3132
namespace itk
3233
{
@@ -542,22 +543,37 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate<TParametersVa
542543
*
543544
* Updates image metadata (origin, spacing, direction cosines matrix) in place.
544545
*
545-
* Only available when input and output space are of the same dimension.
546+
* In C++, only available when input and output space are of the same dimension.
546547
* Only works properly for linear transforms.
547548
*
548549
* The image parameter may be either a SmartPointer or a raw pointer.
549550
* */
550551
/** @ITKStartGrouping */
551552
template <typename TImage>
552553
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
553-
ApplyToImageMetadata(TImage * image) const;
554+
ApplyToImageMetadata(TImage * image) const
555+
{
556+
this->ApplyToImageMetadataInternal(image);
557+
}
554558
template <typename TImage>
555559
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
556560
ApplyToImageMetadata(SmartPointer<TImage> image) const
557561
{
558-
this->ApplyToImageMetadata(image.GetPointer()); // Delegate to the raw pointer signature
562+
this->ApplyToImageMetadataInternal(image.GetPointer());
563+
}
564+
void
565+
ApplyToImageMetadata(ImageBase<VInputDimension> * image) const
566+
{
567+
if (VInputDimension != VOutputDimension)
568+
{
569+
itkExceptionMacro("ApplyToImageMetadata is only usable with transforms with equal input and output dimensions."
570+
" This class is: "
571+
<< this->GetNameOfClass());
572+
}
573+
this->ApplyToImageMetadataInternal(image);
559574
}
560575
/** @ITKEndGrouping */
576+
561577
protected:
562578
/**
563579
* Clone the current transform.
@@ -591,6 +607,10 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate<TParametersVa
591607
}
592608

593609
private:
610+
/** Implementation of ApplyToImageMetadata. */
611+
void
612+
ApplyToImageMetadataInternal(ImageBase<VInputDimension> * image) const;
613+
594614
template <typename TType>
595615
static std::string
596616
GetTransformTypeAsString(TType *)

Modules/Core/Transform/include/itkTransform.hxx

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -456,52 +456,61 @@ Transform<TParametersValueType, VInputDimension, VOutputDimension>::TransformSym
456456
}
457457

458458
template <typename TParametersValueType, unsigned int VInputDimension, unsigned int VOutputDimension>
459-
template <typename TImage>
460-
std::enable_if_t<TImage::ImageDimension == VInputDimension && TImage::ImageDimension == VOutputDimension, void>
461-
Transform<TParametersValueType, VInputDimension, VOutputDimension>::ApplyToImageMetadata(TImage * image) const
459+
void
460+
Transform<TParametersValueType, VInputDimension, VOutputDimension>::ApplyToImageMetadataInternal(
461+
ImageBase<VInputDimension> * image) const
462462
{
463-
using ImageType = TImage;
464-
465-
if (!this->IsLinear())
463+
if constexpr (VInputDimension == VOutputDimension)
466464
{
467-
itkWarningMacro("ApplyToImageMetadata was invoked with non-linear transform of type: "
468-
<< this->GetNameOfClass() << ". This might produce unexpected results.");
469-
}
465+
using ImageType = ImageBase<VInputDimension>;
470466

471-
const typename Self::Pointer inverse = this->GetInverseTransform();
472-
if (inverse.IsNull())
473-
{
474-
itkExceptionMacro(
475-
"ApplyToImageMetadata was invoked with non-invertible transform of type: " << this->GetNameOfClass());
476-
}
477-
478-
// transform origin
479-
typename ImageType::PointType origin = image->GetOrigin();
480-
origin = inverse->TransformPoint(origin);
481-
image->SetOrigin(origin);
467+
if (!this->IsLinear())
468+
{
469+
itkWarningMacro("ApplyToImageMetadata was invoked with non-linear transform of type: "
470+
<< this->GetNameOfClass() << ". This might produce unexpected results.");
471+
}
482472

483-
typename ImageType::SpacingType spacing = image->GetSpacing();
484-
typename ImageType::DirectionType direction = image->GetDirection();
485-
// transform direction cosines and compute new spacing
486-
for (unsigned int i = 0; i < ImageType::ImageDimension; ++i)
487-
{
488-
Vector<typename Self::ParametersValueType, ImageType::ImageDimension> dirVector;
489-
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
473+
const typename Self::Pointer inverse = this->GetInverseTransform();
474+
if (inverse.IsNull())
490475
{
491-
dirVector[k] = direction[k][i];
476+
itkExceptionMacro(
477+
"ApplyToImageMetadata was invoked with non-invertible transform of type: " << this->GetNameOfClass());
492478
}
493479

494-
dirVector *= spacing[i];
495-
dirVector = inverse->TransformVector(dirVector);
496-
spacing[i] = dirVector.Normalize();
480+
// transform origin
481+
typename ImageType::PointType origin = image->GetOrigin();
482+
origin = inverse->TransformPoint(origin);
483+
image->SetOrigin(origin);
497484

498-
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
485+
typename ImageType::SpacingType spacing = image->GetSpacing();
486+
typename ImageType::DirectionType direction = image->GetDirection();
487+
// transform direction cosines and compute new spacing
488+
for (unsigned int i = 0; i < ImageType::ImageDimension; ++i)
499489
{
500-
direction[k][i] = dirVector[k];
490+
Vector<typename Self::ParametersValueType, ImageType::ImageDimension> dirVector;
491+
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
492+
{
493+
dirVector[k] = direction[k][i];
494+
}
495+
496+
dirVector *= spacing[i];
497+
dirVector = inverse->TransformVector(dirVector);
498+
spacing[i] = dirVector.Normalize();
499+
500+
for (unsigned int k = 0; k < ImageType::ImageDimension; ++k)
501+
{
502+
direction[k][i] = dirVector[k];
503+
}
501504
}
505+
image->SetDirection(direction);
506+
image->SetSpacing(spacing);
507+
}
508+
else
509+
{
510+
itkExceptionMacro("ApplyToImageMetadata was invoked with transform of type: "
511+
<< this->GetNameOfClass()
512+
<< " that has different input and output dimensions.");
502513
}
503-
image->SetDirection(direction);
504-
image->SetSpacing(spacing);
505514
}
506515

507516

Modules/Core/Transform/wrapping/test/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ if(ITK_WRAP_PYTHON)
77
${ITK_EXAMPLE_DATA_ROOT}/DiagonalLines.png
88
)
99

10+
itk_python_add_test(
11+
NAME itkApplyToImageMetadataTest
12+
COMMAND
13+
${CMAKE_CURRENT_SOURCE_DIR}/itkApplyToImageMetadataTest.py
14+
)
15+
1016
itk_python_expression_add_test(
1117
NAME PythonInstantiateBSplineTransform2D2D
1218
EXPRESSION "t = itk.BSplineTransform[itk.D, 2, 2].New()"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# ==========================================================================
2+
#
3+
# Copyright NumFOCUS
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0.txt
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# ==========================================================================
18+
19+
import itk
20+
import numpy as np
21+
22+
Dimension = 3
23+
PixelType = itk.F
24+
25+
# Create a simple image with known origin, spacing and direction.
26+
ImageType = itk.Image[PixelType, Dimension]
27+
image = ImageType.New()
28+
size = itk.Size[Dimension]()
29+
size.Fill(4)
30+
region = itk.ImageRegion[Dimension]()
31+
region.SetSize(size)
32+
image.SetRegions(region)
33+
image.SetOrigin([1.0, 2.0, 3.0])
34+
image.SetSpacing([1.0, 1.0, 1.0])
35+
image.Allocate()
36+
37+
# Build a translation transform, which is linear and invertible.
38+
TransformType = itk.TranslationTransform[itk.D, Dimension]
39+
transform = TransformType.New()
40+
translation = itk.Vector[itk.D, Dimension]()
41+
translation[0] = 10.0
42+
translation[1] = 20.0
43+
translation[2] = 30.0
44+
transform.Translate(translation)
45+
46+
# Apply the transform to the image metadata, updating origin/spacing/direction
47+
# in place, without resampling the pixel data.
48+
transform.ApplyToImageMetadata(image)
49+
50+
expected_origin = transform.GetInverseTransform().TransformPoint([1.0, 2.0, 3.0])
51+
52+
for i in range(Dimension):
53+
assert np.isclose(image.GetOrigin()[i], expected_origin[i])
54+
55+
print("ApplyToImageMetadata Test Done")

0 commit comments

Comments
 (0)