From 96b06e674122050a7fc3e19f1fcc0884399747f6 Mon Sep 17 00:00:00 2001 From: Niels Dekker Date: Tue, 25 Aug 2020 08:57:01 +0200 Subject: [PATCH 1/2] WIP: ENH: Executable linked to elastix.lib, subset of elastix.exe functions --- Core/CMakeLists.txt | 6 + Core/Main/ElastixLibBasedExe.cxx | 352 +++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 Core/Main/ElastixLibBasedExe.cxx diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 9605053a8..58c7c1884 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -169,6 +169,12 @@ set_target_properties( elastix_lib PROPERTIES OUTPUT_NAME elastix ) target_compile_definitions( elastix_lib PRIVATE ELX_CMAKE_VERSION="${CMAKE_VERSION}" ) target_link_libraries( elastix_lib ${ELASTIX_TARGET_LINK_LIBRARIES} ) +add_executable( ElastixLibBasedExe + Main/ElastixLibBasedExe.cxx +) +target_link_libraries( ElastixLibBasedExe elastix_lib ) +target_compile_definitions(ElastixLibBasedExe PRIVATE ELX_CMAKE_VERSION="${CMAKE_VERSION}") + #--------------------------------------------------------------------- # Create the transformix executable. diff --git a/Core/Main/ElastixLibBasedExe.cxx b/Core/Main/ElastixLibBasedExe.cxx new file mode 100644 index 000000000..c0bb086a0 --- /dev/null +++ b/Core/Main/ElastixLibBasedExe.cxx @@ -0,0 +1,352 @@ +/*========================================================================= + * + * Copyright UMC Utrecht and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +// Elastix header files: +#include "elastix.h" +#include "elastixlib.h" +#include "elxConversion.h" +#include "elxElastixMain.h" +#include +#include "itkUseMevisDicomTiff.h" + +// ITK header files: +#include +#include +#include + +// Standard C++ header files: +#include +#include // For UINT_MAX. +#include // For size_t. +#include +#include +#include +#include + +constexpr const char * elastixHelpText = + /** Print the version. */ + "elastix version: " ELASTIX_VERSION_STRING "\n\n" + + /** What is elastix? */ + "elastix registers a moving image to a fixed image.\n" + "The registration-process is specified in the parameter file.\n" + " --help, -h displays this message and exit\n" + " --version output version information and exit\n" + " --extended-version output extended version information and exit\n\n" + + /** Mandatory arguments.*/ + "Call elastix from the command line with mandatory arguments:\n" + " -f fixed image\n" + " -m moving image\n" + " -out output directory\n" + " -p parameter file, elastix handles 1 or more \"-p\"\n\n" + + /** Optional arguments.*/ + "Optional extra commands:\n" + " -fMask mask for fixed image\n" + " -mMask mask for moving image\n" + " -fp point set for fixed image\n" + " -mp point set for moving image\n" + " -t0 parameter file for initial transform\n" + " -priority set the process priority to high, abovenormal, normal (default),\n" + " belownormal, or idle (Windows only option)\n" + " -threads set the maximum number of threads of elastix\n\n" + + /** The parameter file.*/ + "The parameter-file must contain all the information " + "necessary for elastix to run properly. That includes which metric to " + "use, which optimizer, which transform, etc. It must also contain " + "information specific for the metric, optimizer, transform, etc. " + "For a usable parameter-file, see the website.\n\n" + + "Need further help? Please check:\n" + " * the elastix website: https://elastix.lumc.nl\n" + " * the source code repository site: https://github.com/SuperElastix/elastix\n" + " * the discussion forum: https://groups.google.com/g/elastix-imageregistration"; + + +namespace +{ +using ImageType = itk::Image; + +// TODO: Check GenerateImageContainer calls in ElastixMain::Run() for +// more extensive reading. +ImageType::Pointer +ReadImage(const std::string & filename) +{ + /** Setup reader. */ + const auto imageFileReader = itk::ImageFileReader::New(); + imageFileReader->SetFileName(filename); + imageFileReader->Update(); + return imageFileReader->GetOutput(); +} +} // namespace + +int +Main(const int argc, const char * const * const argv) +{ + + /** Check if "--help" or "--version" was asked for. */ + if (argc == 1) + { + std::cout << "Use \"elastix --help\" for information about elastix-usage." << std::endl; + return 0; + } + else if (argc == 2) + { + std::string argument(argv[1]); + if (argument == "-help" || argument == "--help" || argument == "-h") + { + std::cout << elastixHelpText << std::endl; + return 0; + } + else if (argument == "--version") + { + std::cout << "elastix version: " ELASTIX_VERSION_STRING << std::endl; + return 0; + } + else if (argument == "--extended-version") + { + std::cout << "elastix version: " ELASTIX_VERSION_STRING << "\nITK version: " << ITK_VERSION_MAJOR << '.' + << ITK_VERSION_MINOR << '.' << ITK_VERSION_PATCH << "\nBuild date: " << __DATE__ << ' ' << __TIME__ +#ifdef _MSC_FULL_VER + << "\nCompiler: Visual C++ version " << _MSC_FULL_VER << '.' << _MSC_BUILD +#endif +#ifdef __clang__ + << "\nCompiler: Clang" +# ifdef __VERSION__ + << " version " << __VERSION__ +# endif +#endif +#if defined(__GNUC__) + << "\nCompiler: GCC" +# ifdef __VERSION__ + << " version " << __VERSION__ +# endif +#endif + << "\nMemory address size: " << std::numeric_limits::digits + << "-bit\nCMake version: " << ELX_CMAKE_VERSION << std::endl; + return 0; + } + else + { + std::cout << "Use \"elastix --help\" for information about elastix-usage." << std::endl; + return 0; + } + } + + /** Some typedef's. */ + typedef elx::ElastixMain ElastixMainType; + typedef ElastixMainType::ObjectPointer ObjectPointer; + typedef ElastixMainType::DataObjectContainerPointer DataObjectContainerPointer; + typedef ElastixMainType::FlatDirectionCosinesType FlatDirectionCosinesType; + + typedef ElastixMainType::ArgumentMapType ArgumentMapType; + typedef ArgumentMapType::value_type ArgumentMapEntryType; + + /** Support Mevis Dicom Tiff (if selected in cmake) */ + RegisterMevisDicomTiff(); + + ArgumentMapType argMap; + std::deque parameterFileList; + std::string outFolder; + + /** Put command line parameters into parameterFileList. */ + for (unsigned int i = 1; static_cast(i) < (argc - 1); i += 2) + { + std::string key(argv[i]); + std::string value(argv[i + 1]); + + if (key == "-p") + { + /** Queue the ParameterFileNames. */ + parameterFileList.push_back(value); + /** The different '-p' are stored in the argMap, with + * keys p(1), p(2), etc. */ + std::ostringstream tempPname; + tempPname << "-p(" << parameterFileList.size() << ")"; + std::string tempPName = tempPname.str(); + argMap.insert(ArgumentMapEntryType(tempPName, value)); + } + else + { + if (key == "-out") + { + /** Make sure that last character of the output folder equals a '/' or '\'. */ + const char last = value.back(); + if (last != '/' && last != '\\') + { + value.append("/"); + } + value = elx::Conversion::ToNativePathNameSeparators(value); + + /** Save this information. */ + outFolder = value; + + } // end if key == "-out" + + /** Attempt to save the arguments in the ArgumentMap. */ + if (argMap.count(key) == 0) + { + argMap.insert(ArgumentMapEntryType(key, value)); + } + else + { + /** Duplicate arguments. */ + std::cerr << "WARNING!" << std::endl; + std::cerr << "Argument " << key << "is only required once." << std::endl; + std::cerr << "Arguments " << key << " " << value << "are ignored" << std::endl; + } + + } // end else (so, if key does not equal "-p") + + } // end for loop + + /** The argv0 argument, required for finding the component.dll/so's. */ + argMap.insert(ArgumentMapEntryType("-argv0", argv[0])); + + int returndummy{}; + + /** Check if at least once the option "-p" is given. */ + if (parameterFileList.empty()) + { + std::cerr << "ERROR: No CommandLine option \"-p\" given!" << std::endl; + returndummy |= -1; + } + + /** Check if the -out option is given. */ + if (!outFolder.empty()) + { + /** Check if the output directory exists. */ + if (!itksys::SystemTools::FileIsDirectory(outFolder)) + { + std::cerr << "ERROR: the output directory \"" << outFolder << "\" does not exist." << std::endl; + std::cerr << "You are responsible for creating it." << std::endl; + returndummy |= -2; + } + else + { + /** Setup xout. */ + const std::string logFileName = outFolder + "elastix.log"; + const int returndummy2{ elx::xoutSetup(logFileName.c_str(), true, true) }; + if (returndummy2 != 0) + { + std::cerr << "ERROR while setting up xout." << std::endl; + } + returndummy |= returndummy2; + } + } + else + { + returndummy = -2; + std::cerr << "ERROR: No CommandLine option \"-out\" given!" << std::endl; + } + + /** Stop if some fatal errors occurred. */ + if (returndummy != 0) + { + return returndummy; + } + + elxout << std::endl; + + /** Declare a timer, start it and print the start time. */ + itk::TimeProbe totaltimer; + totaltimer.Start(); + elxout << "elastix is started at " << GetCurrentDateAndTime() << ".\n" << std::endl; + + /** Print where elastix was run. */ + elxout << "which elastix: " << argv[0] << std::endl; + itksys::SystemInformation info; + info.RunCPUCheck(); + info.RunOSCheck(); + info.RunMemoryCheck(); + elxout << "elastix runs at: " << info.GetHostname() << std::endl; + elxout << " " << info.GetOSName() << " " << info.GetOSRelease() << (info.Is64Bits() ? " (x64), " : ", ") + << info.GetOSVersion() << std::endl; + elxout << " with " << info.GetTotalPhysicalMemory() << " MB memory, and " << info.GetNumberOfPhysicalCPU() + << " cores @ " << static_cast(info.GetProcessorClockFrequency()) << " MHz." << std::endl; + + + /** + * ********************* START REGISTRATION ********************* + * + * Do the (possibly multiple) registration(s). + */ + + + const int result = [parameterFileList, outFolder, argMap] { + const auto nrOfParameterFiles = parameterFileList.size(); + + const auto fixedImageFilename = argMap.at("-f"); + const auto movingImageFilename = argMap.at("-m"); + + using elastix::ELASTIX; + ELASTIX elastixObject; + const auto fixedImage = ReadImage(fixedImageFilename); + const auto movingImage = ReadImage(movingImageFilename); + + std::vector parameterMaps(nrOfParameterFiles); + + for (std::size_t i{}; i < nrOfParameterFiles; ++i) + { + const auto parameterFileParser = itk::ParameterFileParser::New(); + parameterFileParser->SetParameterFileName(parameterFileList[i]); + parameterFileParser->ReadParameterFile(); + auto parameterMap = parameterFileParser->GetParameterMap(); + + parameterMap["FixedImageDimension"] = { std::to_string(fixedImage->GetImageDimension()) }; + parameterMap["MovingImageDimension"] = { std::to_string(movingImage->GetImageDimension()) }; + parameterMaps[i] = parameterMap; + } + + const bool performLogging{ true }; + const bool performCout{ true }; + ELASTIX::ImagePointer fixedMask{}; + ELASTIX::ImagePointer movingMask{}; + + return elastixObject.RegisterImages( + fixedImage, movingImage, parameterMaps, outFolder, performLogging, performCout, fixedMask, movingMask); + }(); + + + elxout << "-------------------------------------------------------------------------\n" << std::endl; + + /** Stop totaltimer and print it. */ + totaltimer.Stop(); + elxout << "Total time elapsed: " << ConvertSecondsToDHMS(totaltimer.GetMean(), 1) << ".\n" << std::endl; + + /** Exit and return the error code. */ + return result; + +} // end Main + + +int +main(int argc, char ** argv) +{ + try + { + return Main(argc, argv); + } + catch (const std::exception & stdException) + { + std::cerr << "An error occurred! Exception: " << stdException.what() << '\n'; + return EXIT_FAILURE; + } +} From 2ff273eb4002dc7f273e2d82c425b2a51475bb26 Mon Sep 17 00:00:00 2001 From: Niels Dekker Date: Thu, 10 Dec 2020 17:37:36 +0100 Subject: [PATCH 2/2] WIP: Test ElastixLibBasedExe instead of the elastix executable --- Testing/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Testing/CMakeLists.txt b/Testing/CMakeLists.txt index 17a6ae728..e1213c480 100644 --- a/Testing/CMakeLists.txt +++ b/Testing/CMakeLists.txt @@ -301,7 +301,7 @@ macro( elx_add_run_test testbasename howtocompare ) # Set maximum test length to 10 minutes add_test( NAME ${testname}_OUTPUT CONFIGURATIONS Release - COMMAND ${EXECUTABLE_OUTPUT_PATH}/elastix + COMMAND ${EXECUTABLE_OUTPUT_PATH}/ElastixLibBasedExe ${elastixargs} -out ${output_dir} ) set_tests_properties( ${testname}_OUTPUT PROPERTIES TIMEOUT 600 ) @@ -449,7 +449,7 @@ file( MAKE_DIRECTORY "${TestOutputDir}/OutputDirectoryPathContainingSpacesTest" file( MAKE_DIRECTORY "${TestOutputDir}/OutputDirectoryPathContainingSpacesTest/path with spaces" ) file( MAKE_DIRECTORY "${TestOutputDir}/OutputDirectoryPathContainingSpacesTest/path with spaces/name with spaces" ) add_test( NAME OutputDirectoryPathContainingSpacesTest - COMMAND ${EXECUTABLE_OUTPUT_PATH}/elastix + COMMAND ${EXECUTABLE_OUTPUT_PATH}/ElastixLibBasedExe -f "${TestDataDir}/2D_2x2_square_object_at_(1,3).mhd" -m "${TestDataDir}/2D_2x2_square_object_at_(2,1).mhd" -p "${TestDataDir}/parameters.2D.NC.translation.ASGD.txt"