Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Executable linked to elastix.lib, offering subset of elastix.exe functionality #230

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
352 changes: 352 additions & 0 deletions Core/Main/ElastixLibBasedExe.cxx
Original file line number Diff line number Diff line change
@@ -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 <Core/elxVersionMacros.h>
#include "itkUseMevisDicomTiff.h"

// ITK header files:
#include <itkTimeProbe.h>
#include <itksys/SystemInformation.hxx>
#include <itksys/SystemTools.hxx>

// Standard C++ header files:
#include <cassert>
#include <climits> // For UINT_MAX.
#include <cstddef> // For size_t.
#include <deque>
#include <iostream>
#include <limits>
#include <vector>

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<float>;

// 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<ImageType>::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<std::size_t>::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<std::string> parameterFileList;
std::string outFolder;

/** Put command line parameters into parameterFileList. */
for (unsigned int i = 1; static_cast<long>(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<unsigned int>(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<ELASTIX::ParameterMapType> 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;
}
}
4 changes: 2 additions & 2 deletions Testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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"
Expand Down