Skip to content

Commit

Permalink
ENH: Support "ResultImageName" in parameter files
Browse files Browse the repository at this point in the history
Allows the user to specify the name of the result file, that would otherwise just be named something like "result.mhd".

This improvement applies mainly to the elastix and transformix *executables*. Moreover, for the elastix library, it also applies to the result image files generated with "WriteResultImageAfterEachResolution" and "WriteResultImageAfterEachIteration".

Addressing issue #672 "Feature request: specify output name for file in transformix/elastix" by Sebastian van der Voort.
  • Loading branch information
N-Dekker committed Dec 22, 2022
1 parent 601f131 commit 3053a73
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 4 deletions.
12 changes: 9 additions & 3 deletions Core/ComponentBaseClasses/elxResamplerBase.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ ResamplerBase<TElastix>::AfterEachResolutionBase()
if (writeResultImageThisResolution)
{
/** Create a name for the final result. */
const auto resultImageName =
this->m_Configuration->RetrieveParameterValue<std::string>("result", "ResultImageName", 0, false);
std::string resultImageFormat = "mhd";
this->m_Configuration->ReadParameter(resultImageFormat, "ResultImageFormat", 0, false);
std::ostringstream makeFileName;
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << "result."
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << resultImageName << '.'
<< this->m_Configuration->GetElastixLevel() << ".R" << level << "." << resultImageFormat;

/** Time the resampling. */
Expand Down Expand Up @@ -148,10 +150,12 @@ ResamplerBase<TElastix>::AfterEachIterationBase()
this->GetElastix()->GetElxTransformBase()->SetFinalParameters();

/** Create a name for the final result. */
const auto resultImageName =
this->m_Configuration->RetrieveParameterValue<std::string>("result", "ResultImageName", 0, false);
std::string resultImageFormat = "mhd";
this->m_Configuration->ReadParameter(resultImageFormat, "ResultImageFormat", 0, false);
std::ostringstream makeFileName;
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << "result."
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << resultImageName << '.'
<< this->m_Configuration->GetElastixLevel() << ".R" << level << ".It" << std::setfill('0')
<< std::setw(7) << iter << "." << resultImageFormat;

Expand Down Expand Up @@ -219,10 +223,12 @@ ResamplerBase<TElastix>::AfterRegistrationBase()
if (writeResultImage == "true")
{
/** Create a name for the final result. */
const auto resultImageName =
this->m_Configuration->RetrieveParameterValue<std::string>("result", "ResultImageName", 0, false);
std::string resultImageFormat = "mhd";
this->m_Configuration->ReadParameter(resultImageFormat, "ResultImageFormat", 0);
std::ostringstream makeFileName;
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << "result."
makeFileName << this->m_Configuration->GetCommandLineArgument("-out") << resultImageName << '.'
<< this->m_Configuration->GetElastixLevel() << "." << resultImageFormat;

/** Time the resampling. */
Expand Down
4 changes: 3 additions & 1 deletion Core/Kernel/elxElastixTemplate.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,12 @@ ElastixTemplate<TFixedImage, TMovingImage>::ApplyTransform(const bool doReadTran
const Configuration & configuration = *Superclass::m_Configuration;

/** Create a name for the final result. */
const auto resultImageName =
configuration.RetrieveParameterValue<std::string>("result", "ResultImageName", 0, false);
std::string resultImageFormat = "mhd";
configuration.ReadParameter(resultImageFormat, "ResultImageFormat", 0, false);
std::ostringstream makeFileName;
makeFileName << configuration.GetCommandLineArgument("-out") << "result." << resultImageFormat;
makeFileName << configuration.GetCommandLineArgument("-out") << resultImageName << '.' << resultImageFormat;

elxResamplerBase.ResampleAndWriteResultImage(makeFileName.str().c_str());
}
Expand Down
76 changes: 76 additions & 0 deletions Core/Main/GTesting/itkElastixRegistrationMethodGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ using elx::CoreMainGTestUtilities::CheckNew;
using elx::CoreMainGTestUtilities::ConvertToOffset;
using elx::CoreMainGTestUtilities::CreateImage;
using elx::CoreMainGTestUtilities::CreateImageFilledWithSequenceOfNaturalNumbers;
using elx::CoreMainGTestUtilities::CreateParameterMap;
using elx::CoreMainGTestUtilities::CreateParameterObject;
using elx::CoreMainGTestUtilities::Deref;
using elx::CoreMainGTestUtilities::DerefSmartPointer;
Expand Down Expand Up @@ -352,6 +353,81 @@ GTEST_TEST(itkElastixRegistrationMethod, WriteResultImage)
}


// Tests registering two images, having a custom "ResultImageName" specified.
GTEST_TEST(itkElastixRegistrationMethod, ResultImageName)
{
constexpr auto ImageDimension = 2U;
using PixelType = float;
using ImageType = itk::Image<PixelType, 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 = CreateImage<PixelType>(imageSize);
FillImageRegion(*fixedImage, fixedImageRegionIndex, regionSize);
const auto movingImage = CreateImage<PixelType>(imageSize);
FillImageRegion(*movingImage, fixedImageRegionIndex + translationOffset, regionSize);

const std::string rootOutputDirectoryPath = GetCurrentBinaryDirectoryPath() + '/' + GetNameOfTest(*this);
itk::FileTools::CreateDirectory(rootOutputDirectoryPath);

const auto numberOfResolutions = 2u;
const std::string customResultImageName = "CustomResultImageName";

const auto getOutputSubdirectoryPath = [rootOutputDirectoryPath](const bool useCustomResultImageName) {
return rootOutputDirectoryPath + '/' +
(useCustomResultImageName ? "DefaultResultImageName" : "CustomResultImageName");
};

for (const bool useCustomResultImageName : { true, false })
{
DefaultConstructibleElastixRegistrationMethod<ImageType, ImageType> registration;

const std::string outputSubdirectoryPath = getOutputSubdirectoryPath(useCustomResultImageName);
itk::FileTools::CreateDirectory(outputSubdirectoryPath);
registration.SetOutputDirectory(outputSubdirectoryPath);
registration.SetFixedImage(fixedImage);
registration.SetMovingImage(movingImage);

auto parameterMap = CreateParameterMap({ // Parameters in alphabetic order:
{ "ImageSampler", "Full" },
{ "MaximumNumberOfIterations", "2" },
{ "Metric", "AdvancedNormalizedCorrelation" },
{ "NumberOfResolutions", std::to_string(numberOfResolutions) },
{ "Optimizer", "AdaptiveStochasticGradientDescent" },
{ "Transform", "TranslationTransform" },
{ "WriteResultImageAfterEachResolution", "true" } });

if (useCustomResultImageName)
{
parameterMap["ResultImageName"] = { customResultImageName };
}
const auto parameterObject = elx::ParameterObject::New();
parameterObject->SetParameterMap(parameterMap);
registration.SetParameterObject(parameterObject);
registration.Update();
}

for (unsigned int resolutionNumber{ 0 }; resolutionNumber < numberOfResolutions; ++resolutionNumber)
{
const auto fileNamePostFix = ".0.R" + std::to_string(resolutionNumber) + ".mhd";
const auto expectedImage =
itk::ReadImage<ImageType>(getOutputSubdirectoryPath(false) + "/result" + fileNamePostFix);
const auto actualImage =
itk::ReadImage<ImageType>(getOutputSubdirectoryPath(true) + '/' + customResultImageName + fileNamePostFix);

ASSERT_NE(expectedImage, nullptr);
ASSERT_NE(actualImage, nullptr);
EXPECT_EQ(*actualImage, *expectedImage);
}
}


// Tests that the origin of the output image is equal to the origin of the fixed image (by default).
GTEST_TEST(itkElastixRegistrationMethod, OutputHasSameOriginAsFixedImage)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(Direction 1 0 0 1)
(FixedImageDimension 2)
(Index 0 0)
(MovingImageDimension 2)
(NumberOfParameters 2)
(Origin 0 0)
(ResultImageName "CustomResultImageName")
(ResultImagePixelType "float")
(Size 5 6)
(Spacing 1 1)
(Transform "TranslationTransform")
(TransformParameters 1 -2)
47 changes: 47 additions & 0 deletions Testing/PythonTests/transformix_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,53 @@ def test_translation_of_images(self) -> None:
actual_pixel_data, expected_pixel_data, atol=max_absolute_difference, rtol=0
)

def test_custom_result_image_name(self) -> None:
"""Tests the ResultImageName parameter"""

source_directory_path = pathlib.Path(__file__).resolve().parent
output_directory_path = self.create_test_function_output_directory()
data_directory_path = source_directory_path / ".." / "Data"
input_file_path = data_directory_path / "2D_2x2_square_object_at_(2,1).mhd";
parameter_directory_path = source_directory_path / "TransformParameters"

subprocess.run(
[
str(self.transformix_exe_file_path),
"-in",
str(input_file_path),
"-tp",
str(parameter_directory_path / "Translation(1,-2).txt"),
"-out",
str(output_directory_path),
],
capture_output=True,
check=True,
)

expected_image = sitk.ReadImage(str(output_directory_path / "result.mhd"))

subprocess.run(
[
str(self.transformix_exe_file_path),
"-in",
str(input_file_path),
"-tp",
str(parameter_directory_path / "Translation(1,-2)-CustomResultImageName.txt"),
"-out",
str(output_directory_path),
],
capture_output=True,
check=True,
)

actual_image = sitk.ReadImage(str(output_directory_path / "CustomResultImageName.mhd"))

self.assert_equal_image_info(actual_image, expected_image)
np.testing.assert_array_equal(
sitk.GetArrayFromImage(actual_image),
sitk.GetArrayFromImage(expected_image),
)

def test_translation_of_points(self) -> None:
"""Tests translation of points"""

Expand Down

0 comments on commit 3053a73

Please sign in to comment.