Skip to content

Commit

Permalink
ENH: Add TOML support to ParameterFileParser
Browse files Browse the repository at this point in the history
  • Loading branch information
N-Dekker committed Jan 30, 2025
1 parent 7241804 commit 2d206b3
Showing 1 changed file with 96 additions and 10 deletions.
106 changes: 96 additions & 10 deletions Common/ParameterFileParser/itkParameterFileParser.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <itksys/SystemTools.hxx>
#include <itksys/RegularExpression.hxx>

// TOML++, "single-header flavour".
#include "tomlplusplus/toml.hpp"

#include <deque>
#include <fstream>

namespace itk
Expand Down Expand Up @@ -237,7 +241,7 @@ CheckLine(const std::string & lineIn, std::string & lineOut)
// Performs the following checks:
// - Is a filename is given
// - Does the file exist
// - Is a text file, i.e. does it end with .txt
// - Is a text or TOML file, i.e. does it end with .txt or .toml
// If one of these conditions fail, an exception is thrown.
void
BasicFileChecking(const std::string & parameterFileName)
Expand All @@ -264,9 +268,10 @@ BasicFileChecking(const std::string & parameterFileName)

/** Check the extension. */
const std::string ext = itksys::SystemTools::GetFilenameLastExtension(parameterFileName);
if (ext != ".txt")
if (ext != ".txt" && ext != ".toml")
{
itkGenericExceptionMacro("ERROR: the file " << parameterFileName << " should be a text file (*.txt).");
itkGenericExceptionMacro("ERROR: the file " << parameterFileName
<< " should be a text file (*.txt) or a TOML file (*.toml).");
}

} // end BasicFileChecking()
Expand Down Expand Up @@ -299,6 +304,80 @@ ReadParameterMapFromInputStream(ParameterFileParser::ParameterMapType & paramete
}


// Parses the specified TOML file, and returns its key-value pairs as an elastix ParameterMap.
auto
ParseTomlFile(const std::string & fileName)
{
ParameterFileParser::ParameterMapType parameterMap;

std::ifstream inputFileStream(fileName);

// Retrieves all lines from the file, as well as the indices to the begin of each line.
const auto [lines, lineIndices] = [&inputFileStream] {
std::string lines;
std::deque<std::size_t> lineIndices{ 0 };
std::string line;

while (getline(inputFileStream, line))
{
lines += line;
lines += '\n';
lineIndices.push_back(lines.size());
}
return std::make_pair(lines, lineIndices);
}();

// Retrieves an elastix parameter value from the specified TOML node.
const auto getParameterValue = [&lines, &lineIndices](const toml::node & tomlNode) -> std::string {

// When the TOML node holds a string, just use it as it was produced by the TOML parser. (The TOML parser removes
// surrounding double-quotes from string values.)
if (const auto * const result = tomlNode.as_string())
{
return result->get();
}

// When the TOML node holds any other type of value, retrieve the original string from the source, instead of using
// the value produced by the TOML parser. (Avoiding an extra conversion round-trip.) Later on, ParameterMapInterface
// may do the conversion to the appropriate parameter value type.
const auto & source = tomlNode.source();
const auto & begin = source.begin;
const auto & end = source.end;

if ((end.line != begin.line) || (end.column <= begin.column))
{
itkGenericExceptionMacro("Incorrect or unexpected TOML source region encountered: " << source);
}

const std::size_t lineIndex{ lineIndices.at(begin.line - 1) };
return std::string(&lines.at(lineIndex + begin.column - 1), std::size_t{ end.column - begin.column });
};

for (const auto & tomlPair : toml::parse(lines))
{
const auto parameterName = tomlPair.first.str();
auto & parameterValues = parameterMap[std::string(parameterName)];

const toml::node & tomlValue = tomlPair.second;

if (const auto tomlArray = tomlValue.as_array())
{
// An elastix parameter that may have multiple values.
for (const toml::node & tomlArrayElement : *tomlArray)
{
parameterValues.push_back(getParameterValue(tomlArrayElement));
}
}
else
{
// An elastix parameter that has just a single value.
parameterValues.push_back(getParameterValue(tomlValue));
}
}

return parameterMap;
}

} // namespace

/**
Expand Down Expand Up @@ -337,16 +416,23 @@ ParameterFileParser::ReadParameterFile()
/** Perform some basic checks. */
BasicFileChecking(m_ParameterFileName);

/** Open the parameter file for reading. */
std::ifstream parameterFile(m_ParameterFileName);

/** Check if it opened. */
if (!parameterFile.is_open())
if (itksys::SystemTools::GetFilenameLastExtension(m_ParameterFileName) == ".toml")
{
itkExceptionMacro("ERROR: could not open " << m_ParameterFileName << " for reading.");
m_ParameterMap = ParseTomlFile(m_ParameterFileName);
}
else
{
/** Open the parameter file for reading. */
std::ifstream parameterFile(m_ParameterFileName);

ReadParameterMapFromInputStream(m_ParameterMap, parameterFile);
/** Check if it opened. */
if (!parameterFile.is_open())
{
itkExceptionMacro("ERROR: could not open " << m_ParameterFileName << " for reading.");
}

ReadParameterMapFromInputStream(m_ParameterMap, parameterFile);
}

} // end ReadParameterFile()

Expand Down

0 comments on commit 2d206b3

Please sign in to comment.