diff --git a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp index c95eeba307..63b4482a96 100644 --- a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp +++ b/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp @@ -1,310 +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 <mitkIOUtil.h> #include <mitkPreferenceListReaderOptionsFunctor.h> #include <mitkMAPRegistrationWrapper.h> #include <mitkMAPAlgorithmHelper.h> #include <mitkPointSet.h> #include <mitkImageTimeSelector.h> #include <itkStdStreamLogOutput.h> // MatchPoint #include <mapRegistrationAlgorithmInterface.h> #include <mapAlgorithmEvents.h> #include <mapAlgorithmWrapperEvent.h> #include <mapExceptionObjectMacros.h> #include <mapImageRegistrationAlgorithmInterface.h> #include <mapPointSetRegistrationAlgorithmInterface.h> #include <mapMaskedRegistrationAlgorithmInterface.h> +#include <mapMetaPropertyAlgorithmInterface.h> +#include <mapMetaProperty.h> #include <mapConvert.h> #include <mapDeploymentDLLAccess.h> #include <mapDeploymentDLLHandle.h> #include <mapRegistrationBase.h> +#include <nlohmann/json.hpp> + 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.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( + "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 paires."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); } bool ConfigureApplicationSettings(std::map<std::string, us::Any> parsedArgs, Settings& settings) { try { if (parsedArgs.size() == 0) return false; settings.movingFileName = us::any_cast<std::string>(parsedArgs["moving"]); settings.targetFileName = us::any_cast<std::string>(parsedArgs["target"]); settings.outFileName = us::any_cast<std::string>(parsedArgs["output"]); settings.algFileName = us::any_cast<std::string>(parsedArgs["algorithm"]); + if (parsedArgs.count("parameters") > 0) + { + settings.parameters = us::any_cast<std::string>(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 <typename TValueType> +map::core::MetaPropertyBase::Pointer +CheckCastAndSetProp(const nlohmann::json& value) +{ + map::core::MetaPropertyBase::Pointer prop; + + try + { + const auto castedValue = value.template get<TValueType>(); + prop = map::core::MetaProperty<TValueType>::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 <typename TValueType> +map::core::MetaPropertyBase::Pointer +CheckCastAndSetItkArrayProp(const nlohmann::json& valueSequence) +{ + using ArrayType = ::itk::Array<TValueType>; + ArrayType castedValue; + map::core::MetaPropertyBase::Pointer prop; + + try + { + castedValue.SetSize(valueSequence.size()); + + typename ::itk::Array<TValueType>::SizeValueType index = 0; + for (const auto& element : valueSequence) + { + const auto castedElement = element.template get<TValueType>(); + castedValue[index] = castedElement; + } + prop = map::core::MetaProperty<::itk::Array<TValueType>>::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<int>(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned int)) { + metaProp = CheckCastAndSetProp<unsigned int>(value); + } + else if (pInfo->getTypeInfo() == typeid(long)) { + metaProp = CheckCastAndSetProp<long>(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned long)) { + metaProp = CheckCastAndSetProp<unsigned long>(value); + } + else if (pInfo->getTypeInfo() == typeid(float)) { + metaProp = CheckCastAndSetProp<float>(value); + } + else if (pInfo->getTypeInfo() == typeid(double)) { + metaProp = CheckCastAndSetProp<double>(value); + } + else if (pInfo->getTypeInfo() == typeid(::itk::Array<double>)) { + 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<map::core::String>::New(value).GetPointer(); + } + + return metaProp; +}; + void OnMapAlgorithmEvent(::itk::Object*, const itk::EventObject& event, void*) { const map::events::AlgorithmEvent* pAlgEvent = dynamic_cast<const map::events::AlgorithmEvent*>(&event); const map::events::AlgorithmWrapperEvent* pWrapEvent = dynamic_cast<const map::events::AlgorithmWrapperEvent*>(&event); const map::events::InitializingAlgorithmEvent* pInitEvent = dynamic_cast<const map::events::InitializingAlgorithmEvent*>(&event); const map::events::StartingAlgorithmEvent* pStartEvent = dynamic_cast<const map::events::StartingAlgorithmEvent*>(&event); const map::events::StoppingAlgorithmEvent* pStoppingEvent = dynamic_cast<const map::events::StoppingAlgorithmEvent*>(&event); const map::events::StoppedAlgorithmEvent* pStoppedEvent = dynamic_cast<const map::events::StoppedAlgorithmEvent*>(&event); const map::events::FinalizingAlgorithmEvent* pFinalizingEvent = dynamic_cast<const map::events::FinalizingAlgorithmEvent*>(&event); const map::events::FinalizedAlgorithmEvent* pFinalizedEvent = dynamic_cast<const map::events::FinalizedAlgorithmEvent*>(&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; Settings settings; mitkCommandLineParser parser; SetupParser(parser); const std::map<std::string, us::Any>& 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<map::algorithm::facet::MetaPropertyAlgorithmInterface*>(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<mitk::Image>(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<mitk::Image>(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; }