Skip to content

Commit 491cafa

Browse files
committed
ENH: Add ElastixFilter unit tests UpdateSerially and UpdateInParallel
Originally based on a code snippet by Konstantinos Ntatsis at pull request #389 ("ENH: Make ElastixMain database creation + loading components thread safe"): #389 (comment)
1 parent 0c84c5b commit 491cafa

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed

Core/Main/GTesting/ElastixFilterGTest.cxx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,123 @@
2525
// GoogleTest header file:
2626
#include <gtest/gtest.h>
2727

28+
#include <array>
2829
#include <algorithm> // For transform
2930
#include <map>
3031
#include <string>
3132
#include <utility> // For pair
33+
#include <vector>
34+
35+
36+
namespace
37+
{
38+
template <typename TImage>
39+
std::vector<std::string>
40+
GetTransformParameters(const elx::ElastixFilter<TImage, TImage> & filter)
41+
{
42+
const auto transformParameterObject = filter.GetTransformParameterObject();
43+
44+
if (transformParameterObject != nullptr)
45+
{
46+
const auto & transformParameterMaps = transformParameterObject->GetParameterMap();
47+
48+
if (transformParameterMaps.size() == 1)
49+
{
50+
const auto & transformParameterMap = transformParameterMaps.front();
51+
const auto found = transformParameterMap.find("TransformParameters");
52+
53+
if (found != transformParameterMap.cend())
54+
{
55+
return found->second;
56+
}
57+
}
58+
}
59+
return {};
60+
}
61+
62+
63+
void
64+
TestUpdatingMultipleFilters(const bool inParallel)
65+
{
66+
// Create an array of black images (pixel value zero) with a little "white"
67+
// region (pixel value 1) inside.
68+
constexpr auto ImageDimension = 2;
69+
using ImageType = itk::Image<float, ImageDimension>;
70+
using SizeType = itk::Size<ImageDimension>;
71+
72+
const auto imageSizeValue = 8;
73+
74+
const itk::ImageRegion<ImageDimension> imageRegion{ itk::Index<ImageDimension>::Filled(imageSizeValue / 2),
75+
SizeType::Filled(2) };
76+
77+
const auto numberOfImages = 10;
78+
std::array<ImageType::Pointer, numberOfImages> images;
79+
80+
std::generate(images.begin(), images.end(), ImageType::New);
81+
82+
for (const auto & image : images)
83+
{
84+
image->SetRegions(SizeType::Filled(imageSizeValue));
85+
image->Allocate(true);
86+
const itk::Experimental::ImageRegionRange<ImageType> imageRegionRange{ *image, imageRegion };
87+
std::fill(std::begin(imageRegionRange), std::end(imageRegionRange), 1.0f);
88+
}
89+
90+
const auto parameterObject = elx::ParameterObject::New();
91+
parameterObject->SetParameterMap(
92+
elx::ParameterObject::ParameterMapType{ // Parameters in alphabetic order:
93+
{ "ImageSampler", { "Full" } },
94+
{ "MaximumNumberOfIterations", { "2" } },
95+
{ "Metric", { "AdvancedNormalizedCorrelation" } },
96+
{ "Optimizer", { "AdaptiveStochasticGradientDescent" } },
97+
{ "Transform", { "TranslationTransform" } },
98+
{ "WriteFinalTransformParameters", { "false" } } });
99+
100+
// Create a filter for each image, to register an image with the next one.
101+
std::array<itk::SmartPointer<elx::ElastixFilter<ImageType, ImageType>>, numberOfImages> filters;
102+
std::generate(filters.begin(), filters.end(), elx::ElastixFilter<ImageType, ImageType>::New);
103+
104+
for (auto i = 0; i < numberOfImages; ++i)
105+
{
106+
auto & filter = *(filters[i]);
107+
108+
filter.LogToConsoleOff();
109+
filter.LogToFileOff();
110+
filter.SetFixedImage(images[i]);
111+
// Choose the next image (from the array of images) as moving image.
112+
filter.SetMovingImage(images[(i + std::size_t{ 1 }) % numberOfImages]);
113+
filter.SetParameterObject(parameterObject);
114+
}
115+
116+
if (inParallel)
117+
{
118+
// Note: The OpenMP implementation of GCC (including GCC 5.5 and GCC 10.2)
119+
// does not seem to support value-initialization of a for-loop index by
120+
// empty pair of braces, as in `for (int i{}; i < n; ++i)`.
121+
#pragma omp parallel for
122+
for (int i = 0; i < numberOfImages; ++i)
123+
{
124+
filters[i]->Update();
125+
}
126+
}
127+
else
128+
{
129+
for (int i{}; i < numberOfImages; ++i)
130+
{
131+
filters[i]->Update();
132+
}
133+
}
134+
135+
// Check if the TransformParameters of each filter are as expected.
136+
const std::vector<std::string> expectedTransformParameters(ImageDimension, "0");
137+
138+
for (const auto & filter : filters)
139+
{
140+
EXPECT_EQ(GetTransformParameters(*filter), expectedTransformParameters);
141+
}
142+
}
143+
144+
} // namespace
32145

33146

34147
// Tests registering two small (5x6) binary images, which are translated with respect to each other.
@@ -108,3 +221,15 @@ GTEST_TEST(ElastixFilter, Translation)
108221
EXPECT_EQ(std::round(std::stod(transformParameters[i])), translationOffset[i]);
109222
}
110223
}
224+
225+
226+
GTEST_TEST(ElastixFilter, UpdateSerially)
227+
{
228+
TestUpdatingMultipleFilters(false);
229+
}
230+
231+
232+
GTEST_TEST(ElastixFilter, UpdateInParallel)
233+
{
234+
TestUpdatingMultipleFilters(true);
235+
}

0 commit comments

Comments
 (0)