Created
January 23, 2025 10:17
-
-
Save N-Dekker/6679b024939f49827686bce10235b26f to your computer and use it in GitHub Desktop.
elastix_txt2yml - converts elastix parameter files from txt to yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// elastix_txt2yml | |
// Converts elastix parameter files from txt to yml | |
// | |
// Usage: elastix_txt2yml <dir> [--just-copy] | |
// | |
// Created by Niels Dekker, LKEB, Leiden University Medical Center, 2025. | |
#include <cassert> | |
#include <cctype> | |
#include <cstring> | |
#include <deque> | |
#include <filesystem> | |
#include <fstream> | |
#include <iostream> | |
#include <regex> | |
#include <sstream> | |
#include <string> | |
namespace fs = std::filesystem; | |
namespace | |
{ | |
using Lines = std::deque<std::string>; | |
auto | |
ReadFile(const fs::path & filePath) | |
{ | |
Lines result; | |
std::ifstream inputFileStream{ filePath }; | |
std::string line; | |
while (std::getline(inputFileStream, line)) | |
{ | |
result.push_back(line); | |
} | |
return result; | |
} | |
void | |
WriteFile(const fs::path & filePath, const Lines & lines) | |
{ | |
std::ofstream outputFileStream{ filePath }; | |
for (const auto & line : lines) | |
{ | |
outputFileStream << line << '\n'; | |
} | |
} | |
void | |
ProcessFile(fs::path filePath, const bool justCopyFile) | |
{ | |
std::cout << filePath << std::endl; | |
const auto inputLines = ReadFile(filePath); | |
auto outputLines = inputLines; | |
filePath.replace_extension(".yml"); | |
unsigned numberOfParameters{}; | |
for (auto & line : outputLines) | |
{ | |
if (!line.empty()) | |
{ | |
if (const auto pos = line.find("//"); pos != std::string::npos) | |
{ | |
line.replace(pos, 2, "#"); | |
} | |
std::match_results<std::string::const_iterator> results; | |
if (std::regex_match(line, results, std::regex{ R"delimiter(\s*\(\s*(\w+)\s+([^\)]+)\s*\)(.*))delimiter" }) && | |
results.size() == 4) | |
{ | |
++numberOfParameters; | |
auto values = results[2].str(); | |
assert(!values.empty()); | |
const auto removeDoubleQuotes = [&values](const std::string_view value) { | |
for (;;) | |
{ | |
if (const auto foundPos = values.find(value); foundPos == std::string::npos) | |
{ | |
break; | |
} | |
else | |
{ | |
values.replace(foundPos, value.size(), std::string_view(value.data() + 1, value.size() - 2)); | |
} | |
} | |
}; | |
removeDoubleQuotes("\"false\""); | |
removeDoubleQuotes("\"true\""); | |
// Replace tabs with spaces. | |
std::replace(values.begin(), values.end(), '\t', ' '); | |
// Replace double spaces with single spaces. | |
for (;;) | |
{ | |
if (const auto foundPos = values.find(" "); foundPos == std::string::npos) | |
{ | |
break; | |
} | |
else | |
{ | |
values.replace(foundPos, 2, " "); | |
} | |
} | |
unsigned numberOfAddedCommas{}; | |
if (const auto numberOfChars = values.size(); numberOfChars > 1) | |
{ | |
bool evenNumberOfDoubleQuotes = values.back() != '"'; | |
// Prepend a comma to each space. | |
for (auto i = numberOfChars - 2; i > 0; --i) | |
{ | |
if (values[i] == '"') | |
{ | |
evenNumberOfDoubleQuotes = !evenNumberOfDoubleQuotes; | |
} | |
else | |
{ | |
if (evenNumberOfDoubleQuotes && (values[i] == ' ')) | |
{ | |
values.replace(i, 1, ", "); | |
++numberOfAddedCommas; | |
} | |
} | |
} | |
} | |
if (numberOfAddedCommas > 0) | |
{ | |
values = '[' + values + ']'; | |
} | |
line = results[1].str() + ": " +values + results[3].str(); | |
} | |
} | |
} | |
const auto containsParameter = [](const auto & str) { | |
return str.find("parameter") != std::string::npos || str.find("Parameter") != std::string::npos; | |
}; | |
if ((numberOfParameters > 0) || containsParameter(filePath.stem().string())) | |
{ | |
WriteFile(filePath, justCopyFile ? inputLines : outputLines); | |
} | |
else | |
{ | |
std::cerr << "Skipped file without elastix parameters: " << filePath << '\n'; | |
} | |
} | |
void | |
ProcessFiles(const std::deque<fs::path> & filePaths, const bool justCopyFiles) | |
{ | |
for (const auto & filePath : filePaths) | |
{ | |
try | |
{ | |
ProcessFile(filePath, justCopyFiles); | |
} | |
catch (const std::exception & stdException) | |
{ | |
std::cerr << "Exception on processing " << filePath << ":\n " << stdException.what(); | |
} | |
} | |
} | |
auto | |
RetrieveFilePaths(const fs::path & directoryPath) | |
{ | |
std::deque<fs::path> filePaths; | |
for (const auto & entry : fs::recursive_directory_iterator{ directoryPath }) | |
{ | |
const auto & path = entry.path(); | |
if (const auto & extension = path.extension(); | |
(!extension.empty() && extension.string() == ".txt") && is_regular_file(path)) | |
{ | |
filePaths.push_back(path); | |
} | |
} | |
return filePaths; | |
} | |
} // namespace | |
int | |
main(int argc, char ** argv) | |
{ | |
if (argc < 2) | |
{ | |
std::cout << "Please specify the source directory path as command-line argument." | |
"\nNote: This program will modify the source files in-place!!!" | |
<< std::endl; | |
} | |
else | |
{ | |
if (argv == nullptr) | |
{ | |
return EXIT_FAILURE; | |
} | |
const char * const arg = argv[1]; | |
if (arg == nullptr) | |
{ | |
return EXIT_FAILURE; | |
} | |
try | |
{ | |
ProcessFiles(RetrieveFilePaths(arg), argc == 3 && argv[2] == std::string_view("--just-copy")); | |
} | |
catch (const std::exception& stdException) | |
{ | |
std::cerr << "Exception: " << stdException.what(); | |
return EXIT_FAILURE; | |
} | |
} | |
std::cout << "Press anything to continue" << std::endl; | |
std::cin.get(); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment