diff --git a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt index 8fbb245098..42465f083c 100644 --- a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt +++ b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt @@ -1,34 +1,34 @@ option(BUILD_MatchPointCmdApps "Build commandline tools for the MatchPoint module" OFF) if(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of CmdApps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( cmdapps - StitchImagesMiniApp^^ - MatchImageMiniApp^^ - MapImageMiniApp^^ + StitchImages^^ + MatchImage^^ + MapImage^^ ) foreach(cmdapp ${cmdapps}) # extract cmd name and dependencies string(REPLACE "^^" "\\;" cmdapp_info ${cmdapp}) set(cmdapp_info_list ${cmdapp_info}) list(GET cmdapp_info_list 0 appname) list(GET cmdapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkMatchPointRegistration ${dependencies_list} ) endforeach() endif(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MapImage.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MapImage.cpp diff --git a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp similarity index 62% rename from Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MatchImage.cpp index bbcb447ea4..17e05af7f5 100644 --- a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp +++ b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp @@ -1,318 +1,482 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCommandLineParser.h" #include #include #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include +#include +#include #include #include #include #include +#include + struct Settings { std::string movingFileName = ""; std::string targetFileName = ""; std::string outFileName = ""; std::string algFileName = ""; + std::string parameters = ""; }; void SetupParser(mitkCommandLineParser& parser) { - parser.setTitle("Image Matcher"); + parser.setTitle("Match Image"); parser.setCategory("Registration Tools"); parser.setDescription(""); parser.setContributor("MIC, German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); // Add command line argument names parser.beginGroup("Required I/O parameters"); parser.addArgument( "moving", "m", mitkCommandLineParser::File, "Moving image files", "Path to the data that should be registred into the target space.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "target", "t", mitkCommandLineParser::File, "Tareget image files", "Path to the data that should be the target data on which the moving data should be registered.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "algorithm", "a", mitkCommandLineParser::File, "Registration algorithm", "Path to the registration algorithm that should be used for registration.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file path", "Path to the generated registration.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); - parser.addArgument("template", - "t", - mitkCommandLineParser::File, - "Output template image.", - "File path to an image that serves as template for the output geometry.", - us::Any(), - false, false, false, mitkCommandLineParser::Input); parser.addArgument( - "registrations", "r", mitkCommandLineParser::StringList, "Registration files", "Pathes to the registrations that should be used to map the input images. If this parameter is not set, identity transforms are assumed. If this parameter is set, it must have the same number of entries then the parameter inputs. If you want to use and identity transform for a specific input, specify an empty string. The application assumes that inputs and registrations have the same order, so the n-th input should use thr n-th registration.", us::Any(), true, false, false, mitkCommandLineParser::Input); + "parameters", "p", mitkCommandLineParser::String, "Parameters", "Json string containing a json object that contains the parameters that should be passed to the algorithm as key value pairs."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); } bool ConfigureApplicationSettings(std::map parsedArgs, Settings& settings) { try { if (parsedArgs.size() == 0) return false; settings.movingFileName = us::any_cast(parsedArgs["moving"]); settings.targetFileName = us::any_cast(parsedArgs["target"]); settings.outFileName = us::any_cast(parsedArgs["output"]); settings.algFileName = us::any_cast(parsedArgs["algorithm"]); + if (parsedArgs.count("parameters") > 0) + { + settings.parameters = us::any_cast(parsedArgs["parameters"]); + } } catch (...) { return false; } return true; } map::deployment::RegistrationAlgorithmBasePointer loadAlgorithm(const Settings& settings) { map::deployment::RegistrationAlgorithmBasePointer spAlgorithmBase = nullptr; std::cout << std::endl << "Load registration algorithm..." << std::endl; map::deployment::DLLHandle::Pointer spHandle = nullptr; spHandle = map::deployment::openDeploymentDLL(settings.algFileName); if (spHandle.IsNull()) { mapDefaultExceptionStaticMacro(<< "Cannot open deployed registration algorithm file."); } std::cout << "... libary opened..." << std::endl; std::cout << "Algorithm information: " << std::endl; spHandle->getAlgorithmUID().Print(std::cout, 2); std::cout << std::endl; //Now load the algorthm from DLL spAlgorithmBase = map::deployment::getRegistrationAlgorithm(spHandle); if (spAlgorithmBase.IsNotNull()) { std::cout << "... done" << std::endl << std::endl; } else { mapDefaultExceptionStaticMacro(<< "Cannot create algorithm instance"); } return spAlgorithmBase; }; mitk::Image::Pointer ExtractFirstFrame(const mitk::Image* dynamicImage) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(dynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } +template +map::core::MetaPropertyBase::Pointer +CheckCastAndSetProp(const nlohmann::json& value) +{ + map::core::MetaPropertyBase::Pointer prop; + + try + { + const auto castedValue = value.get(); + prop = map::core::MetaProperty::New(castedValue).GetPointer(); + } + catch (const std::exception& e) + { + MITK_ERROR << "Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name() << ". Details: " << e.what(); + } + catch (...) + { + MITK_ERROR << "Unkown error. Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name(); + } + + return prop; +}; + +template +map::core::MetaPropertyBase::Pointer +CheckCastAndSetItkArrayProp(const nlohmann::json& valueSequence) +{ + using ArrayType = ::itk::Array; + ArrayType castedValue; + map::core::MetaPropertyBase::Pointer prop; + + try + { + castedValue.SetSize(valueSequence.size()); + + typename ::itk::Array::SizeValueType index = 0; + for (const auto& element : valueSequence) + { + const auto castedElement = element.template get(); + castedValue[index] = castedElement; + } + prop = map::core::MetaProperty<::itk::Array>::New(castedValue).GetPointer(); + } + catch (const std::exception& e) + { + MITK_ERROR << "Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name() << ". Details: " << e.what(); + } + catch (...) + { + MITK_ERROR << "Unkown error. Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name(); + } + + return prop; +}; + +::map::core::MetaPropertyBase::Pointer +WrapIntoMetaProperty(const ::map::algorithm::MetaPropertyInfo* pInfo, const nlohmann::json& value) +{ + map::core::MetaPropertyBase::Pointer metaProp; + + if (pInfo == nullptr) + { + return metaProp; + } + + if (pInfo->getTypeInfo() == typeid(int)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned int)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(long)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned long)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(float)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(double)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(::itk::Array)) { + metaProp = CheckCastAndSetItkArrayProp< double >(value); + } + else if (pInfo->getTypeInfo() == typeid(bool)) { + metaProp = CheckCastAndSetProp< bool >(value); + } + else if (pInfo->getTypeInfo() == typeid(::map::core::String)) + { + metaProp = map::core::MetaProperty::New(value).GetPointer(); + } + + return metaProp; +}; + void OnMapAlgorithmEvent(::itk::Object*, const itk::EventObject& event, void*) { const map::events::AlgorithmEvent* pAlgEvent = dynamic_cast(&event); const map::events::AlgorithmWrapperEvent* pWrapEvent = dynamic_cast(&event); const map::events::InitializingAlgorithmEvent* pInitEvent = dynamic_cast(&event); const map::events::StartingAlgorithmEvent* pStartEvent = dynamic_cast(&event); const map::events::StoppingAlgorithmEvent* pStoppingEvent = dynamic_cast(&event); const map::events::StoppedAlgorithmEvent* pStoppedEvent = dynamic_cast(&event); const map::events::FinalizingAlgorithmEvent* pFinalizingEvent = dynamic_cast(&event); const map::events::FinalizedAlgorithmEvent* pFinalizedEvent = dynamic_cast(&event); if (pInitEvent) { std::cout <<"Initializing algorithm ..." << std::endl; } else if (pStartEvent) { std::cout <<"Starting algorithm ..." << std::endl; } else if (pStoppingEvent) { std::cout <<"Stopping algorithm ..." << std::endl; } else if (pStoppedEvent) { std::cout <<"Stopped algorithm ..." << std::endl; if (!pStoppedEvent->getComment().empty()) { std::cout <<"Stopping condition: " << pStoppedEvent->getComment() << std::endl; } } else if (pFinalizingEvent) { std::cout <<"Finalizing algorithm and results ..." << std::endl; } else if (pFinalizedEvent) { std::cout <<"Finalized algorithm ..." << std::endl; } else if (pAlgEvent && !pWrapEvent) { std::cout << pAlgEvent->getComment() << std::endl; } } int main(int argc, char* argv[]) { - std::cout << "MitkRegistrationMiniApp - Generic light weight image registration tool based on MatchPoint." << std::endl; + std::cout << "MitkMatchImage - Generic light weight image registration tool based on MatchPoint." << std::endl; Settings settings; mitkCommandLineParser parser; SetupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!ConfigureApplicationSettings(parsedArgs, settings)) { MITK_ERROR << "Command line arguments are invalid. To see the correct usage please call with -h or --help to show the help information."; return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } std::cout << std::endl << "*******************************************" << std::endl; std::cout << "Moving file: " << settings.movingFileName << std::endl; std::cout << "Target file: " << settings.targetFileName << std::endl; std::cout << "Output file: " << settings.outFileName << std::endl; std::cout << "Algorithm location: " << settings.algFileName << std::endl; - //load algorithm try { auto algorithm = loadAlgorithm(settings); auto command = ::itk::CStyleCommand::New(); command->SetCallback(OnMapAlgorithmEvent); algorithm->AddObserver(::map::events::AlgorithmEvent(), command); + auto metaPropInterface = dynamic_cast(algorithm.GetPointer()); + + if (!settings.parameters.empty()) + { + if (nullptr == metaPropInterface) + { + MITK_WARN << "loaded algorithm does not support custom parameterization. Passed user parameters are ignored."; + } + else + { + nlohmann::json paramMap; + + std::string parseError = ""; + try + { + paramMap = nlohmann::json::parse(settings.parameters); + } + catch (const std::exception& e) + { + parseError = e.what(); + } + if (!parseError.empty()) + { + mitkThrow() << "Cannot parametrize algorithm. Passed JSON parameter string seems to be invalid. Passed string: \"" << settings.parameters << "\". Error details: " << parseError; + } + + std::cout << "Configuring algorithm with user specified parameters ..." << std::endl; + + for (const auto& [key, val] : paramMap.items()) + { + const auto info = metaPropInterface->getPropertyInfo(key); + + if (info.IsNotNull()) + { + if (info->isWritable()) + { + std::cout << "Set meta property: " << key << " = " << val << std::endl; + ::map::core::MetaPropertyBase::Pointer prop = WrapIntoMetaProperty(info, val); + if (prop.IsNull()) + { + mitkThrow() << "Error. Cannot set specified meta property. Type conversion is not supported or value cannot be converted into type. Property name: " << info->getName() << "; property type: " << info->getTypeName(); + } + else + { + metaPropInterface->setProperty(key, prop); + } + } + else + { + mitkThrow() << "Cannot parametrize algorithm. A passed parameter is not writable for the algorithm. Violating parameter: \"" << key << "\"."; + } + } + else + { + auto knownProps = metaPropInterface->getPropertyInfos(); + std::ostringstream knownPropsNameString; + for (const auto& knownProp : knownProps) + { + knownPropsNameString << knownProp->getName() << "; "; + } + mitkThrow() << "Cannot parametrize algorithm. A parameter is unkown to algorithm. Unkown passed parameter: \"" << key << "\". Known parameters: " << knownPropsNameString.str(); + } + } + } + } + std::cout << "Load moving data..." << std::endl; auto movingImage = mitk::IOUtil::Load(settings.movingFileName); if (movingImage.IsNull()) { MITK_ERROR << "Cannot load moving image."; return EXIT_FAILURE; } if (movingImage->GetTimeSteps() > 1) { movingImage = mitk::SelectImageByTimeStep(movingImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Moving image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Load target data..." << std::endl; auto targetImage = mitk::IOUtil::Load(settings.targetFileName); if (targetImage.IsNull()) { MITK_ERROR << "Cannot load target image."; return EXIT_FAILURE; } if (targetImage->GetTimeSteps() > 1) { targetImage = mitk::SelectImageByTimeStep(targetImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Target image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Start registration...." << std::endl; mitk::MAPAlgorithmHelper helper(algorithm); helper.SetData(movingImage, targetImage); ::itk::StdStreamLogOutput::Pointer spStreamLogOutput = ::itk::StdStreamLogOutput::New(); spStreamLogOutput->SetStream(std::cout); map::core::Logbook::addAdditionalLogOutput(spStreamLogOutput); auto registration = helper.GetRegistration(); // wrap the registration in a data node if (registration.IsNull()) { MITK_ERROR << "No valid registration generated"; return EXIT_FAILURE; } auto regWrapper = mitk::MAPRegistrationWrapper::New(registration); std::cout << "Store registration...." << std::endl; mitk::IOUtil::Save(regWrapper, settings.outFileName); } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } return EXIT_SUCCESS; } diff --git a/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/StitchImages.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/StitchImages.cpp