Skip to content

Commit

Permalink
ENH: Support OutputTransformParameterFileFormat parameter for TOML
Browse files Browse the repository at this point in the history
Supports the parameter `OutputTransformParameterMapFileFormat"` in elastix registration parameter files. Allows specifying the file format of the output transform parameter files.

Possible values:

 - `"txt"`: Use the legacy file format, "TransformParameters.*.txt" (the default)
 - `"TOML"`: Use the TOML file format, "TransformParameters.*.toml"
 - `""` (empty string): Use the default
  • Loading branch information
N-Dekker committed Feb 10, 2025
1 parent 550679e commit 8d758fe
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 46 deletions.
13 changes: 8 additions & 5 deletions Common/GTesting/elxConversionGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,14 @@ GTEST_TEST(Conversion, ToOptimizerParameters)

GTEST_TEST(Conversion, ParameterMapToString)
{
EXPECT_EQ(Conversion::ParameterMapToString({}), std::string{});
EXPECT_EQ(Conversion::ParameterMapToString({ { "A", {} } }), "(A)\n");
EXPECT_EQ(Conversion::ParameterMapToString({ { "Numbers", { "0", "1" } } }), "(Numbers 0 1)\n");
EXPECT_EQ(Conversion::ParameterMapToString({ { "Letters", { "a", "z" } } }), "(Letters \"a\" \"z\")\n");
EXPECT_EQ(Conversion::ParameterMapToString(TestExample::parameterMap), TestExample::parameterMapTextString);
EXPECT_EQ(Conversion::ParameterMapToString({}, elx::ParameterMapStringFormat::LegacyTxt), std::string{});
EXPECT_EQ(Conversion::ParameterMapToString({ { "A", {} } }, elx::ParameterMapStringFormat::LegacyTxt), "(A)\n");
EXPECT_EQ(Conversion::ParameterMapToString({ { "Numbers", { "0", "1" } } }, elx::ParameterMapStringFormat::LegacyTxt),
"(Numbers 0 1)\n");
EXPECT_EQ(Conversion::ParameterMapToString({ { "Letters", { "a", "z" } } }, elx::ParameterMapStringFormat::LegacyTxt),
"(Letters \"a\" \"z\")\n");
EXPECT_EQ(Conversion::ParameterMapToString(TestExample::parameterMap, elx::ParameterMapStringFormat::LegacyTxt),
TestExample::parameterMapTextString);
}


Expand Down
16 changes: 14 additions & 2 deletions Core/ComponentBaseClasses/elxResampleInterpolatorBase.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
#define elxResampleInterpolatorBase_hxx

#include "elxResampleInterpolatorBase.h"
#include "elxConfiguration.h"
#include "elxConversion.h"

#include "itkDeref.h"

#include <cassert>

namespace elastix
Expand All @@ -48,12 +51,21 @@ template <typename TElastix>
void
ResampleInterpolatorBase<TElastix>::WriteToFile(std::ostream & transformationParameterInfo) const
{
const Configuration & configuration = itk::Deref(Superclass::GetConfiguration());

ParameterMapType parameterMap;
this->CreateTransformParameterMap(parameterMap);

const std::string parameterMapFileFormat =
configuration.RetrieveParameterStringValue("", "OutputTransformParameterFileFormat", 0, false);

const auto format = Conversion::StringToParameterMapStringFormat(parameterMapFileFormat);

/** Write ResampleInterpolator specific things. */
transformationParameterInfo << ("\n// ResampleInterpolator specific\n" +
Conversion::ParameterMapToString(parameterMap));
transformationParameterInfo << '\n'
<< Conversion::ParameterMapStartOfCommentString(format)
<< " ResampleInterpolator specific\n " +
Conversion::ParameterMapToString(parameterMap, format);

} // end WriteToFile()

Expand Down
11 changes: 10 additions & 1 deletion Core/ComponentBaseClasses/elxResamplerBase.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -641,11 +641,20 @@ template <typename TElastix>
void
ResamplerBase<TElastix>::WriteToFile(std::ostream & transformationParameterInfo) const
{
const Configuration & configuration = itk::Deref(Superclass::GetConfiguration());

ParameterMapType parameterMap;
Self::CreateTransformParameterMap(parameterMap);

const std::string parameterMapFileFormat =
configuration.RetrieveParameterStringValue("", "OutputTransformParameterFileFormat", 0, false);

const auto format = Conversion::StringToParameterMapStringFormat(parameterMapFileFormat);

/** Write resampler specific things. */
transformationParameterInfo << ("\n// Resampler specific\n" + Conversion::ParameterMapToString(parameterMap));
transformationParameterInfo << '\n'
<< Conversion::ParameterMapStartOfCommentString(format)
<< " Resampler specific\n " + Conversion::ParameterMapToString(parameterMap, format);

} // end WriteToFile()

Expand Down
7 changes: 6 additions & 1 deletion Core/ComponentBaseClasses/elxTransformBase.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,12 @@ TransformBase<TElastix>::WriteToFile(std::ostream & transformationParameterInfo,
}
}

transformationParameterInfo << Conversion::ParameterMapToString(parameterMap);
const std::string parameterMapFileFormat =
configuration.RetrieveParameterStringValue("", "OutputTransformParameterFileFormat", 0, false);

const auto format = Conversion::StringToParameterMapStringFormat(parameterMapFileFormat);

transformationParameterInfo << Conversion::ParameterMapToString(parameterMap, format);

WriteDerivedTransformDataToFile();

Expand Down
3 changes: 2 additions & 1 deletion Core/Configuration/elxConfiguration.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ Configuration::PrintParameterMap() const
// Separate clearly in log-file, before and after writing the parameter map.
log::info_to_log_file(std::ostringstream{}
<< "\n=============== start of ParameterMap ===============\n"
<< Conversion::ParameterMapToString(m_ParameterMapInterface->GetParameterMap())
<< Conversion::ParameterMapToString(m_ParameterMapInterface->GetParameterMap(),
ParameterMapStringFormat::LegacyTxt)
<< "\n=============== end of ParameterMap ===============\n");

} // end PrintParameterMap()
Expand Down
142 changes: 110 additions & 32 deletions Core/Install/elxConversion.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -219,53 +219,103 @@ Conversion::ToOptimizerParameters(const std::vector<double> & stdVector)
*/

std::string
Conversion::ParameterMapToString(const ParameterMapType & parameterMap)
Conversion::ParameterMapToString(const ParameterMapType & parameterMap, const ParameterMapStringFormat format)
{
const auto expectedNumberOfChars = std::accumulate(
parameterMap.cbegin(),
parameterMap.cend(),
std::size_t{},
[](const std::size_t numberOfChars, const std::pair<std::string, ParameterValuesType> & parameter) {
return numberOfChars +
std::accumulate(parameter.second.cbegin(),
parameter.second.cend(),
// Two parentheses and a linebreak are added for each parameter.
parameter.first.size() + 3,
[](const std::size_t numberOfCharsPerParameter, const std::string & value) {
// A space character is added for each of the values.
// Plus two double-quotes, if the value is not a number.
return numberOfCharsPerParameter + value.size() + (Conversion::IsNumber(value) ? 1 : 3);
});
});

std::string result;
result.reserve(expectedNumberOfChars);

for (const auto & parameter : parameterMap)
if (format == ParameterMapStringFormat::Toml)
{
result.push_back('(');
result.append(parameter.first);
const auto parameterValueToTomlValue = [](const std::string & parameterValue) {
// Use the same representation in TOML as in elastix for booleans and numbers.
if (parameterValue == BoolToString(false) || parameterValue == BoolToString(true) || IsNumber(parameterValue))
{
return parameterValue;
}
else
{
return '"' + parameterValue + '"';
}
};

for (const auto & value : parameter.second)
for (const auto & parameter : parameterMap)
{
result.push_back(' ');
result.append(parameter.first).append(" = ");

if (Conversion::IsNumber(value))
const auto & parameterValues = parameter.second;

if (parameterValues.size() == 1)
{
result.append(value);
result.append(parameterValueToTomlValue(parameterValues.front()));
}
else
{
result.push_back('"');
result.append(value);
result.push_back('"');
result.push_back('[');

if (!parameterValues.empty())
{
result.append(parameterValueToTomlValue(parameterValues.front()));

const std::size_t numberOfParameterValues = parameterValues.size();

for (std::size_t i{ 1 }; i < numberOfParameterValues; ++i)
{
result.append(", ").append(parameterValueToTomlValue(parameterValues[i]));
}
}
result.push_back(']');
}
result.push_back('\n');
}
}
else
{
const auto expectedNumberOfChars = std::accumulate(
parameterMap.cbegin(),
parameterMap.cend(),
std::size_t{},
[](const std::size_t numberOfChars, const std::pair<std::string, ParameterValuesType> & parameter) {
return numberOfChars +
std::accumulate(parameter.second.cbegin(),
parameter.second.cend(),
// Two parentheses and a linebreak are added for each parameter.
parameter.first.size() + 3,
[](const std::size_t numberOfCharsPerParameter, const std::string & value) {
// A space character is added for each of the values.
// Plus two double-quotes, if the value is not a number.
return numberOfCharsPerParameter + value.size() +
(Conversion::IsNumber(value) ? 1 : 3);
});
});

result.reserve(expectedNumberOfChars);

for (const auto & parameter : parameterMap)
{
result.push_back('(');
result.append(parameter.first);

for (const auto & value : parameter.second)
{
result.push_back(' ');

if (Conversion::IsNumber(value))
{
result.append(value);
}
else
{
result.push_back('"');
result.append(value);
result.push_back('"');
}
}
result.append(")\n");
}
result.append(")\n");

// Assert that the correct number of characters was reserved.
assert(result.size() == expectedNumberOfChars);
}

// Assert that the correct number of characters was reserved.
assert(result.size() == expectedNumberOfChars);
return result;
}

Expand Down Expand Up @@ -426,4 +476,32 @@ Conversion::StringToValue(const std::string & str, itk::Object *& value)
return hasSucceeded;
}


ParameterMapStringFormat
Conversion::StringToParameterMapStringFormat(const std::string & str)
{
if (str.empty() || str == "txt")
{
return ParameterMapStringFormat::LegacyTxt;
}
if (str == "TOML")
{
return ParameterMapStringFormat::Toml;
}
itkGenericExceptionMacro("Failed to convert to the following string to ParameterMapStringFormat: \"" << str << '"');
}

std::string
Conversion::CreateParameterMapFileNameExtension(const ParameterMapStringFormat format)
{
switch (format)
{
case ParameterMapStringFormat::LegacyTxt:
return ".txt";
case ParameterMapStringFormat::Toml:
return ".toml";
}
itkGenericExceptionMacro("Failed to create a file name extension for '" << static_cast<int>(format) << '\'');
}

} // end namespace elastix
26 changes: 24 additions & 2 deletions Core/Install/elxConversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ class ITK_TEMPLATE_EXPORT OptimizerParameters;

namespace elastix
{
enum class ParameterMapStringFormat
{
LegacyTxt,
Toml
};

/**
* \class Conversion
*
Expand Down Expand Up @@ -68,9 +74,16 @@ class Conversion
static itk::OptimizerParameters<double>
ToOptimizerParameters(const std::vector<double> &);

/** Converts the specified parameter map to a text string, according to the elastix parameter text file format. */
/** Converts the specified parameter map to a string, according to the specified format. */
static std::string
ParameterMapToString(const ParameterMapType &);
ParameterMapToString(const ParameterMapType &, const ParameterMapStringFormat);

/** Resturns the character sequence to indicate the start of a comment, for the specified format. */
static std::string_view
ParameterMapStartOfCommentString(const ParameterMapStringFormat format)
{
return format == ParameterMapStringFormat::Toml ? "#" : "//";
}

/** Convenience function overload to convert a Boolean to a text string. */
static std::string
Expand Down Expand Up @@ -235,6 +248,15 @@ class Conversion
*/
static bool
StringToValue(const std::string & str, itk::Object *& value);

/** Converts the specified string to the corresponding enum value. Returns ParameterMapStringFormat::LegacyTxt when
* the string is empty. */
static ParameterMapStringFormat
StringToParameterMapStringFormat(const std::string & str);

/** Creates a parameter map file name extension for the corresponding format. */
static std::string
CreateParameterMapFileNameExtension(const ParameterMapStringFormat format);
};

} // end namespace elastix
Expand Down
7 changes: 6 additions & 1 deletion Core/Kernel/elxElastixTemplate.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,13 @@ ElastixTemplate<TFixedImage, TMovingImage>::AfterRegistration()

if (writeFinalTansformParameters && !outputDirectoryPath.empty())
{
const std::string parameterMapFileFormat =
configuration.RetrieveParameterStringValue("", "OutputTransformParameterFileFormat", 0, false);
const auto format = Conversion::StringToParameterMapStringFormat(parameterMapFileFormat);

std::ostringstream makeFileName;
makeFileName << outputDirectoryPath << "TransformParameters." << configuration.GetElastixLevel() << ".txt";
makeFileName << outputDirectoryPath << "TransformParameters." << configuration.GetElastixLevel()
<< Conversion::CreateParameterMapFileNameExtension(format);
std::string FileName = makeFileName.str();

/** Create a final TransformParameterFile. */
Expand Down
46 changes: 46 additions & 0 deletions Core/Main/GTesting/itkElastixRegistrationMethodGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2622,3 +2622,49 @@ GTEST_TEST(itkElastixRegistrationMethod, EuclideanDistancePointMetric)
const auto transformParameters = GetTransformParametersFromFilter(registration);
EXPECT_EQ(ConvertToOffset<ImageDimension>(transformParameters), translationOffset);
}


// Tests the use of the OutputTransformParameterFileFormat parameter.
GTEST_TEST(itkElastixRegistrationMethod, OutputTransformParameterFileFormat)
{
const std::string rootOutputDirectoryPath = GetCurrentBinaryDirectoryPath() + '/' + GetNameOfTest(*this);
itk::FileTools::CreateDirectory(rootOutputDirectoryPath);

using ImageType = itk::Image<float>;
elx::DefaultConstruct<ImageType> image{};
image.SetRegions(itk::MakeSize(5, 6));
image.AllocateInitialized();

elx::DefaultConstruct<elx::ParameterObject> parameterObject{};
elx::DefaultConstruct<ElastixRegistrationMethodType<ImageType>> registration{};

registration.SetFixedImage(&image);
registration.SetMovingImage(&image);
registration.SetParameterObject(&parameterObject);

elx::ParameterObject::ParameterMapType parameterMap{ // Parameters in alphabetic order:
{ "ImageSampler", { "Full" } },
{ "MaximumNumberOfIterations", { "2" } },
{ "Metric", { "AdvancedNormalizedCorrelation" } },
{ "Optimizer", { "AdaptiveStochasticGradientDescent" } },
{ "Transform", { "TranslationTransform" } }
};

const auto check = [&parameterMap, &parameterObject, &registration, rootOutputDirectoryPath](
const std::string & outputTransformParameterFileFormat,
const std::string & expectedTransformParameterFileExtension) {
const std::string outputDirectoryPath = rootOutputDirectoryPath + "/" + outputTransformParameterFileFormat;
itk::FileTools::CreateDirectory(outputDirectoryPath);

registration.SetOutputDirectory(outputDirectoryPath);
parameterMap["OutputTransformParameterFileFormat"] = { outputTransformParameterFileFormat };
parameterObject.SetParameterMap(parameterMap);
registration.Update();

EXPECT_TRUE(itksys::SystemTools::FileExists(outputDirectoryPath + "/TransformParameters.0" +
expectedTransformParameterFileExtension));
};

check("TOML", ".toml");
check("txt", ".txt");
}
7 changes: 6 additions & 1 deletion Core/Main/elxParameterObject.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,12 @@ ParameterObject::WriteParameterFile(const ParameterMapType & parameterMap,

try
{
parameterFile << Conversion::ParameterMapToString(parameterMap);
const auto format = itksys::SystemTools::GetFilenameLastExtension(parameterFileName) ==
Conversion::CreateParameterMapFileNameExtension(ParameterMapStringFormat::Toml)
? ParameterMapStringFormat::Toml
: ParameterMapStringFormat::LegacyTxt;

parameterFile << Conversion::ParameterMapToString(parameterMap, format);
}
catch (const std::ios_base::failure & e)
{
Expand Down

0 comments on commit 8d758fe

Please # to comment.