Skip to content

Commit 5105867

Browse files
committed
ENH: Add Transform::ApplyToImageMetadata method
This is equivalent to Slicer's "Harden Transform" operation.
1 parent e08395a commit 5105867

3 files changed

Lines changed: 127 additions & 40 deletions

File tree

Modules/Core/Transform/include/itkTransform.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#ifndef itkTransform_h
1919
#define itkTransform_h
2020

21+
#include <type_traits> // For std::enable_if
2122
#include "itkTransformBase.h"
2223
#include "itkVector.h"
2324
#include "itkSymmetricSecondRankTensor.h"
@@ -542,6 +543,27 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate<TParametersVa
542543
itkLegacyMacro(virtual void ComputeInverseJacobianWithRespectToPosition(const InputPointType & x,
543544
JacobianType & jacobian) const);
544545

546+
/** Apply this transform to an image without resampling.
547+
*
548+
* Updates image metadata (origin, spacing, direction cosines matrix) in place.
549+
*
550+
* Only available when input and output space are of the same dimension.
551+
* Only works properly for linear transforms.
552+
*
553+
* The image parameter may be either a SmartPointer or a raw pointer.
554+
* */
555+
template <typename TImage>
556+
typename std::enable_if<TImage::ImageDimension == NInputDimensions && TImage::ImageDimension == NOutputDimensions,
557+
void>::type
558+
ApplyToImageMetadata(TImage * image) const;
559+
template <typename TImage>
560+
typename std::enable_if<TImage::ImageDimension == NInputDimensions && TImage::ImageDimension == NOutputDimensions,
561+
void>::type
562+
ApplyToImageMetadata(SmartPointer<TImage> image) const
563+
{
564+
this->ApplyToImageMetadata(image.GetPointer()); // Delegate to the raw pointer signature
565+
}
566+
545567
protected:
546568
/**
547569
* Clone the current transform.

Modules/Core/Transform/include/itkTransform.hxx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "itkTransform.h"
2222
#include "itkCrossHelper.h"
2323
#include "vnl/algo/vnl_svd_fixed.h"
24+
#include "itkMetaProgrammingLibrary.h"
2425

2526
namespace itk
2627
{
@@ -470,6 +471,51 @@ Transform<TParametersValueType, NInputDimensions, NOutputDimensions>::TransformS
470471
return outputTensor;
471472
}
472473

474+
template <typename TParametersValueType, unsigned int NInputDimensions, unsigned int NOutputDimensions>
475+
template <typename TImage>
476+
typename std::enable_if<TImage::ImageDimension == NInputDimensions && TImage::ImageDimension == NOutputDimensions,
477+
void>::type
478+
Transform<TParametersValueType, NInputDimensions, NOutputDimensions>::ApplyToImageMetadata(TImage * image) const
479+
{
480+
using ImageType = TImage;
481+
482+
if (!this->IsLinear())
483+
{
484+
itkWarningMacro(<< "ApplyToImageMetadata was invoked with non-linear transform of type: " << this->GetNameOfClass()
485+
<< ". This might produce unexpected results.");
486+
}
487+
488+
typename Self::Pointer inverse = this->GetInverseTransform();
489+
490+
// transform origin
491+
typename ImageType::PointType origin = image->GetOrigin();
492+
origin = inverse->TransformPoint(origin);
493+
image->SetOrigin(origin);
494+
495+
typename ImageType::SpacingType spacing = image->GetSpacing();
496+
typename ImageType::DirectionType direction = image->GetDirection();
497+
// transform direction cosines and compute new spacing
498+
for (unsigned i = 0; i < ImageType::ImageDimension; i++)
499+
{
500+
Vector<typename Self::ParametersValueType, ImageType::ImageDimension> dirVector;
501+
for (unsigned k = 0; k < ImageType::ImageDimension; k++)
502+
{
503+
dirVector[k] = direction[k][i];
504+
}
505+
506+
dirVector *= spacing[i];
507+
dirVector = inverse->TransformVector(dirVector);
508+
spacing[i] = dirVector.Normalize();
509+
510+
for (unsigned k = 0; k < ImageType::ImageDimension; k++)
511+
{
512+
direction[k][i] = dirVector[k];
513+
}
514+
}
515+
image->SetDirection(direction);
516+
image->SetSpacing(spacing);
517+
}
518+
473519

474520
#if !defined(ITK_LEGACY_REMOVE)
475521
template <typename TParametersValueType, unsigned int NInputDimensions, unsigned int NOutputDimensions>

Modules/Core/Transform/test/itkResampleInPlaceImageFilterTest.cxx

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,49 @@ Validate(double input, double desired, double tolerance)
3434
return std::abs<double>(input - desired) > tolerance * std::abs<double>(desired);
3535
}
3636

37+
// Returns true if images are different
38+
template <typename ImageType>
39+
bool
40+
imagesDifferent(ImageType * baselineImage, ImageType * outputImage)
41+
{
42+
double tol = 1.e-3; // tolerance
43+
44+
typename ImageType::PointType origin = outputImage->GetOrigin();
45+
typename ImageType::DirectionType direction = outputImage->GetDirection();
46+
typename ImageType::SpacingType spacing = outputImage->GetSpacing();
47+
48+
typename ImageType::PointType origin_d = baselineImage->GetOrigin();
49+
typename ImageType::DirectionType direction_d = baselineImage->GetDirection();
50+
typename ImageType::SpacingType spacing_d = baselineImage->GetSpacing();
51+
52+
// Image info validation
53+
bool result = false; // no difference by default
54+
for (unsigned int i = 0; i < ImageType::ImageDimension; ++i)
55+
{
56+
result = (result || Validate(origin[i], origin_d[i], tol));
57+
result = (result || Validate(spacing[i], spacing_d[i], tol));
58+
for (unsigned int j = 0; j < ImageType::ImageDimension; ++j)
59+
{
60+
result = (result || Validate(direction(i, j), direction_d(i, j), tol));
61+
}
62+
}
63+
64+
// Voxel contents validation
65+
using ImageConstIterator = itk::ImageRegionConstIterator<ImageType>;
66+
ImageConstIterator it1(outputImage, outputImage->GetRequestedRegion());
67+
ImageConstIterator it2(baselineImage, baselineImage->GetRequestedRegion());
68+
it1.GoToBegin();
69+
it2.GoToBegin();
70+
while (!it1.IsAtEnd())
71+
{
72+
result = (result || Validate(it1.Get(), it2.Get(), tol));
73+
++it1;
74+
++it2;
75+
}
76+
77+
return result;
78+
}
79+
3780
int
3881
itkResampleInPlaceImageFilterTest(int argc, char * argv[])
3982
{
@@ -45,19 +88,12 @@ itkResampleInPlaceImageFilterTest(int argc, char * argv[])
4588
return EXIT_FAILURE;
4689
}
4790

48-
double tol = 1.e-3; // tolerance
49-
5091
constexpr unsigned int Dimension = 3;
5192
using PixelType = short;
5293

5394
using ImageType = itk::Image<PixelType, Dimension>;
5495
using ImagePointer = ImageType::Pointer;
55-
using ImagePointType = ImageType::PointType;
56-
using ImageDirectionType = ImageType::DirectionType;
57-
using ImageSpacingType = ImageType::SpacingType;
58-
using ImageConstIterator = itk::ImageRegionConstIterator<ImageType>;
5996
using TransformType = itk::VersorRigid3DTransform<double>;
60-
6197
using FilterType = itk::ResampleInPlaceImageFilter<ImageType, ImageType>;
6298

6399
// Read in input test image
@@ -92,50 +128,33 @@ itkResampleInPlaceImageFilterTest(int argc, char * argv[])
92128
// Set up the resample filter
93129
FilterType::Pointer filter = FilterType::New();
94130
ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, ResampleInPlaceImageFilter, ImageToImageFilter);
131+
95132
filter->SetInputImage(inputImage);
96133
ITK_TEST_SET_GET_VALUE(inputImage, filter->GetInputImage());
97134
filter->SetRigidTransform(transform);
98135
ITK_TEST_SET_GET_VALUE(transform, filter->GetRigidTransform());
99136
ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update());
100137
ImagePointer outputImage = filter->GetOutput();
101-
102-
// Get image info
103-
ImagePointType origin = outputImage->GetOrigin();
104-
ImageDirectionType direction = outputImage->GetDirection();
105-
ImageSpacingType spacing = outputImage->GetSpacing();
138+
ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteImage(outputImage, argv[3]));
106139

107140
// Read in baseline image
108141
ImagePointer baselineImage = nullptr;
109142
ITK_TRY_EXPECT_NO_EXCEPTION(baselineImage = itk::ReadImage<ImageType>(argv[2]));
110143

111-
ImagePointType origin_d = baselineImage->GetOrigin();
112-
ImageDirectionType direction_d = baselineImage->GetDirection();
113-
ImageSpacingType spacing_d = baselineImage->GetSpacing();
114-
// Image info validation
115-
bool result = false; // test result default = no failure
116-
for (unsigned int i = 0; i < Dimension; ++i)
117-
{
118-
result = (result || Validate(origin[i], origin_d[i], tol));
119-
result = (result || Validate(spacing[i], spacing_d[i], tol));
120-
for (unsigned int j = 0; j < Dimension; ++j)
121-
{
122-
result = (result || Validate(direction(i, j), direction_d(i, j), tol));
123-
}
124-
}
125-
126-
// Voxel contents validation
127-
ImageConstIterator it1(outputImage, outputImage->GetRequestedRegion());
128-
ImageConstIterator it2(baselineImage, baselineImage->GetRequestedRegion());
129-
it1.GoToBegin();
130-
it2.GoToBegin();
131-
while (!it1.IsAtEnd())
132-
{
133-
result = (result || Validate(it1.Get(), it2.Get(), tol));
134-
++it1;
135-
++it2;
136-
}
137-
138-
ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteImage(outputImage, argv[3]));
144+
// Now do comparisons
145+
bool result = imagesDifferent<ImageType>(baselineImage, outputImage);
146+
transform->ApplyToImageMetadata(inputImage);
147+
result = (result || imagesDifferent<ImageType>(baselineImage, inputImage));
148+
149+
// Make sure we can invoke ApplyToImageMetadata via const/raw pointer
150+
TransformType * rawPointerTransform = transform.GetPointer();
151+
rawPointerTransform->ApplyToImageMetadata(inputImage);
152+
rawPointerTransform->ApplyToImageMetadata(inputImage.GetPointer());
153+
const TransformType * constRawPointerTransform = transform.GetPointer();
154+
constRawPointerTransform->ApplyToImageMetadata(inputImage);
155+
TransformType::ConstPointer constPointerTransform = transform.GetPointer();
156+
constPointerTransform->ApplyToImageMetadata(inputImage);
157+
constPointerTransform->ApplyToImageMetadata(inputImage.GetPointer());
139158

140159
return result;
141160
}

0 commit comments

Comments
 (0)