From e4c5a9d2729af6eb32d0f44eef777c081a87dd80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEenan=20Zuki=C4=87?= Date: Tue, 29 Jun 2021 17:47:18 -0400 Subject: [PATCH] ENH: make ApplyToImageMetadata available in Python wrapping --- Modules/Core/Transform/include/itkTransform.h | 32 +++++++- .../Core/Transform/include/itkTransform.hxx | 78 ++++++++++--------- .../Transform/wrapping/test/CMakeLists.txt | 9 +++ .../test/itkApplyToImageMetadataTest.py | 55 +++++++++++++ 4 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 Modules/Core/Transform/wrapping/test/itkApplyToImageMetadataTest.py diff --git a/Modules/Core/Transform/include/itkTransform.h b/Modules/Core/Transform/include/itkTransform.h index f01d54b5c94..f54a46b5dd1 100644 --- a/Modules/Core/Transform/include/itkTransform.h +++ b/Modules/Core/Transform/include/itkTransform.h @@ -27,6 +27,7 @@ #include "vnl/vnl_vector_fixed.h" #include "vnl/vnl_matrix_fixed.h" #include "itkMatrix.h" +#include "itkImageBase.h" namespace itk { @@ -542,22 +543,43 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate * image) const + { + 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 std::enable_if_t - ApplyToImageMetadata(TImage * image) const; + ApplyToImageMetadata(TImage * image) const + { + this->ApplyToImageMetadataInternal(image); + } template std::enable_if_t ApplyToImageMetadata(SmartPointer image) const { - this->ApplyToImageMetadata(image.GetPointer()); // Delegate to the raw pointer signature + this->ApplyToImageMetadataInternal(image.GetPointer()); } +#endif /** @ITKEndGrouping */ + protected: /** * Clone the current transform. @@ -591,6 +613,10 @@ class ITK_TEMPLATE_EXPORT Transform : public TransformBaseTemplate * image) const; + template static std::string GetTransformTypeAsString(TType *) diff --git a/Modules/Core/Transform/include/itkTransform.hxx b/Modules/Core/Transform/include/itkTransform.hxx index 463a5bda464..12bd952dbe1 100644 --- a/Modules/Core/Transform/include/itkTransform.hxx +++ b/Modules/Core/Transform/include/itkTransform.hxx @@ -456,52 +456,60 @@ Transform::TransformSym } template -template -std::enable_if_t -Transform::ApplyToImageMetadata(TImage * image) const +void +Transform::ApplyToImageMetadataInternal( + ImageBase * 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; - 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 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 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); } diff --git a/Modules/Core/Transform/wrapping/test/CMakeLists.txt b/Modules/Core/Transform/wrapping/test/CMakeLists.txt index d97327cdc89..934700001e9 100644 --- a/Modules/Core/Transform/wrapping/test/CMakeLists.txt +++ b/Modules/Core/Transform/wrapping/test/CMakeLists.txt @@ -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()" diff --git a/Modules/Core/Transform/wrapping/test/itkApplyToImageMetadataTest.py b/Modules/Core/Transform/wrapping/test/itkApplyToImageMetadataTest.py new file mode 100644 index 00000000000..63b94872932 --- /dev/null +++ b/Modules/Core/Transform/wrapping/test/itkApplyToImageMetadataTest.py @@ -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")