Skip to content

Commit

Permalink
PERF: GenerateData() should not set "WriteResultImage" (issue #370)
Browse files Browse the repository at this point in the history
Both `ElastixFilter::GenerateData()` and `ElastixRegistrationMethod::GenerateData()` did overwrite a user preference by explicitly setting the "WriteResultImage" parameter. This behavior was added with commit a6e8f10 "ENH: Add ElastixFilter support for initial transform parameter file" by Kasper Marstal, 3 February 2016.

Which causes a performance issue, #370 "Final image transformation takes long time", reported by orange676.

With this commit these `GenerateData()` member functions longer set  "WriteResultImage". When the value of this parameter is false, the elastix library will no longer generate a result image; `GetOutput()` will return an empty image.

Discussed at the internal elastix sprint of 7 June 2021, with Marius, Stefan, and Viktor.
  • Loading branch information
N-Dekker committed Jun 7, 2021
1 parent 6803b26 commit 2115035
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 9 deletions.
3 changes: 3 additions & 0 deletions Core/ComponentBaseClasses/elxResamplerBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ namespace elastix
* result image is resampled and written. Choose from {"true", "false"} \n
* example: <tt>(WriteResultImage "false")</tt> \n
* The default is "true".
* \note When WriteResultImage is false, the executable will not write a
* result image, and the elastix library interface produces an empty image.
*
* \parameter WriteResultImageAfterEachResolution: flag to determine if the intermediate
* result image is resampled and written after each resolution. Choose from {"true", "false"} \n
* example: <tt>(WriteResultImageAfterEachResolution "true" "false" "true")</tt> \n
Expand Down
67 changes: 67 additions & 0 deletions Core/Main/GTesting/ElastixFilterGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,70 @@ GTEST_TEST(ElastixFilter, Translation)
EXPECT_EQ(std::round(std::stod(transformParameters[i])), translationOffset[i]);
}
}

// Tests registering two images, having "WriteResultImage" set to false.
GTEST_TEST(ElastixFilter, WriteResultImageFalse)
{
constexpr auto ImageDimension = 2U;
using ImageType = itk::Image<float, ImageDimension>;
using RegionType = itk::ImageRegion<ImageDimension>;
using SizeType = itk::Size<ImageDimension>;
using IndexType = itk::Index<ImageDimension>;
using OffsetType = itk::Offset<ImageDimension>;

const OffsetType translationOffset{ { 1, -2 } };
const auto regionSize = SizeType::Filled(2);
const SizeType imageSize{ { 5, 6 } };
const IndexType fixedImageRegionIndex{ { 1, 3 } };

const auto fixedImage = ImageType::New();
fixedImage->SetRegions(imageSize);
fixedImage->Allocate(true);
elx::CoreMainGTestUtilities::FillImageRegion(*fixedImage, fixedImageRegionIndex, regionSize);

const auto movingImage = ImageType::New();
movingImage->SetRegions(imageSize);
movingImage->Allocate(true);
elx::CoreMainGTestUtilities::FillImageRegion(*movingImage, fixedImageRegionIndex + translationOffset, regionSize);

const auto parameterObject = elastix::ParameterObject::New();
parameterObject->SetParameterMap(
elx::CoreMainGTestUtilities::CreateParameterMap({ { "ImageSampler", "Full" },
{ "MaximumNumberOfIterations", "2" },
{ "Metric", "AdvancedNormalizedCorrelation" },
{ "Optimizer", "AdaptiveStochasticGradientDescent" },
{ "Transform", "TranslationTransform" },
{ "WriteResultImage", "false" } }));

const auto filter = elx::ElastixFilter<ImageType, ImageType>::New();
ASSERT_NE(filter, nullptr);

filter->SetFixedImage(fixedImage);
filter->SetMovingImage(movingImage);
filter->SetParameterObject(parameterObject);
filter->Update();

// Expect an empty output image.
const auto * const output = filter->GetOutput();
ASSERT_NE(output, nullptr);
EXPECT_EQ(output->GetBufferedRegion().GetSize(), ImageType::SizeType());
EXPECT_EQ(output->GetBufferPointer(), nullptr);

const auto transformParameterObject = filter->GetTransformParameterObject();
const auto & transformParameterMaps = transformParameterObject->GetParameterMap();

ASSERT_TRUE(!transformParameterMaps.empty());
EXPECT_EQ(transformParameterMaps.size(), 1);

const auto & transformParameterMap = transformParameterMaps.front();
const auto found = transformParameterMap.find("TransformParameters");
ASSERT_NE(found, transformParameterMap.cend());

const auto & transformParameters = found->second;
ASSERT_EQ(transformParameters.size(), ImageDimension);

for (unsigned i{}; i < ImageDimension; ++i)
{
EXPECT_EQ(std::round(std::stod(transformParameters[i])), translationOffset[i]);
}
}
68 changes: 68 additions & 0 deletions Core/Main/GTesting/itkElastixRegistrationMethodGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,74 @@ GTEST_TEST(itkElastixRegistrationMethod, Translation)
}


// Tests registering two images, having "WriteResultImage" set to false.
GTEST_TEST(itkElastixRegistrationMethod, WriteResultImageFalse)
{
constexpr auto ImageDimension = 2U;
using ImageType = itk::Image<float, ImageDimension>;
using RegionType = itk::ImageRegion<ImageDimension>;
using SizeType = itk::Size<ImageDimension>;
using IndexType = itk::Index<ImageDimension>;
using OffsetType = itk::Offset<ImageDimension>;

const OffsetType translationOffset{ { 1, -2 } };
const auto regionSize = SizeType::Filled(2);
const SizeType imageSize{ { 5, 6 } };
const IndexType fixedImageRegionIndex{ { 1, 3 } };

const auto fixedImage = ImageType::New();
fixedImage->SetRegions(imageSize);
fixedImage->Allocate(true);
elx::CoreMainGTestUtilities::FillImageRegion(*fixedImage, fixedImageRegionIndex, regionSize);

const auto movingImage = ImageType::New();
movingImage->SetRegions(imageSize);
movingImage->Allocate(true);
elx::CoreMainGTestUtilities::FillImageRegion(*movingImage, fixedImageRegionIndex + translationOffset, regionSize);

const auto parameterObject = elastix::ParameterObject::New();
parameterObject->SetParameterMap(
elx::CoreMainGTestUtilities::CreateParameterMap({ { "ImageSampler", "Full" },
{ "MaximumNumberOfIterations", "2" },
{ "Metric", "AdvancedNormalizedCorrelation" },
{ "Optimizer", "AdaptiveStochasticGradientDescent" },
{ "Transform", "TranslationTransform" },
{ "WriteResultImage", "false" } }));

const auto filter = itk::ElastixRegistrationMethod<ImageType, ImageType>::New();
ASSERT_NE(filter, nullptr);

filter->SetFixedImage(fixedImage);
filter->SetMovingImage(movingImage);
filter->SetParameterObject(parameterObject);
filter->Update();

// Expect an empty output image.
const auto * const output = filter->GetOutput();
ASSERT_NE(output, nullptr);
EXPECT_EQ(output->GetBufferedRegion().GetSize(), ImageType::SizeType());
EXPECT_EQ(output->GetBufferPointer(), nullptr);

const auto transformParameterObject = filter->GetTransformParameterObject();
const auto & transformParameterMaps = transformParameterObject->GetParameterMap();

ASSERT_TRUE(!transformParameterMaps.empty());
EXPECT_EQ(transformParameterMaps.size(), 1);

const auto & transformParameterMap = transformParameterMaps.front();
const auto found = transformParameterMap.find("TransformParameters");
ASSERT_NE(found, transformParameterMap.cend());

const auto & transformParameters = found->second;
ASSERT_EQ(transformParameters.size(), ImageDimension);

for (unsigned i{}; i < ImageDimension; ++i)
{
EXPECT_EQ(std::round(std::stod(transformParameters[i])), translationOffset[i]);
}
}


GTEST_TEST(itkElastixRegistrationMethod, InitialTransformParameterFile)
{
// IndexRange is to be moved from namespace itk::Experimental
Expand Down
18 changes: 13 additions & 5 deletions Core/Main/elxElastixFilter.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
#ifndef elxElastixFilter_hxx
#define elxElastixFilter_hxx

#include <memory> // For unique_ptr.
#include <algorithm> // For find.
#include <memory> // For unique_ptr.

namespace elastix
{
Expand Down Expand Up @@ -131,9 +132,6 @@ ElastixFilter<TFixedImage, TMovingImage>::GenerateData(void)
itkExceptionMacro("Empty parameter map in parameter object.");
}

// Elastix must always write result image to guarantee that the ITK pipeline is in a consistent state
parameterMapVector.back()["WriteResultImage"] = ParameterValueVectorType(1, "true");

// Setup argument map
ArgumentMapType argumentMap;

Expand Down Expand Up @@ -278,7 +276,17 @@ ElastixFilter<TFixedImage, TMovingImage>::GenerateData(void)
}
else
{
itkExceptionMacro("Errors occured during registration: Could not read result image.");
const auto & parameterMap = parameterMapVector.back();
const auto endOfParameterMap = parameterMap.cend();
const bool writeResultImage =
std::find(parameterMap.cbegin(),
endOfParameterMap,
typename ParameterMapType::value_type{ "WriteResultImage", { "false" } }) == endOfParameterMap;

if (writeResultImage)
{
itkExceptionMacro("Errors occured during registration: Could not read result image.");
}
}

// Save parameter map
Expand Down
2 changes: 2 additions & 0 deletions Core/Main/itkElastixRegistrationMethod.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ class ITK_TEMPLATE_EXPORT ElastixRegistrationMethod : public itk::ImageSource<TF
GetOutput(unsigned int idx) const;
ResultImageType *
GetOutput();

/* \note When "WriteResultImage" is false, the output image will be empty. */
const ResultImageType *
GetOutput() const;

Expand Down
17 changes: 13 additions & 4 deletions Core/Main/itkElastixRegistrationMethod.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include "elxPixelType.h"
#include "itkElastixRegistrationMethod.h"

#include <algorithm> // For find.

namespace itk
{

Expand Down Expand Up @@ -150,9 +152,6 @@ ElastixRegistrationMethod<TFixedImage, TMovingImage>::GenerateData()
itkExceptionMacro("Empty parameter map in parameter object.");
}

// Elastix must always write result image to guarantee that the ITK pipeline is in a consistent state
parameterMapVector.back()["WriteResultImage"] = ParameterValueVectorType(1, "true");

// Setup argument map
ArgumentMapType argumentMap;

Expand Down Expand Up @@ -296,7 +295,17 @@ ElastixRegistrationMethod<TFixedImage, TMovingImage>::GenerateData()
}
else
{
itkExceptionMacro("Errors occured during registration: Could not read result image.");
const auto & parameterMap = parameterMapVector.back();
const auto endOfParameterMap = parameterMap.cend();
const bool writeResultImage =
std::find(parameterMap.cbegin(),
endOfParameterMap,
typename ParameterMapType::value_type{ "WriteResultImage", { "false" } }) == endOfParameterMap;

if (writeResultImage)
{
itkExceptionMacro("Errors occured during registration: Could not read result image.");
}
}

// Save parameter map
Expand Down

0 comments on commit 2115035

Please sign in to comment.