diff --git a/CMake/mitkFunctionGetVersionDescription.cmake b/CMake/mitkFunctionGetVersionDescription.cmake index bb52fefe96..80c55c39c7 100644 --- a/CMake/mitkFunctionGetVersionDescription.cmake +++ b/CMake/mitkFunctionGetVersionDescription.cmake @@ -1,68 +1,71 @@ #! \brief Extract the version description from a local working copy #! #! If the given repository is a git repository, the functions runs the #! git rev-parse --exact-match HEAD command #! #! Information provided is stored in ${prefix}_REVISION_DESC an is #! \ul #! \li The exact tag if the HEAD of the source-tree has a tag #! \li the 'git describe' output, which is -<#Commits>-g #! \lu #! In case the working copy contains local changes, the ${prefix}_REVISION_DESC strings will contain #! a suffix [local changes]. #! #! The revision description can be overridden by a ${prefix}_CUSTOM_REVISION_DESC variable. #! #! \param source_dir The directory containing a working copy #! \param prefix A prefix to prepent to the variables containing #! the extracted information #! function(mitkFunctionGetVersionDescription source_dir prefix) if(NOT prefix) message(FATAL_ERROR "prefix argument not specified") endif() if(${prefix}_CUSTOM_REVISION_DESC) set(_wc_description ${${prefix}_CUSTOM_REVISION_DESC}) else() # initialize variable set(_wc_description "unknown_version") set(_dirty_repo_str "-local_changes") find_package(Git) if(GIT_FOUND) GIT_IS_REPO(${source_dir} _is_git_repo) if(_is_git_repo) execute_process(COMMAND ${GIT_EXECUTABLE} describe --exact-match --dirty=${_dirty_repo_str} WORKING_DIRECTORY ${source_dir} OUTPUT_VARIABLE _project_git_tagname - RESULT_VARIABLE _proper_version) + RESULT_VARIABLE _proper_version + ERROR_VARIABLE _error_msg) + if(_proper_version EQUAL 0) set(_wc_description ${_project_git_tagname}) else() # the execution failed, i.e. the HEAD has no tag, # for fallback string: execute again but without the --exact-match execute_process(COMMAND ${GIT_EXECUTABLE} describe --dirty=${_dirty_repo_str} WORKING_DIRECTORY ${source_dir} OUTPUT_VARIABLE _wc_description - RESULT_VARIABLE _proper_version) + RESULT_VARIABLE _proper_version + ERROR_VARIABLE _error_msg) if(NOT _proper_version EQUAL 0) # last fallback, i.e. working copy is a shallow clone, at least use # commit hash execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --dirty=${_dirty_repo_str} WORKING_DIRECTORY ${source_dir} OUTPUT_VARIABLE _wc_description) endif() endif() # remove newline at and of the string string(STRIP "${_wc_description}" _wc_description) endif() endif() endif() set(${prefix}_REVISION_DESC ${_wc_description} PARENT_SCOPE) endfunction() diff --git a/CTestConfig.cmake b/CTestConfig.cmake index 2abf872117..81b115e712 100644 --- a/CTestConfig.cmake +++ b/CTestConfig.cmake @@ -1,9 +1,5 @@ set(CTEST_PROJECT_NAME_SUPERBUILD "MITK-superbuild") set(CTEST_PROJECT_NAME "MITK") -set(CTEST_NIGHTLY_START_TIME "23:00:00 EDT") - -set(CTEST_DROP_METHOD "http") -set(CTEST_DROP_SITE "cdash.mitk.org") -set(CTEST_DROP_LOCATION "/submit.php?project=MITK") -set(CTEST_DROP_SITE_CDASH TRUE) +set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC") +set(CTEST_SUBMIT_URL "https://cdash.mitk.org/submit.php?project=MITK") diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox index 3d38945f38..a34c39f999 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox @@ -1,32 +1,32 @@ /** \page PluginListGeneralPage List of General Purpose Plugins \li \subpage org_mitk_views_basicimageprocessing \li \subpage org_mitk_views_datamanager \li \subpage org_mitk_views_properties - \li \subpage org_mitk_gui_qt_dicom + \li \subpage org_mitk_editors_dicombrowser \li \subpage org_mitk_gui_qt_dicominspector - \li \subpage org_mitk_gui_qt_imagecropper + \li \subpage org_mitk_views_imagecropper \li \subpage org_mitk_views_imagenavigator \li \subpage org_blueberry_ui_qt_log \li \subpage org_mitk_gui_qt_matchpoint_algorithm_batch - \li \subpage org_mitk_gui_qt_matchpoint_algorithm_browser - \li \subpage org_mitk_gui_qt_matchpoint_algorithm_control - \li \subpage org_mitk_gui_qt_matchpoint_evaluator - \li \subpage org_mitk_gui_qt_matchpoint_framereg - \li \subpage org_mitk_gui_qt_matchpoint_manipulator - \li \subpage org_mitk_gui_qt_matchpoint_mapper - \li \subpage org_mitk_gui_qt_matchpoint_visualizer + \li \subpage org_mitk_views_qt_matchpoint_algorithm_browser + \li \subpage org_mitk_views_qt_matchpoint_algorithm_control + \li \subpage org_mitk_views_qt_matchpoint_evaluator + \li \subpage org_mitk_views_qt_matchpoint_framereg + \li \subpage org_mitk_views_qt_matchpoint_manipulator + \li \subpage org_mitk_views_matchpoint_mapper + \li \subpage org_mitk_views_qt_matchpoint_visualizer \li \subpage org_mitk_gui_qt_measurementtoolbox \li \subpage org_mitk_views_moviemaker \li \subpage org_mitk_views_multilabelsegmentation \li \subpage org_mitk_views_pointsetinteraction \li \subpage org_mitk_gui_qt_python \li \subpage org_mitk_gui_qt_remeshing \li \subpage org_mitk_views_screenshotmaker \li \subpage org_mitk_views_segmentation \li \subpage org_mitk_gui_qt_flow_segmentation \li \subpage org_mitk_gui_qt_viewnavigator \li \subpage org_mitk_views_volumevisualization */ diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox index 26d569a356..2bf2a11b12 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox @@ -1,84 +1,84 @@ /** \page PluginListPage MITK Plugin Manuals The plugins and bundles provide much of the extended functionality of MITK. Each encapsulates a solution to a problem and associated features. This way one can easily assemble the necessary capabilites for a workflow without adding a lot of bloat, by combining plugins as needed. \subpage PluginListGeneralPage \subpage PluginListSpecificPage */ diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox index 1edb2f4d54..8be8a5f620 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox @@ -1,45 +1,45 @@ /** \page PluginListSpecificPage List of Application-specific Plugins \li \subpage org_mitk_gui_qt_aicpregistration \li \subpage org_mitk_gui_qt_cest \li \subpage org_mitk_gui_qt_classificationsegmentation \li \subpage org_mitk_gui_qt_flowapplication \li \subpage org_mitk_views_cmdlinemodules \li \subpage org_mitk_gui_qt_pharmacokinetics_concentration_mri - \li \subpage org_mitk_gui_qt_pharmacokinetics_mri + \li \subpage org_mitk_views_pharmacokinetics_mri \li \subpage org_mitk_gui_qt_pharmacokinetics_pet \li \subpage org_mitk_gui_qt_eventrecorder \li \subpage org_mitk_gui_qt_examples \li \subpage org_mitk_gui_qt_geometrytools \li \subpage org_mitk_gui_qt_igtexample \li \subpage org_mitk_gui_qt_igtlplugin \li \subpage org_mitk_gui_qt_igttracking \li \subpage org_mitk_gui_qt_igttrackingsemiautomaticmeasurement \li \subpage org_mitk_views_imagestatistics \li \subpage org_mitk_gui_qt_lasercontrol \li \subpage org_mitk_gui_qt_fit_demo \li \subpage org_mitk_gui_qt_fit_genericfitting \li \subpage org_mitk_gui_qt_fit_inspector \li \subpage org_mitkexamplesopencv \li \subpage org_mitk_gui_qt_overlaymanager \li \subpage org_mitk_gui_qt_mitkphenotyping \li \subpage org_mitk_gui_qt_photoacoustics_pausmotioncompensation \li \subpage org_mitk_example_gui_pcaexample \li \subpage org_mitk_gui_qt_preprocessing_resampling \li \subpage org_mitk_gui_qt_pharmacokinetics_curvedescriptor \li \subpage org_mitk_gui_qt_photoacoustics_imageprocessing \li \subpage org_mitk_gui_qt_pharmacokinetics_simulation \li \subpage org_mitk_gui_qt_pointsetinteractionmultispectrum \li \subpage org_mitk_gui_qt_renderwindowmanager \li \subpage org_mitk_gui_qt_photoacoustics_spectralunmixing \li \subpage org_mitk_gui_qt_spectrocamrecorder \li \subpage org_surfacematerialeditor \li \subpage org_blueberry_ui_qt_objectinspector \li \subpage org_toftutorial \li \subpage org_mitk_gui_qt_ultrasound \li \subpage org_mitk_gui_qt_igt_app_echotrack \li \subpage org_mitk_gui_qt_xnat */ \ No newline at end of file diff --git a/Modules/MatchPointRegistration/CMakeLists.txt b/Modules/MatchPointRegistration/CMakeLists.txt index 97bb55792b..acbcd26ff3 100644 --- a/Modules/MatchPointRegistration/CMakeLists.txt +++ b/Modules/MatchPointRegistration/CMakeLists.txt @@ -1,25 +1,28 @@ MITK_CREATE_MODULE( - INCLUDE_DIRS PUBLIC Rendering Helper algorithms + INCLUDE_DIRS + PUBLIC algorithms + PRIVATE src/Helper src/Rendering DEPENDS MitkCore MitkSceneSerializationBase PACKAGE_DEPENDS PUBLIC MatchPoint ) if(TARGET ${MODULE_TARGET}) set(ALG_PROFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/algorithms) include(${MatchPoint_SOURCE_DIR}/CMake/mapFunctionCreateAlgorithmProfile.cmake) file(GLOB ALG_PROFILE_FILES LIST_DIRECTORIES false RELATIVE ${ALG_PROFILE_DIR} "${ALG_PROFILE_DIR}/*.profile") foreach(profile_file ${ALG_PROFILE_FILES}) get_filename_component(profile_name ${profile_file} NAME_WE) MESSAGE(STATUS "... generate MDRA profile ${profile_name} (from ${profile_file})...") CREATE_ALGORITHM_PROFILE(${profile_name} ${ALG_PROFILE_DIR}/${profile_file}) endforeach(profile_file) ADD_SUBDIRECTORY(autoload/IO) ADD_SUBDIRECTORY(deployment) if(BUILD_TESTING) ADD_SUBDIRECTORY(Testing) endif(BUILD_TESTING) + ADD_SUBDIRECTORY(cmdapps) endif() diff --git a/Modules/MatchPointRegistration/Testing/files.cmake b/Modules/MatchPointRegistration/Testing/files.cmake index 5efdc15458..b1268f0481 100644 --- a/Modules/MatchPointRegistration/Testing/files.cmake +++ b/Modules/MatchPointRegistration/Testing/files.cmake @@ -1,3 +1,4 @@ SET(MODULE_TESTS mitkTimeFramesRegistrationHelperTest.cpp + itkStitchImageFilterTest.cpp ) \ No newline at end of file diff --git a/Modules/MatchPointRegistration/Testing/itkStitchImageFilterTest.cpp b/Modules/MatchPointRegistration/Testing/itkStitchImageFilterTest.cpp new file mode 100644 index 0000000000..10c37646e5 --- /dev/null +++ b/Modules/MatchPointRegistration/Testing/itkStitchImageFilterTest.cpp @@ -0,0 +1,160 @@ +/*============================================================================ + +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 "mitkTestingMacros.h" +#include "mitkTestFixture.h" +#include "mitkTestDynamicImageGenerator.h" + +#include "itkStitchImageFilter.h" +#include "itkTranslationTransform.h" +#include "itkNearestNeighborInterpolateImageFunction.h" + +class itkStitchImageFilterTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(itkStitchImageFilterTestSuite); + // Test the append method + MITK_TEST(StitchWithNoTransformAndNoInterp); + MITK_TEST(StitchWithNoInterp); + MITK_TEST(Stitch); + CPPUNIT_TEST_SUITE_END(); + + using InputImageType = mitk::TestImageType; + using OutputImageType = itk::Image; +private: + using FilterType = itk::StitchImageFilter; + FilterType::Pointer m_Filter; + + InputImageType::Pointer m_Input1; + InputImageType::Pointer m_Input2; + InputImageType::Pointer m_Input3; + +public: + void setUp() override + { + InputImageType::PointType origin; + origin.Fill(0.); + InputImageType::SpacingType spacing; + spacing.Fill(1.); + spacing[1] = 5.; + + m_Filter = FilterType::New(); + m_Input1 = mitk::GenerateTestImage(1); + m_Input1->SetSpacing(spacing); + + m_Input2 = mitk::GenerateTestImage(10); + origin[1] = 10.; + m_Input2->SetOrigin(origin); + m_Input2->SetSpacing(spacing); + + m_Input3 = mitk::GenerateTestImage(100); + origin[1] = 20.; + m_Input3->SetOrigin(origin); + m_Input3->SetSpacing(spacing); + + FilterType::SizeType size = { 3, 9 }; + m_Filter->SetDefaultPixelValue(1000); + m_Filter->SetSize(size); + m_Filter->SetOutputSpacing(spacing); + } + + void tearDown() override + { + } + + bool CheckPixels(const OutputImageType* image, const std::vector& pixels) + { + bool result = true; + + itk::ImageRegionConstIteratorWithIndex iter(image, image->GetLargestPossibleRegion()); + auto refIter = pixels.begin(); + iter.GoToBegin(); + while (!iter.IsAtEnd()) + { + if (refIter == pixels.end()) + { + std::cerr << "Error image to check has a different pixel count then the reference pixel value vector."<SetInput(0, m_Input1); + m_Filter->SetInput(1, m_Input2); + m_Filter->SetInput(2, m_Input3); + + m_Filter->Update(); + auto output = m_Filter->GetOutput(); + + CPPUNIT_ASSERT(CheckPixels(output, {1, 2, 3, 4, 5, 6, 8.5, 14, 19.5, 40, 50, 60, 85, 140, 195, 400, 500, 600, 700, 800, 900, 1000, 1000, 1000, 1000, 1000, 1000})); + } + + void StitchWithNoInterp() + { + m_Filter->SetInput(0, m_Input1); + + using TranslationType = itk::TranslationTransform; + TranslationType::OutputVectorType offset; + offset[0] = 0.; + offset[1] = -5.; + auto translation1 = TranslationType::New(); + translation1->SetOffset(offset); + m_Filter->SetInput(1, m_Input2, translation1); + + offset[1] = -10.; + auto translation2 = TranslationType::New(); + translation2->SetOffset(offset); + m_Filter->SetInput(2, m_Input3, translation2); + + m_Filter->Update(); + auto output = m_Filter->GetOutput(); + + CPPUNIT_ASSERT(CheckPixels(output, { 1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80,90,100,200,300,400,500,600,700,800,900})); + } + + void Stitch() + { + using TranslationType = itk::TranslationTransform; + TranslationType::OutputVectorType offset; + offset[0] = 0; + offset[1] = -7.5; + auto translation1 = TranslationType::New(); + translation1->SetOffset(offset); + + offset[1] = -12.5; + auto translation2 = TranslationType::New(); + translation2->SetOffset(offset); + + m_Filter->SetInput(0, m_Input1); + m_Filter->SetInput(1, m_Input2, translation1, itk::NearestNeighborInterpolateImageFunction::New()); + m_Filter->SetInput(2, m_Input3, translation2); + + m_Filter->Update(); + auto output = m_Filter->GetOutput(); + + CPPUNIT_ASSERT(CheckPixels(output, { 1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80,90,100,200,300,250,350,450,550,650,750 })); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(itkStitchImageFilter) diff --git a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt new file mode 100644 index 0000000000..f02ab70bc2 --- /dev/null +++ b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt @@ -0,0 +1,33 @@ +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^^ + ) + + 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} + PACKAGE_DEPENDS ITK + ) + endforeach() + +endif(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp new file mode 100644 index 0000000000..ef8fc82bd2 --- /dev/null +++ b/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp @@ -0,0 +1,226 @@ +/*============================================================================ + +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. + +============================================================================*/ + +// std includes +#include +#include + +// itk includes +#include "itksys/SystemTools.hxx" + +// CTK includes +#include "mitkCommandLineParser.h" + +// MITK includes +#include +#include +#include +#include +#include + +mitkCommandLineParser::StringContainerType inFilenames; +mitkCommandLineParser::StringContainerType regFilenames; +std::string outFileName; +std::string refGeometryFileName; + +std::vector images; +std::vector registrations; +mitk::BaseGeometry::Pointer refGeometry; +mitk::ImageMappingInterpolator::Type interpolatorType = mitk::ImageMappingInterpolator::Linear; +double paddingValue = 0; +itk::StitchStrategy stitchStratgy = itk::StitchStrategy::Mean; + +void setupParser(mitkCommandLineParser& parser) +{ + // set general information about your MiniApp + parser.setCategory("Mapping Tools"); + parser.setTitle("Stitch 3D Images"); + parser.setDescription("MiniApp that allows to map and stitch 3D images into a given output geometry."); + parser.setContributor("DKFZ MIC"); + //! [create parser] + + //! [add arguments] + // how should arguments be prefixed + parser.setArgumentPrefix("--", "-"); + // add each argument, unless specified otherwise each argument is optional + // see mitkCommandLineParser::addArgument for more information + parser.beginGroup("Required I/O parameters"); + parser.addArgument( + "inputs", "i", mitkCommandLineParser::StringList, "Input files", "Pathes to the input images that should be mapped and stitched", us::Any(), false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", + "o", + mitkCommandLineParser::File, + "Output file path", + "Path to the fused 3D+t image.", + 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); + parser.addArgument("interpolator", "n", mitkCommandLineParser::Int, "Interpolator type", "Interpolator used for mapping the images. Default: 2; allowed values: 1: Nearest Neighbour, 2: Linear, 3: BSpline 3, 4: WSinc Hamming, 5: WSinc Welch", us::Any(2), true); + parser.addArgument("strategy", "s", mitkCommandLineParser::Int, "Stitch strategy", "Strategy used for stitching the images. 0: Mean -> computes the mean value of all input images that cover an output pixel (default strategy). 1: BorderDistance -> Uses the input pixel that has the largest minimal distance to its image borders", us::Any(2), true); + parser.addArgument("padding", "p", mitkCommandLineParser::Float, "Padding value", "Value used for output voxels that are not covered by any input image.", us::Any(0.), true); + parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); + parser.endGroup(); + //! [add arguments] +} + +bool configureApplicationSettings(std::map parsedArgs) +{ + try + { + if (parsedArgs.size() == 0) + return false; + + inFilenames = us::any_cast(parsedArgs["inputs"]); + outFileName = us::any_cast(parsedArgs["output"]); + + if (parsedArgs.count("template")) + { + refGeometryFileName = us::any_cast(parsedArgs["template"]); + } + + if (parsedArgs.count("registrations")) + { + regFilenames = us::any_cast(parsedArgs["registrations"]); + } + else + { + regFilenames.resize(inFilenames.size()); + std::fill(regFilenames.begin(), regFilenames.end(), ""); + } + + if (parsedArgs.count("interpolator")) + { + auto interpolator = us::any_cast(parsedArgs["interpolator"]); + interpolatorType = static_cast(interpolator); + } + + if (parsedArgs.count("padding")) + { + paddingValue = us::any_cast(parsedArgs["padding"]); + } + + if (parsedArgs.count("strategy")) + { + auto temp = us::any_cast(parsedArgs["strategy"]); + stitchStratgy = static_cast(temp); + } + } + catch (...) + { + return false; + } + + return true; +} + +int main(int argc, char* argv[]) +{ + mitkCommandLineParser parser; + setupParser(parser); + + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); + + const std::map& parsedArgs = parser.parseArguments(argc, argv); + if (!configureApplicationSettings(parsedArgs)) + { + 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; + } + + if(regFilenames.size() != inFilenames.size()) + { + MITK_ERROR << "Cannot stitch inputs. The number of specified registrations does not match the number of inputs."; + return EXIT_FAILURE; + } + + //! [do processing] + try + { + std::cout << "Load images:" << std::endl; + + unsigned int index = 0; + for (auto path : inFilenames) + { + std::cout << "#"<(path, &readerFilterFunctor); + images.push_back(image.GetPointer()); + if (regFilenames[index].empty()) + { + std::cout << " associated registration: identity" << std::endl; + registrations.push_back(mitk::GenerateIdentityRegistration3D().GetPointer()); + } + else + { + std::cout << " associated registration: " << regFilenames[index] << std::endl; + auto reg = mitk::IOUtil::Load(regFilenames[index]); + registrations.push_back(reg.GetPointer()); + } + ++index; + } + std::cout << "Reference image: " << refGeometryFileName << std::endl << std::endl; + auto refImage = mitk::IOUtil::Load(refGeometryFileName, &readerFilterFunctor); + if (refImage.IsNotNull()) + { + refGeometry = refImage->GetGeometry(); + } + std::cout << "Padding value: " << paddingValue << std::endl; + std::cout << "Stitch strategy: "; + if (itk::StitchStrategy::Mean == stitchStratgy) + { + std::cout << "Mean " << std::endl; + } + else + { + std::cout << "BorderDistance" << std::endl; + } + + std::cout << "Stitch the images ..." << std::endl; + + auto output = mitk::StitchImages(images, registrations, refGeometry,paddingValue,stitchStratgy,interpolatorType); + + std::cout << "Save output image: " << outFileName << std::endl; + + mitk::IOUtil::Save(output, outFileName); + + std::cout << "Processing finished." << std::endl; + + return EXIT_SUCCESS; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + MITK_ERROR << "Unexpected error encountered."; + return EXIT_FAILURE; + } +} diff --git a/Modules/MatchPointRegistration/files.cmake b/Modules/MatchPointRegistration/files.cmake index 1b9c8ff7e1..0bf01d264c 100644 --- a/Modules/MatchPointRegistration/files.cmake +++ b/Modules/MatchPointRegistration/files.cmake @@ -1,65 +1,32 @@ +file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE TPP_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.tpp") + set(CPP_FILES mitkMAPRegistrationWrapper.cpp mitkMAPRegistrationWrapperObjectFactory.cpp mitkRegEvaluationObjectFactory.cpp mitkRegEvaluationObject.cpp Helper/mitkUIDHelper.cpp Helper/mitkMAPAlgorithmHelper.cpp Helper/mitkMaskedAlgorithmHelper.cpp Helper/mitkRegistrationHelper.cpp Helper/mitkImageMappingHelper.cpp + Helper/mitkImageStitchingHelper.cpp Helper/mitkPointSetMappingHelper.cpp Helper/mitkResultNodeGenerationHelper.cpp Helper/mitkTimeFramesRegistrationHelper.cpp Rendering/mitkRegistrationWrapperMapper2D.cpp Rendering/mitkRegistrationWrapperMapper3D.cpp Rendering/mitkRegistrationWrapperMapperBase.cpp Rendering/mitkRegEvaluationMapper2D.cpp Rendering/mitkRegVisStyleProperty.cpp Rendering/mitkRegVisDirectionProperty.cpp Rendering/mitkRegVisColorStyleProperty.cpp Rendering/mitkRegVisPropertyTags.cpp Rendering/mitkRegVisHelper.cpp Rendering/mitkRegEvalStyleProperty.cpp Rendering/mitkRegEvalWipeStyleProperty.cpp ) -set(H_FILES - mitkMatchPointPropertyTags.h - mitkMAPRegistrationWrapper.h - mitkMAPRegistrationWrapperObjectFactory.h - mitkRegEvaluationObjectFactory.h - mitkRegEvaluationObject.h - algorithms/mitkMultiModalAffineDefaultRegistrationAlgorithm.h - algorithms/mitkMultiModalRigidDefaultRegistrationAlgorithm.h - algorithms/mitkMultiModalTransDefaultRegistrationAlgorithm.h - algorithms/mitkFastSymmetricForcesDemonsMultiResDefaultRegistrationAlgorithm.h - algorithms/mitkLevelSetMotionMultiResDefaultRegistrationAlgorithm.h - algorithms/mitkRigidClosedFormPointsDefaultRegistrationAlgorithm.h - algorithms/mitkRigidICPDefaultRegistrationAlgorithm.h - Helper/mitkUIDHelper.h - Helper/mitkMAPAlgorithmHelper.h - Helper/mitkMaskedAlgorithmHelper.h - Helper/mitkRegistrationHelper.h - Helper/mitkImageMappingHelper.h - Helper/mitkPointSetMappingHelper.h - Helper/mitkResultNodeGenerationHelper.h - Helper/mitkTimeFramesRegistrationHelper.h - Rendering/mitkRegistrationWrapperMapper2D.h - Rendering/mitkRegistrationWrapperMapper3D.h - Rendering/mitkRegistrationWrapperMapperBase.h - Rendering/mitkRegVisStyleProperty.h - Rendering/mitkRegVisDirectionProperty.h - Rendering/mitkRegVisColorStyleProperty.h - Rendering/mitkRegVisPropertyTags.h - Rendering/mitkRegVisHelper.h - Rendering/mitkRegEvaluationMapper2D.h - Rendering/mitkRegEvalStyleProperty.h - Rendering/mitkRegEvalWipeStyleProperty.h -) - -set(TPP_FILES -) - set(MOC_H_FILES ) diff --git a/Modules/MatchPointRegistration/Helper/QmitkAlgorithmListModel.h b/Modules/MatchPointRegistration/include/QmitkAlgorithmListModel.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/QmitkAlgorithmListModel.h rename to Modules/MatchPointRegistration/include/QmitkAlgorithmListModel.h diff --git a/Modules/MatchPointRegistration/Helper/QmitkMapPropertyDelegate.h b/Modules/MatchPointRegistration/include/QmitkMapPropertyDelegate.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/QmitkMapPropertyDelegate.h rename to Modules/MatchPointRegistration/include/QmitkMapPropertyDelegate.h diff --git a/Modules/MatchPointRegistration/include/itkStitchImageFilter.h b/Modules/MatchPointRegistration/include/itkStitchImageFilter.h new file mode 100644 index 0000000000..5d5edce5d0 --- /dev/null +++ b/Modules/MatchPointRegistration/include/itkStitchImageFilter.h @@ -0,0 +1,330 @@ +/*============================================================================ + +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. + +============================================================================*/ + +#ifndef itkStitchImageFilter_h +#define itkStitchImageFilter_h + +#include "itkFixedArray.h" +#include "itkTransform.h" +#include "itkImageRegionIterator.h" +#include "itkImageToImageFilter.h" +#include "itkLinearInterpolateImageFunction.h" +#include "itkSize.h" +#include "itkDefaultConvertPixelTraits.h" +#include "itkDataObjectDecorator.h" + + +namespace itk +{ + enum class StitchStrategy + { + Mean = 0, //use the mean value of all inputs that can provide a pixel vaule + BorderDistance = 1 //use the value that is largest minimal distance to its image borders (use e.g. if vaules tend to be not reliable at borders) + }; + + std::ostream& operator<< (std::ostream& os, const itk::StitchStrategy& strategy) + { + if (itk::StitchStrategy::Mean == strategy) + os << "Mean"; + else if (itk::StitchStrategy::BorderDistance == strategy) + os << "BorderDistance"; + else + os << "unkown"; + + return os; + }; + + /** \class StitchImageFilter + * \brief ITK filter that resamples/stitches multiple images into a given reference geometry. + * + * StitchImageFilter is similar to itk's ResampleImageFilter, but in difference to the last + * mentioned StitchImageFilter is able to resample multiple input images at once (with a transform + * for each input image). If multiple input images cover the output region the behavior depends on + * the StitchStragy: + * - Mean: a weighted sum of all voxels mapped input pixel values will be calculated. + * - BorderDistance: the voxels will be choosen that have the largest minimal distance to its own image borders. + * + * All other behaviors are similar to itk::ResampleImageFilter. See the filter's description for + * more details. + */ +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType = double, + typename TTransformPrecisionType = TInterpolatorPrecisionType> +class StitchImageFilter : + public ImageToImageFilter< TInputImage, TOutputImage > +{ +public: + /** Standard class typedefs. */ + typedef StitchImageFilter Self; + typedef ImageToImageFilter< TInputImage, TOutputImage > Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename InputImageType::ConstPointer InputImageConstPointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + typedef typename InputImageType::RegionType InputImageRegionType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(StitchImageFilter, ImageToImageFilter); + + /** Number of dimensions. */ + itkStaticConstMacro(ImageDimension, unsigned int, + TOutputImage::ImageDimension); + itkStaticConstMacro(InputImageDimension, unsigned int, + TInputImage::ImageDimension); + + /** base type for images of the current ImageDimension */ + typedef ImageBase< itkGetStaticConstMacro(ImageDimension) > ImageBaseType; + + /** + * Transform typedef. + */ + typedef Transform< TTransformPrecisionType, + itkGetStaticConstMacro(ImageDimension), + itkGetStaticConstMacro(ImageDimension) > TransformType; + typedef typename TransformType::ConstPointer TransformPointerType; + typedef DataObjectDecorator DecoratedTransformType; + typedef typename DecoratedTransformType::Pointer DecoratedTransformPointer; + + + /** Interpolator typedef. */ + typedef InterpolateImageFunction< InputImageType, + TInterpolatorPrecisionType > InterpolatorType; + typedef typename InterpolatorType::Pointer InterpolatorPointerType; + + typedef typename InterpolatorType::OutputType InterpolatorOutputType; + + typedef DefaultConvertPixelTraits< InterpolatorOutputType > InterpolatorConvertType; + + typedef typename InterpolatorConvertType::ComponentType ComponentType; + + typedef LinearInterpolateImageFunction< InputImageType, + TInterpolatorPrecisionType > LinearInterpolatorType; + typedef typename LinearInterpolatorType::Pointer + LinearInterpolatorPointerType; + + /** Image size typedef. */ + typedef Size< itkGetStaticConstMacro(ImageDimension) > SizeType; + + /** Image index typedef. */ + typedef typename TOutputImage::IndexType IndexType; + + /** Image point typedef. */ + typedef typename InterpolatorType::PointType PointType; + //typedef typename TOutputImage::PointType PointType; + + /** Image pixel value typedef. */ + typedef typename TOutputImage::PixelType PixelType; + typedef typename TInputImage::PixelType InputPixelType; + + typedef DefaultConvertPixelTraits PixelConvertType; + + typedef typename PixelConvertType::ComponentType PixelComponentType; + + /** Input pixel continuous index typdef */ + typedef ContinuousIndex< TTransformPrecisionType, ImageDimension > + ContinuousInputIndexType; + + /** Typedef to describe the output image region type. */ + typedef typename TOutputImage::RegionType OutputImageRegionType; + + /** Image spacing,origin and direction typedef */ + typedef typename TOutputImage::SpacingType SpacingType; + typedef typename TOutputImage::PointType OriginPointType; + typedef typename TOutputImage::DirectionType DirectionType; + + using Superclass::GetInput; + + /** Typedef the reference image type to be the ImageBase of the OutputImageType */ + typedef ImageBase ReferenceImageBaseType; + + using Superclass::SetInput; + void SetInput(const InputImageType* image) override; + void SetInput(unsigned int index, const InputImageType* image) override; + /** Convinience methods that allows setting of input image and its transform in + one call.*/ + virtual void SetInput(unsigned int index, const InputImageType* image, const TransformType* transform); + virtual void SetInput(unsigned int index, const InputImageType* image, const TransformType* transform, InterpolatorType* interpolator); + + const TransformType* GetTransform(unsigned int index) const; + + const InterpolatorType* GetInterpolator(unsigned int index) const; + + /** Get/Set the size of the output image. */ + itkSetMacro(Size, SizeType); + itkGetConstReferenceMacro(Size, SizeType); + + /** Get/Set the pixel value when a transformed pixel is outside of the + * image. The default default pixel value is 0. */ + itkSetMacro(DefaultPixelValue, PixelType); + itkGetConstReferenceMacro(DefaultPixelValue, PixelType); + + /** Set the output image spacing. */ + itkSetMacro(OutputSpacing, SpacingType); + virtual void SetOutputSpacing(const double *values); + + /** Get the output image spacing. */ + itkGetConstReferenceMacro(OutputSpacing, SpacingType); + + /** Set the output image origin. */ + itkSetMacro(OutputOrigin, OriginPointType); + virtual void SetOutputOrigin(const double *values); + + /** Get the output image origin. */ + itkGetConstReferenceMacro(OutputOrigin, OriginPointType); + + /** Set the output direciton cosine matrix. */ + itkSetMacro(OutputDirection, DirectionType); + itkGetConstReferenceMacro(OutputDirection, DirectionType); + + /** Helper method to set the output parameters based on this image. */ + void SetOutputParametersFromImage(const ImageBaseType *image); + + /** Set the start index of the output largest possible region. + * The default is an index of all zeros. */ + itkSetMacro(OutputStartIndex, IndexType); + + /** Get the start index of the output largest possible region. */ + itkGetConstReferenceMacro(OutputStartIndex, IndexType); + + /** Set a reference image to use to define the output information. + * By default, output information is specificed through the + * SetOutputSpacing, Origin, and Direction methods. Alternatively, + * this method can be used to specify an image from which to + * copy the information. UseReferenceImageOn must be set to utilize the + * reference image. */ + itkSetInputMacro(ReferenceImage, ReferenceImageBaseType); + + /** Get the reference image that is defining the output information. */ + itkGetInputMacro(ReferenceImage, ReferenceImageBaseType); + + /** Turn on/off whether a specified reference image should be used to define + * the output information. */ + itkSetMacro(UseReferenceImage, bool); + itkBooleanMacro(UseReferenceImage); + itkGetConstMacro(UseReferenceImage, bool); + + itkSetMacro(StitchStrategy, StitchStrategy); + itkGetConstMacro(StitchStrategy, StitchStrategy); + + /** StitchImageFilter produces an image which is a different size + * than its input. As such, it needs to provide an implementation + * for GenerateOutputInformation() in order to inform the pipeline + * execution model. The original documentation of this method is + * below. \sa ProcessObject::GenerateOutputInformaton() */ + virtual void GenerateOutputInformation() ITK_OVERRIDE; + + /** StitchImageFilter needs a different input requested region than + * the output requested region. As such, StitchImageFilter needs + * to provide an implementation for GenerateInputRequestedRegion() + * in order to inform the pipeline execution model. + * \sa ProcessObject::GenerateInputRequestedRegion() */ + virtual void GenerateInputRequestedRegion() ITK_OVERRIDE; + + /** Set up state of filter before multi-threading. + * InterpolatorType::SetInputImage is not thread-safe and hence + * has to be set up before ThreadedGenerateData */ + virtual void BeforeThreadedGenerateData() ITK_OVERRIDE; + + /** Set the state of the filter after multi-threading. */ + virtual void AfterThreadedGenerateData() ITK_OVERRIDE; + + /** Compute the Modified Time based on the changed components. */ + ModifiedTimeType GetMTime(void) const ITK_OVERRIDE; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro( OutputHasNumericTraitsCheck, + ( Concept::HasNumericTraits< PixelComponentType > ) ); + // End concept checking +#endif + +protected: + StitchImageFilter(); + ~StitchImageFilter() ITK_OVERRIDE {} + void PrintSelf(std::ostream & os, Indent indent) const ITK_OVERRIDE; + + /** Override VeriyInputInformation() since this filter's inputs do + * not need to occoupy the same physical space. + * + * \sa ProcessObject::VerifyInputInformation + */ + virtual void VerifyInputInformation() ITK_OVERRIDE { } + + /** StitchImageFilter can be implemented as a multithreaded filter. + * Therefore, this implementation provides a ThreadedGenerateData() + * routine which is called for each processing thread. The output + * image data is allocated automatically by the superclass prior + * to calling ThreadedGenerateData(). + * ThreadedGenerateData can only write to the portion of the output image + * specified by the parameter "outputRegionForThread" + * \sa ImageToImageFilter::ThreadedGenerateData(), + * ImageToImageFilter::GenerateData() */ + virtual void ThreadedGenerateData(const OutputImageRegionType & outputRegionForThread, + ThreadIdType threadId) ITK_OVERRIDE; + + /** Cast pixel from interpolator output to PixelType. */ + virtual PixelType CastPixelWithBoundsChecking( const InterpolatorOutputType value, + const ComponentType minComponent, + const ComponentType maxComponent) const; + + void SetTransform(unsigned int index, const TransformType* transform); + + /** Helper that ensures that a transform is specified for every input image. + If a input image has no specified transforms, an identity transform will + be created and set as default.*/ + void EnsureTransforms(); + + /** Helper that ensures that an interpolator is specified for every input image. + If a input image has no specified interpolator, a linear interpolator will + be created and set as default.*/ + void EnsureInterpolators(); + + static std::string GetTransformInputName(unsigned int index); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(StitchImageFilter); + + typedef std::vector InputImageVectorType; + typedef std::map TransformMapType; + typedef std::map InterpolatorMapType; + + InputImageVectorType GetInputs(); + TransformMapType GetTransforms(); + + InterpolatorMapType m_Interpolators; // Image function for + // interpolation + PixelType m_DefaultPixelValue; // default pixel value + // if the point is + // outside the image + SizeType m_Size; // Size of the output image + SpacingType m_OutputSpacing; // output image spacing + OriginPointType m_OutputOrigin; // output image origin + DirectionType m_OutputDirection; // output image direction cosines + IndexType m_OutputStartIndex; // output image start index + bool m_UseReferenceImage; + StitchStrategy m_StitchStrategy; +}; +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +#include "itkStitchImageFilter.tpp" +#endif + +#endif diff --git a/Modules/MatchPointRegistration/include/itkStitchImageFilter.tpp b/Modules/MatchPointRegistration/include/itkStitchImageFilter.tpp new file mode 100644 index 0000000000..1ec8334ada --- /dev/null +++ b/Modules/MatchPointRegistration/include/itkStitchImageFilter.tpp @@ -0,0 +1,639 @@ +/*============================================================================ + +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. + +============================================================================*/ + +#ifndef itkStitchImageFilter_hxx +#define itkStitchImageFilter_hxx + +#include "itkStitchImageFilter.h" +#include "itkObjectFactory.h" +#include "itkIdentityTransform.h" +#include "itkProgressReporter.h" +#include "itkImageRegionIteratorWithIndex.h" +#include "itkImageScanlineIterator.h" +#include "itkSpecialCoordinatesImage.h" +#include "itkDefaultConvertPixelTraits.h" +#include "itkSimpleDataObjectDecorator.h" + +#include + +namespace itk +{ + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::StitchImageFilter() : + m_OutputSpacing( 1.0 ), + m_OutputOrigin( 0.0 ), + m_UseReferenceImage( false ), + m_StitchStrategy(StitchStrategy::Mean) +{ + + m_Size.Fill( 0 ); + m_OutputStartIndex.Fill( 0 ); + + m_OutputDirection.SetIdentity(); + + // Pipeline input configuration + + // implicit input index set: + // #1 "ReferenceImage" optional + Self::AddOptionalInputName("ReferenceImage"); + + m_DefaultPixelValue + = NumericTraits::ZeroValue( m_DefaultPixelValue ); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > + void + StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > + ::SetInput(const InputImageType* image) +{ + this->SetInput(0, image, itk::IdentityTransform< TTransformPrecisionType, ImageDimension>::New().GetPointer(), LinearInterpolatorType::New().GetPointer()); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetInput(unsigned int index, const InputImageType* image) +{ + this->SetInput(index, image, itk::IdentityTransform< TTransformPrecisionType, ImageDimension>::New().GetPointer(), LinearInterpolatorType::New().GetPointer()); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetInput(unsigned int index, const InputImageType* image, const TransformType* transform) +{ + this->SetInput(index, image, transform, LinearInterpolatorType::New().GetPointer()); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetInput(unsigned int index, const InputImageType* image, const TransformType* transform, InterpolatorType* interpolator) +{ + Superclass::SetInput(index, image); + m_Interpolators[image] = interpolator; + + this->SetTransform(index, transform); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > + void + StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > + ::SetTransform(unsigned int index, const TransformType* transform) +{ + const auto transformName = this->GetTransformInputName(index); + typedef SimpleDataObjectDecorator< TransformPointerType > DecoratorType; + const DecoratorType* oldInput = itkDynamicCastInDebugMode< const DecoratorType* >(this->ProcessObject::GetInput(transformName)); + if (!oldInput || oldInput->Get() != transform) + { + typename DecoratorType::Pointer newInput = DecoratorType::New(); + // Process object is not const-correct so the const_cast is required here + newInput->Set(const_cast(transform)); + this->ProcessObject::SetInput(transformName, newInput); + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > + const typename StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType >::TransformType* + StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > + ::GetTransform(unsigned int index) const +{ + typedef SimpleDataObjectDecorator< TransformPointerType > DecoratorType; + const DecoratorType* input = itkDynamicCastInDebugMode< const DecoratorType* >(this->ProcessObject::GetInput(this->GetTransformInputName(index))); + + if (nullptr != input) + { + return input->Get(); + } + + return nullptr; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > + const typename StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType >::InterpolatorType* + StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > + ::GetInterpolator(unsigned int index) const +{ + auto input = this->GetInput(index); + if (m_Interpolators.find(input) != std::end(m_Interpolators)) + { + return m_Interpolators[input]; + } + + return nullptr; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetOutputSpacing(const double *spacing) +{ + SpacingType s; + for(unsigned int i = 0; i < TOutputImage::ImageDimension; ++i) + { + s[i] = static_cast< typename SpacingType::ValueType >(spacing[i]); + } + this->SetOutputSpacing(s); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetOutputOrigin(const double *origin) +{ + OriginPointType p(origin); + + this->SetOutputOrigin(p); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::SetOutputParametersFromImage(const ImageBaseType *image) +{ + this->SetOutputOrigin ( image->GetOrigin() ); + this->SetOutputSpacing ( image->GetSpacing() ); + this->SetOutputDirection ( image->GetDirection() ); + this->SetOutputStartIndex ( image->GetLargestPossibleRegion().GetIndex() ); + this->SetSize ( image->GetLargestPossibleRegion().GetSize() ); +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::BeforeThreadedGenerateData() +{ + this->EnsureInterpolators(); + this->EnsureTransforms(); + + for (const auto& interpolator : m_Interpolators) + { + interpolator.second->SetInputImage(interpolator.first); + } + + unsigned int nComponents + = DefaultConvertPixelTraits::GetNumberOfComponents( + m_DefaultPixelValue ); + + if (nComponents == 0) + { + PixelComponentType zeroComponent + = NumericTraits::ZeroValue( zeroComponent ); + nComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); + NumericTraits::SetLength(m_DefaultPixelValue, nComponents ); + for (unsigned int n=0; n +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::AfterThreadedGenerateData() +{ + // Disconnect input image from the interpolator + for (auto& interpolator : m_Interpolators) + { + interpolator.second->SetInputImage(ITK_NULLPTR); + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::ThreadedGenerateData(const OutputImageRegionType & outputRegionForThread, + ThreadIdType threadId) +{ + + if( outputRegionForThread.GetNumberOfPixels() == 0 ) + { + return; + } + + // Get the output pointers + OutputImageType* outputPtr = this->GetOutput(); + // Get this input pointers + InputImageVectorType inputs = this->GetInputs(); + TransformMapType transforms = this->GetTransforms(); + + std::map lowerIndices; + std::map upperIndices; + for (const auto& input : inputs) + { + const auto largestRegion = input->GetLargestPossibleRegion(); + lowerIndices[input] = largestRegion.GetIndex(); + upperIndices[input] = largestRegion.GetUpperIndex(); + } + + // Create an iterator that will walk the output region for this thread. + typedef ImageRegionIteratorWithIndex< OutputImageType > OutputIterator; + OutputIterator outIt(outputPtr, outputRegionForThread); + + // Define a few indices that will be used to translate from an input pixel + // to an output pixel + PointType outputPoint; // Coordinates of current output pixel + PointType inputPoint; // Coordinates of current input pixel + + ContinuousInputIndexType inputIndex; + + // Support for progress methods/callbacks + ProgressReporter progress(this, + threadId, + outputRegionForThread.GetNumberOfPixels()); + + // Min/max values of the output pixel type AND these values + // represented as the output type of the interpolator + const PixelComponentType minValue = NumericTraits< PixelComponentType >::NonpositiveMin(); + const PixelComponentType maxValue = NumericTraits< PixelComponentType >::max(); + + typedef typename InterpolatorType::OutputType OutputType; + const ComponentType minOutputValue = static_cast(minValue); + const ComponentType maxOutputValue = static_cast(maxValue); + + // Walk the output region + outIt.GoToBegin(); + + while (!outIt.IsAtEnd()) + { + // Determine the index of the current output pixel + outputPtr->TransformIndexToPhysicalPoint(outIt.GetIndex(), outputPoint); + + std::vector pixvals; + std::vector pixDistance; + + for (const auto& input : inputs) + { + // Compute corresponding input pixel position + inputPoint = transforms[input]->TransformPoint(outputPoint); + const bool isInsideInput = input->TransformPhysicalPointToContinuousIndex(inputPoint, inputIndex); + + // Evaluate input at right position and copy to the output + if (m_Interpolators[input]->IsInsideBuffer(inputIndex) && isInsideInput) + { + OutputType value = m_Interpolators[input]->EvaluateAtContinuousIndex(inputIndex); + pixvals.emplace_back(this->CastPixelWithBoundsChecking(value, minOutputValue, maxOutputValue)); + + ContinuousInputIndexType indexDistance; + const auto spacing = input->GetSpacing(); + + double minBorderDistance = std::numeric_limits::max(); + for (unsigned int i = 0; i < ImageDimension; ++i) + { + minBorderDistance = std::min(minBorderDistance, std::min(std::abs(lowerIndices[input][i] - inputIndex[i]) * spacing[i], std::abs(upperIndices[input][i] - inputIndex[i]) * spacing[i])); + } + pixDistance.emplace_back(minBorderDistance); + } + } + + if (!pixvals.empty()) + { //at least one input provided a value + if (StitchStrategy::Mean == m_StitchStrategy) + { + double sum = std::accumulate(pixvals.begin(), pixvals.end(), 0.0); + outIt.Set(sum / pixvals.size()); + } + else + { + auto finding = std::max_element(pixDistance.begin(), pixDistance.end()); + outIt.Set(pixvals[std::distance(pixDistance.begin(), finding)]); + } + } + else + { + outIt.Set(m_DefaultPixelValue); // default background value + } + + progress.CompletedPixel(); + ++outIt; + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +typename StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::PixelType +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::CastPixelWithBoundsChecking(const InterpolatorOutputType value, + const ComponentType minComponent, + const ComponentType maxComponent ) const +{ + const unsigned int nComponents = InterpolatorConvertType::GetNumberOfComponents(value); + PixelType outputValue; + + NumericTraits::SetLength( outputValue, nComponents ); + + for (unsigned int n = 0; n < nComponents; n++) + { + ComponentType component = InterpolatorConvertType::GetNthComponent( n, value ); + + if ( component < minComponent ) + { + PixelConvertType::SetNthComponent( n, outputValue, static_cast( minComponent ) ); + } + else if ( component > maxComponent ) + { + PixelConvertType::SetNthComponent( n, outputValue, static_cast( maxComponent ) ); + } + else + { + PixelConvertType::SetNthComponent(n, outputValue, + static_cast( component ) ); + } + } + + return outputValue; +} + +template +typename StitchImageFilter::InputImageVectorType +StitchImageFilter +::GetInputs() +{ + InputImageVectorType inputs; + for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) + { + auto input = this->GetInput(i); + if (nullptr != input) + { + inputs.push_back(input); + } + } + return inputs; +} + +template +typename StitchImageFilter::TransformMapType +StitchImageFilter +::GetTransforms() +{ + TransformMapType transforms; + for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) + { + auto input = this->GetInput(i); + auto transform = this->GetTransform(i); + transforms[input] = transform; + } + return transforms; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::GenerateInputRequestedRegion() +{ + // Call the superclass' implementation of this method + Superclass::GenerateInputRequestedRegion(); + + if ( !this->GetInput() ) + { + return; + } + + // Get pointers to the input + auto inputs = this->GetInputs(); + + for (auto& input : inputs) + { + InputImagePointer inputPtr = + const_cast(input); + // Determining the actual input region is non-trivial, especially + // when we cannot assume anything about the transform being used. + // So we do the easy thing and request the entire input image. + // + inputPtr->SetRequestedRegionToLargestPossibleRegion(); + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::GenerateOutputInformation() +{ + // Call the superclass' implementation of this method + Superclass::GenerateOutputInformation(); + + // Get pointers to the input and output + OutputImageType *outputPtr = this->GetOutput(); + if ( !outputPtr ) + { + return; + } + + const ReferenceImageBaseType *referenceImage = this->GetReferenceImage(); + + // Set the size of the output region + if ( m_UseReferenceImage && referenceImage ) + { + outputPtr->SetLargestPossibleRegion( + referenceImage->GetLargestPossibleRegion() ); + } + else + { + typename TOutputImage::RegionType outputLargestPossibleRegion; + outputLargestPossibleRegion.SetSize(m_Size); + outputLargestPossibleRegion.SetIndex(m_OutputStartIndex); + outputPtr->SetLargestPossibleRegion(outputLargestPossibleRegion); + } + + // Set spacing and origin + if ( m_UseReferenceImage && referenceImage ) + { + outputPtr->SetSpacing( referenceImage->GetSpacing() ); + outputPtr->SetOrigin( referenceImage->GetOrigin() ); + outputPtr->SetDirection( referenceImage->GetDirection() ); + } + else + { + outputPtr->SetSpacing(m_OutputSpacing); + outputPtr->SetOrigin(m_OutputOrigin); + outputPtr->SetDirection(m_OutputDirection); + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +ModifiedTimeType +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::GetMTime(void) const +{ + ModifiedTimeType latestTime = Object::GetMTime(); + + for (const auto& interpolator : m_Interpolators) + { + if (interpolator.second.GetPointer()) + { + if (latestTime < interpolator.second->GetMTime()) + { + latestTime = interpolator.second->GetMTime(); + } + } + } + + return latestTime; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "DefaultPixelValue: " + << static_cast< typename NumericTraits< PixelType >::PrintType > + ( m_DefaultPixelValue ) + << std::endl; + os << indent << "Size: " << m_Size << std::endl; + os << indent << "OutputStartIndex: " << m_OutputStartIndex << std::endl; + os << indent << "OutputSpacing: " << m_OutputSpacing << std::endl; + os << indent << "OutputOrigin: " << m_OutputOrigin << std::endl; + os << indent << "OutputDirection: " << m_OutputDirection << std::endl; + for (const auto& interpolator : m_Interpolators) + { + os << indent << "Interpolator: " << interpolator.second.GetPointer() << std::endl; + } + os << indent << "UseReferenceImage: " << ( m_UseReferenceImage ? "On" : "Off" ) + << std::endl; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::EnsureTransforms() +{ + const auto inputCount = this->GetNumberOfIndexedInputs(); + for (unsigned int i = 0; i < inputCount; ++i) + { + auto input = this->GetInput(i); + + if (nullptr == input) + { + itkExceptionMacro(<< "Nth input image is not set (n: " << i << ")."); + } + + auto transform = this->GetTransform(i); + if (nullptr == transform) + { + this->SetTransform(i, itk::IdentityTransform< TTransformPrecisionType, ImageDimension>::New().GetPointer()); + } + } +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +void +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::EnsureInterpolators() +{ + const auto inputCount = this->GetNumberOfIndexedInputs(); + InterpolatorMapType newInterpolatorMap; + + for (unsigned int i = 0; i < inputCount; ++i) + { + auto input = this->GetInput(i); + + if (nullptr == input) + { + itkExceptionMacro(<< "Nth input image is not set (n: " << i << ")."); + } + + if (m_Interpolators[input].IsNull()) + { + newInterpolatorMap[input] = LinearInterpolatorType::New().GetPointer(); + } + else + { + newInterpolatorMap[input] = m_Interpolators[input]; + } + } + m_Interpolators = newInterpolatorMap; +} + +template< typename TInputImage, + typename TOutputImage, + typename TInterpolatorPrecisionType, + typename TTransformPrecisionType > +std::string +StitchImageFilter< TInputImage, TOutputImage, TInterpolatorPrecisionType, TTransformPrecisionType > +::GetTransformInputName(unsigned int index) +{ + return "transform_" + std::to_string(index); +} + +} // end namespace itk + +#endif diff --git a/Modules/MatchPointRegistration/Helper/mitkImageMappingHelper.h b/Modules/MatchPointRegistration/include/mitkImageMappingHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkImageMappingHelper.h rename to Modules/MatchPointRegistration/include/mitkImageMappingHelper.h diff --git a/Modules/MatchPointRegistration/include/mitkImageStitchingHelper.h b/Modules/MatchPointRegistration/include/mitkImageStitchingHelper.h new file mode 100644 index 0000000000..8d4b12f3ec --- /dev/null +++ b/Modules/MatchPointRegistration/include/mitkImageStitchingHelper.h @@ -0,0 +1,67 @@ +/*============================================================================ + +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. + +============================================================================*/ + + +#ifndef MITK_IMAGE_STITCHING_HELPER_H +#define MITK_IMAGE_STITCHING_HELPER_H + +#include "mapRegistrationBase.h" +#include "mitkImage.h" +#include "mitkGeometry3D.h" + +#include "mitkMAPRegistrationWrapper.h" +#include "mitkImageMappingHelper.h" + +#include + +#include "MitkMatchPointRegistrationExports.h" + +namespace mitk +{ + /**Helper that stitches a given vector of input images + * @param inputs vector of input images that should be stitched. + * @param registrations vector of registrations that should be used for mapping of the inputs before stitching. + * the method assumes that order of registrations is the same as the order of inputs, thus for the n-th input + * the n-th registration will be used. + * @param resultGeometry Pointer to the Geometry object that specifies the grid of the result image. + * @param paddingValue Indicates the value that should be used if an out of input error occurs (and throwOnOutOfInputAreaError is false). + * @param interpolatorType Indicates the type of interpolation strategy that should be used. + * @param stitchStrategy Strategy used if more than one input can contribute. for more details see the documentation of itk::StitchStrategy. + * @pre inputs must not be empty and contain valid instances + * @pre registration must have same size as inputs and contain valid instances. + * @pre Dimensionality of the registrations must match with the inputs + * @pre resultGeometry must be valid. + * @remark The helper currently only supports 3D images. + * @result Pointer to the resulting mapped image.h*/ + MITKMATCHPOINTREGISTRATION_EXPORT Image::Pointer StitchImages(std::vector inputs, + std::vector<::map::core::RegistrationBase::ConstPointer> registrations, + const BaseGeometry* resultGeometry, + const double& paddingValue = 0, itk::StitchStrategy stitchStrategy = itk::StitchStrategy::Mean, + mitk::ImageMappingInterpolator::Type interpolatorType = mitk::ImageMappingInterpolator::Linear); + + MITKMATCHPOINTREGISTRATION_EXPORT Image::Pointer StitchImages(std::vector inputs, + std::vector registrations, + const BaseGeometry* resultGeometry, + const double& paddingValue = 0, itk::StitchStrategy stitchStrategy = itk::StitchStrategy::Mean, + mitk::ImageMappingInterpolator::Type interpolatorType = mitk::ImageMappingInterpolator::Linear); + + /**@overload + * Convinience version that uses identity transforms form the registrations. + */ + MITKMATCHPOINTREGISTRATION_EXPORT Image::Pointer StitchImages(std::vector inputs, + const BaseGeometry* resultGeometry, + const double& paddingValue = 0, itk::StitchStrategy stitchStrategy = itk::StitchStrategy::Mean, + mitk::ImageMappingInterpolator::Type interpolatorType = mitk::ImageMappingInterpolator::Linear); + +} + +#endif diff --git a/Modules/MatchPointRegistration/Helper/mitkMAPAlgorithmHelper.h b/Modules/MatchPointRegistration/include/mitkMAPAlgorithmHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkMAPAlgorithmHelper.h rename to Modules/MatchPointRegistration/include/mitkMAPAlgorithmHelper.h diff --git a/Modules/MatchPointRegistration/mitkMAPRegistrationWrapper.h b/Modules/MatchPointRegistration/include/mitkMAPRegistrationWrapper.h similarity index 100% rename from Modules/MatchPointRegistration/mitkMAPRegistrationWrapper.h rename to Modules/MatchPointRegistration/include/mitkMAPRegistrationWrapper.h diff --git a/Modules/MatchPointRegistration/mitkMAPRegistrationWrapperObjectFactory.h b/Modules/MatchPointRegistration/include/mitkMAPRegistrationWrapperObjectFactory.h similarity index 100% rename from Modules/MatchPointRegistration/mitkMAPRegistrationWrapperObjectFactory.h rename to Modules/MatchPointRegistration/include/mitkMAPRegistrationWrapperObjectFactory.h diff --git a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h b/Modules/MatchPointRegistration/include/mitkMaskedAlgorithmHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.h rename to Modules/MatchPointRegistration/include/mitkMaskedAlgorithmHelper.h diff --git a/Modules/MatchPointRegistration/mitkMatchPointPropertyTags.h b/Modules/MatchPointRegistration/include/mitkMatchPointPropertyTags.h similarity index 100% rename from Modules/MatchPointRegistration/mitkMatchPointPropertyTags.h rename to Modules/MatchPointRegistration/include/mitkMatchPointPropertyTags.h diff --git a/Modules/MatchPointRegistration/Helper/mitkPointSetMappingHelper.h b/Modules/MatchPointRegistration/include/mitkPointSetMappingHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkPointSetMappingHelper.h rename to Modules/MatchPointRegistration/include/mitkPointSetMappingHelper.h diff --git a/Modules/MatchPointRegistration/Helper/mitkQMAPAlgorithmModel.h b/Modules/MatchPointRegistration/include/mitkQMAPAlgorithmModel.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkQMAPAlgorithmModel.h rename to Modules/MatchPointRegistration/include/mitkQMAPAlgorithmModel.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvalStyleProperty.h b/Modules/MatchPointRegistration/include/mitkRegEvalStyleProperty.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvalStyleProperty.h rename to Modules/MatchPointRegistration/include/mitkRegEvalStyleProperty.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvalWipeStyleProperty.h b/Modules/MatchPointRegistration/include/mitkRegEvalWipeStyleProperty.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvalWipeStyleProperty.h rename to Modules/MatchPointRegistration/include/mitkRegEvalWipeStyleProperty.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h b/Modules/MatchPointRegistration/include/mitkRegEvaluationMapper2D.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h rename to Modules/MatchPointRegistration/include/mitkRegEvaluationMapper2D.h diff --git a/Modules/MatchPointRegistration/mitkRegEvaluationObject.h b/Modules/MatchPointRegistration/include/mitkRegEvaluationObject.h similarity index 100% rename from Modules/MatchPointRegistration/mitkRegEvaluationObject.h rename to Modules/MatchPointRegistration/include/mitkRegEvaluationObject.h diff --git a/Modules/MatchPointRegistration/mitkRegEvaluationObjectFactory.h b/Modules/MatchPointRegistration/include/mitkRegEvaluationObjectFactory.h similarity index 100% rename from Modules/MatchPointRegistration/mitkRegEvaluationObjectFactory.h rename to Modules/MatchPointRegistration/include/mitkRegEvaluationObjectFactory.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisColorStyleProperty.h b/Modules/MatchPointRegistration/include/mitkRegVisColorStyleProperty.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisColorStyleProperty.h rename to Modules/MatchPointRegistration/include/mitkRegVisColorStyleProperty.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisDirectionProperty.h b/Modules/MatchPointRegistration/include/mitkRegVisDirectionProperty.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisDirectionProperty.h rename to Modules/MatchPointRegistration/include/mitkRegVisDirectionProperty.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisHelper.h b/Modules/MatchPointRegistration/include/mitkRegVisHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisHelper.h rename to Modules/MatchPointRegistration/include/mitkRegVisHelper.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h b/Modules/MatchPointRegistration/include/mitkRegVisPropertyTags.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h rename to Modules/MatchPointRegistration/include/mitkRegVisPropertyTags.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisStyleProperty.h b/Modules/MatchPointRegistration/include/mitkRegVisStyleProperty.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisStyleProperty.h rename to Modules/MatchPointRegistration/include/mitkRegVisStyleProperty.h diff --git a/Modules/MatchPointRegistration/Helper/mitkRegistrationHelper.h b/Modules/MatchPointRegistration/include/mitkRegistrationHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkRegistrationHelper.h rename to Modules/MatchPointRegistration/include/mitkRegistrationHelper.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper2D.h b/Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapper2D.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper2D.h rename to Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapper2D.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper3D.h b/Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapper3D.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper3D.h rename to Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapper3D.h diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapperBase.h b/Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapperBase.h similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapperBase.h rename to Modules/MatchPointRegistration/include/mitkRegistrationWrapperMapperBase.h diff --git a/Modules/MatchPointRegistration/Helper/mitkResultNodeGenerationHelper.h b/Modules/MatchPointRegistration/include/mitkResultNodeGenerationHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkResultNodeGenerationHelper.h rename to Modules/MatchPointRegistration/include/mitkResultNodeGenerationHelper.h diff --git a/Modules/MatchPointRegistration/Helper/mitkTimeFramesRegistrationHelper.h b/Modules/MatchPointRegistration/include/mitkTimeFramesRegistrationHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkTimeFramesRegistrationHelper.h rename to Modules/MatchPointRegistration/include/mitkTimeFramesRegistrationHelper.h diff --git a/Modules/MatchPointRegistration/Helper/mitkUIDHelper.h b/Modules/MatchPointRegistration/include/mitkUIDHelper.h similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkUIDHelper.h rename to Modules/MatchPointRegistration/include/mitkUIDHelper.h diff --git a/Modules/MatchPointRegistration/Helper/QmitkAlgorithmListModel.cpp b/Modules/MatchPointRegistration/src/Helper/QmitkAlgorithmListModel.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/QmitkAlgorithmListModel.cpp rename to Modules/MatchPointRegistration/src/Helper/QmitkAlgorithmListModel.cpp diff --git a/Modules/MatchPointRegistration/Helper/QmitkMapPropertyDelegate.cpp b/Modules/MatchPointRegistration/src/Helper/QmitkMapPropertyDelegate.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/QmitkMapPropertyDelegate.cpp rename to Modules/MatchPointRegistration/src/Helper/QmitkMapPropertyDelegate.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkImageMappingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkImageMappingHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp diff --git a/Modules/MatchPointRegistration/src/Helper/mitkImageStitchingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkImageStitchingHelper.cpp new file mode 100644 index 0000000000..e0fcb5f46a --- /dev/null +++ b/Modules/MatchPointRegistration/src/Helper/mitkImageStitchingHelper.cpp @@ -0,0 +1,229 @@ +/*============================================================================ + +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 "mitkImageStitchingHelper.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mapRegistration.h" + +#include "mitkRegistrationHelper.h" + +template +typename ::itk::InterpolateImageFunction< TImage >::Pointer generateInterpolator(mitk::ImageMappingInterpolator::Type interpolatorType) +{ + typedef ::itk::InterpolateImageFunction< TImage > BaseInterpolatorType; + typename BaseInterpolatorType::Pointer result; + + switch (interpolatorType) + { + case mitk::ImageMappingInterpolator::NearestNeighbor: + { + result = ::itk::NearestNeighborInterpolateImageFunction::New(); + break; + } + case mitk::ImageMappingInterpolator::BSpline_3: + { + typename ::itk::BSplineInterpolateImageFunction::Pointer spInterpolator = ::itk::BSplineInterpolateImageFunction::New(); + spInterpolator->SetSplineOrder(3); + result = spInterpolator; + break; + } + case mitk::ImageMappingInterpolator::WSinc_Hamming: + { + result = ::itk::WindowedSincInterpolateImageFunction::New(); + break; + } + case mitk::ImageMappingInterpolator::WSinc_Welch: + { + result = ::itk::WindowedSincInterpolateImageFunction >::New(); + break; + } + default: + { + result = ::itk::LinearInterpolateImageFunction::New(); + break; + } + + } + + return result; +}; + +template +void doMITKStitching(const ::itk::Image* /*input1*/, + mitk::Image::Pointer& result, + std::vector inputs, + std::vector<::map::core::RegistrationBase::ConstPointer> registrations, + const mitk::BaseGeometry* resultGeometry, + const double& paddingValue, itk::StitchStrategy stitchStrategy, mitk::ImageMappingInterpolator::Type interpolatorType) +{ + using ConcreteRegistrationType = ::map::core::Registration; + using ItkImageType = itk::Image; + + using StitchingFilterType = ::itk::StitchImageFilter; + auto stitcher = StitchingFilterType::New(); + + stitcher->SetDefaultPixelValue(paddingValue); + + stitcher->SetOutputOrigin(resultGeometry->GetOrigin()); + + const auto spacing = resultGeometry->GetSpacing(); + stitcher->SetOutputSpacing(spacing); + + typename StitchingFilterType::DirectionType itkDirection; + const auto mitkDirection = resultGeometry->GetIndexToWorldTransform()->GetMatrix(); + for (unsigned int i = 0; i < VImageDimension; ++i) + { + for (unsigned int j = 0; j < VImageDimension; ++j) + { + itkDirection[i][j] = mitkDirection[i][j] / spacing[j]; + } + } + stitcher->SetOutputDirection(itkDirection); + + typename ItkImageType::SizeType size; + size[0] = resultGeometry->GetExtent(0); + size[1] = resultGeometry->GetExtent(1); + size[2] = resultGeometry->GetExtent(2); + stitcher->SetSize(size); + stitcher->SetNumberOfThreads(1); + stitcher->SetStitchStrategy(stitchStrategy); + + auto inputIter = inputs.begin(); + auto regIter = registrations.begin(); + unsigned int index = 0; + + while (inputIter != inputs.end()) + { + auto itkInput = mitk::ImageToItkImage(*inputIter); + + auto castedReg = dynamic_cast(regIter->GetPointer()); + + auto kernel = dynamic_cast* >(&(castedReg->getInverseMapping())); + if (nullptr == kernel) + { + mitkThrow() << "Cannot stitch images. At least passed registration object #"<SetInput(index, itkInput, kernel->getTransformModel(), generateInterpolator< ::itk::Image >(interpolatorType)); + ++inputIter; + ++regIter; + ++index; + } + + stitcher->Update(); + mitk::CastToMitkImage<>(stitcher->GetOutput(),result); +} + +mitk::Image::Pointer +mitk::StitchImages(std::vector inputs, + std::vector<::map::core::RegistrationBase::ConstPointer> registrations, + const BaseGeometry* resultGeometry, + const double& paddingValue, itk::StitchStrategy stitchStrategy, + mitk::ImageMappingInterpolator::Type interpolatorType) +{ + if (inputs.size() != registrations.size()) + { + mitkThrow() << "Cannot stitch images. Passed inputs vector and registrations vector have different sizes."; + } + + if (inputs.empty()) + { + mitkThrow() << "Cannot stitch images. No input images are defined."; + } + + auto inputDim = inputs.front()->GetDimension(); + auto inputPixelType = inputs.front()->GetPixelType(); + + for (const auto& input : inputs) + { + if (input->GetDimension() != inputDim) + { + mitkThrow() << "Cannot stitch images. Images have different dimensions. Dimeonsion of first input: " << inputDim << "; wrong dimension: " << input->GetDimension(); + } + if (input->GetPixelType() != inputPixelType) + { + mitkThrow() << "Cannot stitch images. Input images have different pixeltypes. The current implementation does only support stitching of images with same pixel type. Dimeonsion of first input: " << inputPixelType.GetTypeAsString() << "; wrong dimension: " << input->GetPixelType().GetTypeAsString(); + } + if (input->GetTimeSteps() > 1) + { + mitkThrow() << "Cannot stitch dynamic images. At least one input image has multiple time steps."; + } + } + + for (const auto& reg : registrations) + { + if (reg->getMovingDimensions() != inputDim) + { + mitkThrow() << "Cannot stitch images. At least one registration has a different moving dimension then the inputs. Dimeonsion of inputs: " << inputDim << "; wrong dimension: " << reg->getMovingDimensions(); + } + if (reg->getTargetDimensions() != inputDim) + { + mitkThrow() << "Cannot stitch images. At least one registration has a different target dimension then the inputs. Dimeonsion of inputs: " << inputDim << "; wrong dimension: " << reg->getTargetDimensions(); + } + } + + Image::Pointer result; + + AccessFixedDimensionByItk_n(inputs.front(), doMITKStitching, 3, (result, inputs, registrations, resultGeometry, paddingValue, stitchStrategy, interpolatorType)); + + return result; +} + +mitk::Image::Pointer +mitk::StitchImages(std::vector inputs, + std::vector registrations, + const BaseGeometry* resultGeometry, + const double& paddingValue, itk::StitchStrategy stitchStrategy, + mitk::ImageMappingInterpolator::Type interpolatorType) +{ + + std::vector<::map::core::RegistrationBase::ConstPointer> unwrappedRegs; + for (const auto& reg : registrations) + { + if (!reg) + { + mitkThrow() << "Cannot stitch images. At least one passed registration wrapper pointer is nullptr."; + } + unwrappedRegs.push_back(reg->GetRegistration()); + } + + Image::Pointer result = StitchImages(inputs, unwrappedRegs, resultGeometry, paddingValue, stitchStrategy, interpolatorType); + return result; +} + +mitk::Image::Pointer +mitk::StitchImages(std::vector inputs, + const BaseGeometry* resultGeometry, + const double& paddingValue, itk::StitchStrategy stitchStrategy, + mitk::ImageMappingInterpolator::Type interpolatorType) +{ + auto defaultReg = GenerateIdentityRegistration3D(); + std::vector<::map::core::RegistrationBase::ConstPointer> defaultRegs; + defaultRegs.resize(inputs.size()); + std::fill(defaultRegs.begin(), defaultRegs.end(), defaultReg->GetRegistration()); + + Image::Pointer result = StitchImages(inputs, defaultRegs, resultGeometry, paddingValue, stitchStrategy, interpolatorType); + return result; +} + diff --git a/Modules/MatchPointRegistration/Helper/mitkMAPAlgorithmHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkMAPAlgorithmHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkMAPAlgorithmHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkMAPAlgorithmHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkMaskedAlgorithmHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkMaskedAlgorithmHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkMaskedAlgorithmHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkPointSetMappingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkPointSetMappingHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkPointSetMappingHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkPointSetMappingHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkQMAPAlgorithmModel.cpp b/Modules/MatchPointRegistration/src/Helper/mitkQMAPAlgorithmModel.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkQMAPAlgorithmModel.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkQMAPAlgorithmModel.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkRegistrationHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkRegistrationHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkRegistrationHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkRegistrationHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkResultNodeGenerationHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkResultNodeGenerationHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkResultNodeGenerationHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkResultNodeGenerationHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkTimeFramesRegistrationHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkTimeFramesRegistrationHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkTimeFramesRegistrationHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkTimeFramesRegistrationHelper.cpp diff --git a/Modules/MatchPointRegistration/Helper/mitkUIDHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkUIDHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Helper/mitkUIDHelper.cpp rename to Modules/MatchPointRegistration/src/Helper/mitkUIDHelper.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvalStyleProperty.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegEvalStyleProperty.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvalStyleProperty.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegEvalStyleProperty.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvalWipeStyleProperty.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegEvalWipeStyleProperty.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvalWipeStyleProperty.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegEvalWipeStyleProperty.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegEvaluationMapper2D.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegEvaluationMapper2D.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisColorStyleProperty.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegVisColorStyleProperty.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisColorStyleProperty.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegVisColorStyleProperty.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisDirectionProperty.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegVisDirectionProperty.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisDirectionProperty.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegVisDirectionProperty.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisHelper.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegVisHelper.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisHelper.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegVisHelper.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegVisPropertyTags.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegVisPropertyTags.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisStyleProperty.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegVisStyleProperty.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegVisStyleProperty.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegVisStyleProperty.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper2D.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapper2D.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper2D.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapper2D.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper3D.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapper3D.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapper3D.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapper3D.cpp diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapperBase.cpp b/Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapperBase.cpp similarity index 100% rename from Modules/MatchPointRegistration/Rendering/mitkRegistrationWrapperMapperBase.cpp rename to Modules/MatchPointRegistration/src/Rendering/mitkRegistrationWrapperMapperBase.cpp diff --git a/Modules/MatchPointRegistration/mitkMAPRegistrationWrapper.cpp b/Modules/MatchPointRegistration/src/mitkMAPRegistrationWrapper.cpp similarity index 100% rename from Modules/MatchPointRegistration/mitkMAPRegistrationWrapper.cpp rename to Modules/MatchPointRegistration/src/mitkMAPRegistrationWrapper.cpp diff --git a/Modules/MatchPointRegistration/mitkMAPRegistrationWrapperObjectFactory.cpp b/Modules/MatchPointRegistration/src/mitkMAPRegistrationWrapperObjectFactory.cpp similarity index 100% rename from Modules/MatchPointRegistration/mitkMAPRegistrationWrapperObjectFactory.cpp rename to Modules/MatchPointRegistration/src/mitkMAPRegistrationWrapperObjectFactory.cpp diff --git a/Modules/MatchPointRegistration/mitkRegEvaluationObject.cpp b/Modules/MatchPointRegistration/src/mitkRegEvaluationObject.cpp similarity index 100% rename from Modules/MatchPointRegistration/mitkRegEvaluationObject.cpp rename to Modules/MatchPointRegistration/src/mitkRegEvaluationObject.cpp diff --git a/Modules/MatchPointRegistration/mitkRegEvaluationObjectFactory.cpp b/Modules/MatchPointRegistration/src/mitkRegEvaluationObjectFactory.cpp similarity index 100% rename from Modules/MatchPointRegistration/mitkRegEvaluationObjectFactory.cpp rename to Modules/MatchPointRegistration/src/mitkRegEvaluationObjectFactory.cpp diff --git a/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp b/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp index a2cd40129f..de03cf4055 100644 --- a/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp +++ b/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp @@ -1,167 +1,167 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include mitkCommandLineParser::StringContainerType inFilenames; std::string outFileName; std::vector images; std::vector timebounds; void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("Fuse 3D to 4D Image"); parser.setDescription("MiniApp that allows to fuse several 3D images (with same geometry) into a 3D+t (4D) image that can be processed as dynamic data."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "inputs", "i", mitkCommandLineParser::StringList, "Input files", "Pathes to the input images that should be fused", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file path", "Path to the fused 3D+t image.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "time", "t", mitkCommandLineParser::StringList, "Time bounds", "Defines the time geometry of the resulting dynamic image in [ms]. The first number is the start time point of the first time step. All other numbers are the max bound of a time step. So the structure is [minBound0 maxBound1 [maxBound2 [... maxBoundN]]]; e.g. \"2 3.5 10\" encodes a time geometry with two time steps and that starts at 2 ms and the second time step starts at 3.5 ms and ends at 10 ms. If not set e propertional time geometry with 1 ms duration will be generated!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; inFilenames = us::any_cast(parsedArgs["inputs"]); outFileName = us::any_cast(parsedArgs["output"]); if (parsedArgs.count("time")) { auto timeBoundsStr = us::any_cast(parsedArgs["time"]); for (const auto& timeBoundStr : timeBoundsStr) { std::istringstream stream; stream.imbue(std::locale("C")); stream.str(timeBoundStr); mitk::TimePointType time = 0 ; stream >> time; timebounds.emplace_back(time); } } return true; } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } if (timebounds.empty()) { timebounds.resize(inFilenames.size()+1); std::iota(timebounds.begin(), timebounds.end(), 0.0); } else if (inFilenames.size() + 1 != timebounds.size()) { std::cerr << "Cannot fuse images. Explicitly specified time bounds do not match. Use --help for more information on how to specify time bounds correctly."; return EXIT_FAILURE; }; //! [do processing] try { std::cout << "Load images:" << std::endl; auto filter = mitk::TemporalJoinImagesFilter::New(); unsigned int step = 0; for (auto path : inFilenames) { std::cout << "Time step #"<(path, &readerFilterFunctor); images.push_back(image); filter->SetInput(step, image); ++step; } filter->SetFirstMinTimeBound(timebounds[0]); filter->SetMaxTimeBounds({ timebounds.begin() + 1, timebounds.end() }); std::cout << "Fuse the images ..." << std::endl; filter->Update(); auto output = filter->GetOutput(); std::cout << "Save output image: " << outFileName << std::endl; mitk::IOUtil::Save(output, outFileName); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp b/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp index 68984e081d..0e45f362ed 100644 --- a/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp +++ b/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp @@ -1,360 +1,360 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include #include #include #include #include #include #include #include std::string inFilename; std::string outFileName; std::string maskFileName; bool verbose(false); bool roibased(false); std::string functionName; std::string formular; mitk::Image::Pointer image; mitk::Image::Pointer mask; void onFitEvent(::itk::Object* caller, const itk::EventObject & event, void* /*data*/) { itk::ProgressEvent progressEvent; if (progressEvent.CheckEvent(&event)) { mitk::ParameterFitImageGeneratorBase* castedReporter = dynamic_cast(caller); std::cout <GetProgress()*100 << "% "; } } void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("Generic Fitting"); parser.setDescription("MiniApp that allows to make a pixel based fitting on the intensity signal over time for a given model function."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Model parameters"); parser.addArgument( "function", "f", mitkCommandLineParser::String, "Model function", "Function that should be used to fit the intensity signals. Options are: \"Linear\" or \"\" (for generic formulas).", us::Any(std::string("Linear"))); parser.addArgument( "formular", "y", mitkCommandLineParser::String, "Generic model function formular", "Formular of a generic model (if selected) that will be parsed and fitted.", us::Any()); parser.endGroup(); parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file template", "where to save the output parameter images. The specified path will be used as template to determine the format (via extension) and the name \"root\". For each parameter a suffix will be added to the name.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "mask", "m", mitkCommandLineParser::File, "Mask file", "Mask that defines the spatial image region that should be fitted. Must have the same geometry as the input image!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument( "roibased", "r", mitkCommandLineParser::Bool, "Roi based fitting", "Will compute a mean intesity signal over the ROI before fitting it. If this mode is used a mask must be specified."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; // parse, cast and set required arguments functionName = "Linear"; if (parsedArgs.count("function")) { functionName = us::any_cast(parsedArgs["function"]); } if (parsedArgs.count("formular")) { formular = us::any_cast(parsedArgs["formular"]); } inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } roibased = false; if (parsedArgs.count("roibased")) { roibased = us::any_cast(parsedArgs["roibased"]); } if (parsedArgs.count("mask")) { maskFileName = us::any_cast(parsedArgs["mask"]); } return true; } void configureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) { mitk::GenericParamModelParameterizer* genericParameterizer = dynamic_cast(parameterizer); if (genericParameterizer) { genericParameterizer->SetFunctionString(formular); } } mitk::ModelFitFunctorBase::Pointer createDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(true); return fitFunctor.GetPointer(); } template void generateModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& /*modelFitInfo*/, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); configureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(mask); fitGenerator->SetDynamicImage(image); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); } template void generateModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& /*modelFitInfo*/, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); configureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(mask); signalGenerator->SetDynamicImage(image); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(mask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(image)); generator = fitGenerator.GetPointer(); } void doFitting() { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; ::itk::CStyleCommand::Pointer command = ::itk::CStyleCommand::New(); command->SetCallback(onFitEvent); bool isLinearFactory = functionName == "Linear"; if (isLinearFactory) { std::cout << "Model: linear" << std::endl; if (!roibased) { generateModelFit_PixelBased(fitSession, generator); } else { generateModelFit_ROIBased(fitSession, generator); } } else { std::cout << "Model: generic (2 parameter)" << std::endl; if (!roibased) { generateModelFit_PixelBased(fitSession, generator); } else { generateModelFit_ROIBased(fitSession, generator); } } if (generator.IsNotNull() ) { std::cout << "Started fitting process..." << std::endl; generator->AddObserver(::itk::AnyEvent(), command); generator->Generate(); std::cout << std::endl << "Finished fitting process" << std::endl; mitk::storeModelFitGeneratorResults(outFileName, generator, fitSession); } else { mitkThrow() << "Fitting error! Could not initalize fitting job."; } } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; if (!maskFileName.empty()) { mask = mitk::IOUtil::Load(maskFileName, &readerFilterFunctor); std::cout << "Mask: " << maskFileName << std::endl; } else { std::cout << "Mask: none" << std::endl; } if (roibased && mask.IsNull()) { mitkThrow() << "Error. Cannot fit. Please specify mask if you select roi based fitting."; } std::cout << "Style: "; if (roibased) { std::cout << "ROI based"; } else { std::cout << "pixel based"; } std::cout << std::endl; doFitting(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp b/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp index 2c50a02d4b..d3323ed319 100644 --- a/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp +++ b/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp @@ -1,447 +1,447 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" #include "itkImageRegionConstIteratorWithIndex.h" #include "itkCastImageFilter.h" #include "itkExtractImageFilter.h" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" mitkCommandLineParser::StringContainerType inFilenames; std::string outFileName; std::string maskFileName; mitkCommandLineParser::StringContainerType captions; using ImageVectorType = std::vector; ImageVectorType images; mitk::Image::Pointer mask; bool verbose(false); typedef itk::Image InternalImageType; typedef std::map InternalImageMapType; InternalImageMapType internalImages; itk::ImageRegion<3> relevantRegion; InternalImageType::PointType relevantOrigin; InternalImageType::SpacingType relevantSpacing; InternalImageType::DirectionType relevantDirection; typedef itk::Index<3> DumpIndexType; typedef std::vector DumpedValuesType; struct DumpIndexCompare { bool operator() (const DumpIndexType& lhs, const DumpIndexType& rhs) const { if (lhs[0] < rhs[0]) { return true; } else if (lhs[0] > rhs[0]) { return false; } if (lhs[1] < rhs[1]) { return true; } else if (lhs[1] > rhs[1]) { return false; } return lhs[2] < rhs[2]; } }; typedef std::map DumpPixelMapType; DumpPixelMapType dumpedPixels; void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Generic Analysis Tools"); parser.setTitle("Pixel Dumper"); parser.setDescription("MiniApp that allows to dump the pixel values of all passed files into a csv. The region of dumping can defined by a mask. All images (and mask) must have the same geometrie."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "inputs", "i", mitkCommandLineParser::StringList, "Input files", "list of the images that should be dumped.", us::Any(), false); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "where to save the csv.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "mask", "m", mitkCommandLineParser::File, "Mask file", "Mask that defines the spatial image region that should be dumped. Must have the same geometry as the input images!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "captions", "c", mitkCommandLineParser::StringList, "Captions of image columns", "If provided the pixel columns of the csv will be named according to the passed values instead of using the image pathes. Number of images and names must be equal.", us::Any(), false); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; // parse, cast and set required arguments inFilenames = us::any_cast(parsedArgs["inputs"]); outFileName = us::any_cast(parsedArgs["output"]); if (parsedArgs.count("mask")) { maskFileName = us::any_cast(parsedArgs["mask"]); } captions = inFilenames; if (parsedArgs.count("captions")) { captions = us::any_cast(parsedArgs["captions"]); } return true; } template < typename TPixel, unsigned int VImageDimension > void ExtractRelevantInformation( const itk::Image< TPixel, VImageDimension > *image) { relevantRegion = image->GetLargestPossibleRegion(); relevantOrigin = image->GetOrigin(); relevantSpacing = image->GetSpacing(); relevantDirection = image->GetDirection(); } template < typename TPixel, unsigned int VImageDimension > void DoInternalImageConversion( const itk::Image< TPixel, VImageDimension > *image, InternalImageType::Pointer& internalImage) { typedef itk::Image< TPixel, VImageDimension > ImageType; //check if image fit to geometry // Make sure that spacing are the same typename ImageType::SpacingType imageSpacing = image->GetSpacing(); typename ImageType::PointType zeroPoint; zeroPoint.Fill(0.0); if ((zeroPoint + imageSpacing).SquaredEuclideanDistanceTo((zeroPoint + relevantSpacing)) > 1e-6) // for the dumper we are not as strict as mitk normally would be (mitk::eps) { mitkThrow() << "Images need to have same spacing! (Image spacing: " << imageSpacing << "; relevant spacing: " << relevantSpacing << ")"; } // Make sure that orientation of mask and image are the same typename ImageType::DirectionType imageDirection = image->GetDirection(); for (unsigned int i = 0; i < imageDirection.RowDimensions; ++i) { for (unsigned int j = 0; j < imageDirection.ColumnDimensions; ++j) { double differenceDirection = imageDirection[i][j] - relevantDirection[i][j]; if (fabs(differenceDirection) > 1e-6) // SD: 1e6 wird hier zum zweiten mal als Magic Number benutzt -> Konstante { // for the dumper we are not as strict as mitk normally would be (mitk::eps) mitkThrow() << "Images need to have same direction! (Image direction: " << imageDirection << "; relevant direction: " << relevantDirection << ")"; } } } // Make sure that origin of mask and image are the same typename ImageType::PointType imageOrigin = image->GetOrigin(); if (imageOrigin.SquaredEuclideanDistanceTo(relevantOrigin) > 1e-6) { // for the dumper we are not as strict as mitk normally would be (mitk::eps) mitkThrow() << "Image need to have same spacing! (Image spacing: " << imageSpacing << "; relevant spacing: " << relevantOrigin << ")"; } typename ImageType::RegionType imageRegion = image->GetLargestPossibleRegion(); if (!imageRegion.IsInside(relevantRegion) && imageRegion != relevantRegion) { mitkThrow() << "Images need to have same region! (Image region: " << imageRegion << "; relevant region: " << relevantRegion << ")"; } //convert to internal image typedef itk::ExtractImageFilter ExtractFilterType; typename ExtractFilterType::Pointer extractFilter = ExtractFilterType::New(); typedef itk::CastImageFilter CastFilterType; typename CastFilterType::Pointer castFilter = CastFilterType::New(); extractFilter->SetInput(image); extractFilter->SetExtractionRegion(relevantRegion); castFilter->SetInput(extractFilter->GetOutput()); castFilter->Update(); internalImage = castFilter->GetOutput(); } template < typename TPixel, unsigned int VImageDimension > void DoMaskedCollecting( const itk::Image< TPixel, VImageDimension > *image) { typedef itk::Image< TPixel, VImageDimension > ImageType; itk::ImageRegionConstIteratorWithIndex it(image, relevantRegion); it.GoToBegin(); while (!it.IsAtEnd()) { if (mask.IsNull() || it.Get() > 0) { DumpedValuesType values; const auto index = it.GetIndex(); for (auto& imagePos : internalImages) { double value = imagePos.second->GetPixel(index); values.push_back(value); } dumpedPixels.insert(std::make_pair(index, values)); } ++it; } } InternalImageMapType ConvertImageTimeSteps(mitk::Image* image) { InternalImageMapType map; InternalImageType::Pointer internalImage; for (unsigned int i = 0; i < image->GetTimeSteps(); ++i) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer imageTimePoint = imageTimeSelector->GetOutput(); AccessFixedDimensionByItk_1(imageTimePoint, DoInternalImageConversion, 3, internalImage); std::stringstream stream; stream << "[" << i << "]"; map.insert(std::make_pair(stream.str(), internalImage)); } return map; } void doDumping() { if (mask.IsNotNull() && mask->GetTimeSteps() > 1) { std::cout << "Pixel Dumper: Selected mask has multiple timesteps. Only use first timestep to mask the pixel dumping." << std::endl; mitk::ImageTimeSelector::Pointer maskTimeSelector = mitk::ImageTimeSelector::New(); maskTimeSelector->SetInput(mask); maskTimeSelector->SetTimeNr(0); maskTimeSelector->UpdateLargestPossibleRegion(); mask = maskTimeSelector->GetOutput(); } try { if (mask.IsNotNull()) { // if mask exist, we use the mask because it could be a sub region. AccessFixedDimensionByItk(mask, ExtractRelevantInformation, 3); } else { AccessFixedDimensionByItk(images.front(), ExtractRelevantInformation, 3); } } catch (const std::exception& e) { std::cerr << "Error extracting image geometry. Error text: " << e.what(); throw; } for (unsigned int index = 0; index < images.size(); ++index) { try { InternalImageMapType conversionMap = ConvertImageTimeSteps(images[index]); if (conversionMap.size() == 1) { internalImages.insert(std::make_pair(captions[index], conversionMap.begin()->second)); } else if (conversionMap.size() > 1) { for (auto& pos : conversionMap) { std::stringstream namestream; namestream << captions[index] << " " << pos.first; internalImages.insert(std::make_pair(namestream.str(), pos.second)); } } } catch (const std::exception& e) { std::stringstream errorStr; errorStr << "Inconsistent image \"" << captions[index] << "\" will be excluded from the collection. Error: " << e.what(); std::cerr << errorStr.str() << std::endl; } } if (mask.IsNotNull()) { // if mask exist, we use the mask because it could be a sub region. AccessFixedDimensionByItk(mask, DoMaskedCollecting, 3); } else { AccessFixedDimensionByItk(images.front(), DoMaskedCollecting, 3); } } void storeCSV() { std::ofstream resultfile; resultfile.open(outFileName.c_str()); resultfile << "x,y,z"; for (auto aImage : internalImages) { resultfile << "," << aImage.first; } resultfile << std::endl; for (auto dumpPos : dumpedPixels) { resultfile << dumpPos.first[0] << "," << dumpPos.first[1] << "," << dumpPos.first[2]; for(auto d : dumpPos.second) { resultfile << "," << d; } resultfile << std::endl; } } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } if (!captions.empty() && inFilenames.size() != captions.size()) { std::cerr << "Cannot dump images. Number of given captions does not equal number of given images."; return EXIT_FAILURE; }; //! [do processing] try { std::cout << "Load images:" << std::endl; for (auto path : inFilenames) { std::cout << "Input: " << path << std::endl; auto image = mitk::IOUtil::Load(path, &readerFilterFunctor); images.push_back(image); } if (!maskFileName.empty()) { mask = mitk::IOUtil::Load(maskFileName, &readerFilterFunctor); std::cout << "Mask: " << maskFileName << std::endl; } else { std::cout << "Mask: none" << std::endl; } doDumping(); storeCSV(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/Pharmacokinetics/cmdapps/CurveDescriptorMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/CurveDescriptorMiniApp.cpp index dd52eec7f8..df8257534f 100644 --- a/Modules/Pharmacokinetics/cmdapps/CurveDescriptorMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/CurveDescriptorMiniApp.cpp @@ -1,242 +1,242 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include #include #include #include #include std::string inFilename; std::string outFileName; std::string maskFileName; bool verbose(false); bool preview(false); mitk::Image::Pointer image; mitk::Image::Pointer mask; void onFitEvent(::itk::Object* caller, const itk::EventObject & event, void* /*data*/) { itk::ProgressEvent progressEvent; if (progressEvent.CheckEvent(&event)) { mitk::PixelBasedDescriptionParameterImageGenerator* castedReporter = dynamic_cast(caller); std::cout <GetProgress()*100 << "% "; } } void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("Curve Descriptor"); parser.setDescription("MiniApp that allows to generate curve descriptor maps for dynamic image."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file template", "where to save the output parameter images. The specified path will be used as template to determine the format (via extension) and the name \"root\". For each parameter a suffix will be added to the name.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "mask", "m", mitkCommandLineParser::File, "Mask file", "Mask that defines the spatial image region that should be fitted. Must have the same geometry as the input image!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument( "preview", "p", mitkCommandLineParser::Bool, "Preview outputs", "The application previews the outputs (filename, type) it would produce with the current settings."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } if (parsedArgs.count("mask")) { maskFileName = us::any_cast(parsedArgs["mask"]); } preview = false; if (parsedArgs.count("preview")) { preview = us::any_cast(parsedArgs["preview"]); } inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); return true; } void ConfigureFunctor(mitk::CurveParameterFunctor* functor) { mitk::CurveDescriptionParameterBase::Pointer parameterFunction = mitk::AreaUnderTheCurveDescriptionParameter::New().GetPointer(); functor->RegisterDescriptionParameter("AUC", parameterFunction); parameterFunction = mitk::AreaUnderFirstMomentDescriptionParameter::New().GetPointer(); functor->RegisterDescriptionParameter("AUMC", parameterFunction); parameterFunction = mitk::MeanResidenceTimeDescriptionParameter::New().GetPointer(); functor->RegisterDescriptionParameter("MRT", parameterFunction); parameterFunction = mitk::TimeToPeakCurveDescriptionParameter::New().GetPointer(); functor->RegisterDescriptionParameter("TimeToPeak", parameterFunction); }; void doDescription() { mitk::PixelBasedDescriptionParameterImageGenerator::Pointer generator = mitk::PixelBasedDescriptionParameterImageGenerator::New(); mitk::CurveParameterFunctor::Pointer functor = mitk::CurveParameterFunctor::New(); ConfigureFunctor(functor); functor->SetGrid(mitk::ExtractTimeGrid(image)); generator->SetFunctor(functor); generator->SetDynamicImage(image); generator->SetMask(mask); ::itk::CStyleCommand::Pointer command = ::itk::CStyleCommand::New(); command->SetCallback(onFitEvent); std::cout << "Started curve descriptor computation process..." << std::endl; generator->AddObserver(::itk::AnyEvent(), command); generator->Generate(); std::cout << std::endl << "Finished computation process" << std::endl; for (auto imageIterator : generator->GetParameterImages()) { mitk::storeParameterResultImage(outFileName, imageIterator.first, imageIterator.second); } } void doPreview() { mitk::CurveParameterFunctor::Pointer functor = mitk::CurveParameterFunctor::New(); ConfigureFunctor(functor); auto pNames = functor->GetDescriptionParameterNames(); for (auto aName : pNames) { auto fullPath = mitk::generateModelFitResultImagePath(outFileName, aName); std::cout << "Store result parameter: " << aName << " -> " << fullPath << std::endl; } } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { if (preview) { doPreview(); } else { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; if (!maskFileName.empty()) { mask = mitk::IOUtil::Load(maskFileName, &readerFilterFunctor); std::cout << "Mask: " << maskFileName << std::endl; } else { std::cout << "Mask: none" << std::endl; } doDescription(); } std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/Pharmacokinetics/cmdapps/MRPerfusionMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/MRPerfusionMiniApp.cpp index 9fc08615b9..d871108746 100644 --- a/Modules/Pharmacokinetics/cmdapps/MRPerfusionMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/MRPerfusionMiniApp.cpp @@ -1,890 +1,890 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::string inFilename; std::string outFileName; std::string maskFileName; std::string aifMaskFileName; std::string aifImageFileName; mitk::Image::Pointer image; mitk::Image::Pointer mask; mitk::Image::Pointer aifImage; mitk::Image::Pointer aifMask; bool useConstraints(false); bool verbose(false); bool roibased(false); bool preview(false); std::string modelName; float aifHematocritLevel(0); float brixInjectionTime(0); const std::string MODEL_NAME_2SL = "2SL"; const std::string MODEL_NAME_3SL = "3SL"; const std::string MODEL_NAME_descriptive = "descriptive"; const std::string MODEL_NAME_tofts = "tofts"; const std::string MODEL_NAME_2CX = "2CX"; void onFitEvent(::itk::Object* caller, const itk::EventObject & event, void* /*data*/) { itk::ProgressEvent progressEvent; if (progressEvent.CheckEvent(&event)) { mitk::ParameterFitImageGeneratorBase* castedReporter = dynamic_cast(caller); std::cout <GetProgress()*100 << "% "; } } void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("MR Perfusion"); parser.setDescription("MiniApp that allows to fit MRI perfusion models and generates the according parameter maps. IMPORTANT!!!: The app assumes that the input images (signal and AIF) are concentration images. If your images do not hold this assumption, convert the image date before using this app (e.g. by using the signal-to-concentration-converter mini app."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Model parameters"); parser.addArgument( "model", "l", mitkCommandLineParser::String, "Model function", "Model that should be used to fit the concentration signal. Options are: \""+MODEL_NAME_descriptive+"\" (descriptive pharmacokinetic Brix model),\"" + MODEL_NAME_2SL + "\" (two step linear model),\""+MODEL_NAME_3SL+"\" (three step linear model), \""+MODEL_NAME_tofts+"\" (extended tofts model) or \""+MODEL_NAME_2CX+"\" (two compartment exchange model).", us::Any(std::string(MODEL_NAME_tofts))); parser.addArgument( "injectiontime", "j", mitkCommandLineParser::Float, "Injection time [min]", "Injection time of the bolus. This information is needed for the descriptive pharmacokinetic Brix model.", us::Any()); parser.endGroup(); parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file template", "where to save the output parameter images. The specified path will be used as template to determine the format (via extension) and the name \"root\". For each parameter a suffix will be added to the name.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("AIF parameters"); parser.addArgument( "aifmask", "n", mitkCommandLineParser::File, "AIF mask file", "Mask that defines the spatial image region that should be used as AIF for models that need one. Must have the same geometry as the AIF input image!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "aifimage", "a", mitkCommandLineParser::File, "AIF image file", "3D+t image that defines the image that containes the AIF signal. If this flag is not set and the model needs a AIF, the CLI will assume that the AIF is encoded in the normal image. Must have the same geometry as the AIF mask!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "hematocrit", "h", mitkCommandLineParser::Float, "Hematocrit Level", "Value needed for correct AIF computation. Only needed if model needs an AIF. Default value is 0.45.", us::Any(0.45)); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "mask", "m", mitkCommandLineParser::File, "Mask file", "Mask that defines the spatial image region that should be fitted. Must have the same geometry as the input image!", us::Any(), true, false, false, mitkCommandLineParser::Input); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument( "roibased", "r", mitkCommandLineParser::Bool, "Roi based fitting", "Will compute a mean intesity signal over the ROI before fitting it. If this mode is used a mask must be specified."); parser.addArgument( "constraints", "c", mitkCommandLineParser::Bool, "Constraints", "Indicates if constraints should be used for the fitting (if flag is set the default contraints will be used.).", us::Any(false)); parser.addArgument( "preview", "p", mitkCommandLineParser::Bool, "Preview outputs", "The application previews the outputs (filename, type) it would produce with the current settings."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; // parse, cast and set required arguments modelName = MODEL_NAME_tofts; if (parsedArgs.count("model")) { modelName = us::any_cast(parsedArgs["model"]); } inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); if (parsedArgs.count("mask")) { maskFileName = us::any_cast(parsedArgs["mask"]); } if (parsedArgs.count("aifimage")) { aifImageFileName = us::any_cast(parsedArgs["aifimage"]); } if (parsedArgs.count("aifmask")) { aifMaskFileName = us::any_cast(parsedArgs["aifmask"]); } verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } preview = false; if (parsedArgs.count("preview")) { preview = us::any_cast(parsedArgs["preview"]); } roibased = false; if (parsedArgs.count("roibased")) { roibased = us::any_cast(parsedArgs["roibased"]); } useConstraints = false; if (parsedArgs.count("constraints")) { useConstraints = us::any_cast(parsedArgs["constraints"]); } aifHematocritLevel = 0.45; if (parsedArgs.count("hematocrit")) { aifHematocritLevel = us::any_cast(parsedArgs["hematocrit"]); } brixInjectionTime = 0.0; if (parsedArgs.count("injectiontime")) { brixInjectionTime = us::any_cast(parsedArgs["injectiontime"]); } return true; } mitk::ModelFitFunctorBase::Pointer createDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer, const mitk::ModelFactoryBase* modelFactory) { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (useConstraints) { fitFunctor->SetConstraintChecker(modelFactory->CreateDefaultConstraints().GetPointer()); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(true); return fitFunctor.GetPointer(); } /**Helper that ensures that the mask (if it exists) is always 3D image. If the mask is originally an 4D image, the first time step will be used.*/ mitk::Image::Pointer getMask3D() { mitk::Image::Pointer result; if (mask.IsNotNull()) { result = mask; //mask settings if (mask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit."; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(mask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); result = maskedImageTimeSelector->GetOutput(); } } return result; } void getAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (aifMask.IsNotNull()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(aifHematocritLevel); std::cout << "AIF hematocrit level: " << aifHematocritLevel << std::endl; mitk::Image::Pointer selectedAIFMask = aifMask; //mask settings if (aifMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit."; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(aifMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); aifMask = maskedImageTimeSelector->GetOutput(); } aifGenerator->SetMask(aifMask); mitk::Image::Pointer selectedAIFImage = image; //image settings if (aifImage.IsNotNull()) { selectedAIFImage = aifImage; } aifGenerator->SetDynamicImage(selectedAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. AIF mask was not specified or correctly loaded."; } } void generateDescriptiveBrixModel_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelParameterizer::New(); mitk::Image::Pointer mask3D = getMask3D(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(brixInjectionTime); std::cout << "Injection time [min]: " << brixInjectionTime << std::endl; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::DescriptivePharmacokineticBrixModelParameterizer::BaseImageType::Pointer baseImage; mitk::CastToItkImage(imageTimeSelector->GetOutput(), baseImage); modelParameterizer->SetBaseImage(baseImage); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = mitk::DescriptivePharmacokineticBrixModelFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (mask3D.IsNotNull()) { fitGenerator->SetMask(mask3D); roiUID = mask->GetUID(); } fitGenerator->SetDynamicImage(image); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), roiUID); } void generateDescriptiveBrixModel_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::Image::Pointer mask3D = getMask3D(); if (mask3D.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(mask3D); signalGenerator->SetDynamicImage(image); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(brixInjectionTime); std::cout << "Injection time [min]: " << brixInjectionTime << std::endl; modelParameterizer->SetBaseValue(roiSignal[0]); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = mitk::DescriptivePharmacokineticBrixModelFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(mask3D); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(image)); generator = fitGenerator.GetPointer(); std::string roiUID = mask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void GenerateLinearModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); mitk::Image::Pointer mask3D = getMask3D(); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = TFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (mask3D.IsNotNull()) { fitGenerator->SetMask(mask3D); roiUID = mask->GetUID(); } fitGenerator->SetDynamicImage(image); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), roiUID); } template void GenerateLinearModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::Image::Pointer mask3D = getMask3D(); if (mask3D.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(mask3D); signalGenerator->SetDynamicImage(image); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = TFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(mask3D); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(image)); generator = fitGenerator.GetPointer(); std::string roiUID = mask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void generateAIFbasedModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; getAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); mitk::Image::Pointer mask3D = getMask3D(); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = TFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (mask3D.IsNotNull()) { fitGenerator->SetMask(mask3D); roiUID = mask->GetUID(); } fitGenerator->SetDynamicImage(image); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void generateAIFbasedModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::Image::Pointer mask3D = getMask3D(); if (mask3D.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; getAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(mask3D); signalGenerator->SetDynamicImage(image); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFactoryBase::Pointer factory = TFactory::New().GetPointer(); mitk::ModelFitFunctorBase::Pointer fitFunctor = createDefaultFitFunctor(modelParameterizer, factory); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(mask3D); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(image)); generator = fitGenerator.GetPointer(); std::string roiUID = mask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, image, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void storeResultImage(const std::string& name, mitk::Image* image, mitk::modelFit::Parameter::Type nodeType, const mitk::modelFit::ModelFitInfo* modelFitInfo) { mitk::modelFit::SetModelFitDataProperties(image, name, nodeType, modelFitInfo); std::string ext = ::itksys::SystemTools::GetFilenameLastExtension(outFileName); std::string dir = itksys::SystemTools::GetFilenamePath(outFileName); dir = itksys::SystemTools::ConvertToOutputPath(dir); std::string rootName = itksys::SystemTools::GetFilenameWithoutLastExtension(outFileName); std::string fileName = rootName + "_" + name + ext; std::vector pathElements; pathElements.push_back(dir); pathElements.push_back(fileName); std::string fullOutPath = itksys::SystemTools::ConvertToOutputPath(dir + "/" + fileName); mitk::IOUtil::Save(image, fullOutPath); std::cout << "Store result (parameter: "<(fitSession, generator); } else { GenerateLinearModelFit_ROIBased(fitSession, generator); } } else if (is2SLFactory) { std::cout << "Model: two step linear model" << std::endl; if (!roibased) { GenerateLinearModelFit_PixelBased(fitSession, generator); } else { GenerateLinearModelFit_ROIBased(fitSession, generator); } } else if (isToftsFactory) { std::cout << "Model: extended tofts model" << std::endl; if (!roibased) { generateAIFbasedModelFit_PixelBased(fitSession, generator); } else { generateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (is2CXMFactory) { std::cout << "Model: two compartment exchange model" << std::endl; if (!roibased) { generateAIFbasedModelFit_PixelBased(fitSession, generator); } else { generateAIFbasedModelFit_ROIBased(fitSession, generator); } } else { std::cerr << "ERROR. Model flag is unknown. Given flag: " << modelName << std::endl; } } void doFitting() { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; ::itk::CStyleCommand::Pointer command = ::itk::CStyleCommand::New(); command->SetCallback(onFitEvent); createFitGenerator(fitSession, generator); if (generator.IsNotNull() ) { std::cout << "Started fitting process..." << std::endl; generator->AddObserver(::itk::AnyEvent(), command); generator->Generate(); std::cout << std::endl << "Finished fitting process" << std::endl; mitk::storeModelFitGeneratorResults(outFileName, generator, fitSession); } else { mitkThrow() << "Fitting error! Could not initialize fitting job."; } } void doPreview() { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; createFitGenerator(fitSession, generator); if (generator.IsNotNull()) { mitk::previewModelFitGeneratorResults(outFileName, generator); } else { mitkThrow() << "Fitting error! Could not initialize fitting job."; } } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; if (!maskFileName.empty()) { mask = mitk::IOUtil::Load(maskFileName, &readerFilterFunctor); std::cout << "Mask: " << maskFileName << std::endl; } else { std::cout << "Mask: none" << std::endl; } if (modelName != MODEL_NAME_descriptive && modelName != MODEL_NAME_3SL && MODEL_NAME_2SL != modelName) { if (!aifMaskFileName.empty()) { aifMask = mitk::IOUtil::Load(aifMaskFileName, &readerFilterFunctor); std::cout << "AIF mask: " << aifMaskFileName << std::endl; } else { mitkThrow() << "Error. Cannot fit. Choosen model needs an AIF. Please specify AIF mask (--aifmask)."; } if (!aifImageFileName.empty()) { aifImage = mitk::IOUtil::Load(aifImageFileName, &readerFilterFunctor); std::cout << "AIF image: " << aifImageFileName << std::endl; } else { std::cout << "AIF image: none (using signal image)" << std::endl; } } if (roibased && mask.IsNull()) { mitkThrow() << "Error. Cannot fit. Please specify mask if you select roi based fitting."; } std::cout << "Style: "; if (roibased) { std::cout << "ROI based"; } else { std::cout << "pixel based"; } std::cout << std::endl; if (preview) { doPreview(); } else { doFitting(); } std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp index bd517b3abb..c9057a5bc8 100644 --- a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp @@ -1,287 +1,287 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include std::string inFilename; std::string outFileName; mitk::Image::Pointer image; bool verbose(false); bool t1_absolute(false); bool t1_relative(false); bool t1_flash(false); bool t2(false); float k(1.0); float te(0); float rec_time(0); float relaxivity(0); float rel_time(0); void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("MR Signal to Concentration Converter"); parser.setDescription("MiniApp that allows to convert a T1 or T2 signal image into a concentration image for perfusion analysis."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "where to save the output concentration image.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Conversion parameters"); parser.addArgument( "t1-absolute", "", mitkCommandLineParser::Bool, "T1 absolute signal enhancement", "Activate conversion for T1 absolute signal enhancement."); parser.addArgument( "t1-relative", "", mitkCommandLineParser::Bool, "T1 relative signal enhancement", "Activate conversion for T1 relative signal enhancement."); parser.addArgument( "t1-flash", "", mitkCommandLineParser::Bool, "T1 turbo flash", "Activate specific conversion for T1 turbo flash sequences."); parser.addArgument( "t2", "", mitkCommandLineParser::Bool, "T2 signal conversion", "Activate conversion for T2 signal enhancement to concentration."); parser.addArgument( "k", "k", mitkCommandLineParser::Float, "Conversion factor k", "Needed for the following conversion modes: T1-absolute, T1-relative, T2. Default value is 1.", us::Any(1)); parser.addArgument( "recovery-time", "", mitkCommandLineParser::Float, "Recovery time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxivity", "", mitkCommandLineParser::Float, "Relaxivity", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxation-time", "", mitkCommandLineParser::Float, "Relaxation time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "te", "", mitkCommandLineParser::Float, "Echo time TE", "Needed for the following conversion modes: T2.", us::Any(1)); parser.beginGroup("Optional parameters"); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } t1_absolute = false; if (parsedArgs.count("t1-absolute")) { t1_absolute = us::any_cast(parsedArgs["t1-absolute"]); } t1_relative = false; if (parsedArgs.count("t1-relative")) { t1_relative = us::any_cast(parsedArgs["t1-relative"]); } t1_flash = false; if (parsedArgs.count("t1-flash")) { t1_flash = us::any_cast(parsedArgs["t1-flash"]); } t2 = false; if (parsedArgs.count("t2")) { t2 = us::any_cast(parsedArgs["t2"]); } k = 0.0; if (parsedArgs.count("k")) { k = us::any_cast(parsedArgs["k"]); } relaxivity = 0.0; if (parsedArgs.count("relaxivity")) { relaxivity = us::any_cast(parsedArgs["relaxivity"]); } rec_time = 0.0; if (parsedArgs.count("recovery-time")) { rec_time = us::any_cast(parsedArgs["recovery-time"]); } rel_time = 0.0; if (parsedArgs.count("relaxation-time")) { rel_time = us::any_cast(parsedArgs["relaxation-time"]); } te = 0.0; if (parsedArgs.count("te")) { te = us::any_cast(parsedArgs["te"]); } //consistency checks int modeCount = 0; if (t1_absolute) ++modeCount; if (t1_flash) ++modeCount; if (t1_relative) ++modeCount; if (t2) ++modeCount; if (modeCount==0) { mitkThrow() << "Invalid program call. Please select the type of conversion."; } if (modeCount >1) { mitkThrow() << "Invalid program call. Please select only ONE type of conversion."; } if (!k && (t2 || t1_absolute || t1_relative)) { mitkThrow() << "Invalid program call. Please set 'k', if you use t1-absolute, t1-relative or t2."; } if (!te && t2) { mitkThrow() << "Invalid program call. Please set 'te', if you use t2 mode."; } if ((!rec_time||!rel_time||!relaxivity) && t1_flash) { mitkThrow() << "Invalid program call. Please set 'recovery-time', 'relaxation-time' and 'relaxivity', if you use t1-flash mode."; } return true; } void doConversion() { mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(image); concentrationGen->SetisTurboFlashSequence(t1_flash); concentrationGen->SetAbsoluteSignalEnhancement(t1_absolute); concentrationGen->SetRelativeSignalEnhancement(t1_relative); concentrationGen->SetisT2weightedImage(t2); if (t1_flash) { concentrationGen->SetRecoveryTime(rec_time); concentrationGen->SetRelaxationTime(rel_time); concentrationGen->SetRelaxivity(relaxivity); } else if (t2) { concentrationGen->SetT2Factor(k); concentrationGen->SetT2EchoTime(te); } else { concentrationGen->SetFactor(k); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); mitk::IOUtil::Save(concentrationImage, outFileName); std::cout << "Store result: " << outFileName << std::endl; } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; }; - mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" }); + mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; doConversion(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h index 69a7cf348b..6a22193017 100644 --- a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h +++ b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h @@ -1,201 +1,197 @@ /*============================================================================ 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. ============================================================================*/ -#ifndef MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED -#define MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED +#ifndef MITKPLANARFIGUREINTERACTOR_H +#define MITKPLANARFIGUREINTERACTOR_H #include #include "mitkCommon.h" #include "mitkDataInteractor.h" #include "mitkNumericTypes.h" #pragma GCC visibility push(default) #include #pragma GCC visibility pop namespace mitk { class DataNode; class PlaneGeometry; class PlanarFigure; class PositionEvent; class BaseRenderer; class InteractionPositionEvent; class StateMachineAction; #pragma GCC visibility push(default) // Define events for PlanarFigure interaction notifications itkEventMacro(PlanarFigureEvent, itk::AnyEvent); itkEventMacro(StartPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(SelectPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(ContextMenuPlanarFigureEvent, PlanarFigureEvent); #pragma GCC visibility pop /** * \brief Interaction with mitk::PlanarFigure objects via control-points * * @ingroup MitkPlanarFigureModule */ class MITKPLANARFIGURE_EXPORT PlanarFigureInteractor : public DataInteractor { public: mitkClassMacro(PlanarFigureInteractor, DataInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /** \brief Sets the amount of precision */ - void SetPrecision(ScalarType precision); + /** \brief Sets the amount of precision */ + void SetPrecision(ScalarType precision); /** \brief Sets the minimal distance between two control points. */ void SetMinimumPointDistance(ScalarType minimumDistance); protected: PlanarFigureInteractor(); ~PlanarFigureInteractor() override; void ConnectActionsAndFunctions() override; //////// Conditions //////// bool CheckFigurePlaced(const InteractionEvent *interactionEvent); bool CheckFigureHovering(const InteractionEvent *interactionEvent); bool CheckControlPointHovering(const InteractionEvent *interactionEvent); bool CheckSelection(const InteractionEvent *interactionEvent); bool CheckPointValidity(const InteractionEvent *interactionEvent); bool CheckFigureFinished(const InteractionEvent *interactionEvent); bool CheckResetOnPointSelect(const InteractionEvent *interactionEvent); bool CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent); bool CheckMinimalFigureFinished(const InteractionEvent *interactionEvent); bool CheckFigureIsExtendable(const InteractionEvent *interactionEvent); bool CheckFigureIsDeletable(const InteractionEvent *interactionEvent); bool CheckFigureIsEditable(const InteractionEvent *interactionEvent); //////// Actions //////// void FinalizeFigure(StateMachineAction *, InteractionEvent *interactionEvent); void MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent); void DeselectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent); void StartHovering(StateMachineAction *, InteractionEvent *interactionEvent); void EndHovering(StateMachineAction *, InteractionEvent *interactionEvent); void DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent); void PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *interactionEvent); void SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent); void HidePreviewPoint(StateMachineAction *, InteractionEvent *interactionEvent); void HideControlPoints(StateMachineAction *, InteractionEvent *interactionEvent); void RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent); void RequestContextMenu(StateMachineAction *, InteractionEvent *interactionEvent); void SelectFigure(StateMachineAction *, InteractionEvent *interactionEvent); void SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void EndInteraction(StateMachineAction *, InteractionEvent *interactionEvent); bool FilterEvents(InteractionEvent *interactionEvent, DataNode *) override; /** \brief Used when clicking to determine if a point is too close to the previous point. */ bool IsMousePositionAcceptableAsNewControlPoint(const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *); bool TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D); bool TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const; /** \brief Returns true if the first specified point is in proximity of the line defined * the other two point; false otherwise. * * Proximity is defined as the rectangle around the line with pre-defined distance * from the line. */ bool IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const; /** \brief Returns true if the point contained in the passed event (in display coordinates) * is over the planar figure (with a pre-defined tolerance range); false otherwise. */ int IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const; /** \brief Returns the index of the marker (control point) over which the point contained * in the passed event (in display coordinates) currently is; -1 if the point is not over * a marker. */ int IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const; void LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure); void ConfigurationChanged() override; private: /** \brief to store the value of precision to pick a point */ ScalarType m_Precision; /** \brief Store the minimal distance between two control points. */ ScalarType m_MinimumPointDistance; /** \brief True if the mouse is currently hovering over the image. */ bool m_IsHovering; - - bool m_LastPointWasValid; - - // mitk::PlanarFigure::Pointer m_PlanarFigure; }; } -#endif // MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED +#endif // MITKPLANARFIGUREINTERACTOR_H diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 82f60c6d4c..363c3ef628 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,949 +1,1113 @@ /*============================================================================ 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. ============================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" mitk::PlanarFigureInteractor::PlanarFigureInteractor() - : DataInteractor(), m_Precision(6.5), m_MinimumPointDistance(25.0), m_IsHovering(false), m_LastPointWasValid(false) + : DataInteractor() + , m_Precision(6.5) + , m_MinimumPointDistance(25.0) + , m_IsHovering(false) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) CONNECT_FUNCTION("set_preview_point_position", SetPreviewPointPosition) CONNECT_FUNCTION("move_current_point", MoveCurrentPoint); CONNECT_FUNCTION("deselect_point", DeselectPoint); CONNECT_FUNCTION("add_new_point", AddPoint); CONNECT_FUNCTION("add_initial_point", AddInitialPoint); CONNECT_FUNCTION("remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION("request_context_menu", RequestContextMenu); CONNECT_FUNCTION("select_figure", SelectFigure); CONNECT_FUNCTION("select_point", SelectPoint); CONNECT_FUNCTION("end_interaction", EndInteraction); CONNECT_FUNCTION("start_hovering", StartHovering) CONNECT_FUNCTION("end_hovering", EndHovering); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *positionEvent = dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } - // Extract point in 2D world coordinates (relative to PlaneGeometry of - // PlanarFigure) + // Extract point in 2D world coordinates (relative to PlaneGeometry of PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty("planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction); // hide the control points if necessary // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction); // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); planarFigure->InvokeEvent(EndPlacementPlanarFigureEvent()); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent(EndHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", false); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - return (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - return (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable(const InteractionEvent * /*interactionEvent*/) { bool isExtendable(false); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const bool wasSelected = planarFigure->DeselectControlPoint(); if (wasSelected) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); - // GetDataNode()->SetBoolProperty( "planarfigure.ishovering", false ); GetDataNode()->Modified(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; - - const DataNode::Pointer node = this->GetDataNode(); - const BaseData::Pointer data = node->GetData(); + } /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are necessary because of a macOS specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ + const DataNode::Pointer node = this->GetDataNode(); + const BaseData::Pointer data = node->GetData(); + bool isFigureFinished = false; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } - auto *planarFigure = dynamic_cast(data.GetPointer()); + auto planarFigure = dynamic_cast(data.GetPointer()); + if (nullptr == planarFigure) + { + return; + } // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - auto *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { - planarFigureGeometry = planeGeometry; planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; - if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) + if (!this->TransformPositionEventToPoint2D(positionEvent, planeGeometry, point2D)) { return; } // Place PlanarFigure at this point planarFigure->PlaceFigure(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } if (!m_IsHovering) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent(StartHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", true); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { + return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); if (selected && isExtendable && isEditable) { renderer->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - auto *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return false; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); + return isHovering; +} - if (isHovering) +bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) { - return true; + return false; } - else + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) { return false; } - return false; -} + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return false; + } -bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) -{ - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); - if (abstractTransformGeometry != nullptr) - return false; - int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); - if (pointIndex >= 0) - { - return true; - } - else - { - return false; - } + return pointIndex >= 0; } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return; + } + + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { + return; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } bool mitk::PlanarFigureInteractor::CheckPointValidity(const InteractionEvent *interactionEvent) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) - // Extract display position - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - m_LastPointWasValid = IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); - return m_LastPointWasValid; + return IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); + mitk::BaseRenderer *renderer = interactionEvent->GetSender(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if (!selected) { planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } planarFigure->InvokeEvent(ContextMenuPlanarFigureEvent()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { - const auto *posEvent = - dynamic_cast(interactionEvent); - - if (posEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::Point3D worldPoint3D = posEvent->GetPositionInWorld(); - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - - const mitk::PlaneGeometry *planarFigurePlaneGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return false; + } - const double planeThickness = planarFigurePlaneGeometry->GetExtentInMM(2); - if (planarFigurePlaneGeometry->Distance(worldPoint3D) > planeThickness) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) { - // don't react, when interaction is too far away return false; } - return true; + + const double planeThickness = planarFigureGeometry->GetExtentInMM(2); + return planarFigureGeometry->Distance(worldPoint3D) <= planeThickness; } void mitk::PlanarFigureInteractor::SetPrecision(mitk::ScalarType precision) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance(ScalarType minimumDistance) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D) { + if (nullptr == positionEvent || nullptr == planarFigureGeometry) + { + return false; + } + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if (planarFigureGeometry->Distance(worldPoint3D) > 0.1) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map(worldPoint3D, point2D); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { + if (nullptr == objectGeometry || nullptr == rendererGeometry || nullptr == renderer) + { + return false; + } + mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, displayPoint); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry) + { + return -1; + } + mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for (unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { break; // Poly line invalid (not on current 2D plane) --> skip it } if (firstPoint) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if (this->IsPointNearLine(displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine)) { // Point is close enough to line segment --> Return index of the segment return std::distance(polyLine.begin(), it); } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if (planarFigure->IsClosed() && this->IsPointNearLine(displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine)) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry || nullptr == renderer) + { + return -1; + } + const mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // TODO: variable size of markers if (displayPosition.SquaredEuclideanDistanceTo(displayControlPoint) < 20.0) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure) { + if (nullptr == planarFigure) + { + MITK_INFO << "PlanarFigure invalid."; + } + MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for (unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i) { MITK_INFO << "* " << planarFigure->GetFeatureName(i) << ": " << planarFigure->GetQuantity(i) << " " << planarFigure->GetFeatureUnit(i); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure) { - assert(positionEvent && planarFigure); + if (nullptr == positionEvent || nullptr == planarFigure) + { + return false; + + } const BaseRenderer *renderer = positionEvent->GetSender(); - assert(renderer); + if (nullptr == renderer) + { + return false; + } // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); - const mitk::PlaneGeometry *planarFigureGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); + auto planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr == planarFigureGeometry) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr != abstractTransformGeometry) + { return false; + } Point2D point2D; // Get the point2D from the positionEvent if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. const Point2D correctedPoint = const_cast(planarFigure)->ApplyControlPointConstraints(0, point2D); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map(correctedPoint, newPoint3D); mitk::Point2D newDisplayPosition; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map(planarFigure->GetControlPoint(i), previousPoint3D); if (renderer->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance); } if (tooClose) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { const mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType)6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType)25.0; } } diff --git a/Modules/Segmentation/Controllers/mitkToolManager.cpp b/Modules/Segmentation/Controllers/mitkToolManager.cpp index 852d3b52f1..4a71cac9a9 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.cpp +++ b/Modules/Segmentation/Controllers/mitkToolManager.cpp @@ -1,523 +1,603 @@ /*============================================================================ 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 "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include "mitkCoreObjectFactory.h" #include #include #include #include "mitkInteractionEventObserver.h" #include "mitkSegTool2D.h" +#include "mitkRenderingManager.h" +#include "mitkSliceNavigationController.h" #include "usGetModuleContext.h" #include "usModuleContext.h" mitk::ToolManager::ToolManager(DataStorage *storage) : m_ActiveTool(nullptr), m_ActiveToolID(-1), m_RegisteredClients(0), m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools // are registered) - bug 1029 this->InitializeTools(); } +void mitk::ToolManager::EnsureTimeObservation() +{ + if (nullptr != mitk::RenderingManager::GetInstance() && nullptr != mitk::RenderingManager::GetInstance()->GetTimeNavigationController()) + { + auto timeController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); + + m_LastTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + auto currentTimeController = m_CurrentTimeNavigationController.Lock(); + + if (timeController != currentTimeController) + { + if (currentTimeController.IsNotNull()) + { + currentTimeController->RemoveObserver(m_TimePointObserverTag); + } + + itk::MemberCommand::Pointer command = itk::MemberCommand::New(); + command->SetCallbackFunction(this, &ToolManager::OnTimeChanged); + command->SetCallbackFunction(this, &ToolManager::OnTimeChangedConst); + m_CurrentTimeNavigationController = timeController; + m_TimePointObserverTag = timeController->AddObserver(SliceNavigationController::GeometryTimeEvent(nullptr,0), command); + } + } +} + +void mitk::ToolManager::StopTimeObservation() +{ + auto currentTimeController = m_CurrentTimeNavigationController.Lock(); + + if (currentTimeController.IsNotNull()) + { + currentTimeController->RemoveObserver(m_TimePointObserverTag); + m_CurrentTimeNavigationController = nullptr; + m_TimePointObserverTag = 0; + } +} + mitk::ToolManager::~ToolManager() { for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) (*dataIter)->RemoveObserver(m_WorkingDataObserverTags[(*dataIter)]); if (this->GetDataStorage() != nullptr) this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for (auto observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter) { observerTagMapIter->first->RemoveObserver(observerTagMapIter->second); } + this->StopTimeObservation(); } void mitk::ToolManager::InitializeTools() { // clear all previous tool pointers (tools may be still activated from another recently used plugin) if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } m_Tools.clear(); // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for (auto iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter) { if (auto *tool = dynamic_cast(iter->GetPointer())) { tool->InitializeStateMachine(); tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1(this, &ToolManager::OnToolErrorMessage); tool->GeneralMessage += MessageDelegate1(this, &ToolManager::OnGeneralToolMessage); m_Tools.push_back(tool); } } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter) { resultList.push_back(iter->GetPointer()); } return resultList; } mitk::Tool *mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch (const std::exception &) { return nullptr; } } bool mitk::ToolManager::ActivateTool(int id) { - if (id != -1 && !this->GetToolById(id)->CanHandle(this->GetReferenceData(0)->GetData())) + const auto workingDataNode = this->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } + + const auto referenceDataNode = this->GetReferenceData(0); + const mitk::BaseData* referenceData = nullptr; + if (nullptr != referenceDataNode) + { + referenceData = referenceDataNode->GetData(); + } + + if (id != -1 && !this->GetToolById(id)->CanHandle(referenceData, workingData)) return false; if (this->GetDataStorage()) { this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); } if (GetToolById(id) == m_ActiveTool) return true; // no change needed static int nextTool = -1; nextTool = id; static bool inActivateTool = false; if (inActivateTool) { return true; } inActivateTool = true; while (nextTool != m_ActiveToolID) { // Deactivate all other active tools to ensure a globally single active tool for (const auto& toolManager : ToolManagerProvider::GetInstance()->GetToolManagers()) { if (nullptr != toolManager.second->m_ActiveTool) { toolManager.second->m_ActiveTool->Deactivated(); toolManager.second->m_ActiveToolRegistration.Unregister(); // The active tool of *this* ToolManager is handled below this loop if (this != toolManager.second) { toolManager.second->m_ActiveTool = nullptr; toolManager.second->m_ActiveToolID = -1; toolManager.second->ActiveToolChanged.Send(); } } } m_ActiveTool = GetToolById(nextTool); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { + this->EnsureTimeObservation(); if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } } inActivateTool = false; return (m_ActiveTool != nullptr); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { auto searchIter = m_ReferenceDataObserverTags.find(*dataIter); if (searchIter != m_ReferenceDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeletedConst); m_ReferenceDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheReferenceDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_ReferenceDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetReferenceData(v); } void mitk::ToolManager::SetReferenceData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if (data != m_WorkingData) { // remove observers from old nodes for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { auto searchIter = m_WorkingDataObserverTags.find(*dataIter); if (searchIter != m_WorkingDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_WorkingData = data; // TODO tell active tool? // Quick workaround for bug #16598 if (m_WorkingData.empty()) this->ActivateTool(-1); // workaround end // attach new observers m_WorkingDataObserverTags.clear(); for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeletedConst); m_WorkingDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheWorkingDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_WorkingDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetWorkingData(v); } void mitk::ToolManager::SetWorkingData(DataNode *data) { DataVectorType v; if (data) // don't allow for nullptr nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { auto searchIter = m_RoiDataObserverTags.find(*dataIter); if (searchIter != m_RoiDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeletedConst); m_RoiDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheRoiDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_RoiDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetRoiData(v); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode *mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode *mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::DataStorage *mitk::ToolManager::GetDataStorage() { if (!m_DataStorage.IsExpired()) { return m_DataStorage.Lock(); } else { return nullptr; } } void mitk::ToolManager::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } mitk::DataNode *mitk::ToolManager::GetWorkingData(unsigned int idx) { if (m_WorkingData.empty()) return nullptr; if (m_WorkingData.size() > idx) return m_WorkingData[idx]; return nullptr; } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool *mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if (m_RegisteredClients < 1) return; --m_RegisteredClients; if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } } } int mitk::ToolManager::GetToolID(const Tool *tool) { int id(0); for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (tool == iter->GetPointer()) { return id; } } return -1; } void mitk::ToolManager::OnNodeRemoved(const mitk::DataNode *node) { // check all storage vectors OnOneOfTheReferenceDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheRoiDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheWorkingDataDeleted(const_cast(node), itk::DeleteEvent()); } + +void mitk::ToolManager::OnTimeChanged(itk::Object* caller, const itk::EventObject& e) +{ + this->OnTimeChangedConst(caller, e); +} + +void mitk::ToolManager::OnTimeChangedConst(const itk::Object* caller, const itk::EventObject& /*e*/) +{ + auto currentController = m_CurrentTimeNavigationController.Lock(); + if (caller == currentController) + { + const auto currentTimePoint = currentController->GetSelectedTimePoint(); + if (currentTimePoint != m_LastTimePoint) + { + m_LastTimePoint = currentTimePoint; + SelectedTimePointChanged.Send(); + } + } +} + +mitk::TimePointType mitk::ToolManager::GetCurrentTimePoint() const +{ + return m_LastTimePoint; +} diff --git a/Modules/Segmentation/Controllers/mitkToolManager.h b/Modules/Segmentation/Controllers/mitkToolManager.h index 6edbac0e7c..4cdfbf263a 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.h +++ b/Modules/Segmentation/Controllers/mitkToolManager.h @@ -1,282 +1,302 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkToolManager_h_Included #define mitkToolManager_h_Included #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkTool.h" #include "mitkWeakPointer.h" #include #pragma GCC visibility push(default) #include #pragma GCC visibility pop #include namespace mitk { class Image; class PlaneGeometry; /** \brief Manages and coordinates instances of mitk::Tool. \sa QmitkToolSelectionBox \sa Tool \sa QmitkSegmentationView \ingroup Interaction \ingroup ToolManagerEtAl There is a separate page describing the general design of QmitkSegmentationView: \ref QmitkSegmentationTechnicalPage This class creates and manages several instances of mitk::Tool. \li ToolManager creates instances of mitk::Tool by asking the itk::ObjectFactory to list all known implementations of mitk::Tool. As a result, one has to implement both a subclass of mitk::Tool and a matching subclass of itk::ObjectFactoryBase that is registered to the top-level itk::ObjectFactory. For an example, see mitkContourToolFactory.h. (this limitiation of one-class-one-factory is due to the implementation of itk::ObjectFactory). In MITK, the right place to register the factories to itk::ObjectFactory is the mitk::QMCoreObjectFactory or mitk::SBCoreObjectFactory. \li ToolManager knows a set of "reference" DataNodes and a set of "working" DataNodes. The first application are segmentation tools, where the reference is the original image and the working data the (kind of) binary segmentation. However, ToolManager is implemented more generally, so that there could be other tools that work, e.g., with surfaces. \li There is a set of events that are sent by ToolManager. At the moment these are TODO update documentation: - mitk::ToolReferenceDataChangedEvent whenever somebody calls SetReferenceData. Most of the time this actually means that the data has changed, but there might be cases where the same data is passed to SetReferenceData a second time, so don't rely on the assumption that something actually changed. - mitk::ToolSelectedEvent is sent when a (truly) different tool was activated. In reaction to this event you can ask for the active Tool using GetActiveTool or GetActiveToolID (where nullptr or -1 indicate that NO tool is active at the moment). Design descisions: \li Not a singleton, because there could be two functionalities using tools, each one with different reference/working data. $Author$ */ class MITKSEGMENTATION_EXPORT ToolManager : public itk::Object { public: typedef std::vector ToolVectorType; typedef std::vector ToolVectorTypeConst; typedef std::vector DataVectorType; // has to be observed for delete events! typedef std::map NodeTagMapType; Message<> NodePropertiesChanged; Message<> NewNodesGenerated; Message1 NewNodeObjectsGenerated; Message<> ActiveToolChanged; Message<> ReferenceDataChanged; Message<> WorkingDataChanged; Message<> RoiDataChanged; + Message<> SelectedTimePointChanged; Message1 ToolErrorMessage; Message1 GeneralToolMessage; mitkClassMacroItkParent(ToolManager, itk::Object); mitkNewMacro1Param(ToolManager, DataStorage *); /** \brief Gives you a list of all tools. This is const on purpose. */ const ToolVectorTypeConst GetTools(); int GetToolID(const Tool *tool); - /* + /** \param id The tool of interest. Counting starts with 0. */ Tool *GetToolById(int id); /** \param id The tool to activate. Provide -1 for disabling any tools. Counting starts with 0. Registeres a listner for NodeRemoved event at DataStorage (see mitk::ToolManager::OnNodeRemoved). */ bool ActivateTool(int id); template int GetToolIdByToolType() { int id = 0; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (dynamic_cast(iter->GetPointer())) { return id; } } return -1; } /** \return -1 for "No tool is active" */ int GetActiveToolID(); /** \return nullptr for "No tool is active" */ Tool *GetActiveTool(); - /* + /** \brief Set a list of data/images as reference objects. */ void SetReferenceData(DataVectorType); - /* + /** \brief Set single data item/image as reference object. */ void SetReferenceData(DataNode *); - /* + /** \brief Set a list of data/images as working objects. */ void SetWorkingData(DataVectorType); - /* + /** \brief Set single data item/image as working object. */ void SetWorkingData(DataNode *); - /* + /** \brief Set a list of data/images as roi objects. */ void SetRoiData(DataVectorType); - /* + /** \brief Set a single data item/image as roi object. */ void SetRoiData(DataNode *); - /* + /** \brief Get the list of reference data. */ DataVectorType GetReferenceData(); - /* + /** \brief Get the current reference data. \warning If there is a list of items, this method will only return the first list item. */ DataNode *GetReferenceData(int); - /* + /** \brief Get the list of working data. */ DataVectorType GetWorkingData(); - /* + /** \brief Get the current working data. \warning If there is a list of items, this method will only return the first list item. */ DataNode *GetWorkingData(unsigned int); - /* + /** \brief Get the current roi data */ DataVectorType GetRoiData(); - /* + /** \brief Get the roi data at position idx */ DataNode *GetRoiData(int idx); DataStorage *GetDataStorage(); void SetDataStorage(DataStorage &storage); - /* + /** Get the current selected time point of the RenderManager + */ + TimePointType GetCurrentTimePoint() const; + + /** \brief Tell that someone is using tools. GUI elements should call this when they become active. This method increases an internal "client count". */ void RegisterClient(); - /* + /** \brief Tell that someone is NOT using tools. GUI elements should call this when they become active. This method increases an internal "client count". */ void UnregisterClient(); /** \brief Initialize all classes derived from mitk::Tool by itkObjectFactoy */ void InitializeTools(); void OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e); void OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &e); void OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e); void OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &e); void OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e); void OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &e); - /* + /** \brief Connected to tool's messages This method just resends error messages coming from any of the tools. This way clients (GUIs) only have to observe one message. */ void OnToolErrorMessage(std::string s); void OnGeneralToolMessage(std::string s); protected: /** You may specify a list of tool "groups" that should be available for this ToolManager. Every Tool can report its group as a string. This constructor will try to find the tool's group inside the supplied string. If there is a match, the tool is accepted. Effectively, you can provide a human readable list like "default, lymphnodevolumetry, oldERISstuff". */ ToolManager(DataStorage *storage); // purposely hidden ~ToolManager() override; ToolVectorType m_Tools; Tool *m_ActiveTool; int m_ActiveToolID; us::ServiceRegistration m_ActiveToolRegistration; DataVectorType m_ReferenceData; NodeTagMapType m_ReferenceDataObserverTags; DataVectorType m_WorkingData; NodeTagMapType m_WorkingDataObserverTags; DataVectorType m_RoiData; NodeTagMapType m_RoiDataObserverTags; int m_RegisteredClients; WeakPointer m_DataStorage; /// \brief Callback for NodeRemove events void OnNodeRemoved(const mitk::DataNode *node); + + /** Callback for time changed events*/ + void OnTimeChangedConst(const itk::Object* caller, const itk::EventObject& e); + void OnTimeChanged(itk::Object* caller, const itk::EventObject& e); + + void EnsureTimeObservation(); + void StopTimeObservation(); + + private: + /** Time point of last detected change*/ + TimePointType m_LastTimePoint = 0; + /** Tag of the observer that listens to time changes*/ + unsigned long m_TimePointObserverTag = 0; + /** Pointer to the observed time stepper*/ + WeakPointer m_CurrentTimeNavigationController; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp index 306d0fcac8..64de93da55 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp @@ -1,117 +1,117 @@ /*============================================================================ 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 "mitkAdaptiveRegionGrowingTool.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkToolManager.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AdaptiveRegionGrowingTool, "AdaptiveRegionGrowingTool"); } mitk::AdaptiveRegionGrowingTool::AdaptiveRegionGrowingTool() { m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("3D_Regiongrowing_Seedpoint")); m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PointSet = mitk::PointSet::New(); m_PointSetNode->SetData(m_PointSet); } mitk::AdaptiveRegionGrowingTool::~AdaptiveRegionGrowingTool() { } -bool mitk::AdaptiveRegionGrowingTool::CanHandle(BaseData *referenceData) const +bool mitk::AdaptiveRegionGrowingTool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::AdaptiveRegionGrowingTool::GetXPM() const { return nullptr; } const char *mitk::AdaptiveRegionGrowingTool::GetName() const { return "Region Growing 3D"; } us::ModuleResource mitk::AdaptiveRegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } void mitk::AdaptiveRegionGrowingTool::Activated() { Superclass::Activated(); if (!GetDataStorage()->Exists(m_PointSetNode)) GetDataStorage()->Add(m_PointSetNode, GetWorkingData()); m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); m_SeedPointInteractor->SetDataNode(m_PointSetNode); } void mitk::AdaptiveRegionGrowingTool::Deactivated() { m_PointSet->Clear(); GetDataStorage()->Remove(m_PointSetNode); Superclass::Deactivated(); } void mitk::AdaptiveRegionGrowingTool::ConfirmSegmentation() { m_ToolManager->ActivateTool(-1); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetReferenceData() { return this->m_ToolManager->GetReferenceData(0); } mitk::DataStorage *mitk::AdaptiveRegionGrowingTool::GetDataStorage() { return this->m_ToolManager->GetDataStorage(); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetWorkingData() { return this->m_ToolManager->GetWorkingData(0); } mitk::DataNode::Pointer mitk::AdaptiveRegionGrowingTool::GetPointSetNode() { return m_PointSetNode; } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h index 31797bffbc..7924978442 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h @@ -1,128 +1,128 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkAdaptiveRegionGrowingTool_h_Included #define mitkAdaptiveRegionGrowingTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataStorage.h" #include "mitkPointSet.h" #include "mitkSinglePointDataInteractor.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Dummy Tool for AdaptiveRegionGrowingToolGUI to get Tool functionality for AdaptiveRegionGrowing. The actual logic is implemented in QmitkAdaptiveRegionGrowingToolGUI. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AdaptiveRegionGrowingTool : public AutoSegmentationTool { public: /** * @brief mitkClassMacro */ mitkClassMacro(AdaptiveRegionGrowingTool, AutoSegmentationTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** * @brief Get XPM * @return nullptr */ const char **GetXPM() const override; /** * @brief Get name * @return name of the Tool */ const char *GetName() const override; /** * @brief Get icon resource * @return the resource Object of the Icon */ us::ModuleResource GetIconResource() const override; /** * @brief Adds interactor for the seedpoint and creates a seedpoint if neccessary. * * */ void Activated() override; /** * @brief Removes all set points and interactors. * * */ void Deactivated() override; /** * @brief get pointset node * @return the point set node */ virtual DataNode::Pointer GetPointSetNode(); /** * @brief get reference data * @return the current reference data. */ mitk::DataNode *GetReferenceData(); /** * @brief Get working data * @return a list of all working data. */ mitk::DataNode *GetWorkingData(); /** * @brief Get datastorage * @return the current data storage. */ mitk::DataStorage *GetDataStorage(); void ConfirmSegmentation(); protected: /** * @brief constructor */ AdaptiveRegionGrowingTool(); // purposely hidden /** * @brief destructor */ ~AdaptiveRegionGrowingTool() override; private: PointSet::Pointer m_PointSet; SinglePointDataInteractor::Pointer m_SeedPointInteractor; DataNode::Pointer m_PointSetNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index c360e1db47..96ba360d0f 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,98 +1,97 @@ /*============================================================================ 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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImage(const mitk::Image* image, unsigned int timestep) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImageByTimePoint(const mitk::Image* image, TimePointType timePoint) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; - return this->Get3DImage(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); + return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { m_OverwriteExistingSegmentation = overwrite; } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() { - mitk::DataNode::Pointer emptySegmentation; - if (m_OverwriteExistingSegmentation) - { - emptySegmentation = m_ToolManager->GetWorkingData(0); - } - else + mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); + if (!m_OverwriteExistingSegmentation) { mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); if (refNode.IsNull()) { // TODO create and use segmentation exceptions instead!! MITK_ERROR << "No valid reference data!"; return nullptr; } - std::string nodename = m_ToolManager->GetReferenceData(0)->GetName() + "_" + this->GetName(); + + std::string nodename = refNode->GetName() + "_" + this->GetName(); mitk::Color color; color.SetRed(1); color.SetBlue(0); color.SetGreen(0); - emptySegmentation = CreateEmptySegmentationNode(dynamic_cast(refNode->GetData()), nodename, color); - m_ToolManager->GetDataStorage()->Add(emptySegmentation, refNode); + //create a new segmentation node based on the current segmentation as template + segmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); + + m_ToolManager->GetDataStorage()->Add(segmentationNode, refNode); } - return emptySegmentation; + return segmentationNode; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index 1a2d458950..dfb36b908c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,65 +1,71 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); + /** This function controls wether a confirmed segmentation should replace the old + * segmentation/working node (true) or if it should be stored as new and additional + * node (false). + */ void SetOverwriteExistingSegmentation(bool overwrite); /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Depending on the selected mode either returns the currently selected segmentation * or creates a new one from the selected reference data and adds the new segmentation * to the datastorage * @return a mitk::DataNode which contains a segmentation image */ virtual mitk::DataNode *GetTargetSegmentationNode(); protected: AutoSegmentationTool(); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; - virtual Image::ConstPointer Get3DImage(const Image* image, unsigned int timestep) const; - virtual Image::ConstPointer Get3DImageByTimePoint(const Image* image, TimePointType timePoint) const; + /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); + /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); bool m_OverwriteExistingSegmentation; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp new file mode 100644 index 0000000000..8e9b4291f7 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -0,0 +1,474 @@ +/*============================================================================ + +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 "mitkAutoSegmentationWithPreviewTool.h" + +#include "mitkToolManager.h" + +#include "mitkColorProperty.h" +#include "mitkLevelWindowProperty.h" +#include "mitkProperties.h" + +#include "mitkDataStorage.h" +#include "mitkRenderingManager.h" +#include + +#include "mitkImageAccessByItk.h" +#include "mitkImageCast.h" +#include "mitkImageStatisticsHolder.h" +#include "mitkImageTimeSelector.h" +#include "mitkLabelSetImage.h" +#include "mitkMaskAndCutRoiImageFilter.h" +#include "mitkPadImageFilter.h" +#include "mitkNodePredicateGeometry.h" + +mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) +{ + m_ProgressCommand = mitk::ToolCommand::New(); +} + +mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() +{ +} + +bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const +{ + if (!Superclass::CanHandle(referenceData, workingData)) + return false; + + if (workingData == nullptr) + return true; + + auto* labelSet = dynamic_cast(workingData); + + if (labelSet != nullptr) + return true; + + auto* image = dynamic_cast(workingData); + + if (image == nullptr) + return false; + + //if it is a normal image and not a label set image is used as working data + //it must have the same pixel type as a label set. + return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); +} + +void mitk::AutoSegmentationWithPreviewTool::Activated() +{ + Superclass::Activated(); + + m_ToolManager->RoiDataChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); + m_SegmentationInputNode = m_ReferenceDataNode; + + m_LastTimePointOfUpdate = 0; + + if (m_PreviewSegmentationNode.IsNull()) + { + m_PreviewSegmentationNode = DataNode::New(); + m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); + m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); + m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); + m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); + m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); + } + + if (m_SegmentationInputNode.IsNotNull()) + { + this->ResetPreviewNode(); + this->InitiateToolByInput(); + } + else + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::Deactivated() +{ + m_ToolManager->RoiDataChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_SegmentationInputNode = nullptr; + m_ReferenceDataNode = nullptr; + + try + { + if (DataStorage *storage = m_ToolManager->GetDataStorage()) + { + storage->Remove(m_PreviewSegmentationNode); + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (...) + { + // don't care + } + + m_PreviewSegmentationNode->SetData(nullptr); + + Superclass::Deactivated(); +} + +void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() +{ + if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) + { // The tool should create all time steps but is currently in lazy mode, + // thus ensure that a preview for all time steps is available. + this->UpdatePreview(true); + } + + CreateResultSegmentationFromPreview(); + + RenderingManager::GetInstance()->RequestUpdateAll(); + + if (!m_KeepActiveAfterAccept) + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() +{ + //default implementation does nothing. + //implement in derived classes to change behavior +} + +mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() +{ + if (m_PreviewSegmentationNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_PreviewSegmentationNode->GetData()); +} + +mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() +{ + return m_PreviewSegmentationNode; +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const +{ + if (m_SegmentationInputNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_SegmentationInputNode->GetData()); +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const +{ + if (m_ReferenceDataNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_ReferenceDataNode->GetData()); +} + +void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() +{ + itk::RGBPixel previewColor; + previewColor[0] = 0.0f; + previewColor[1] = 1.0f; + previewColor[2] = 0.0f; + + const auto image = this->GetSegmentationInput(); + if (nullptr != image) + { + mitk::LabelSetImage::ConstPointer workingImage = + dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + + if (workingImage.IsNotNull()) + { + auto newPreviewImage = workingImage->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage); + + // Let's paint the feedback node green... + newPreviewImage->GetActiveLabel()->SetColor(previewColor); + newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); + } + else + { + mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + if (workingImageBin.IsNotNull()) + { + auto newPreviewImage = workingImageBin->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); + } + else + { + mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; + } + } + + m_PreviewSegmentationNode->SetColor(previewColor); + m_PreviewSegmentationNode->SetOpacity(0.5); + + int layer(50); + m_ReferenceDataNode->GetIntProperty("layer", layer); + m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); + + if (DataStorage *ds = m_ToolManager->GetDataStorage()) + { + if (!ds->Exists(m_PreviewSegmentationNode)) + ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); + } + } +} + +template +static void ITKSetVolume(const itk::Image *originalImage, + mitk::Image *segmentation, + unsigned int timeStep) +{ + auto constPixelContainer = originalImage->GetPixelContainer(); + //have to make a const cast because itk::PixelContainer does not provide a const correct access :( + auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); + + segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); +} + +void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) +{ + try + { + Image::ConstPointer image3D = this->GetImageByTimeStep(sourceImage, timeStep); + + if (image3D->GetPixelType() != destinationImage->GetPixelType()) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " + << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() + << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); + } + + if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_PRECISION, false)) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; + } + + if (image3D->GetDimension() == 2) + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 2, destinationImage, timeStep); + } + else + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 3, destinationImage, timeStep); + } + } + catch (...) + { + Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); + throw; + } +} + +void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() +{ + const auto segInput = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + if (nullptr != segInput && nullptr != previewImage) + { + DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); + + if (resultSegmentationNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); + + // REMARK: the following code in this scope assumes that previewImage and resultSegmentation + // are clones of the working image (segmentation provided to the tool). Therefore they have + // the same time geometry. + if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) + { + mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." + << " Preview segmentation and segmentation result image have different time geometries."; + } + + if (m_CreateAllTimeSteps) + { + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) + { + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + } + else + { + const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + + // since we are maybe working on a smaller image, pad it to the size of the original image + if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) + { + mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); + + padFilter->SetInput(0, resultSegmentation); + padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); + padFilter->SetBinaryFilter(true); + padFilter->SetUpperThreshold(1); + padFilter->SetLowerThreshold(1); + padFilter->Update(); + + resultSegmentationNode->SetData(padFilter->GetOutput()); + } + + m_ToolManager->SetWorkingData(resultSegmentationNode); + m_ToolManager->GetWorkingData(0)->Modified(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() +{ + mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); + + if (node.IsNotNull()) + { + mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); + mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); + + if (image.IsNull()) + return; + + roiFilter->SetInput(image); + roiFilter->SetRegionOfInterest(node->GetData()); + roiFilter->Update(); + + mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); + tmpNode->SetData(roiFilter->GetOutput()); + + m_SegmentationInputNode = tmpNode; + } + else + m_SegmentationInputNode = m_ReferenceDataNode; + + this->ResetPreviewNode(); + this->InitiateToolByInput(); + this->UpdatePreview(); +} + +void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() +{ + if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; + if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) + { //we only need to update either because we are lazzy + //or because we have a static segmentation with a dynamic image + this->UpdatePreview(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) +{ + const auto inputImage = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + int progress_steps = 200; + + this->UpdatePrepare(); + + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + try + { + this->CurrentlyBusy.Send(true); + if (nullptr != inputImage && nullptr != previewImage) + { + m_ProgressCommand->AddStepsToDo(progress_steps); + + if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) + { + for (unsigned int timeStep = 0; timeStep < inputImage->GetTimeSteps(); ++timeStep) + { + auto feedBackImage3D = this->GetImageByTimeStep(inputImage, timeStep); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + } + else + { + auto feedBackImage3D = this->GetImageByTimePoint(inputImage, timePoint); + auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (itk::ExceptionObject & excep) + { + MITK_ERROR << "Exception caught: " << excep.GetDescription(); + + m_ProgressCommand->SetProgress(progress_steps); + + std::string msg = excep.GetDescription(); + ErrorMessage.Send(msg); + } + catch (...) + { + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); + throw; + } + + this->UpdateCleanUp(); + m_LastTimePointOfUpdate = timePoint; + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const +{ + return m_LastTimePointOfUpdate; +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h new file mode 100644 index 0000000000..848ff0c005 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -0,0 +1,152 @@ +/*============================================================================ + +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. + +============================================================================*/ + +#ifndef mitkAutoSegmentationWithPreviewTool_h_Included +#define mitkAutoSegmentationWithPreviewTool_h_Included + +#include "mitkAutoSegmentationTool.h" +#include "mitkCommon.h" +#include "mitkDataNode.h" +#include "mitkToolCommand.h" +#include + +namespace mitk +{ + /** + \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. + + This tool class implements a lot basic logic to handle auto segmentation tools with preview, + Time point and ROI support. Derived classes will ask to update the segmentation preview if needed + (e.g. because the ROI or the current time point has changed) or because derived tools + indicated the need to update themselves. + This class also takes care to properly transfer a confirmed preview into the segementation + result. + + \ingroup ToolManagerEtAl + \sa mitk::Tool + \sa QmitkInteractiveSegmentation + */ + class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool + { + public: + + mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); + + void Activated() override; + void Deactivated() override; + + void ConfirmSegmentation(); + + itkSetMacro(CreateAllTimeSteps, bool); + itkGetMacro(CreateAllTimeSteps, bool); + itkBooleanMacro(CreateAllTimeSteps); + + itkSetMacro(KeepActiveAfterAccept, bool); + itkGetMacro(KeepActiveAfterAccept, bool); + itkBooleanMacro(KeepActiveAfterAccept); + + itkSetMacro(IsTimePointChangeAware, bool); + itkGetMacro(IsTimePointChangeAware, bool); + itkBooleanMacro(IsTimePointChangeAware); + + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; + + /** Triggers the actualization of the preview + * @param ignoreLazyPreviewSetting If set true UpdatePreview will always + * generate the preview for all time steps. If set to false, UpdatePreview + * will regard the setting specified by the constructor. + * To define the update generation for time steps implement DoUpdatePreview. + * To alter what should be done directly before or after the update of the preview, + * reimplement UpdatePrepare() or UpdateCleanUp().*/ + void UpdatePreview(bool ignoreLazyPreviewSetting = false); + + protected: + mitk::ToolCommand::Pointer m_ProgressCommand; + + /** Member is always called if GetSegmentationInput() has changed + * (e.g. because a new ROI was defined, or on activation) to give derived + * classes the posibility to initiate their state accordingly. + * Reimplement this function to implement special behavior. + */ + virtual void InitiateToolByInput(); + + virtual void UpdatePrepare(); + virtual void UpdateCleanUp(); + + /** This function does the real work. Here the preview for a given + * input image should be computed and stored in the also passed + * preview image at the passed time step. + */ + virtual void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; + + AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); + ~AutoSegmentationWithPreviewTool() override; + + /** Returns the image that contains the preview of the current segmentation. + * Returns null if the node is not set or does not contain an image.*/ + Image* GetPreviewSegmentation(); + DataNode* GetPreviewSegmentationNode(); + + /** Returns the input that should be used for any segmentation/preview or tool update. + * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask + * provided by the tool manager. Derived classes should regard this as the relevant + * input data for any processing. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetSegmentationInput() const; + + /** Returns the image that is provided by the ReferenceDataNode. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetReferenceData() const; + + /** Resets the preview node so it is empty and ready to be filled by the tool*/ + void ResetPreviewNode(); + + TimePointType GetLastTimePointOfUpdate() const; + + private: + void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); + + void CreateResultSegmentationFromPreview(); + + void OnRoiDataChanged(); + void OnTimePointChanged(); + + /** Node that containes the preview data generated and managed by this class or derived ones.*/ + DataNode::Pointer m_PreviewSegmentationNode; + /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ + DataNode::Pointer m_ReferenceDataNode; + /** Node that containes the data that should be used as input for any auto segmentation. It might + * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ + DataNode::Pointer m_SegmentationInputNode; + + /** Indicates if Accepting the threshold should transfer/create the segmentations + of all time steps (true) or only of the currently selected timepoint (false).*/ + bool m_CreateAllTimeSteps = false; + + /** Indicates if the tool should kept active after accepting the segmentation or not.*/ + bool m_KeepActiveAfterAccept = false; + + /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). + * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. + * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. + * Therefore it always has to update the preview if current time point has changed and it has to (re)compute + * all timeframes if ConfirmSegmentation() is called.*/ + bool m_LazyDynamicPreviews = false; + + bool m_IsTimePointChangeAware = true; + + TimePointType m_LastTimePointOfUpdate = 0.; + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp new file mode 100644 index 0000000000..a8ee655db0 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -0,0 +1,126 @@ +/*============================================================================ + +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 "mitkBinaryThresholdBaseTool.h" + +#include "mitkImageAccessByItk.h" +#include "mitkImageCast.h" +#include "mitkImageStatisticsHolder.h" +#include "mitkLabelSetImage.h" +#include +#include + +mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() + : m_SensibleMinimumThresholdValue(-100), + m_SensibleMaximumThresholdValue(+100), + m_CurrentLowerThresholdValue(1), + m_CurrentUpperThresholdValue(1) +{ +} + +mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() +{ +} + +void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) +{ + /* If value is not in the min/max range, do nothing. In that case, this + method will be called again with a proper value right after. The only + known case where this happens is with an [0.0, 1.0[ image, where value + could be an epsilon greater than the max. */ + if (lower < m_SensibleMinimumThresholdValue + || lower > m_SensibleMaximumThresholdValue + || upper < m_SensibleMinimumThresholdValue + || upper > m_SensibleMaximumThresholdValue) + { + return; + } + + m_CurrentLowerThresholdValue = lower; + m_CurrentUpperThresholdValue = upper; + + if (nullptr != this->GetPreviewSegmentation()) + { + UpdatePreview(); + } +} + +void mitk::BinaryThresholdBaseTool::InitiateToolByInput() +{ + const auto referenceImage = this->GetReferenceData(); + if (nullptr != referenceImage) + { + m_SensibleMinimumThresholdValue = std::numeric_limits::max(); + m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); + Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); + for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) + { + m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); + m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); + } + + if (m_LockedUpperThreshold) + { + m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; + m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; + } + else + { + double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; + m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; + m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; + } + + bool isFloatImage = false; + if ((referenceImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && + (referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || + referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) + { + isFloatImage = true; + } + + IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); + ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); + } +} + +template +static void ITKThresholding(const itk::Image *originalImage, + mitk::Image *segmentation, + double lower, + double upper, + unsigned int timeStep) +{ + typedef itk::Image ImageType; + typedef itk::Image SegmentationType; + typedef itk::BinaryThresholdImageFilter ThresholdFilterType; + + typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); + filter->SetInput(originalImage); + filter->SetLowerThreshold(lower); + filter->SetUpperThreshold(upper); + filter->SetInsideValue(1); + filter->SetOutsideValue(0); + filter->Update(); + + segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); +} + +void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +{ + if (nullptr != inputAtTimeStep && nullptr != previewImage) + { + AccessByItk_n(inputAtTimeStep, + ITKThresholding, + (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); + } +} diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h new file mode 100644 index 0000000000..cdc15f18be --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -0,0 +1,70 @@ +/*============================================================================ + +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. + +============================================================================*/ + +#ifndef mitkBinaryThresholdBaseTool_h_Included +#define mitkBinaryThresholdBaseTool_h_Included + +#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkCommon.h" +#include "mitkDataNode.h" +#include + +#include +#include + +namespace mitk +{ + /** + \brief Base class for binary threshold tools. + + \ingroup ToolManagerEtAl + \sa mitk::Tool + \sa QmitkInteractiveSegmentation + */ + class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationWithPreviewTool + { + public: + Message3 IntervalBordersChanged; + Message2 ThresholdingValuesChanged; + + mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationWithPreviewTool); + + virtual void SetThresholdValues(double lower, double upper); + + protected: + BinaryThresholdBaseTool(); // purposely hidden + ~BinaryThresholdBaseTool() override; + + itkSetMacro(LockedUpperThreshold, bool); + itkGetMacro(LockedUpperThreshold, bool); + itkBooleanMacro(LockedUpperThreshold); + + itkGetMacro(SensibleMinimumThresholdValue, ScalarType); + itkGetMacro(SensibleMaximumThresholdValue, ScalarType); + + void InitiateToolByInput() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + + private: + ScalarType m_SensibleMinimumThresholdValue; + ScalarType m_SensibleMaximumThresholdValue; + ScalarType m_CurrentLowerThresholdValue; + ScalarType m_CurrentUpperThresholdValue; + + /** Indicates if the tool should behave like a single threshold tool (true) + or like a upper/lower threshold tool (false)*/ + bool m_LockedUpperThreshold = false; + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp index 5ade4cb9f4..332383f5e6 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp @@ -1,418 +1,55 @@ /*============================================================================ 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 "mitkBinaryThresholdTool.h" -#include "mitkBoundingObjectToSegmentationFilter.h" -#include "mitkToolManager.h" - -#include "mitkColorProperty.h" -#include "mitkDataStorage.h" -#include "mitkLevelWindowProperty.h" -#include "mitkProperties.h" -#include "mitkRenderingManager.h" -#include "mitkVtkResliceInterpolationProperty.h" -#include - -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageStatisticsHolder.h" -#include "mitkImageTimeSelector.h" -#include "mitkLabelSetImage.h" -#include "mitkMaskAndCutRoiImageFilter.h" -#include "mitkPadImageFilter.h" -#include -#include - // us #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleResource.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, BinaryThresholdTool, "Thresholding tool"); } -mitk::BinaryThresholdTool::BinaryThresholdTool() - : m_SensibleMinimumThresholdValue(-100), - m_SensibleMaximumThresholdValue(+100), - m_CurrentThresholdValue(0.0), - m_IsFloatImage(false) +mitk::BinaryThresholdTool::BinaryThresholdTool() : BinaryThresholdBaseTool() { - m_ThresholdFeedbackNode = DataNode::New(); - m_ThresholdFeedbackNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_ThresholdFeedbackNode->SetProperty("name", StringProperty::New("Thresholding feedback")); - m_ThresholdFeedbackNode->SetProperty("opacity", FloatProperty::New(0.3)); - m_ThresholdFeedbackNode->SetProperty("binary", BoolProperty::New(true)); - m_ThresholdFeedbackNode->SetProperty("helper object", BoolProperty::New(true)); + LockedUpperThresholdOn(); } mitk::BinaryThresholdTool::~BinaryThresholdTool() { } const char **mitk::BinaryThresholdTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::BinaryThresholdTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Threshold_48x48.png"); return resource; } const char *mitk::BinaryThresholdTool::GetName() const { return "Threshold"; } -void mitk::BinaryThresholdTool::Activated() -{ - Superclass::Activated(); - - m_ToolManager->RoiDataChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnRoiDataChanged); - - m_OriginalImageNode = m_ToolManager->GetReferenceData(0); - m_NodeForThresholding = m_OriginalImageNode; - - if (m_NodeForThresholding.IsNotNull()) - { - SetupPreviewNode(); - } - else - { - m_ToolManager->ActivateTool(-1); - } -} - -void mitk::BinaryThresholdTool::Deactivated() -{ - m_ToolManager->RoiDataChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnRoiDataChanged); - m_NodeForThresholding = nullptr; - m_OriginalImageNode = nullptr; - try - { - if (DataStorage *storage = m_ToolManager->GetDataStorage()) - { - storage->Remove(m_ThresholdFeedbackNode); - RenderingManager::GetInstance()->RequestUpdateAll(); - } - } - catch (...) - { - // don't care - } - m_ThresholdFeedbackNode->SetData(nullptr); - - Superclass::Deactivated(); -} void mitk::BinaryThresholdTool::SetThresholdValue(double value) { - if (m_ThresholdFeedbackNode.IsNotNull()) - { - /* If value is not in the min/max range, do nothing. In that case, this - method will be called again with a proper value right after. The only - known case where this happens is with an [0.0, 1.0[ image, where value - could be an epsilon greater than the max. */ - if (value < m_SensibleMinimumThresholdValue - || value > m_SensibleMaximumThresholdValue) - { - return; - } - - m_CurrentThresholdValue = value; - // Bug 19250: The range of 0.01 is rather random. It was 0.001 before and probably due to rounding error propagation - // in VTK code - // it leads to strange banding effects on floating point images with a huge range (like -40000 - 40000). 0.01 lowers - // this effect - // enough to work with our images. Might not work on images with really huge ranges, though. Anyways, still seems to - // be low enough - // to work for floating point images with a range between 0 and 1. A better solution might be to dynamically - // calculate the value - // based on the value range of the current image (as big as possible, as small as necessary). - // m_ThresholdFeedbackNode->SetProperty( "levelwindow", LevelWindowProperty::New( - // LevelWindow(m_CurrentThresholdValue, 0.01) ) ); - UpdatePreview(); - } -} - -void mitk::BinaryThresholdTool::AcceptCurrentThresholdValue() -{ - CreateNewSegmentationFromThreshold(m_NodeForThresholding); - - RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdTool::CancelThresholding() -{ - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdTool::SetupPreviewNode() -{ - itk::RGBPixel pixel; - pixel[0] = 0.0f; - pixel[1] = 1.0f; - pixel[2] = 0.0f; - - if (m_NodeForThresholding.IsNotNull()) - { - Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - Image::Pointer originalImage = dynamic_cast(m_OriginalImageNode->GetData()); - - if (image.IsNotNull()) - { - mitk::LabelSetImage::Pointer workingImage = - dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - - if (workingImage.IsNotNull()) - { - m_ThresholdFeedbackNode->SetData(workingImage->Clone()); - m_IsOldBinary = false; - - // Let's paint the feedback node green... - mitk::LabelSetImage::Pointer previewImage = - dynamic_cast(m_ThresholdFeedbackNode->GetData()); - - if (previewImage.IsNull()) - { - MITK_ERROR << "Cannot create helper objects."; - return; - } - - previewImage->GetActiveLabel()->SetColor(pixel); - previewImage->GetActiveLabelSet()->UpdateLookupTable(previewImage->GetActiveLabel()->GetValue()); - } - else - { - mitk::Image::Pointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - if (workingImageBin) - { - m_ThresholdFeedbackNode->SetData(workingImageBin->Clone()); - m_IsOldBinary = true; - } - else - m_ThresholdFeedbackNode->SetData(mitk::Image::New()); - } - - m_ThresholdFeedbackNode->SetColor(pixel); - m_ThresholdFeedbackNode->SetOpacity(0.5); - - int layer(50); - m_NodeForThresholding->GetIntProperty("layer", layer); - m_ThresholdFeedbackNode->SetIntProperty("layer", layer + 1); - - if (DataStorage *ds = m_ToolManager->GetDataStorage()) - { - if (!ds->Exists(m_ThresholdFeedbackNode)) - ds->Add(m_ThresholdFeedbackNode, m_OriginalImageNode); - } - - if (image.GetPointer() == originalImage.GetPointer()) - { - Image::StatisticsHolderPointer statistics = originalImage->GetStatistics(); - m_SensibleMinimumThresholdValue = static_cast(statistics->GetScalarValueMin()); - m_SensibleMaximumThresholdValue = static_cast(statistics->GetScalarValueMax()); - } - - if ((originalImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && - (originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || - originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) - m_IsFloatImage = true; - else - m_IsFloatImage = false; - - m_CurrentThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; - - IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, m_IsFloatImage); - ThresholdingValueChanged.Send(m_CurrentThresholdValue); - } - } -} - -template -static void ITKSetVolume(itk::Image *originalImage, - mitk::Image *segmentation, - unsigned int timeStep) -{ - segmentation->SetVolume((void *)originalImage->GetPixelContainer()->GetBufferPointer(), timeStep); -} - -void mitk::BinaryThresholdTool::CreateNewSegmentationFromThreshold(DataNode *node) -{ - if (node) - { - Image::Pointer feedBackImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (feedBackImage.IsNotNull()) - { - DataNode::Pointer emptySegmentation = GetTargetSegmentationNode(); - - if (emptySegmentation) - { - // actually perform a thresholding and ask for an organ type - for (unsigned int timeStep = 0; timeStep < feedBackImage->GetTimeSteps(); ++timeStep) - { - try - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(feedBackImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer image3D = timeSelector->GetOutput(); - - if (image3D->GetDimension() == 2) - { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - else - { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - } - catch (...) - { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - } - } - - if (m_OriginalImageNode.GetPointer() != m_NodeForThresholding.GetPointer()) - { - mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); - - padFilter->SetInput(0, dynamic_cast(emptySegmentation->GetData())); - padFilter->SetInput(1, dynamic_cast(m_OriginalImageNode->GetData())); - padFilter->SetBinaryFilter(true); - padFilter->SetUpperThreshold(1); - padFilter->SetLowerThreshold(1); - padFilter->Update(); - - emptySegmentation->SetData(padFilter->GetOutput()); - } - - m_ToolManager->SetWorkingData(emptySegmentation); - m_ToolManager->GetWorkingData(0)->Modified(); - } - } - } -} - -void mitk::BinaryThresholdTool::OnRoiDataChanged() -{ - mitk::DataNode::Pointer node = m_ToolManager->GetRoiData(0); - - if (node.IsNotNull()) - { - mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); - mitk::Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - - if (image.IsNull()) - return; - - roiFilter->SetInput(image); - roiFilter->SetRegionOfInterest(node->GetData()); - roiFilter->Update(); - - mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); - tmpNode->SetData(roiFilter->GetOutput()); - - m_SensibleMaximumThresholdValue = static_cast(roiFilter->GetMaxValue()); - m_SensibleMinimumThresholdValue = static_cast(roiFilter->GetMinValue()); - - m_NodeForThresholding = tmpNode; - } - else - { - m_NodeForThresholding = m_OriginalImageNode; - } - - this->SetupPreviewNode(); - this->UpdatePreview(); -} - -template -void mitk::BinaryThresholdTool::ITKThresholding(itk::Image *originalImage, - Image *segmentation, - double thresholdValue, - unsigned int timeStep) -{ - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(thresholdValue); - filter->SetUpperThreshold(m_SensibleMaximumThresholdValue); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -template -void mitk::BinaryThresholdTool::ITKThresholdingOldBinary(itk::Image *originalImage, - Image *segmentation, - double thresholdValue, - unsigned int timeStep) -{ - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(thresholdValue); - filter->SetUpperThreshold(m_SensibleMaximumThresholdValue); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::BinaryThresholdTool::UpdatePreview() -{ - mitk::Image::Pointer thresholdImage = dynamic_cast(m_NodeForThresholding->GetData()); - mitk::Image::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (thresholdImage && previewImage) - { - for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(thresholdImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer feedBackImage3D = timeSelector->GetOutput(); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, ITKThresholdingOldBinary, (previewImage, m_CurrentThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, ITKThresholding, (previewImage, m_CurrentThresholdValue, timeStep)); - } - } - - RenderingManager::GetInstance()->RequestUpdateAll(); - } + this->SetThresholdValues(value, this->GetSensibleMaximumThresholdValue()); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h index e2a8d712e8..aed3e99328 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h @@ -1,96 +1,56 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkBinaryThresholdTool_h_Included #define mitkBinaryThresholdTool_h_Included -#include "mitkAutoSegmentationTool.h" -#include "mitkCommon.h" -#include "mitkDataNode.h" +#include "mitkBinaryThresholdBaseTool.h" #include -#include - namespace us { class ModuleResource; } namespace mitk { /** \brief Calculates the segmented volumes for binary images. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation Last contributor: $Author$ */ - class MITKSEGMENTATION_EXPORT BinaryThresholdTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT BinaryThresholdTool : public BinaryThresholdBaseTool { public: - Message3 IntervalBordersChanged; - Message1 ThresholdingValueChanged; - mitkClassMacro(BinaryThresholdTool, AutoSegmentationTool); + mitkClassMacro(BinaryThresholdTool, BinaryThresholdBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char **GetXPM() const override; + const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; - void Activated() override; - void Deactivated() override; - virtual void SetThresholdValue(double value); - virtual void AcceptCurrentThresholdValue(); - virtual void CancelThresholding(); protected: BinaryThresholdTool(); // purposely hidden ~BinaryThresholdTool() override; - - void SetupPreviewNode(); - - void CreateNewSegmentationFromThreshold(DataNode *node); - - void OnRoiDataChanged(); - void UpdatePreview(); - - template - void ITKThresholding(itk::Image *originalImage, - mitk::Image *segmentation, - double thresholdValue, - unsigned int timeStep); - template - void ITKThresholdingOldBinary(itk::Image *originalImage, - mitk::Image *segmentation, - double thresholdValue, - unsigned int timeStep); - - DataNode::Pointer m_ThresholdFeedbackNode; - DataNode::Pointer m_OriginalImageNode; - DataNode::Pointer m_NodeForThresholding; - - double m_SensibleMinimumThresholdValue; - double m_SensibleMaximumThresholdValue; - double m_CurrentThresholdValue; - bool m_IsFloatImage; - - bool m_IsOldBinary = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp index ab917f34fe..7e729be00e 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp @@ -1,406 +1,50 @@ /*============================================================================ 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 "mitkBinaryThresholdULTool.h" -#include "mitkToolManager.h" - -#include "mitkColorProperty.h" -#include "mitkLevelWindowProperty.h" -#include "mitkProperties.h" - -#include "mitkDataStorage.h" -#include "mitkRenderingManager.h" -#include - -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageStatisticsHolder.h" -#include "mitkImageTimeSelector.h" -#include "mitkLabelSetImage.h" -#include "mitkMaskAndCutRoiImageFilter.h" -#include "mitkPadImageFilter.h" -#include -#include - // us #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" #include "usModuleResource.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, BinaryThresholdULTool, "ThresholdingUL tool"); } -mitk::BinaryThresholdULTool::BinaryThresholdULTool() - : m_SensibleMinimumThresholdValue(-100), - m_SensibleMaximumThresholdValue(+100), - m_CurrentLowerThresholdValue(1), - m_CurrentUpperThresholdValue(1) +mitk::BinaryThresholdULTool::BinaryThresholdULTool() : BinaryThresholdBaseTool() { - m_ThresholdFeedbackNode = DataNode::New(); - m_ThresholdFeedbackNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_ThresholdFeedbackNode->SetProperty("name", StringProperty::New("Thresholding feedback")); - m_ThresholdFeedbackNode->SetProperty("opacity", FloatProperty::New(0.3)); - m_ThresholdFeedbackNode->SetProperty("binary", BoolProperty::New(true)); - m_ThresholdFeedbackNode->SetProperty("helper object", BoolProperty::New(true)); } mitk::BinaryThresholdULTool::~BinaryThresholdULTool() { } const char **mitk::BinaryThresholdULTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::BinaryThresholdULTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("TwoThresholds_48x48.png"); return resource; } const char *mitk::BinaryThresholdULTool::GetName() const { return "UL Threshold"; } -void mitk::BinaryThresholdULTool::Activated() -{ - Superclass::Activated(); - - m_ToolManager->RoiDataChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdULTool::OnRoiDataChanged); - - m_OriginalImageNode = m_ToolManager->GetReferenceData(0); - m_NodeForThresholding = m_OriginalImageNode; - - if (m_NodeForThresholding.IsNotNull()) - { - SetupPreviewNode(); - } - else - { - m_ToolManager->ActivateTool(-1); - } -} - -void mitk::BinaryThresholdULTool::Deactivated() -{ - m_ToolManager->RoiDataChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdULTool::OnRoiDataChanged); - m_NodeForThresholding = nullptr; - m_OriginalImageNode = nullptr; - try - { - if (DataStorage *storage = m_ToolManager->GetDataStorage()) - { - storage->Remove(m_ThresholdFeedbackNode); - RenderingManager::GetInstance()->RequestUpdateAll(); - } - } - catch (...) - { - // don't care - } - m_ThresholdFeedbackNode->SetData(nullptr); - - Superclass::Deactivated(); -} - -void mitk::BinaryThresholdULTool::SetThresholdValues(double lower, double upper) -{ - if (m_ThresholdFeedbackNode.IsNotNull()) - { - m_CurrentLowerThresholdValue = lower; - m_CurrentUpperThresholdValue = upper; - UpdatePreview(); - } -} - -void mitk::BinaryThresholdULTool::AcceptCurrentThresholdValue() -{ - CreateNewSegmentationFromThreshold(m_NodeForThresholding); - - RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdULTool::CancelThresholding() -{ - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdULTool::SetupPreviewNode() -{ - itk::RGBPixel pixel; - pixel[0] = 0.0f; - pixel[1] = 1.0f; - pixel[2] = 0.0f; - - if (m_NodeForThresholding.IsNotNull()) - { - Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - Image::Pointer originalImage = dynamic_cast(m_OriginalImageNode->GetData()); - - if (image.IsNotNull()) - { - mitk::LabelSetImage::Pointer workingImage = - dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - - if (workingImage.IsNotNull()) - { - m_ThresholdFeedbackNode->SetData(workingImage->Clone()); - m_IsOldBinary = false; - - // Let's paint the feedback node green... - mitk::LabelSetImage::Pointer previewImage = - dynamic_cast(m_ThresholdFeedbackNode->GetData()); - - if (previewImage.IsNull()) - { - MITK_ERROR << "Cannot create helper objects."; - return; - } - - previewImage->GetActiveLabel()->SetColor(pixel); - previewImage->GetActiveLabelSet()->UpdateLookupTable(previewImage->GetActiveLabel()->GetValue()); - } - else - { - mitk::Image::Pointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - if (workingImageBin) - { - m_ThresholdFeedbackNode->SetData(workingImageBin->Clone()); - m_IsOldBinary = true; - } - else - m_ThresholdFeedbackNode->SetData(mitk::Image::New()); - } - - m_ThresholdFeedbackNode->SetColor(pixel); - m_ThresholdFeedbackNode->SetOpacity(0.5); - - int layer(50); - m_NodeForThresholding->GetIntProperty("layer", layer); - m_ThresholdFeedbackNode->SetIntProperty("layer", layer + 1); - - if (DataStorage *ds = m_ToolManager->GetDataStorage()) - { - if (!ds->Exists(m_ThresholdFeedbackNode)) - ds->Add(m_ThresholdFeedbackNode, m_OriginalImageNode); - } - - if (image.GetPointer() == originalImage.GetPointer()) - { - Image::StatisticsHolderPointer statistics = originalImage->GetStatistics(); - m_SensibleMinimumThresholdValue = static_cast(statistics->GetScalarValueMin()); - m_SensibleMaximumThresholdValue = static_cast(statistics->GetScalarValueMax()); - } - - double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; - m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; - m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; - - bool isFloatImage = false; - if ((originalImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && - (originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || - originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) - { - isFloatImage = true; - } - - IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); - ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); - } - } -} - -template -static void ITKSetVolume(itk::Image *originalImage, - mitk::Image *segmentation, - unsigned int timeStep) -{ - segmentation->SetVolume((void *)originalImage->GetPixelContainer()->GetBufferPointer(), timeStep); -} - -void mitk::BinaryThresholdULTool::CreateNewSegmentationFromThreshold(DataNode *node) -{ - if (node) - { - Image::Pointer feedBackImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (feedBackImage.IsNotNull()) - { - // create a new image of the same dimensions and smallest possible pixel type - DataNode::Pointer emptySegmentation = GetTargetSegmentationNode(); - - if (emptySegmentation) - { - // actually perform a thresholding and ask for an organ type - for (unsigned int timeStep = 0; timeStep < feedBackImage->GetTimeSteps(); ++timeStep) - { - try - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(feedBackImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer feedBackImage3D = timeSelector->GetOutput(); - - if (feedBackImage3D->GetDimension() == 2) - { - AccessFixedDimensionByItk_2( - feedBackImage3D, ITKSetVolume, 2, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - else - { - AccessFixedDimensionByItk_2( - feedBackImage3D, ITKSetVolume, 3, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - } - catch (...) - { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - } - } - - // since we are maybe working on a smaller image, pad it to the size of the original image - if (m_OriginalImageNode.GetPointer() != m_NodeForThresholding.GetPointer()) - { - mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); - - padFilter->SetInput(0, dynamic_cast(emptySegmentation->GetData())); - padFilter->SetInput(1, dynamic_cast(m_OriginalImageNode->GetData())); - padFilter->SetBinaryFilter(true); - padFilter->SetUpperThreshold(1); - padFilter->SetLowerThreshold(1); - padFilter->Update(); - - emptySegmentation->SetData(padFilter->GetOutput()); - } - - m_ToolManager->SetWorkingData(emptySegmentation); - m_ToolManager->GetWorkingData(0)->Modified(); - } - } - } -} - -void mitk::BinaryThresholdULTool::OnRoiDataChanged() -{ - mitk::DataNode::Pointer node = m_ToolManager->GetRoiData(0); - - if (node.IsNotNull()) - { - mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); - mitk::Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - - if (image.IsNull()) - return; - - roiFilter->SetInput(image); - roiFilter->SetRegionOfInterest(node->GetData()); - roiFilter->Update(); - - mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); - tmpNode->SetData(roiFilter->GetOutput()); - - m_SensibleMinimumThresholdValue = static_cast(roiFilter->GetMinValue()); - m_SensibleMaximumThresholdValue = static_cast(roiFilter->GetMaxValue()); - - m_NodeForThresholding = tmpNode; - } - else - m_NodeForThresholding = m_OriginalImageNode; - - this->SetupPreviewNode(); - this->UpdatePreview(); -} - -template -static void ITKThresholding(itk::Image *originalImage, - mitk::Image *segmentation, - double lower, - double upper, - unsigned int timeStep) -{ - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(lower); - filter->SetUpperThreshold(upper); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -template -static void ITKThresholdingOldBinary(itk::Image *originalImage, - mitk::Image *segmentation, - double lower, - double upper, - unsigned int timeStep) -{ - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(lower); - filter->SetUpperThreshold(upper); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::BinaryThresholdULTool::UpdatePreview() -{ - mitk::Image::Pointer thresholdImage = dynamic_cast(m_NodeForThresholding->GetData()); - mitk::Image::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (thresholdImage && previewImage) - { - for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(thresholdImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer feedBackImage3D = timeSelector->GetOutput(); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, - ITKThresholdingOldBinary, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, - ITKThresholding, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - } - RenderingManager::GetInstance()->RequestUpdateAll(); - } -} diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.h index a29b5f3f7d..b6f15824a2 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.h @@ -1,91 +1,54 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkBinaryThresholdULTool_h_Included #define mitkBinaryThresholdULTool_h_Included -#include "mitkAutoSegmentationTool.h" -#include "mitkCommon.h" -#include "mitkDataNode.h" +#include "mitkBinaryThresholdBaseTool.h" #include -#include -#include namespace us { class ModuleResource; } namespace mitk { /** \brief Calculates the segmented volumes for binary images. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation Last contributor: $Author$ */ - class MITKSEGMENTATION_EXPORT BinaryThresholdULTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT BinaryThresholdULTool : public BinaryThresholdBaseTool { public: - Message3 IntervalBordersChanged; - Message2 ThresholdingValuesChanged; - - mitkClassMacro(BinaryThresholdULTool, AutoSegmentationTool); + mitkClassMacro(BinaryThresholdULTool, BinaryThresholdBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char **GetXPM() const override; + const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; - void Activated() override; - void Deactivated() override; - - virtual void SetThresholdValues(double lower, double upper); - virtual void AcceptCurrentThresholdValue(); - virtual void CancelThresholding(); - protected: BinaryThresholdULTool(); // purposely hidden ~BinaryThresholdULTool() override; - - void SetupPreviewNode(); - - void CreateNewSegmentationFromThreshold(DataNode *node); - - void OnRoiDataChanged(); - void UpdatePreview(); - - DataNode::Pointer m_ThresholdFeedbackNode; - DataNode::Pointer m_OriginalImageNode; - DataNode::Pointer m_NodeForThresholding; - - mitk::ScalarType m_SensibleMinimumThresholdValue; - mitk::ScalarType m_SensibleMaximumThresholdValue; - mitk::ScalarType m_CurrentLowerThresholdValue; - mitk::ScalarType m_CurrentUpperThresholdValue; - - bool m_IsOldBinary = false; - - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; // this is sure for new segmentations - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - ThresholdFilterType::Pointer m_ThresholdFilter; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp index 4e5191b32f..48a1301bf6 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp @@ -1,460 +1,334 @@ /*============================================================================ 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 "mitkFastMarchingTool3D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" -#include "itkOrImageFilter.h" -#include "mitkImageCast.h" -#include "mitkImageTimeSelector.h" +#include "mitkImageAccessByItk.h" + +// itk filter +#include "itkBinaryThresholdImageFilter.h" +#include "itkCurvatureAnisotropicDiffusionImageFilter.h" +#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" +#include "itkSigmoidImageFilter.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool3D, "FastMarching3D tool"); } mitk::FastMarchingTool3D::FastMarchingTool3D() - : /*FeedbackContourTool*/ AutoSegmentationTool(), - m_NeedUpdate(true), - m_CurrentTimeStep(0), + : AutoSegmentationWithPreviewTool(), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0), m_PointSetAddObserverTag(0), m_PointSetRemoveObserverTag(0) { } mitk::FastMarchingTool3D::~FastMarchingTool3D() { } -bool mitk::FastMarchingTool3D::CanHandle(BaseData *referenceData) const +bool mitk::FastMarchingTool3D::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { + if(!Superclass::CanHandle(referenceData, workingData)) + return false; + if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingTool3D::GetXPM() const { return nullptr; // mitkFastMarchingTool3D_xpm; } us::ModuleResource mitk::FastMarchingTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } const char *mitk::FastMarchingTool3D::GetName() const { return "Fast Marching 3D"; } void mitk::FastMarchingTool3D::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; - m_SigmoidFilter->SetBeta(m_Beta); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - m_NeedUpdate = true; } } } void mitk::FastMarchingTool3D::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; - m_SigmoidFilter->SetAlpha(m_Alpha); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::Activated() { Superclass::Activated(); - m_ResultImageNode = mitk::DataNode::New(); - m_ResultImageNode->SetName("FastMarching_Preview"); - m_ResultImageNode->SetBoolProperty("helper object", true); - m_ResultImageNode->SetColor(0.0, 1.0, 0.0); - m_ResultImageNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_ResultImageNode, m_ToolManager->GetReferenceData(0)); - m_SeedsAsPointSet = mitk::PointSet::New(); m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("3D_FastMarching_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // Create PointSetData Interactor m_SeedPointInteractor = mitk::PointSetDataInteractor::New(); // Load the according state machine for regular point set interaction m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); // Set the configuration file that defines the triggers for the transitions m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); // set the DataNode (which already is added to the DataStorage m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); - m_ReferenceImageAsITK = InternalImageType::New(); - - m_ProgressCommand = mitk::ToolCommand::New(); - - m_ThresholdFilter = ThresholdingFilterType::New(); - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_ThresholdFilter->SetOutsideValue(0); - m_ThresholdFilter->SetInsideValue(1.0); - - m_SmoothFilter = SmoothingFilterType::New(); - m_SmoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SmoothFilter->SetTimeStep(0.05); - m_SmoothFilter->SetNumberOfIterations(2); - m_SmoothFilter->SetConductanceParameter(9.0); - - m_GradientMagnitudeFilter = GradientFilterType::New(); - m_GradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - - m_SigmoidFilter = SigmoidFilterType::New(); - m_SigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SigmoidFilter->SetAlpha(m_Alpha); - m_SigmoidFilter->SetBeta(m_Beta); - m_SigmoidFilter->SetOutputMinimum(0.0); - m_SigmoidFilter->SetOutputMaximum(1.0); - - m_FastMarchingFilter = FastMarchingFilterType::New(); - m_FastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); + m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); m_SeedContainer = NodeContainer::New(); m_SeedContainer->Initialize(); - m_FastMarchingFilter->SetTrialPoints(m_SeedContainer); - - // set up pipeline - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_GradientMagnitudeFilter->SetInput(m_SmoothFilter->GetOutput()); - m_SigmoidFilter->SetInput(m_GradientMagnitudeFilter->GetOutput()); - m_FastMarchingFilter->SetInput(m_SigmoidFilter->GetOutput()); - m_ThresholdFilter->SetInput(m_FastMarchingFilter->GetOutput()); - - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); - - this->Initialize(); } void mitk::FastMarchingTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_ResultImageNode); - m_ToolManager->GetDataStorage()->Remove(this->m_SeedsAsPointSetNode); this->ClearSeeds(); - this->m_SmoothFilter->RemoveAllObservers(); - this->m_SigmoidFilter->RemoveAllObservers(); - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - this->m_FastMarchingFilter->RemoveAllObservers(); - m_ResultImageNode = nullptr; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - unsigned int numberOfPoints = m_SeedsAsPointSet->GetSize(); - for (unsigned int i = 0; i < numberOfPoints; ++i) - { - mitk::Point3D point = m_SeedsAsPointSet->GetPoint(i); - auto *doOp = new mitk::PointOperation(mitk::OpREMOVE, point, 0); - m_SeedsAsPointSet->ExecuteOperation(doOp); - } // Deactivate Interaction m_SeedPointInteractor->SetDataNode(nullptr); m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); + m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } -void mitk::FastMarchingTool3D::Initialize() -{ - m_ReferenceImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - if (m_ReferenceImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(m_ReferenceImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - m_ReferenceImage = timeSelector->GetOutput(); - } - CastToItkImage(m_ReferenceImage, m_ReferenceImageAsITK); - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_NeedUpdate = true; -} - -void mitk::FastMarchingTool3D::ConfirmSegmentation() -{ - // combine preview image with current working segmentation - if (dynamic_cast(m_ResultImageNode->GetData())) - { - // logical or combination of preview and segmentation slice - OutputImageType::Pointer segmentationImageInITK = OutputImageType::New(); - - mitk::Image::Pointer workingImage = dynamic_cast(GetTargetSegmentationNode()->GetData()); - if (workingImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(workingImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - CastToItkImage(timeSelector->GetOutput(), segmentationImageInITK); - } - else - { - CastToItkImage(workingImage, segmentationImageInITK); - } - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput(0, m_ThresholdFilter->GetOutput()); - orFilter->SetInput(1, segmentationImageInITK); - orFilter->Update(); - - // set image volume in current time step from itk image - workingImage->SetVolume((void *)(m_ThresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), - m_CurrentTimeStep); - this->m_ResultImageNode->SetVisibility(false); - this->ClearSeeds(); - workingImage->Modified(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - void mitk::FastMarchingTool3D::OnAddPoint() { // Add a new seed point for FastMarching algorithm mitk::Point3D clickInIndex; - m_ReferenceImage->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), + this->GetReferenceData()->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), clickInIndex); itk::Index<3> seedPosition; seedPosition[0] = clickInIndex[0]; seedPosition[1] = clickInIndex[1]; seedPosition[2] = clickInIndex[2]; NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - - m_ReadyMessage.Send(); + this->UpdatePreview(); } void mitk::FastMarchingTool3D::OnDelete() { // delete last seed point if (!(this->m_SeedContainer->empty())) { // delete last element of seeds container this->m_SeedContainer->pop_back(); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - } -} - -void mitk::FastMarchingTool3D::Update() -{ - const unsigned int progress_steps = 200; - - if (m_NeedUpdate) - { - m_ProgressCommand->AddStepsToDo(progress_steps); - - // remove interaction with poinset while updating - m_SeedPointInteractor->SetDataNode(nullptr); - CurrentlyBusy.Send(true); - try - { - m_ThresholdFilter->Update(); - } - catch (itk::ExceptionObject &excep) - { - MITK_ERROR << "Exception caught: " << excep.GetDescription(); - - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - std::string msg = excep.GetDescription(); - ErrorMessage.Send(msg); - - return; - } - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - // make output visible - mitk::Image::Pointer result = mitk::Image::New(); - CastToMitkImage(m_ThresholdFilter->GetOutput(), result); - result->GetGeometry()->SetOrigin(m_ReferenceImage->GetGeometry()->GetOrigin()); - result->GetGeometry()->SetIndexToWorldTransform(m_ReferenceImage->GetGeometry()->GetIndexToWorldTransform()); - m_ResultImageNode->SetData(result); - m_ResultImageNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - // add interaction with poinset again - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); + this->UpdatePreview(); } } void mitk::FastMarchingTool3D::ClearSeeds() { // clear seeds for FastMarching as well as the PointSet for visualization if (this->m_SeedContainer.IsNotNull()) this->m_SeedContainer->Initialize(); if (this->m_SeedsAsPointSet.IsNotNull()) { // remove observers from current pointset m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("Seeds_Preview"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // add callback function for adding and removing points itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); } +} - if (this->m_FastMarchingFilter.IsNotNull()) - m_FastMarchingFilter->Modified(); +template +void mitk::FastMarchingTool3D::DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep) +{ + typedef itk::Image InputImageType; + + /* typedefs for itk pipeline */ + + typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; + typedef itk::Image OutputImageType; + + typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; + typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; + typedef itk::SigmoidImageFilter SigmoidFilterType; + typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; + + auto smoothFilter = SmoothingFilterType::New(); + smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + smoothFilter->SetTimeStep(0.05); + smoothFilter->SetNumberOfIterations(2); + smoothFilter->SetConductanceParameter(9.0); + + auto gradientMagnitudeFilter = GradientFilterType::New(); + gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + gradientMagnitudeFilter->SetSigma(m_Sigma); + + auto sigmoidFilter = SigmoidFilterType::New(); + sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + sigmoidFilter->SetAlpha(m_Alpha); + sigmoidFilter->SetBeta(m_Beta); + sigmoidFilter->SetOutputMinimum(0.0); + sigmoidFilter->SetOutputMaximum(1.0); + + auto fastMarchingFilter = FastMarchingFilterType::New(); + fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + fastMarchingFilter->SetStoppingValue(m_StoppingValue); + fastMarchingFilter->SetTrialPoints(m_SeedContainer); + + auto thresholdFilter = ThresholdingFilterType::New(); + thresholdFilter->SetLowerThreshold(m_LowerThreshold); + thresholdFilter->SetUpperThreshold(m_UpperThreshold); + thresholdFilter->SetOutsideValue(0); + thresholdFilter->SetInsideValue(1.0); - this->m_NeedUpdate = true; + // set up pipeline + smoothFilter->SetInput(inputImage); + gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); + sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); + fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); + thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); + thresholdFilter->Update(); + + segmentation->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -void mitk::FastMarchingTool3D::Reset() +void mitk::FastMarchingTool3D::UpdatePrepare() { - // clear all seeds and preview empty result - this->ClearSeeds(); - - m_ResultImageNode->SetVisibility(false); + // remove interaction with poinset while updating + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(nullptr); +} - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +void mitk::FastMarchingTool3D::UpdateCleanUp() +{ + // add interaction with poinset again + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); } -void mitk::FastMarchingTool3D::SetCurrentTimeStep(int t) +void mitk::FastMarchingTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { - if (m_CurrentTimeStep != t) + if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedContainer.IsNotNull() && !m_SeedContainer->empty()) { - m_CurrentTimeStep = t; - - this->Initialize(); + AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h index eeefc20b4f..652e0d550b 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h @@ -1,164 +1,124 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkFastMarchingTool3D_h_Included #define mitkFastMarchingTool3D_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" -#include - -#include "mitkMessage.h" #include "itkImage.h" - -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkFastMarchingImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" + +#include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ - class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationWithPreviewTool { - mitkNewMessageMacro(Ready); - public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationTool); + mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /* typedefs for itk pipeline */ - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; + void Activated() override; + void Deactivated() override; + /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); - /// \brief Adds the feedback image to the current working image. - virtual void ConfirmSegmentation(); - - /// \brief Set the working time step. - virtual void SetCurrentTimeStep(int t); - /// \brief Clear all seed points. void ClearSeeds(); - /// \brief Updates the itk pipeline and shows the result of FastMarching. - void Update(); - protected: FastMarchingTool3D(); ~FastMarchingTool3D() override; - void Activated() override; - void Deactivated() override; - virtual void Initialize(); - /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(); /// \brief Delete action of StateMachine pattern virtual void OnDelete(); - /// \brief Reset all relevant inputs of the itk pipeline. - void Reset(); - - mitk::ToolCommand::Pointer m_ProgressCommand; - - Image::Pointer m_ReferenceImage; - - bool m_NeedUpdate; + void UpdatePrepare() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - int m_CurrentTimeStep; + template + void DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - InternalImageType::Pointer m_ReferenceImageAsITK; // the reference image as itk::Image + typedef float InternalPixelType; + typedef itk::Image InternalImageType; + typedef itk::FastMarchingImageFilter FastMarchingFilterType; + typedef FastMarchingFilterType::NodeContainer NodeContainer; + typedef FastMarchingFilterType::NodeType NodeType; - mitk::DataNode::Pointer m_ResultImageNode; // holds the result as a preview image + NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points mitk::PointSet::Pointer m_SeedsAsPointSet; mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; unsigned int m_PointSetAddObserverTag; unsigned int m_PointSetRemoveObserverTag; - - ThresholdingFilterType::Pointer m_ThresholdFilter; - SmoothingFilterType::Pointer m_SmoothFilter; - GradientFilterType::Pointer m_GradientMagnitudeFilter; - SigmoidFilterType::Pointer m_SigmoidFilter; - FastMarchingFilterType::Pointer m_FastMarchingFilter; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index 917451a48c..9a5f4cc388 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,277 +1,253 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkImageAccessByItk.h" #include "mitkLabelSetImage.h" #include "mitkOtsuSegmentationFilter.h" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include #include #include #include #include #include // ITK #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } -mitk::OtsuTool3D::OtsuTool3D() +mitk::OtsuTool3D::OtsuTool3D() : AutoSegmentationWithPreviewTool(true) { } mitk::OtsuTool3D::~OtsuTool3D() { } +void mitk::OtsuTool3D::SetSelectedRegions(const SelectedRegionVectorType& regions) +{ + if (m_SelectedRegions != regions) + { + m_SelectedRegions = regions; + //Note: we do not call this->Modified() on puprose. Reason: changing the + //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. + } +} + +mitk::OtsuTool3D::SelectedRegionVectorType mitk::OtsuTool3D::GetSelectedRegions() const +{ + return this->m_SelectedRegions; +} + void mitk::OtsuTool3D::Activated() { Superclass::Activated(); - if (m_ToolManager) - { - m_OriginalImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - - m_BinaryPreviewNode = mitk::DataNode::New(); - m_BinaryPreviewNode->SetName("Binary_Preview"); - m_BinaryPreviewNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_BinaryPreviewNode->SetProperty("opacity", FloatProperty::New(0.3)); - // m_BinaryPreviewNode->SetBoolProperty("helper object", true); - // m_BinaryPreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); - m_ToolManager->GetDataStorage()->Add(this->m_BinaryPreviewNode); - - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MultiLabelResultNode->SetVisibility(true); - - m_MaskedImagePreviewNode = mitk::DataNode::New(); - m_MaskedImagePreviewNode->SetName("Volume_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MaskedImagePreviewNode->SetVisibility(false); - - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - } + m_SelectedRegions = {}; + m_NumberOfBins = 128; + m_NumberOfRegions = 2; + m_UseValley = false; + + m_OtsuResultNode = mitk::DataNode::New(); + m_OtsuResultNode->SetName("Otsu_Preview"); + // m_MultiLabelResultNode->SetBoolProperty("helper object", true); + m_OtsuResultNode->SetVisibility(true); + m_OtsuResultNode->SetOpacity(1.0); + + m_ToolManager->GetDataStorage()->Add(m_OtsuResultNode); } void mitk::OtsuTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_BinaryPreviewNode); - m_BinaryPreviewNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_MaskedImagePreviewNode); - m_MaskedImagePreviewNode = nullptr; + m_ToolManager->GetDataStorage()->Remove(m_OtsuResultNode); + m_OtsuResultNode = nullptr; Superclass::Deactivated(); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } -void mitk::OtsuTool3D::RunSegmentation(int regions, bool useValley, int numberOfBins) +const char* mitk::OtsuTool3D::GetName() const { - int numberOfThresholds = regions - 1; + return "Otsu"; +} +void mitk::OtsuTool3D::UpdateCleanUp() +{ + if (m_OtsuResultNode.IsNotNull()) + m_OtsuResultNode->SetVisibility(m_SelectedRegions.empty()); - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(m_OriginalImage, timePoint); + if (nullptr != this->GetPreviewSegmentationNode()) + this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedRegions.empty()); - if (nullptr == image3D) + if (m_SelectedRegions.empty()) { - MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; - return; + this->ResetPreviewNode(); } +} - mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); - otsuFilter->SetNumberOfThresholds(numberOfThresholds); - otsuFilter->SetValleyEmphasis(useValley); - otsuFilter->SetNumberOfBins(numberOfBins); - otsuFilter->SetInput(image3D); +void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +{ + int numberOfThresholds = m_NumberOfRegions - 1; - try + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + mitk::LabelSetImage::Pointer otsuResultImage = dynamic_cast(this->m_OtsuResultNode->GetData()); + + if (nullptr == m_OtsuResultNode->GetData() + || this->GetMTime() > m_OtsuResultNode->GetData()->GetMTime() + || this->m_LastOtsuTimeStep != timeStep //this covers the case where dynamic + //segmentations have to compute a preview + //for all time steps on confirmation + || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg + //previews work with dynamic images + //with avoiding unnecessary other otsu computations + ) { - otsuFilter->Update(); + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastOtsuTimeStep = timeStep; + + mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); + otsuFilter->SetNumberOfThresholds(numberOfThresholds); + otsuFilter->SetValleyEmphasis(m_UseValley); + otsuFilter->SetNumberOfBins(m_NumberOfBins); + otsuFilter->SetInput(inputAtTimeStep); + otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + try + { + otsuFilter->Update(); + } + catch (...) + { + mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + } + + otsuResultImage = mitk::LabelSetImage::New(); + otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); + this->m_OtsuResultNode->SetData(otsuResultImage); + this->m_OtsuResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); + mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); + renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); + this->m_OtsuResultNode->SetProperty("Image Rendering.Mode", renderingMode); + mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); + mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); + vtkSmartPointer lookupTable = vtkSmartPointer::New(); + lookupTable->SetHueRange(1.0, 0.0); + lookupTable->SetSaturationRange(1.0, 1.0); + lookupTable->SetValueRange(1.0, 1.0); + lookupTable->SetTableRange(-1.0, 1.0); + lookupTable->Build(); + lut->SetVtkLookupTable(lookupTable); + prop->SetLookupTable(lut); + this->m_OtsuResultNode->SetProperty("LookupTable", prop); + mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); + mitk::LevelWindow levelwindow; + levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); + levWinProp->SetLevelWindow(levelwindow); + this->m_OtsuResultNode->SetProperty("levelwindow", levWinProp); } - catch (...) + + if (!m_SelectedRegions.empty()) { - mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + AccessByItk_n(otsuResultImage, CalculatePreview, (previewImage, timeStep)); } - - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - m_MultiLabelResultNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - m_MultiLabelResultNode->SetOpacity(1.0); - - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); - this->m_MultiLabelResultNode->SetData(resultImage); - m_MultiLabelResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); - mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); - renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); - m_MultiLabelResultNode->SetProperty("Image Rendering.Mode", renderingMode); - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); - vtkSmartPointer lookupTable = vtkSmartPointer::New(); - lookupTable->SetHueRange(1.0, 0.0); - lookupTable->SetSaturationRange(1.0, 1.0); - lookupTable->SetValueRange(1.0, 1.0); - lookupTable->SetTableRange(-1.0, 1.0); - lookupTable->Build(); - lut->SetVtkLookupTable(lookupTable); - prop->SetLookupTable(lut); - m_MultiLabelResultNode->SetProperty("LookupTable", prop); - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); - mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); - levWinProp->SetLevelWindow(levelwindow); - m_MultiLabelResultNode->SetProperty("levelwindow", levWinProp); - - // m_BinaryPreviewNode->SetVisibility(false); - // m_MultiLabelResultNode->SetVisibility(true); - // this->m_OtsuSegmentationDialog->setCursor(Qt::ArrowCursor); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ConfirmSegmentation() -{ - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(dynamic_cast(m_BinaryPreviewNode->GetData())); - GetTargetSegmentationNode()->SetData(resultImage); - - m_ToolManager->ActivateTool(-1); -} - -void mitk::OtsuTool3D::UpdateBinaryPreview(std::vector regionIDs) -{ - m_MultiLabelResultNode->SetVisibility(false); - mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - AccessByItk_1(multiLabelSegmentation, CalculatePreview, regionIDs); } template -void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, std::vector regionIDs) +void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; - typename OutputImageType::Pointer itkBinaryTempImage1; - typename OutputImageType::Pointer itkBinaryTempImage2; typename OutputImageType::Pointer itkBinaryResultImage; - // mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - // mitk::CastToItkImage(multiLabelSegmentation, itkImage); - filter->SetInput(itkImage); - filter->SetLowerThreshold(regionIDs[0]); - filter->SetUpperThreshold(regionIDs[0]); + filter->SetLowerThreshold(m_SelectedRegions[0]); + filter->SetUpperThreshold(m_SelectedRegions[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); + filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); - itkBinaryTempImage2 = filter->GetOutput(); - - typename itk::OrImageFilter::Pointer orFilter = - itk::OrImageFilter::New(); + itkBinaryResultImage = filter->GetOutput(); + itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions - for (auto it = regionIDs.begin(); it != regionIDs.end(); ++it) + for (const auto regionID : m_SelectedRegions) { - filter->SetLowerThreshold(*it); - filter->SetUpperThreshold(*it); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - itkBinaryTempImage1 = filter->GetOutput(); - - orFilter->SetInput1(itkBinaryTempImage1); - orFilter->SetInput2(itkBinaryTempImage2); - - orFilter->UpdateLargestPossibleRegion(); - itkBinaryResultImage = orFilter->GetOutput(); - itkBinaryTempImage2 = itkBinaryResultImage; + if (regionID != m_SelectedRegions[0]) + { + filter->SetLowerThreshold(regionID); + filter->SetUpperThreshold(regionID); + filter->SetInsideValue(1); + filter->SetOutsideValue(0); + filter->Update(); + + typename OutputImageType::Pointer tempImage = filter->GetOutput(); + + typename itk::OrImageFilter::Pointer orFilter = + itk::OrImageFilter::New(); + orFilter->SetInput1(tempImage); + orFilter->SetInput2(itkBinaryResultImage); + orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + orFilter->UpdateLargestPossibleRegion(); + itkBinaryResultImage = orFilter->GetOutput(); + } } //---------------------------------------------------------------------------------------------------- - mitk::Image::Pointer binarySegmentation; - mitk::CastToMitkImage(itkBinaryResultImage, binarySegmentation); - m_BinaryPreviewNode->SetData(binarySegmentation); - m_BinaryPreviewNode->SetVisibility(true); - m_BinaryPreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(false)); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -const char *mitk::OtsuTool3D::GetName() const -{ - return "Otsu"; -} -void mitk::OtsuTool3D::UpdateVolumePreview(bool volumeRendering) -{ - if (volumeRendering) - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", true); - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering.uselod", true); - } - else - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", false); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ShowMultiLabelResultNode(bool show) -{ - m_MultiLabelResultNode->SetVisibility(show); - m_BinaryPreviewNode->SetVisibility(!show); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } -int mitk::OtsuTool3D::GetNumberOfBins() +unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { - ScalarType min = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMin(); - ScalarType max = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMaxNoRecompute(); - return static_cast(max - min) + 1; + const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); + const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); + return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index f4e22cab62..7761ec60dd 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,68 +1,80 @@ /*============================================================================ 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. ============================================================================*/ #ifndef MITKOTSUTOOL3D_H #define MITKOTSUTOOL3D_H #include "itkImage.h" -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkDataNode.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; - class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(OtsuTool3D, AutoSegmentationTool); + mitkClassMacro(OtsuTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char *GetName() const override; + const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; - void RunSegmentation(int regions, bool useValley, int numberOfBins); - void ConfirmSegmentation(); - // void UpdateBinaryPreview(int regionID); - void UpdateBinaryPreview(std::vector regionIDs); - void UpdateVolumePreview(bool volumeRendering); - void ShowMultiLabelResultNode(bool); + itkSetMacro(NumberOfBins, unsigned int); + itkGetConstMacro(NumberOfBins, unsigned int); - int GetNumberOfBins(); + itkSetMacro(NumberOfRegions, unsigned int); + itkGetConstMacro(NumberOfRegions, unsigned int); + + itkSetMacro(UseValley, bool); + itkGetConstMacro(UseValley, bool); + itkBooleanMacro(UseValley); + + using SelectedRegionVectorType = std::vector; + void SetSelectedRegions(const SelectedRegionVectorType& regions); + SelectedRegionVectorType GetSelectedRegions() const; + + /**Returns the number of max bins based on the current input image.*/ + unsigned int GetMaxNumberOfBins() const; protected: OtsuTool3D(); ~OtsuTool3D() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + template - void CalculatePreview(itk::Image *itkImage, std::vector regionIDs); + void CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep); - itk::SmartPointer m_OriginalImage; - // holds the user selected binary segmentation - mitk::DataNode::Pointer m_BinaryPreviewNode; - // holds the multilabel result as a preview image - mitk::DataNode::Pointer m_MultiLabelResultNode; - // holds the user selected binary segmentation masked original image - mitk::DataNode::Pointer m_MaskedImagePreviewNode; + unsigned int m_NumberOfBins = 128; + unsigned int m_NumberOfRegions = 2; + bool m_UseValley = false; + SelectedRegionVectorType m_SelectedRegions = {}; + // holds the multilabel result as a preview image + mitk::DataNode::Pointer m_OtsuResultNode; + TimeStepType m_LastOtsuTimeStep = 0; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 689b0dbded..988d50cc5c 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,663 +1,664 @@ /*============================================================================ 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 "mitkRegionGrowingTool.h" #include "mitkApplicationCursor.h" #include "mitkBaseRenderer.h" #include "mitkImageDataItem.h" #include "mitkImageToContourModelFilter.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkLabelSetImage.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), m_PaintingPixelValue(0), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } // Check whether index lies inside a segmentation template void mitk::RegionGrowingTool::IsInsideSegmentation(const itk::Image *itkImage, const itk::Index& index, bool *result) { if (itkImage->GetPixel(index) > 0) { *result = true; } else { *result = false; } } // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; m_ToolManager->GetReferenceData(0)->GetLevelWindow(levelWindow); - m_ThresholdExtrema = { std::numeric_limits::lowest(), std::numeric_limits::max() }; + m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); + m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; MITK_DEBUG << "OnMousePressed"; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (m_WorkingSlice.IsNotNull()) // can't do anything without a working slice (i.e. a possibly empty segmentation) { MITK_DEBUG << "OnMousePressed: got working slice"; // 2. Determine if the user clicked inside or outside of the segmentation/working slice (i.e. the whole volume) mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; if (workingSliceGeometry->IsIndexInside(m_SeedPoint)) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " << m_SeedPoint << ") is inside working slice"; // 3. determine the pixel value under the last click to determine what to do bool inside(true); AccessFixedDimensionByItk_2(m_WorkingSlice, IsInsideSegmentation, 2, indexInWorkingSlice2D, &inside); m_PaintingPixelValue = inside ? 0 : 1; if (inside) { MITK_DEBUG << "Clicked inside segmentation"; // For now, we're doing nothing when the user clicks inside the segmentation. Behaviour can be implemented via // OnMousePressedInside() // When you do, be sure to remove the m_PaintingPixelValue check in OnMouseMoved() and OnMouseReleased() return; } else { MITK_DEBUG << "Clicked outside of segmentation"; OnMousePressedOutside(nullptr, interactionEvent); } } } } // Use this to implement a behaviour for when the user clicks inside a segmentation (for example remove something) // Old IpPic code is kept as comment for reference void mitk::RegionGrowingTool::OnMousePressedInside() { // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent // ); // //const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in // OnMousePressed // // 3.1.1. Create a skeletonization of the segmentation and try to find a nice cut // // apply the skeletonization-and-cut algorithm // // generate contour to remove // // set m_ReferenceSlice = nullptr so nothing will happen during mouse move // // remember to fill the contour with 0 in mouserelease // mitkIpPicDescriptor* segmentationHistory = ipMITKSegmentationCreateGrowerHistory( workingPicSlice, // m_LastWorkingSeed, nullptr ); // free again // if (segmentationHistory) // { // tCutResult cutContour = ipMITKSegmentationGetCutPoints( workingPicSlice, segmentationHistory, // initialWorkingOffset ); // tCutResult is a ipSegmentation type // mitkIpPicFree( segmentationHistory ); // if (cutContour.cutIt) // { // int timestep = positionEvent->GetSender()->GetTimeStep(); // // 3.1.2 copy point from float* to mitk::Contour // ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // contourInImageIndexCoordinates->Expand(timestep + 1); // contourInImageIndexCoordinates->SetClosed(true, timestep); // Point3D newPoint; // for (int index = 0; index < cutContour.deleteSize; ++index) // { // newPoint[0] = cutContour.deleteCurve[ 2 * index + 0 ] - 0.5;//correction is needed because the // output of the algorithm is center based // newPoint[1] = cutContour.deleteCurve[ 2 * index + 1 ] - 0.5;//and we want our contour displayed // corner based. // newPoint[2] = 0.0; // contourInImageIndexCoordinates->AddVertex( newPoint, timestep ); // } // free(cutContour.traceline); // free(cutContour.deleteCurve); // perhaps visualize this for fun? // free(cutContour.onGradient); // ContourModel::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( // m_WorkingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for // ipSegmentation correction // FeedbackContourTool::SetFeedbackContour( contourInWorldCoordinates ); // FeedbackContourTool::SetFeedbackContourVisible(true); // mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); // m_FillFeedbackContour = true; // } // else // { // m_FillFeedbackContour = false; // } // } // else // { // m_FillFeedbackContour = false; // } // m_ReferenceSlice = nullptr; // return true; } void mitk::RegionGrowingTool::OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; mitk::BaseGeometry::Pointer referenceSliceGeometry; referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); itk::Index<3> indexInReferenceSlice; itk::Index<2> indexInReferenceSlice2D; referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; // Get seed neighborhood ScalarType averageValue(0); AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); m_SeedValue = averageValue; MITK_DEBUG << "Seed value is " << m_SeedValue; // Calculate initial thresholds AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); m_Thresholds[0] = m_InitialThresholds[0]; m_Thresholds[1] = m_InitialThresholds[1]; // Perform region growing mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Extract contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); // this is not a beautiful solution, just one that works, check T22412 for details auto t = positionEvent->GetSender()->GetTimeStep(); FeedbackContourTool::SetFeedbackContour(0 != t ? ContourModelUtils::MoveZerothContourTimeStep(resultContourWorld, t) : resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_ReferenceSlice.IsNotNull() && positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); // Moving the mouse up and down adjusts the width of the threshold window, // moving it left and right shifts the threshold window m_Thresholds[0] = std::min(m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); m_Thresholds[1] = std::max(m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); // Do not exceed the pixel type extrema of the reference slice, though m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); // Perform region growing again and show the result mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Update the contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); // this is not a beautiful solution, just one that works, check T22412 for details int timestep = positionEvent->GetSender()->GetTimeStep(); if (0 != timestep) { int size = resultContourWorld->GetNumberOfVertices(0); auto resultContourTimeWorld = mitk::ContourModel::New(); resultContourTimeWorld->Expand(timestep + 1); for (int loop = 0; loop < size; ++loop) { resultContourTimeWorld->AddVertex(resultContourWorld->GetVertexAt(loop, 0), timestep); } FeedbackContourTool::SetFeedbackContour(resultContourTimeWorld); } else { FeedbackContourTool::SetFeedbackContour(resultContourWorld); } FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_WorkingSlice.IsNotNull() && m_FillFeedbackContour && positionEvent) { // Project contour into working slice ContourModel *feedbackContour(FeedbackContourTool::GetFeedbackContour()); ContourModel::Pointer projectedContour; // this is not a beautiful solution, just one that works, check T22412 for details int timestep = positionEvent->GetSender()->GetTimeStep(); if (0 != timestep) { int size = feedbackContour->GetNumberOfVertices(timestep); auto feedbackContourTime = mitk::ContourModel::New(); feedbackContourTime->Expand(timestep + 1); for (int loop = 0; loop < size; ++loop) { feedbackContourTime->AddVertex(feedbackContour->GetVertexAt(loop, timestep), 0); } projectedContour = FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContourTime, false, false); } else { projectedContour = FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContour, false, false); } // If there is a projected contour, fill it if (projectedContour.IsNotNull()) { // Get working data to pass to following method so we don't overwrite locked labels in a LabelSetImage mitk::DataNode *workingNode(m_ToolManager->GetWorkingData(0)); mitk::LabelSetImage *labelImage = workingNode != nullptr ? dynamic_cast(workingNode->GetData()) : nullptr; MITK_DEBUG << "Filling Segmentation"; if (labelImage != nullptr) { // m_PaintingPixelValue only decides whether to paint or not // For LabelSetImages we want to paint with the active label value auto activeLabel = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); mitk::ContourModelUtils::FillContourInSlice(projectedContour, 0, m_WorkingSlice, labelImage, m_PaintingPixelValue * activeLabel); } else { mitk::ContourModelUtils::FillContourInSlice(projectedContour, 0, m_WorkingSlice, m_WorkingSlice, m_PaintingPixelValue); } this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice); FeedbackContourTool::SetFeedbackContourVisible(false); } m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 6b17128971..2c2f32e1e5 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,330 +1,333 @@ /*============================================================================ 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 "mitkTool.h" #include #include "mitkDisplayInteractor.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include // us #include #include // itk #include mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig("DisplayConfigMITK.xml"), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractorConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } -bool mitk::Tool::CanHandle(BaseData *) const +bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { + if (referenceData == nullptr) + return false; + return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractorConfigs.clear(); std::vector> listEventObserver = us::GetModuleContext()->GetServiceReferences(); for (auto it = listEventObserver.begin(); it != listEventObserver.end(); ++it) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig(m_EventConfig.c_str()); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayActionEventBroadcast != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->SetEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_DisplayInteractorConfigs.begin(); it != m_DisplayInteractorConfigs.end(); ++it) { if (it->first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(it->first)); if (displayActionEventBroadcast != nullptr) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(it->second); } } } m_DisplayInteractorConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->GetActiveLabelSet()->AddLabel(label); segmentation->GetActiveLabelSet()->SetActiveLabel(1); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index e9110e2a7a..16b2cf15a9 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,265 +1,270 @@ /*============================================================================ 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. ============================================================================*/ #ifndef mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::StateMachine, which can follow any transition pattern that it likes. One important thing to know is, that every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the StateMachine in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the StateMachine from time to time. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a StateMachine pattern name to the superclass. Names for valid patterns can be found in StateMachine.xml (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color); DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color); - virtual bool CanHandle(BaseData *referenceData) const; + /** Function used to check if a tool can handle the referenceData and (if specified) the working data. + @pre referenceData must be a valid pointer + @param referenceData Pointer to the data that should be checked as valid reference for the tool. + @param workingData Pointer to the data that should be checked as valid working data for this tool. + This parameter can be null if no working data is specified so far.*/ + virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(); // purposely hidden Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; ToolManager *m_ToolManager; private: // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractorConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp index 15fd3ff488..47e29e4da4 100644 --- a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp +++ b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp @@ -1,188 +1,188 @@ /*============================================================================ 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 "mitkWatershedTool.h" #include "mitkIOUtil.h" #include "mitkITKImageImport.h" #include "mitkImage.h" #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLevelWindowManager.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkRenderingModeProperty.h" #include "mitkToolCommand.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, WatershedTool, "Watershed tool"); } mitk::WatershedTool::WatershedTool() : m_Threshold(0.0), m_Level(0.0) { } mitk::WatershedTool::~WatershedTool() { } void mitk::WatershedTool::Activated() { Superclass::Activated(); } void mitk::WatershedTool::Deactivated() { Superclass::Deactivated(); } us::ModuleResource mitk::WatershedTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); return resource; } const char **mitk::WatershedTool::GetXPM() const { return nullptr; } const char *mitk::WatershedTool::GetName() const { return "Watershed"; } void mitk::WatershedTool::DoIt() { // get image from tool manager mitk::DataNode::Pointer referenceData = m_ToolManager->GetReferenceData(0); mitk::Image::ConstPointer input = dynamic_cast(referenceData->GetData()); if (input.IsNull()) return; const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - input = Get3DImageByTimePoint(input, timePoint); + input = GetImageByTimePoint(input, timePoint); if (nullptr == input) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; return; } mitk::Image::Pointer output; try { // create and run itk filter pipeline AccessByItk_1(input.GetPointer(), ITKWatershed, output); mitk::LabelSetImage::Pointer labelSetOutput = mitk::LabelSetImage::New(); labelSetOutput->InitializeByLabeledImage(output); // create a new datanode for output mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(labelSetOutput); // set name of data node std::string name = referenceData->GetName() + "_Watershed"; dataNode->SetName(name); // look, if there is already a node with this name mitk::DataStorage::SetOfObjects::ConstPointer children = m_ToolManager->GetDataStorage()->GetDerivations(referenceData); mitk::DataStorage::SetOfObjects::ConstIterator currentNode = children->Begin(); mitk::DataNode::Pointer removeNode; while (currentNode != children->End()) { if (dataNode->GetName().compare(currentNode->Value()->GetName()) == 0) { removeNode = currentNode->Value(); } currentNode++; } // remove node with same name if (removeNode.IsNotNull()) m_ToolManager->GetDataStorage()->Remove(removeNode); // add output to the data storage m_ToolManager->GetDataStorage()->Add(dataNode, referenceData); } catch (itk::ExceptionObject &e) { MITK_ERROR << "Watershed Filter Error: " << e.GetDescription(); } RenderingManager::GetInstance()->RequestUpdateAll(); } template void mitk::WatershedTool::ITKWatershed(const itk::Image *originalImage, mitk::Image::Pointer &segmentation) { typedef itk::WatershedImageFilter> WatershedFilter; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter, itk::Image> MagnitudeFilter; // at first add a gradient magnitude filter typename MagnitudeFilter::Pointer magnitude = MagnitudeFilter::New(); magnitude->SetInput(originalImage); magnitude->SetSigma(1.0); // use the progress bar mitk::ToolCommand::Pointer command = mitk::ToolCommand::New(); command->AddStepsToDo(60); // then add the watershed filter to the pipeline typename WatershedFilter::Pointer watershed = WatershedFilter::New(); watershed->SetInput(magnitude->GetOutput()); watershed->SetThreshold(m_Threshold); watershed->SetLevel(m_Level); watershed->AddObserver(itk::ProgressEvent(), command); watershed->Update(); // then make sure, that the output has the desired pixel type typedef itk::CastImageFilter> CastFilter; typename CastFilter::Pointer cast = CastFilter::New(); cast->SetInput(watershed->GetOutput()); // start the whole pipeline cast->Update(); // reset the progress bar by setting progress command->SetProgress(10); // since we obtain a new image from our pipeline, we have to make sure, that our mitk::Image::Pointer // is responsible for the memory management of the output image segmentation = mitk::GrabItkImageMemory(cast->GetOutput()); } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 071e038786..1deb993fd5 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,124 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp - #DataManagement/mitkContourElement.cpp - #DataManagement/mitkContourModel.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp + Interactions/mitkAutoSegmentationWithPreviewTool.cpp + Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCorrectorTool2D.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO - #IO/mitkContourModelIOFactory.cpp - #IO/mitkContourModelReader.cpp - #IO/mitkContourModelWriter.cpp - #IO/mitkContourModelWriterFactory.cpp Rendering/mitkContourMapper2D.cpp - #Rendering/mitkContourModelGLMapper2D.cpp - #Rendering/mitkContourModelMapper2D.cpp - #Rendering/mitkContourModelMapper3D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Correction_48x48.png Correction_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp index 5ba6c5a14b..a20b0fcc50 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp @@ -1,995 +1,995 @@ /*============================================================================ 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 "QmitkAdaptiveRegionGrowingToolGUI.h" #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateDataType.h" #include "mitkProperties.h" #include "mitkTransferFunctionProperty.h" #include "mitkImageStatisticsHolder.h" #include "itkMaskImageFilter.h" #include "itkNumericTraits.h" #include #include #include #include #include "QmitkConfirmSegmentationDialog.h" #include "itkOrImageFilter.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkImageCast.h" MITK_TOOL_GUI_MACRO(, QmitkAdaptiveRegionGrowingToolGUI, "") QmitkAdaptiveRegionGrowingToolGUI::QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent) : QmitkToolGUI(), m_DataStorage(nullptr), m_UseVolumeRendering(false), m_UpdateSuggestedThreshold(true), m_SuggestedThValue(0.0) { this->setParent(parent); m_Controls.setupUi(this); m_Controls.m_ThresholdSlider->setDecimals(1); m_Controls.m_ThresholdSlider->setSpinBoxAlignment(Qt::AlignVCenter); m_Controls.m_PreviewSlider->setEnabled(false); m_Controls.m_PreviewSlider->setSingleStep(0.5); // Not yet available // m_Controls.m_PreviewSlider->InvertedAppearance(true); //3D preview doesn't work: T24430. Postponed until reimplementation of segmentation m_Controls.m_cbVolumeRendering->setVisible(false); this->CreateConnections(); this->SetDataNodeNames("labeledRGSegmentation", "RGResult", "RGFeedbackSurface", "maskedSegmentation"); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAdaptiveRegionGrowingToolGUI::~QmitkAdaptiveRegionGrowingToolGUI() { // Removing the observer of the PointSet node if (m_RegionGrow3DTool->GetPointSetNode().IsNotNull()) { m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetAddObserverTag); m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetMoveObserverTag); } this->RemoveHelperNodes(); } void QmitkAdaptiveRegionGrowingToolGUI::OnNewToolAssociated(mitk::Tool *tool) { m_RegionGrow3DTool = dynamic_cast(tool); if (m_RegionGrow3DTool.IsNotNull()) { SetInputImageNode(this->m_RegionGrow3DTool->GetReferenceData()); this->m_DataStorage = this->m_RegionGrow3DTool->GetDataStorage(); this->EnableControls(true); // Watch for point added or modified itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded); m_PointSetAddObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); m_PointSetMoveObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetMoveEvent(), pointAddedCommand); } else { this->EnableControls(false); } } void QmitkAdaptiveRegionGrowingToolGUI::RemoveHelperNodes() { mitk::DataNode::Pointer imageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (imageNode.IsNotNull()) { m_DataStorage->Remove(imageNode); } mitk::DataNode::Pointer maskedSegmentationNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (maskedSegmentationNode.IsNotNull()) { m_DataStorage->Remove(maskedSegmentationNode); } } void QmitkAdaptiveRegionGrowingToolGUI::CreateConnections() { // Connecting GUI components connect((QObject *)(m_Controls.m_pbRunSegmentation), SIGNAL(clicked()), this, SLOT(RunSegmentation())); connect(m_Controls.m_PreviewSlider, SIGNAL(valueChanged(double)), this, SLOT(ChangeLevelWindow(double))); connect((QObject *)(m_Controls.m_pbConfirmSegementation), SIGNAL(clicked()), this, SLOT(ConfirmSegmentation())); connect( m_Controls.m_ThresholdSlider, SIGNAL(maximumValueChanged(double)), this, SLOT(SetUpperThresholdValue(double))); connect( m_Controls.m_ThresholdSlider, SIGNAL(minimumValueChanged(double)), this, SLOT(SetLowerThresholdValue(double))); } void QmitkAdaptiveRegionGrowingToolGUI::SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, std::string surface, std::string maskedSegmentation) { m_NAMEFORLABLEDSEGMENTATIONIMAGE = labledSegmentation; m_NAMEFORBINARYIMAGE = binaryImage; m_NAMEFORSURFACE = surface; m_NAMEFORMASKEDSEGMENTATION = maskedSegmentation; } void QmitkAdaptiveRegionGrowingToolGUI::SetDataStorage(mitk::DataStorage *dataStorage) { m_DataStorage = dataStorage; } void QmitkAdaptiveRegionGrowingToolGUI::SetInputImageNode(mitk::DataNode *node) { m_InputImageNode = node; mitk::Image *inputImage = dynamic_cast(m_InputImageNode->GetData()); if (inputImage) { mitk::ScalarType max = inputImage->GetStatistics()->GetScalarValueMax(); mitk::ScalarType min = inputImage->GetStatistics()->GetScalarValueMin(); m_Controls.m_ThresholdSlider->setMaximum(max); m_Controls.m_ThresholdSlider->setMinimum(min); // Just for initialization m_Controls.m_ThresholdSlider->setMaximumValue(max); m_Controls.m_ThresholdSlider->setMinimumValue(min); } } template static void AccessPixel(mitk::PixelType /*ptype*/, mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } /**Overloaded const verison*/ template static void AccessPixel(mitk::PixelType /*ptype*/, const mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } void QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded() { if (m_RegionGrow3DTool.IsNull()) return; mitk::DataNode *node = m_RegionGrow3DTool->GetPointSetNode(); if (node != nullptr) { mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); if (pointSet.IsNull()) { QMessageBox::critical(nullptr, "QmitkAdaptiveRegionGrowingToolGUI", "PointSetNode does not contain a pointset"); return; } m_Controls.m_lblSetSeedpoint->setText(""); const mitk::Image *image = dynamic_cast(m_InputImageNode->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(image, timePoint); + auto image3D = GetImageByTimePoint(image, timePoint); if (nullptr == image3D) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected " "reference image. Time point: " << timePoint; return; } if (!pointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; mitk::Point3D seedPoint = pointSet ->GetPointSet(static_cast(pointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint))) ->GetPoints() ->ElementAt(0); if (image3D->GetGeometry()->IsInside(seedPoint)) mitkPixelTypeMultiplex3( AccessPixel, image3D->GetChannelDescriptor().GetPixelType(), image3D, seedPoint, m_SeedpointValue) else return; /* In this case the seedpoint is placed e.g. in the lung or bronchialtree * The lowerFactor sets the windowsize depending on the regiongrowing direction */ m_CurrentRGDirectionIsUpwards = true; if (m_SeedpointValue < -500) { m_CurrentRGDirectionIsUpwards = false; } // Initializing the region by the area around the seedpoint m_SeedPointValueMean = 0; itk::Index<3> currentIndex, runningIndex; mitk::ScalarType pixelValues[125]; unsigned int pos(0); image3D->GetGeometry(0)->WorldToIndex(seedPoint, currentIndex); runningIndex = currentIndex; for (int i = runningIndex[0] - 2; i <= runningIndex[0] + 2; i++) { for (int j = runningIndex[1] - 2; j <= runningIndex[1] + 2; j++) { for (int k = runningIndex[2] - 2; k <= runningIndex[2] + 2; k++) { currentIndex[0] = i; currentIndex[1] = j; currentIndex[2] = k; if (image3D->GetGeometry()->IsIndexInside(currentIndex)) { int component = 0; m_InputImageNode->GetIntProperty("Image.Displayed Component", component); mitkPixelTypeMultiplex4(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, nullptr, currentIndex, pixelValues[pos]); pos++; } else { pixelValues[pos] = std::numeric_limits::min(); pos++; } } } } // Now calculation mean of the pixelValues // Now calculation mean of the pixelValues unsigned int numberOfValues(0); for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { m_SeedPointValueMean += pixelValue; numberOfValues++; } } m_SeedPointValueMean = m_SeedPointValueMean / numberOfValues; mitk::ScalarType var = 0; if (numberOfValues > 1) { for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { var += (pixelValue - m_SeedPointValueMean) * (pixelValue - m_SeedPointValueMean); } } var /= numberOfValues - 1; } mitk::ScalarType stdDev = sqrt(var); /* * Here the upper- and lower threshold is calculated: * The windowSize is 20% of the maximum range of the intensity values existing in the current image * If the RG direction is upwards the lower TH is meanSeedValue-0.15*windowSize and upper TH is * meanSeedValue+0.85*windowsSize * if the RG direction is downwards the lower TH is meanSeedValue-0.85*windowSize and upper TH is * meanSeedValue+0.15*windowsSize */ const auto timeStepOfImage = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ScalarType min = image->GetStatistics()->GetScalarValueMin(timeStepOfImage); mitk::ScalarType max = image->GetStatistics()->GetScalarValueMax(timeStepOfImage); mitk::ScalarType windowSize = max - min; windowSize = 0.15 * windowSize; if (m_CurrentRGDirectionIsUpwards) { m_LOWERTHRESHOLD = m_SeedPointValueMean - stdDev; m_UPPERTHRESHOLD = m_SeedpointValue + windowSize; if (m_UPPERTHRESHOLD > max) m_UPPERTHRESHOLD = max; m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); } else { m_UPPERTHRESHOLD = m_SeedPointValueMean; if (m_SeedpointValue > m_SeedPointValueMean) m_UPPERTHRESHOLD = m_SeedpointValue; m_LOWERTHRESHOLD = m_SeedpointValue - windowSize; if (m_LOWERTHRESHOLD < min) m_LOWERTHRESHOLD = min; m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); } } } -mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::Get3DImageByTimePoint(const mitk::Image *image, +mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; if (image->GetDimension() != 4) return image; auto imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(image->GetTimeGeometry()->TimePointToTimeStep(timePoint))); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } void QmitkAdaptiveRegionGrowingToolGUI::RunSegmentation() { if (m_InputImageNode.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please specify the image in Datamanager!"); return; } mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please insert a seed point inside the " "image.\n\nFirst press the \"Define Seed " "Point\" button,\nthen click left mouse " "button inside the image."); return; } // safety if no pointSet or pointSet empty mitk::PointSet::Pointer seedPointSet = dynamic_cast(node->GetData()); if (seedPointSet.IsNull()) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!seedPointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Point set is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(seedPointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (!(seedPointSet->GetSize(timeStep))) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); mitk::PointSet::PointType seedPoint = seedPointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); - auto image3D = Get3DImageByTimePoint(orgImage, timePoint); + auto image3D = GetImageByTimePoint(orgImage, timePoint); if (image3D.IsNotNull()) { // QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //set the cursor to waiting AccessByItk_2(image3D, StartRegionGrowing, image3D->GetGeometry(), seedPoint); // QApplication::restoreOverrideCursor();//reset cursor } else { QApplication::restoreOverrideCursor(); // reset cursor QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "Only images of dimension 3 or 4 can be processed!"); return; } } EnableControls(true); // Segmentation ran successfully, so enable all controls. node->SetVisibility(true); QApplication::restoreOverrideCursor(); // reset cursor } template void QmitkAdaptiveRegionGrowingToolGUI::StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedAdaptiveThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; if (!imageGeometry->IsInside(seedPoint)) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information(nullptr, "Segmentation functionality", "The seed point is outside of the image! Please choose a position inside the image!"); return; } IndexType seedIndex; imageGeometry->WorldToIndex(seedPoint, seedIndex); // convert world coordinates to image indices if (m_SeedpointValue > m_UPPERTHRESHOLD || m_SeedpointValue < m_LOWERTHRESHOLD) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information( nullptr, "Segmentation functionality", "The seed point is outside the defined thresholds! Please set a new seed point or adjust the thresholds."); MITK_INFO << "Mean: " << m_SeedPointValueMean; return; } // Setting the direction of the regiongrowing. For dark structures e.g. the lung the regiongrowing // is performed starting at the upper value going to the lower one regionGrower->SetGrowingDirectionIsUpwards(m_CurrentRGDirectionIsUpwards); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); // In some cases we have to subtract 1 for the lower threshold and add 1 to the upper. // Otherwise no region growing is done. Maybe a bug in the ConnectiveAdaptiveThresholdFilter regionGrower->SetLower(m_LOWERTHRESHOLD - 1); regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); try { regionGrower->Update(); } catch (itk::ExceptionObject &exc) { QMessageBox errorInfo; errorInfo.setWindowTitle("Adaptive RG Segmentation Functionality"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during region growing!"); errorInfo.setDetailedText(exc.what()); errorInfo.exec(); return; // can't work } catch (...) { QMessageBox::critical(nullptr, "Adaptive RG Segmentation Functionality", "An error occurred during region growing!"); return; } mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput())->Clone(); // initialize slider m_Controls.m_PreviewSlider->setMinimum(m_LOWERTHRESHOLD); mitk::ScalarType max = m_SeedpointValue + resultImage->GetStatistics()->GetScalarValueMax(); if (max < m_UPPERTHRESHOLD) m_Controls.m_PreviewSlider->setMaximum(max); else m_Controls.m_PreviewSlider->setMaximum(m_UPPERTHRESHOLD); this->m_DetectedLeakagePoint = regionGrower->GetLeakagePoint(); if (m_CurrentRGDirectionIsUpwards) { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean - 1); } else { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean + 1); } this->m_SliderInitialized = true; // create new node and then delete the old one if there is one mitk::DataNode::Pointer newNode = mitk::DataNode::New(); newNode->SetData(resultImage); // set some properties newNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORLABLEDSEGMENTATIONIMAGE)); newNode->SetProperty("helper object", mitk::BoolProperty::New(true)); newNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); newNode->SetProperty("layer", mitk::IntProperty::New(1)); newNode->SetProperty("opacity", mitk::FloatProperty::New(0.7)); // delete the old image, if there was one: mitk::DataNode::Pointer binaryNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); m_DataStorage->Remove(binaryNode); // now add result to data tree m_DataStorage->Add(newNode, m_InputImageNode); typename InputImageType::Pointer inputImageItk; mitk::CastToItkImage(resultImage, inputImageItk); // volume rendering preview masking typename ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(inputImageItk); thresholdFilter->SetInsideValue(1); thresholdFilter->SetOutsideValue(0); double sliderVal = this->m_Controls.m_PreviewSlider->value(); if (m_CurrentRGDirectionIsUpwards) { thresholdFilter->SetLowerThreshold(sliderVal); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(sliderVal); } thresholdFilter->SetInPlace(false); typename MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(inputImageItk); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMask; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMask); mitk::DataNode::Pointer maskedNode = mitk::DataNode::New(); maskedNode->SetData(mitkMask); // set some properties maskedNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORMASKEDSEGMENTATION)); maskedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); maskedNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); maskedNode->SetProperty("layer", mitk::IntProperty::New(1)); maskedNode->SetProperty("opacity", mitk::FloatProperty::New(0.0)); // delete the old image, if there was one: mitk::DataNode::Pointer deprecatedMask = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); m_DataStorage->Remove(deprecatedMask); // now add result to data tree m_DataStorage->Add(maskedNode, m_InputImageNode); this->InitializeLevelWindow(); if (m_UseVolumeRendering) this->EnableVolumeRendering(true); m_UpdateSuggestedThreshold = true; // reset first stored threshold value // Setting progress to finished mitk::ProgressBar::GetInstance()->Progress(357); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::InitializeLevelWindow() { // get the preview from the datatree mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); mitk::ScalarType *level = new mitk::ScalarType(0.0); mitk::ScalarType *window = new mitk::ScalarType(1.0); int upper; if (m_CurrentRGDirectionIsUpwards) { upper = m_UPPERTHRESHOLD - m_SeedpointValue; } else { upper = m_SeedpointValue - m_LOWERTHRESHOLD; } tempLevelWindow.SetRangeMinMax(mitk::ScalarType(0), mitk::ScalarType(upper)); // get the suggested threshold from the detected leakage-point and adjust the slider if (m_CurrentRGDirectionIsUpwards) { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = m_UPPERTHRESHOLD - (m_SeedpointValue) + 0.5; } else { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = (m_SeedpointValue)-m_LOWERTHRESHOLD + 0.5; } tempLevelWindow.SetLevelWindow(*level, *window); newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // update the widgets mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_SliderInitialized = true; // inquiry need to fix bug#1828 static int lastSliderPosition = 0; if ((this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1) == lastSliderPosition) { this->ChangeLevelWindow(lastSliderPosition); } lastSliderPosition = this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1; if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(*level + 0.5)); // lower threshold for labeled image } void QmitkAdaptiveRegionGrowingToolGUI::ChangeLevelWindow(double newValue) { if (m_SliderInitialized) { // do nothing, if no preview exists mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // get the levelWindow associated with the preview mitk::ScalarType level; // = this->m_UPPERTHRESHOLD - newValue + 0.5; mitk::ScalarType *window = new mitk::ScalarType(1); // adjust the levelwindow according to the position of the slider (newvalue) if (m_CurrentRGDirectionIsUpwards) { level = m_UPPERTHRESHOLD - newValue + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } else { level = newValue - m_LOWERTHRESHOLD + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(level - 0.5)); // lower threshold for labeled image newNode->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::DecreaseSlider() { // moves the slider one step to the left, when the "-"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->minimum()) { int newValue = this->m_Controls.m_PreviewSlider->value() - 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::IncreaseSlider() { // moves the slider one step to the right, when the "+"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->maximum()) { int newValue = this->m_Controls.m_PreviewSlider->value() + 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::ConfirmSegmentation() { // get image node if (m_InputImageNode.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "Please specify the image in Datamanager!"); return; } // get image data mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Image found!"); return; } // get labeled segmentation mitk::Image::Pointer labeledSeg = (mitk::Image *)m_DataStorage->GetNamedObject(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (labeledSeg.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Segmentation Preview found!"); return; } mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_RegionGrow3DTool->GetCurrentSegmentationName()); dialog.SetSegmentationName(segName); int result = dialog.exec(); switch (result) { case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(false); break; case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(true); break; case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: return; } mitk::Image::Pointer img = dynamic_cast(newNode->GetData()); AccessByItk(img, ITKThresholding); // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); newNode->SetVisibility(false); m_Controls.m_cbVolumeRendering->setChecked(false); // TODO disable slider etc... if (m_RegionGrow3DTool.IsNotNull()) { m_RegionGrow3DTool->ConfirmSegmentation(); } } template void QmitkAdaptiveRegionGrowingToolGUI::ITKThresholding(itk::Image *itkImage) { mitk::Image::Pointer originalSegmentation = dynamic_cast(this->m_RegionGrow3DTool->GetTargetSegmentationNode()->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!originalSegmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(originalSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (originalSegmentation) { typedef itk::Image InputImageType; typedef itk::Image SegmentationType; // select single 3D volume if we have more than one time step typename SegmentationType::Pointer originalSegmentationInITK = SegmentationType::New(); if (originalSegmentation->GetTimeGeometry()->CountTimeSteps() > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(originalSegmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); CastToItkImage(timeSelector->GetOutput(), originalSegmentationInITK); } else // use original { CastToItkImage(originalSegmentation, originalSegmentationInITK); } // Fill current preiview image in segmentation image originalSegmentationInITK->FillBuffer(0); itk::ImageRegionIterator itOutput(originalSegmentationInITK, originalSegmentationInITK->GetLargestPossibleRegion()); itk::ImageRegionIterator itInput(itkImage, itkImage->GetLargestPossibleRegion()); itOutput.GoToBegin(); itInput.GoToBegin(); // calculate threhold from slider value int currentTreshold = 0; if (m_CurrentRGDirectionIsUpwards) { currentTreshold = m_UPPERTHRESHOLD - m_Controls.m_PreviewSlider->value() + 1; } else { currentTreshold = m_Controls.m_PreviewSlider->value() - m_LOWERTHRESHOLD; } // iterate over image and set pixel in segmentation according to thresholded labeled image while (!itOutput.IsAtEnd() && !itInput.IsAtEnd()) { // Use threshold slider to determine if pixel is set to 1 if (itInput.Value() != 0 && itInput.Value() >= static_cast::PixelType>(currentTreshold)) { itOutput.Set(1); } ++itOutput; ++itInput; } // combine current working segmentation image with our region growing result originalSegmentation->SetVolume((void *)(originalSegmentationInITK->GetPixelContainer()->GetBufferPointer()), timeStep); originalSegmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::EnableControls(bool enable) { if (m_RegionGrow3DTool.IsNull()) return; // Check if seed point is already set, if not leave RunSegmentation disabled // if even m_DataStorage is nullptr leave node nullptr mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { this->m_Controls.m_pbRunSegmentation->setEnabled(false); } else { this->m_Controls.m_pbRunSegmentation->setEnabled(enable); } // Check if a segmentation exists, if not leave segmentation dependent disabled. // if even m_DataStorage is nullptr leave node nullptr node = m_DataStorage ? m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE) : nullptr; if (node.IsNull()) { this->m_Controls.m_PreviewSlider->setEnabled(false); this->m_Controls.m_pbConfirmSegementation->setEnabled(false); } else { this->m_Controls.m_PreviewSlider->setEnabled(enable); this->m_Controls.m_pbConfirmSegementation->setEnabled(enable); } this->m_Controls.m_cbVolumeRendering->setEnabled(enable); } void QmitkAdaptiveRegionGrowingToolGUI::EnableVolumeRendering(bool enable) { mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (node.IsNull()) return; if (enable) { node->SetBoolProperty("volumerendering", enable); node->SetBoolProperty("volumerendering.uselod", true); } else { node->SetBoolProperty("volumerendering", enable); } double val = this->m_Controls.m_PreviewSlider->value(); this->ChangeLevelWindow(val); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::UpdateVolumeRenderingThreshold(int) { typedef short PixelType; typedef itk::Image InputImageType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; mitk::DataNode::Pointer grownImageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::Image::Pointer grownImage = dynamic_cast(grownImageNode->GetData()); if (!grownImage) { MITK_ERROR << "Missing data node for labeled segmentation image."; return; } InputImageType::Pointer itkGrownImage; mitk::CastToItkImage(grownImage, itkGrownImage); ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(itkGrownImage); thresholdFilter->SetInPlace(false); double sliderVal = this->m_Controls.m_PreviewSlider->value(); PixelType threshold = itk::NumericTraits::min(); if (m_CurrentRGDirectionIsUpwards) { threshold = static_cast(m_UPPERTHRESHOLD - sliderVal + 0.5); thresholdFilter->SetLowerThreshold(threshold); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { threshold = sliderVal - m_LOWERTHRESHOLD + 0.5; thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(threshold); } thresholdFilter->UpdateLargestPossibleRegion(); MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(itkGrownImage); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMaskedImage; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMaskedImage); mitk::DataNode::Pointer maskNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); maskNode->SetData(mitkMaskedImage); } void QmitkAdaptiveRegionGrowingToolGUI::UseVolumeRendering(bool on) { m_UseVolumeRendering = on; this->EnableVolumeRendering(on); } void QmitkAdaptiveRegionGrowingToolGUI::SetLowerThresholdValue(double lowerThreshold) { m_LOWERTHRESHOLD = lowerThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::SetUpperThresholdValue(double upperThreshold) { m_UPPERTHRESHOLD = upperThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::Deactivated() { // make the segmentation preview node invisible mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (node.IsNotNull()) { node->SetVisibility(false); } // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); m_Controls.m_cbVolumeRendering->setChecked(false); } void QmitkAdaptiveRegionGrowingToolGUI::Activated() { } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h index 12d71f09b4..c0d645772d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h @@ -1,228 +1,228 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QMITK_QmitkAdaptiveRegionGrowingToolGUI_H #define QMITK_QmitkAdaptiveRegionGrowingToolGUI_H #include "itkImage.h" #include "mitkDataStorage.h" #include "mitkGeometry3D.h" #include "mitkPointSet.h" #include "qwidget.h" #include "ui_QmitkAdaptiveRegionGrowingToolGUIControls.h" #include #include "QmitkToolGUI.h" #include "mitkAdaptiveRegionGrowingTool.h" class DataNode; class QmitkAdaptiveRegionGrowingToolGUIControls; /*! * * \brief QmitkAdaptiveRegionGrowingToolGUI * * Adaptive Region Growing View class of the segmentation. * */ class MITKSEGMENTATIONUI_EXPORT QmitkAdaptiveRegionGrowingToolGUI : public QmitkToolGUI { Q_OBJECT public: /** * @brief mitkClassMacro */ mitkClassMacro(QmitkAdaptiveRegionGrowingToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent = nullptr); /** \brief Method to create the connections for the component. This Method is obligatory even if no connections is * needed*/ virtual void CreateConnections(); ///** \brief Method to set the default data storage.*/ virtual void SetDataStorage(mitk::DataStorage *dataStorage); /** * @brief Method to set the name of a data node. * @param labledSegmentation Name of the labeled segmentation * @param binaryImage Name of the binary image * @param surface Name of the surface */ void SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, /*std::string vesselTree,*/ std::string surface, std::string maskedSegmentation); /** * @brief Method to enable/disable controls for region growing * * This method checks if a seed point is set and a segmentation exists. * @param enable/disable controls */ void EnableControls(bool enable); /** * @brief Method to set the input image node * @param data node */ void SetInputImageNode(mitk::DataNode *node); void Deactivated(); void Activated(); /** * @brief The created GUI from the .ui-File. This Attribute is obligatory */ Ui::QmitkAdaptiveRegionGrowingToolGUIControls m_Controls; protected slots: /** * @brief Method to start the segmentation * * This method is called, when the "Start Segmentation" button is clicked. */ void RunSegmentation(); /** * @brief Method to change the level window * * This method is called, when the level window slider is changed via the slider in the control widget * @param new value */ void ChangeLevelWindow(double newValue); /** * @brief Method to increase the preview slider * * This method is called, when the + button is clicked and increases the value by 1 */ void IncreaseSlider(); /** * @brief Method to decrease the preview slider * * This method is called, when the - button is clicked and decreases the value by 1 */ void DecreaseSlider(); /** * @brief Method to confirm the preview segmentation * * This method is called, when the "Confirm Segmentation" button is clicked. */ void ConfirmSegmentation(); /** * @brief Method to switch the volume rendering on/off * @param on/off */ void UseVolumeRendering(bool on); /** * @brief Method to set the lower threshold * * This method is called, when the minimum threshold slider has changed * @param lower threshold */ void SetLowerThresholdValue(double lowerThreshold); /** * @brief Method to set upper threshold * * This Method is called, when the maximum threshold slider has changed * @param upper threshold */ void SetUpperThresholdValue(double upperThreshold); /** * @brief Method to determine which tool to activate * * This method listens to the tool manager and activates this tool if requested otherwise disables this view */ void OnNewToolAssociated(mitk::Tool *); protected: mitk::AdaptiveRegionGrowingTool::Pointer m_RegionGrow3DTool; /** \brief Destructor. */ ~QmitkAdaptiveRegionGrowingToolGUI() override; mitk::DataStorage *m_DataStorage; mitk::DataNode::Pointer m_InputImageNode; /** * @brief Method to calculate parameter settings, when a seed point is set */ void OnPointAdded(); /** * @brief Method to extract a 3D image based on a given time point that can be taken from the SliceNavigationController * * This ensures that the seed point is taken from the current selected 3D image */ - mitk::Image::ConstPointer Get3DImageByTimePoint(const mitk::Image *image, + mitk::Image::ConstPointer GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const; private: std::string m_NAMEFORORGIMAGE; std::string m_NAMEFORLABLEDSEGMENTATIONIMAGE; std::string m_NAMEFORBINARYIMAGE; std::string m_NAMEFORSURFACE; std::string m_NAMEFORMASKEDSEGMENTATION; mitk::ScalarType m_LOWERTHRESHOLD; // Hounsfield value mitk::ScalarType m_UPPERTHRESHOLD; // Hounsfield value mitk::ScalarType m_SeedPointValueMean; void RemoveHelperNodes(); int m_DetectedLeakagePoint; bool m_CurrentRGDirectionIsUpwards; // defines fixed threshold (true = LOWERTHRESHOLD fixed, false = UPPERTHRESHOLD // fixed) int m_SeedpointValue; bool m_SliderInitialized; bool m_UseVolumeRendering; bool m_UpdateSuggestedThreshold; float m_SuggestedThValue; long m_PointSetAddObserverTag; long m_PointSetMoveObserverTag; template void StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint); template void ITKThresholding(itk::Image *inputImage); void InitializeLevelWindow(); void EnableVolumeRendering(bool enable); void UpdateVolumeRenderingThreshold(int thValue); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp index b950c20c1c..7c720d4a58 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp @@ -1,212 +1,168 @@ /*============================================================================ 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 "QmitkBinaryThresholdToolGUI.h" -#include "QmitkConfirmSegmentationDialog.h" -#include "QmitkNewSegmentationDialog.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdToolGUI, "") QmitkBinaryThresholdToolGUI::QmitkBinaryThresholdToolGUI() - : QmitkToolGUI(), - m_Slider(nullptr), - m_Spinner(nullptr), - m_isFloat(false), - m_RangeMin(0), - m_RangeMax(0), - m_ChangingSlider(false), - m_ChangingSpinner(false) + : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); - m_Spinner = new QDoubleSpinBox(); - m_Spinner->setMaximum(20); - m_Spinner->setMinimum(5); - m_Spinner->setValue(1); - - connect(m_Spinner, SIGNAL(valueChanged(double)), this, SLOT(OnSpinnerValueChanged())); - layout->addWidget(m_Spinner); - - // m_Slider = new QSlider( 5, 20, 1, 1, Qt::Horizontal, this ); - m_Slider = new QSlider(Qt::Horizontal, this); - m_Slider->setMinimum(5); - m_Slider->setMaximum(20); - m_Slider->setPageStep(1); - m_Slider->setValue(1); - connect(m_Slider, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); - layout->addWidget(m_Slider); - + m_ThresholdSlider = new ctkSliderWidget(); + connect( + m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnSliderValueChanged(double))); + layout->addWidget(m_ThresholdSlider); mainLayout->addLayout(layout); + m_ThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); + m_CheckProcessAll = new QCheckBox("Process all time steps", this); + m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + + mainLayout->addWidget(m_CheckProcessAll); + + m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); + m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); + mainLayout->addWidget(m_CheckCreateNew); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdToolGUI::~QmitkBinaryThresholdToolGUI() { - // !!! if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValueChanged -= mitk::MessageDelegate1( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValueChanged); + m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValueChanged -= mitk::MessageDelegate1( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValueChanged); + m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdTool = dynamic_cast(tool); if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValueChanged += mitk::MessageDelegate1( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValueChanged); - } -} + m_BinaryThresholdTool->ThresholdingValuesChanged += mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); -void QmitkBinaryThresholdToolGUI::OnSpinnerValueChanged() -{ - if (m_BinaryThresholdTool.IsNotNull()) - { - m_ChangingSpinner = true; - double doubleVal = m_Spinner->value(); - int intVal = this->DoubleToSliderInt(doubleVal); - m_BinaryThresholdTool->SetThresholdValue(doubleVal); - if (m_ChangingSlider == false) - m_Slider->setValue(intVal); - m_ChangingSpinner = false; - } -} - -void QmitkBinaryThresholdToolGUI::OnSliderValueChanged(int value) -{ - if (m_BinaryThresholdTool.IsNotNull()) - { - m_ChangingSlider = true; - double doubleVal = SliderIntToDouble(value); - if (m_ChangingSpinner == false) - m_Spinner->setValue(doubleVal); - m_ChangingSlider = false; + m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); + m_CheckProcessAll->setVisible(m_BinaryThresholdTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps()>1); } } void QmitkBinaryThresholdToolGUI::OnAcceptThresholdPreview() { - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_BinaryThresholdTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_BinaryThresholdTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (m_CheckCreateNew->isChecked()) + { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } + } + + m_BinaryThresholdTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - if (m_BinaryThresholdTool.IsNotNull()) - { this->thresholdAccepted(); - m_BinaryThresholdTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { - m_isFloat = isFloat; - m_RangeMin = lower; - m_RangeMax = upper; - - m_Spinner->setRange(lower, upper); - if (!m_isFloat) + m_InternalUpdate = true; + if (!isFloat) { - m_Slider->setRange(int(lower), int(upper)); - m_Spinner->setDecimals(0); - m_Spinner->setSingleStep(1); + m_ThresholdSlider->setRange(int(lower), int(upper)); + m_ThresholdSlider->setSingleStep(1); + m_ThresholdSlider->setDecimals(0); } else { - m_Slider->setRange(0, 99); - m_Spinner->setDecimals(2); - m_Range = upper - lower; - m_Spinner->setSingleStep(m_Range / 100); + m_ThresholdSlider->setRange(lower, upper); } + m_InternalUpdate = false; } -void QmitkBinaryThresholdToolGUI::OnThresholdingValueChanged(double current) +void QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType /*upper*/) { - m_Slider->setValue(DoubleToSliderInt(current)); - m_Spinner->setValue(current); + m_ThresholdSlider->setValue(lower); } -double QmitkBinaryThresholdToolGUI::SliderIntToDouble(int val) +void QmitkBinaryThresholdToolGUI::OnSliderValueChanged(double value) { - if (!m_isFloat) + if (m_BinaryThresholdTool.IsNotNull() && !m_InternalUpdate) { - return double(val); - } - else - { - return double(val * (m_Range) / 100 + m_RangeMin); + m_BinaryThresholdTool->SetThresholdValue(value); } } -int QmitkBinaryThresholdToolGUI::DoubleToSliderInt(double val) +void QmitkBinaryThresholdToolGUI::BusyStateChanged(bool value) { - if (!m_isFloat) + if (value) { - return int(val); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } else { - int intVal = int(((val - m_RangeMin) / m_Range) * 100); - return intVal; + QApplication::restoreOverrideCursor(); } + + m_ThresholdSlider->setEnabled(!value); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h index bab07a7824..5696c1ff98 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h @@ -1,95 +1,80 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkBinaryThresholdToolGUI_h_Included #define QmitkBinaryThresholdToolGUI_h_Included #include "QmitkToolGUI.h" #include "mitkBinaryThresholdTool.h" #include -#include +#include "ctkSliderWidget.h" +#include -class QSlider; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. There is only a slider for INT values in QT. So, if the working image has a float/double pixeltype, we need to convert the original float intensity into a respective int value for the slider. The slider range is then between 0 and 99. If the pixeltype is INT, then we do not need any conversion. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); - void OnThresholdingValueChanged(double current); + void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); + void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: /// \brief Emitted when threshold Accepted void thresholdAccepted(); /// \brief Emitted when threshold Canceled void thresholdCanceled(); public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); - /// \brief Called when Spinner value has changed. Consider: Spinner contains DOUBLE values - void OnSpinnerValueChanged(); - - /// \brief Called when Slider value has changed. Consider: Slider contains INT values - void OnSliderValueChanged(int value); + void OnSliderValueChanged(double value); protected: QmitkBinaryThresholdToolGUI(); ~QmitkBinaryThresholdToolGUI() override; - /// \brief When Slider (int value) has changed, we need to convert it to a respective double value for the spinner - double SliderIntToDouble(int val); - - /// \brief When Spinner (double value) has changed, we need to convert it to a respective int value for the slider - int DoubleToSliderInt(double val); - - QSlider *m_Slider; - QDoubleSpinBox *m_Spinner; - - /// \brief is image float or int? - bool m_isFloat; + void BusyStateChanged(bool) override; - double m_RangeMin; - double m_RangeMax; - double m_Range; + ctkSliderWidget* m_ThresholdSlider = nullptr; + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; - /// \brief helper bool values to find out, which of the GUI elements has been touched by the user. - bool m_ChangingSlider, m_ChangingSpinner; + bool m_InternalUpdate = false; mitk::BinaryThresholdTool::Pointer m_BinaryThresholdTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp index 603fb305db..5954acff4c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp @@ -1,138 +1,163 @@ /*============================================================================ 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 "QmitkBinaryThresholdULToolGUI.h" -#include "QmitkConfirmSegmentationDialog.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdULToolGUI, "") QmitkBinaryThresholdULToolGUI::QmitkBinaryThresholdULToolGUI() : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); m_DoubleThresholdSlider = new ctkRangeWidget(); connect( m_DoubleThresholdSlider, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdsChanged(double, double))); layout->addWidget(m_DoubleThresholdSlider); mainLayout->addLayout(layout); m_DoubleThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); + m_CheckProcessAll = new QCheckBox("Process all time steps", this); + m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + mainLayout->addWidget(m_CheckProcessAll); + + m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); + m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); + mainLayout->addWidget(m_CheckCreateNew); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdULToolGUI::~QmitkBinaryThresholdULToolGUI() { - // !!! if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdULToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdULTool = dynamic_cast(tool); if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); + + m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); + m_CheckProcessAll->setVisible(m_BinaryThresholdULTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkBinaryThresholdULToolGUI::OnAcceptThresholdPreview() { - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_BinaryThresholdULTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_BinaryThresholdULTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (m_CheckCreateNew->isChecked()) + { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } + } - if (m_BinaryThresholdULTool.IsNotNull()) - { - m_BinaryThresholdULTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdULTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); + + m_BinaryThresholdULTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { if (!isFloat) { m_DoubleThresholdSlider->setRange(int(lower), int(upper)); m_DoubleThresholdSlider->setSingleStep(1); m_DoubleThresholdSlider->setDecimals(0); } else { m_DoubleThresholdSlider->setRange(lower, upper); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) { m_DoubleThresholdSlider->setValues(lower, upper); } void QmitkBinaryThresholdULToolGUI::OnThresholdsChanged(double min, double max) { m_BinaryThresholdULTool->SetThresholdValues(min, max); } + +void QmitkBinaryThresholdULToolGUI::BusyStateChanged(bool value) +{ + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else + { + QApplication::restoreOverrideCursor(); + } + + m_DoubleThresholdSlider->setEnabled(!value); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h index 67965c9ef2..ec0c130821 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h @@ -1,62 +1,68 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkBinaryThresholdULToolGUI_h_Included #define QmitkBinaryThresholdULToolGUI_h_Included #include "QmitkToolGUI.h" #include "ctkRangeWidget.h" +#include #include "mitkBinaryThresholdULTool.h" + #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdULToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdULToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); + void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); void OnThresholdsChanged(double min, double max); protected: QmitkBinaryThresholdULToolGUI(); ~QmitkBinaryThresholdULToolGUI() override; + void BusyStateChanged(bool) override; + ctkRangeWidget *m_DoubleThresholdSlider; + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; mitk::BinaryThresholdULTool::Pointer m_BinaryThresholdULTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp index 855a67e49c..b83f50a5eb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp @@ -1,370 +1,361 @@ /*============================================================================ 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 "QmitkFastMarchingTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingTool3DGUI, "") -QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI(), m_TimeIsConnected(false) +QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI() { this->setContentsMargins(0, 0, 0, 0); // create the visible widgets QVBoxLayout *widgetLayout = new QVBoxLayout(this); widgetLayout->setContentsMargins(0, 0, 0, 0); QFont fntHelp; fntHelp.setBold(true); QLabel *lblHelp = new QLabel(this); lblHelp->setText("Press shift-click to add seeds repeatedly."); lblHelp->setFont(fntHelp); widgetLayout->addWidget(lblHelp); // Sigma controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Sigma: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slSigma = new ctkSliderWidget(this); m_slSigma->setMinimum(0.1); m_slSigma->setMaximum(5.0); m_slSigma->setPageStep(0.1); m_slSigma->setSingleStep(0.01); m_slSigma->setValue(1.0); m_slSigma->setTracking(false); m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); widgetLayout->addWidget(m_slSigma); // Alpha controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Alpha: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slAlpha = new ctkSliderWidget(this); m_slAlpha->setMinimum(-10); m_slAlpha->setMaximum(0); m_slAlpha->setPageStep(0.1); m_slAlpha->setSingleStep(0.01); m_slAlpha->setValue(-2.5); m_slAlpha->setTracking(false); m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); widgetLayout->addWidget(m_slAlpha); // Beta controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Beta: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slBeta = new ctkSliderWidget(this); m_slBeta->setMinimum(0); m_slBeta->setMaximum(100); m_slBeta->setPageStep(0.1); m_slBeta->setSingleStep(0.01); m_slBeta->setValue(3.5); m_slBeta->setTracking(false); m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); widgetLayout->addWidget(m_slBeta); // stopping value controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Stopping value: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slStoppingValue = new ctkSliderWidget(this); m_slStoppingValue->setMinimum(0); m_slStoppingValue->setMaximum(10000); m_slStoppingValue->setPageStep(10); m_slStoppingValue->setSingleStep(1); m_slStoppingValue->setValue(2000); m_slStoppingValue->setDecimals(0); m_slStoppingValue->setTracking(false); m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); widgetLayout->addWidget(m_slStoppingValue); // threshold controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Threshold: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slwThreshold = new ctkRangeWidget(this); m_slwThreshold->setMinimum(-100); m_slwThreshold->setMaximum(5000); m_slwThreshold->setMinimumValue(-100); m_slwThreshold->setMaximumValue(2000); m_slwThreshold->setDecimals(0); m_slwThreshold->setTracking(false); m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); widgetLayout->addWidget(m_slwThreshold); m_btClearSeeds = new QPushButton("Clear"); m_btClearSeeds->setToolTip("Clear current result and start over again"); m_btClearSeeds->setEnabled(false); widgetLayout->addWidget(m_btClearSeeds); connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); m_btConfirm = new QPushButton("Confirm Segmentation"); m_btConfirm->setToolTip("Incorporate current result in your working session."); m_btConfirm->setEnabled(false); widgetLayout->addWidget(m_btConfirm); connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); + m_CheckProcessAll = new QCheckBox("Process all time steps", this); + m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + widgetLayout->addWidget(m_CheckProcessAll); + + m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); + m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); + widgetLayout->addWidget(m_CheckCreateNew); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); m_slSigma->setDecimals(2); m_slBeta->setDecimals(2); m_slAlpha->setDecimals(2); this->EnableWidgets(false); } QmitkFastMarchingTool3DGUI::~QmitkFastMarchingTool3DGUI() { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } } void QmitkFastMarchingTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } m_FastMarchingTool = dynamic_cast(tool); if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->AddReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); - // listen to timestep change events - mitk::BaseRenderer::Pointer renderer; - renderer = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - if (renderer.IsNotNull() && !m_TimeIsConnected) - { - new QmitkStepperAdapter(this, renderer->GetSliceNavigationController()->GetTime(), "stepper"); - // connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(Refetch())); - m_TimeIsConnected = true; - } + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->SetOverwriteExistingSegmentation(true); + m_FastMarchingTool->ClearSeeds(); + m_CheckProcessAll->setVisible(m_FastMarchingTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkFastMarchingTool3DGUI::Update() { if (m_FastMarchingTool.IsNotNull()) { - m_FastMarchingTool->SetLowerThreshold(this->m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(this->m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(this->m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(this->m_slSigma->value()); - m_FastMarchingTool->SetAlpha(this->m_slAlpha->value()); - m_FastMarchingTool->SetBeta(this->m_slBeta->value()); - m_FastMarchingTool->Update(); + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->UpdatePreview(); } } void QmitkFastMarchingTool3DGUI::OnThresholdChanged(double lower, double upper) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetLowerThreshold(lower); m_FastMarchingTool->SetUpperThreshold(upper); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnBetaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetBeta(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnSigmaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetSigma(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnAlphaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetAlpha(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnStoppingValueChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetStoppingValue(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnConfirmSegmentation() { - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_FastMarchingTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_FastMarchingTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (m_CheckCreateNew->isChecked()) + { m_FastMarchingTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_FastMarchingTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } - if (m_FastMarchingTool.IsNotNull()) - { + } + + m_FastMarchingTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); + m_btConfirm->setEnabled(false); m_FastMarchingTool->ConfirmSegmentation(); } } -void QmitkFastMarchingTool3DGUI::SetStepper(mitk::Stepper *stepper) -{ - this->m_TimeStepper = stepper; -} - -void QmitkFastMarchingTool3DGUI::Refetch() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->SetCurrentTimeStep(m_TimeStepper->GetPos()); -} - void QmitkFastMarchingTool3DGUI::OnClearSeeds() { // event from image navigator recieved - timestep has changed m_FastMarchingTool->ClearSeeds(); m_btClearSeeds->setEnabled(false); m_btConfirm->setEnabled(false); this->EnableWidgets(false); this->Update(); } void QmitkFastMarchingTool3DGUI::BusyStateChanged(bool value) { if (value) + { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + this->EnableWidgets(false); + } else + { QApplication::restoreOverrideCursor(); -} - -void QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady() -{ - this->EnableWidgets(true); - this->m_btClearSeeds->setEnabled(true); - this->m_btConfirm->setEnabled(true); + this->EnableWidgets(true); + } } void QmitkFastMarchingTool3DGUI::EnableWidgets(bool enable) { m_slSigma->setEnabled(enable); m_slAlpha->setEnabled(enable); m_slBeta->setEnabled(enable); m_slStoppingValue->setEnabled(enable); m_slwThreshold->setEnabled(enable); + m_btClearSeeds->setEnabled(enable); + m_btConfirm->setEnabled(enable); + m_CheckCreateNew->setEnabled(enable); + m_CheckProcessAll->setEnabled(enable); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h index 67c8204881..a02b18de12 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h @@ -1,84 +1,79 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkFastMarchingTool3DGUI_h_Included #define QmitkFastMarchingTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkFastMarchingTool3D.h" #include class ctkSliderWidget; class ctkRangeWidget; class QPushButton; - -#include "QmitkStepperAdapter.h" +class QCheckBox; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::FastMarchingTool. \sa mitk::FastMarchingTool */ class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdChanged(int current); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnThresholdChanged(double, double); void OnAlphaChanged(double); void OnBetaChanged(double); void OnSigmaChanged(double); void OnStoppingValueChanged(double); void OnConfirmSegmentation(); - void Refetch(); - void SetStepper(mitk::Stepper *); void OnClearSeeds(); protected: QmitkFastMarchingTool3DGUI(); ~QmitkFastMarchingTool3DGUI() override; void BusyStateChanged(bool) override; void Update(); ctkRangeWidget *m_slwThreshold; ctkSliderWidget *m_slStoppingValue; ctkSliderWidget *m_slSigma; ctkSliderWidget *m_slAlpha; ctkSliderWidget *m_slBeta; QPushButton *m_btConfirm; QPushButton *m_btClearSeeds; - mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; - - bool m_TimeIsConnected; - mitk::Stepper::Pointer m_TimeStepper; + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; - void OnFastMarchingToolReady(); + mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; private: void EnableWidgets(bool); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 2d9b8f8032..031b353dab 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,184 +1,214 @@ /*============================================================================ 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 "QmitkOtsuTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkToolGUI(), m_NumberOfRegions(0) { m_Controls.setupUi(this); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnSpinboxValueAccept())); connect(m_Controls.m_selectionListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(OnRegionSelectionChanged())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.m_ConfSegButton, SIGNAL(clicked()), this, SLOT(OnSegmentationRegionAccept())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); } QmitkOtsuTool3DGUI::~QmitkOtsuTool3DGUI() { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnRegionSelectionChanged() { m_SelectedItems = m_Controls.m_selectionListWidget->selectedItems(); - if (m_SelectedItems.size() == 0) - { - m_Controls.m_ConfSegButton->setEnabled(false); - m_OtsuTool3DTool->ShowMultiLabelResultNode(true); - return; - } - if (m_OtsuTool3DTool.IsNotNull()) { // update preview of region QList::Iterator it; std::vector regionIDs; for (it = m_SelectedItems.begin(); it != m_SelectedItems.end(); ++it) regionIDs.push_back((*it)->text().toInt()); - m_OtsuTool3DTool->UpdateBinaryPreview(regionIDs); - m_Controls.m_ConfSegButton->setEnabled(true); + + m_OtsuTool3DTool->SetSelectedRegions(regionIDs); + m_OtsuTool3DTool->UpdatePreview(); + + m_Controls.m_ConfSegButton->setEnabled(!regionIDs.empty()); } } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); if (toggled) { - int max = m_OtsuTool3DTool->GetNumberOfBins(); + int max = m_OtsuTool3DTool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } + m_OtsuTool3DTool = dynamic_cast(tool); + + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + + m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); + m_OtsuTool3DTool->IsTimePointChangeAwareOff(); + m_Controls.m_CheckProcessAll->setVisible(m_OtsuTool3DTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); + } } void QmitkOtsuTool3DGUI::OnSegmentationRegionAccept() { QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_OtsuTool3DTool->GetCurrentSegmentationName()); - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_OtsuTool3DTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (this->m_Controls.m_CheckCreateNew->isChecked()) + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } + } - if (m_OtsuTool3DTool.IsNotNull() && m_Controls.m_selectionListWidget->currentItem() != nullptr) - { + m_OtsuTool3DTool->SetCreateAllTimeSteps(this->m_Controls.m_CheckProcessAll->isChecked()); + + this->m_Controls.m_ConfSegButton->setEnabled(false); m_OtsuTool3DTool->ConfirmSegmentation(); } } void QmitkOtsuTool3DGUI::OnSpinboxValueAccept() { if (m_NumberOfRegions == m_Controls.m_Spinbox->value() && m_UseValleyEmphasis == m_Controls.m_ValleyCheckbox->isChecked() && m_NumberOfBins == m_Controls.m_BinsSpinBox->value()) return; if (m_OtsuTool3DTool.IsNotNull()) { try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } m_NumberOfRegions = m_Controls.m_Spinbox->value(); m_UseValleyEmphasis = m_Controls.m_ValleyCheckbox->isChecked(); m_NumberOfBins = m_Controls.m_BinsSpinBox->value(); + m_OtsuTool3DTool->SetNumberOfRegions(m_NumberOfRegions); + m_OtsuTool3DTool->SetUseValley(m_UseValleyEmphasis); + m_OtsuTool3DTool->SetNumberOfBins(m_NumberOfBins); - this->setCursor(Qt::WaitCursor); - m_OtsuTool3DTool->RunSegmentation(m_NumberOfRegions, m_UseValleyEmphasis, m_NumberOfBins); - this->setCursor(Qt::ArrowCursor); + m_OtsuTool3DTool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } + // insert regions into widget QString itemName; QListWidgetItem *item; m_Controls.m_selectionListWidget->clear(); for (int i = 0; i < m_Controls.m_Spinbox->value(); ++i) { itemName = QString::number(i); item = new QListWidgetItem(itemName); m_Controls.m_selectionListWidget->addItem(item); } // deactivate 'confirm segmentation'-button m_Controls.m_ConfSegButton->setEnabled(false); + m_OtsuTool3DTool->IsTimePointChangeAwareOn(); } } -void QmitkOtsuTool3DGUI::OnVolumePreviewChecked(int state) +void QmitkOtsuTool3DGUI::BusyStateChanged(bool value) { - if (state == 1) + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else { + QApplication::restoreOverrideCursor(); } + + m_Controls.m_ValleyCheckbox->setEnabled(!value); + m_Controls.binLabel->setEnabled(!value); + m_Controls.m_BinsSpinBox->setEnabled(!value); + m_Controls.m_ConfSegButton->setEnabled(!m_OtsuTool3DTool->GetSelectedRegions().empty() && !value); + m_Controls.m_CheckProcessAll->setEnabled(!value); + m_Controls.m_CheckCreateNew->setEnabled(!value); + m_Controls.previewButton->setEnabled(!value); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index f562f73560..ce2ad862fe 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,83 +1,83 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkOtsuTool3D.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include #include #include class QSpinBox; class QLabel; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); signals : public slots : protected slots : void OnNewToolAssociated(mitk::Tool *); void OnSpinboxValueAccept(); void OnSegmentationRegionAccept(); void OnRegionSelectionChanged(); void OnRegionSpinboxChanged(int); - void OnVolumePreviewChecked(int); - private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() override; + void BusyStateChanged(bool value) override; + mitk::OtsuTool3D::Pointer m_OtsuTool3DTool; Ui_QmitkOtsuToolWidgetControls m_Controls; int m_NumberOfRegions; bool m_UseValleyEmphasis; int m_NumberOfBins; QList m_SelectedItems; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui index e5484bf97b..27e1d87ff8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui @@ -1,214 +1,234 @@ QmitkOtsuToolWidgetControls 0 0 192 - 293 + 300 0 0 100 0 100000 100000 QmitkOtsuToolWidget Move to adjust the segmentation QLayout::SetNoConstraint 0 0 Number of Regions: 0 0 40 16777215 2 32 0 0 0 32 Advanced settings Qt::ToolButtonTextBesideIcon true Use Valley Emphasis Number of Histogram Bins: 2 4096 128 0 0 10000000 100 0 QAbstractItemView::MultiSelection QListView::Adjust 0 0 100000 16777215 Preview false 0 0 100000 16777215 Confirm Segmentation + + + + Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step. + + + Process all time steps + + + + + + + Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected. + + + Create as new segmentation + + + ctkExpandButton QToolButton
ctkExpandButton.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 2dcb1b21fe..43ca2e2c15 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,716 +1,722 @@ /*============================================================================ 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. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr), m_EnabledMode(EnabledWithReferenceAndWorkingDataVisible) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // muellerm // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(toolButtonClicked(int))); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } void QmitkToolSelectionBox::SetEnabledMode(EnabledMode mode) { m_EnabledMode = mode; SetGUIEnabledAccordingToToolManagerState(); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; // QToolButton* toolButton = dynamic_cast( Q3ButtonGroup::find(id) ); QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools // mmueller toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } // m_LastToolGUI->reparent(nullptr, QPoint(0,0)); // TODO: reparent <-> setParent, Daniel fragen m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); // mitk::Tool* tool = m_ToolManager->GetActiveTool(); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { // toolButton = dynamic_cast( Q3ButtonGroup::find( m_ButtonIDForToolID[id] ) ); toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; QList::iterator it; for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); // mmueller // gui->reparent(m_ToolGUIWidget, gui->geometry().topLeft(), true ); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { // mmueller layout->addWidget(gui); // layout->add( gui ); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); // QToolButton* selectedToolButton = dynamic_cast( Q3ButtonGroup::find( Q3ButtonGroup::selectedId() ) // ); if (selectedToolButton) { // mmueller selectedToolButton->setChecked(false); // selectedToolButton->setOn(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } /** Implementes the logic, which decides, when tools are activated/deactivated. */ void QmitkToolSelectionBox::SetGUIEnabledAccordingToToolManagerState() { mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); // MITK_DEBUG << this->name() << ": SetGUIEnabledAccordingToToolManagerState: referenceNode " << (void*)referenceNode // << " workingNode " << (void*)workingNode << " isVisible() " << isVisible(); bool enabled = true; switch (m_EnabledMode) { default: case EnabledWithReferenceAndWorkingDataVisible: enabled = referenceNode && workingNode && referenceNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && isVisible(); break; case EnabledWithReferenceData: enabled = referenceNode && isVisible(); break; case EnabledWithWorkingData: enabled = workingNode && isVisible(); break; case AlwaysEnabled: enabled = isVisible(); break; } if (QWidget::isEnabled() == enabled) return; // nothing to change QWidget::setEnabled(enabled); if (enabled) { m_ToolManager->RegisterClient(); int id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } /** External enableization... */ void QmitkToolSelectionBox::setEnabled(bool /*enable*/) { SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; /* // remove all buttons that are there QObjectList *l = Q3ButtonGroup::queryList( "QButton" ); QObjectListIt it( *l ); // iterate over all buttons QObject *obj; while ( (obj = it.current()) != 0 ) { ++it; QButton* button = dynamic_cast(obj); if (button) { Q3ButtonGroup::remove(button); delete button; } } delete l; // delete the list, not the objects */ // mmueller Qt impl QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); // this->removeChild(btn); delete btn; } // end mmueller Qt impl mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } // try to change layout... bad? // Q3GroupBox::setColumnLayout ( m_LayoutColumns, Qt::Horizontal ); // mmueller using gridlayout instead of Q3GroupBox // this->setLayout(0); if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; /*else delete m_ButtonLayout;*/ int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { // button->setTextPosition( QToolButton::BesideIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { // button->setTextPosition( QToolButton::BelowIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } // button->setToggleButton( true ); // mmueller button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { /* button->setUsesTextLabel(true); button->setTextLabel( label ); // a label QToolTip::add( button, tooltip ); */ // mmueller Qt button->setText(label); // a label button->setToolTip(tooltip); // mmueller QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } mitk::DataNode *dataNode = m_ToolManager->GetReferenceData(0); + const auto workingDataNode = m_ToolManager->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } - if (dataNode != nullptr && !tool->CanHandle(dataNode->GetData())) + if (nullptr == dataNode || !tool->CanHandle(dataNode->GetData(), workingData)) button->setEnabled(false); m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegExp("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } void QmitkToolSelectionBox::setTitle(const QString & /*title*/) { } void QmitkToolSelectionBox::showEvent(QShowEvent *e) { QWidget::showEvent(e); SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::hideEvent(QHideEvent *e) { QWidget::hideEvent(e); SetGUIEnabledAccordingToToolManagerState(); } diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png index 7cb45a0828..8dcec773a9 100644 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png and b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png index af303e721c..f4a6453618 100644 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png and b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox index 2bcd21d2bb..ef2b68b13d 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox +++ b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox @@ -1,37 +1,52 @@ /** -\page org_mitk_gui_qt_imagecropper The Image Cropper +\page org_mitk_views_imagecropper The Image Cropper \imageMacro{crop.svg,"Icon of the Image Cropper Plugin.",20} -\tableofcontents +\section org_mitk_views_imagecropperUsage Usage -\section org_mitk_gui_qt_imagecropperUsage Usage +The Image Cropper Plugin allows to crop and mask subvolumes out of the original image volume by defining a cubic bounding shape. -The Image Cropper Plugin allows to crop subvolumes out of your original image volume by defining a cubic bounding box. +\imageMacro{BoundingBox_ImageCropperView.png,"Bounding Shape.",12.00} -This box can be placed at an arbitrary position in the volume and can be easily adjusted by using the handles on each of the faces. -Touching the handles changes the size of the box whereas touching the box itself changes its position. +A new bounding shape can be created by selecting an image and pressing the 'New' button. The bounding shape appears as a child node in the data manager. Alternatively, an existing bounding shape can be selected. -As soon as the bounding box is placed at the desired position, pressing the button 'Crop' creates a new image assigned to the original image -as child node containing only the selected subvolume. The size of the subvolume equals the size of the bounding box. -Pressing the "Mask" button keeps the original image size but masks out the area not contained within the bounding box bounds. -In case of 3D+t images the whole time series is cropped by default. - -\imageMacro{BoundingBox_ImageCropperView.png,"Bounding Box.",12.00} \imageMacro{Basic_ImageCropperView.png,"Basic Settings.",7.09} -\section org_mitk_gui_qt_imagecropperAdvanced Advanced settings -In the advanced settings view you find additional features to manipulate the bounding box. +This bounding shape can be placed at an arbitrary position in the volume and can be easily adjusted by using the handles on each of the faces. When activated, the handles are shown in red, otherwise, they are colored white. Hovering over either the shape or a single handle allows modifying the bounding shape. Moving the handles changes the respective extent of the bounding shape, whereas moving the shape itself changes its position. + + +As soon as the bounding shape is placed at the desired position, pressing the button 'Crop' creates a new image assigned to the original image as a child node containing only the selected subvolume. The size of the subvolume equals the size of the bounding shape. +Pressing the 'Mask' button keeps the original image size but masks out the area not contained within the bounding shape bounds. +In the case of 3D+t images, the whole time series is cropped by default. + + +\section org_mitk_views_imagecropperAdvanced Advanced settings + +In the advanced settings view you find additional features: \imageMacro{Advanced_ImageCropperView.png,"Advanced Settings.",7.09} -\subsection org_mitk_gui_qt_imagecropperAdvancedOverwrite Overwrite original image +\subsection org_mitk_views_imagecropperMaskOutsidePixel Mask with outside pixel + +Assigns the value of the voxels outside of the bounding shape when 'Mask' is used. + + +\subsection org_mitk_views_imagecropperAdvancedOverwrite Overwrite original image + By enabling this checkbox the image is replaced by the cropped subvolume. Be careful to use this option since there is no undo action available. -\subsection org_mitk_gui_qt_imagecropperAdvancedTimestep Crop current time step only -If this checkbox is enabled the xD + t image is reduced to a xD image (e.g., 3D+t --> 3D) with the time step visible in the widget. This is useful if you want to extract a single image or its corresponding subvolume of the time series. The whole time series is cropped by default using the timeGeometry of the time step visible in the widget. +\subsection org_mitk_views_imagecropperAdvancedTimestep Crop current time step only + +If you have an xD + t image, the whole time series (all timesteps) is cropped by default. In this case, the 'time geometry' of the current time step is used. + +If the checkbox 'Only crop current time step' is ticked, the xD + t image is reduced to an xD image (e.g., 3D+t --> 3D) with the current time step only. That can be useful if you want to extract a single image or its corresponding subvolume of the time series. + +\section org_mitk_views_imagecropperIssues Current issues -\section org_mitk_gui_qt_imagecropperIssues Current issues Cropping 2D images is not supported unless the are 3D images containing only a single slice. The user will be notified by a warning and the input is handled as a single label image. -Right now changing the shape or rotation of the bounding box is not supported but might be integrated in the future. -*/ \ No newline at end of file +Right now changing the shape or rotation of the bounding shape is not supported but might be integrated in the future. + +Furthermore, a warning appears when the bounding shape is not aligned with the image. In this case, the handles can not be used correctly and get deactivated. You can continue to alter them by performing a 'Reinit' on the image. + +*/ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png deleted file mode 100644 index 221f3f7ac1..0000000000 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index 84835e20fd..3594ac014f 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,494 +1,496 @@ /*============================================================================ 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 "QmitkImageCropperView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.imageSelectionWidget->SetNodePredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); + m_Controls.imageSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); + m_Controls.boundingBoxSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; } void QmitkImageCropperView::OnImageSelectionChanged(QList) { bool rotationEnabled = false; auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::ImageIOBase::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } if (nullptr == imageNode->GetData()) { return; } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Shape"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); boundingBoxNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("handle size factor", mitk::DoubleProperty::New(1.0 / 40.0)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); return; } const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto image = dynamic_cast(imageNode->GetData()); auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { QString imageName; if (mask) { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) auto labelsetImageInput = dynamic_cast(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); croppedImageNode->SetProperty("layer", mitk::IntProperty::New(99)); // arbitrary, copied from segmentation functionality if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(cutter->GetOutput()); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { m_Controls.labelWarningRotation->setVisible(false); m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/Manual.dox index fcc483e40c..5d4fcf4622 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/Manual.dox @@ -1,39 +1,40 @@ /** -\page org_mitk_gui_qt_matchpoint_algorithm_control The MatchPoint Algorithm Control View +\page org_mitk_views_matchpoint_algorithm_control The MatchPoint Algorithm Control View \imageMacro{map_icon_run_doc.svg,"Icon of the MatchPoint Algorithm Control",3.0} \tableofcontents \section MAP_RUN_Introduction Introduction -This plugin offers the user a way to use a selected registration algorithm in order to determine a registration for two selected images. For the selection of an algorithm please see MatchPoint Algorithm Browser (\ref org_mitk_gui_qt_matchpoint_algorithm_browser). - -\section MAP_RUN_Contact Contact information -This plug-in is being developed by the SIDT group (Software development for Integrated Diagnostics -and Therapy) at the German Cancer Research Center (DKFZ). If you have any questions, need support, -find a bug or have a feature request, feel free to contact us at www.mitk.org. +This plugin offers the user a way to register data (images or point sets) in order to establish a spatial correlation/mapping (stored as registration object in the data storage). To determine a registration the user has to select +a registration algorithm. For the selection of an algorithm please see MatchPoint Algorithm Browser (\ref org_mitk_views_matchpoint_algorithm_browser). \section MAP_RUN_Usage Usage \imageMacro{map_control_example.png, "Example screenshot showing the control plugin in use.", 10} -To use the plugin a registration algorithm must be loaded and a moving as well as a target image must be selected.\n -The moving image is registered onto the target image. Thus the result is a mapped input image in the geometry (field of view, orientation, spacing) defined by the target image.\n -All images are selected in the data manager using multi select (press the CTRL-key while selecting the nodes in the data manager). The first selection is the moving image, the second is the target image.\n -If an algorithm is loaded and input images are selected, the plugin will automatically switch to the "Execution" tab. +To use the plugin a registration algorithm must be loaded and moving data as well as target data must be selected. + +The type of data supported depends on the chosen algorithm. Basically the plugin supports the registration of images and point sets. +The moving data is registered onto the target data. Thus in case of images, the result is a mapped input image in the geometry (field of view, orientation, spacing) defined by the target image.\n +All inputs that should be used for the registration are selected via the respective selection widgets shown in the image below. The box with the mask input selectors are only shown if the chosen algorithm supports masks. It is also optional to set only one ore both masks. (Remark: implications of which mask to set may depend on the specific algorithms; e.g. sometimes it is computationally more efficient to set at least the moving mask if possible.) + +\imageMacro{map_control_inputs.png, "Input selections for the registration. Moving and target inputs are mandatory. Moving and target masks are only available if the algorithm supports them and are optional.", 5} + +If an algorithm is loaded and inputs are selected, the plugin will automatically switch to the "Execution" tab. \subsection MAP_RUN_Usage_selection Selection tab \imageMacro{map_control_step1_selection.png, "Details of the selection tab.", 5} In this tab registration algorithms that are selected in the MatchPoint Algorithm Browser can be chosen. In the tab you see the ID of the algorithm selected by the browser and its profile information.\n -If you press "Load selected algorithm", the algorithm will be used by the control plugin. The name of the algorithm occurs in the text field "Loaded algorithm" (at the top of the plugin view).\n +If you press the button "Load selected algorithm", the algorithm will be used by the control plugin. The name of the algorithm occurs in the text field "Loaded algorithm" (at the top of the plugin view).\n At this point, it has no effect if you change the selection in the browser. The control plugin will keep the loaded algorithm until you choose to load another one. \subsection MAP_RUN_Usage_exec Execution tab \imageMacro{map_control_step2_execution.png, "Details of the execution tab.", 5} -In this tab you can specify a name for the registration job (this will determine the names of the result nodes in the data manager).\n +In this tab you can specify a name for the registration job (this will determine the names of the result nodes in the Data Manager View and the data selection widgets).\n You can also choose to "store registration" (which is normally the goal of the whole process, because this is the very result of the algorithm ;).\n Additionally you can choose "Generate + store mapped result". This is a convenience feature which often saves you the time to use the mapper plugin afterwards. It will do the same like using the mapper plugin with the moving and target image, setting padding value "0" and using "linear interpolation". If you need other settings, skip the convenience generation and use the MatchPoint mapper plugin directly.\n "Start" will trigger the registration process. Some algorithms can be stopped while processing takes place. In those cases, there is a "Stop" button enabled, as soon as the registration process starts. \subsection MAP_RUN_Usage_settings Settings tab \imageMacro{map_control_step3_settings.png, "Details of the settings tab.", 5} In this tab, you can change the parametrization of the loaded algorithm (before it starts), if it offers any possibility to do so. */ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_example.png b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_example.png index 424ae7a57e..fb4a6ad9a1 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_example.png and b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_example.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_inputs.png b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_inputs.png new file mode 100644 index 0000000000..e590870991 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_inputs.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step1_selection.png b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step1_selection.png index 8057a6c1f9..5294d1fcd4 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step1_selection.png and b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step1_selection.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step2_execution.png b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step2_execution.png index d6eeee4edb..5c0ee8c92c 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step2_execution.png and b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step2_execution.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step3_settings.png b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step3_settings.png index 2628a21384..7f62e21273 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step3_settings.png and b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/documentation/UserManual/map_control_step3_settings.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp index 637b5db3b3..431cd29901 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp @@ -1,867 +1,871 @@ /*============================================================================ 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 "org_mitk_gui_qt_matchpoint_algorithmcontrol_Activator.h" // Blueberry #include #include #include #include // Mitk #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include "QmitkMatchPoint.h" #include #include // Qt #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include #include #include const std::string QmitkMatchPoint::VIEW_ID = "org.mitk.views.matchpoint.algorithm.control"; QmitkMatchPoint::QmitkMatchPoint() : m_Parent(nullptr), m_LoadedDLLHandle(nullptr), m_LoadedAlgorithm(nullptr) { m_CanLoadAlgorithm = false; m_ValidInputs = false; m_Working = false; m_spSelectedTargetData = nullptr; m_spSelectedMovingData = nullptr; m_spSelectedTargetMaskData = nullptr; m_spSelectedMovingMaskData = nullptr; } QmitkMatchPoint::~QmitkMatchPoint() { // remove selection service berry::ISelectionService* s = this->GetSite()->GetWorkbenchWindow()->GetSelectionService(); if (s) { s->RemoveSelectionListener(m_AlgorithmSelectionListener.data()); } } void QmitkMatchPoint::SetFocus() { } void QmitkMatchPoint::CreateConnections() { connect(m_Controls.targetNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.movingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.targetMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.movingMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); // ------ // Tab 1 - Shared library loading interface // ------ connect(m_Controls.m_pbLoadSelected, SIGNAL(clicked()), this, SLOT(OnLoadAlgorithmButtonPushed())); // ----- // Tab 2 - Execution // ----- connect(m_Controls.m_pbStartReg, SIGNAL(clicked()), this, SLOT(OnStartRegBtnPushed())); connect(m_Controls.m_pbStopReg, SIGNAL(clicked()), this, SLOT(OnStopRegBtnPushed())); connect(m_Controls.m_pbSaveLog, SIGNAL(clicked()), this, SLOT(OnSaveLogBtnPushed())); } const map::deployment::DLLInfo* QmitkMatchPoint::GetSelectedAlgorithmDLL() const { return m_SelectedAlgorithmInfo; } void QmitkMatchPoint::OnSelectedAlgorithmChanged() { std::stringstream descriptionString; ::map::deployment::DLLInfo::ConstPointer currentItemInfo = GetSelectedAlgorithmDLL(); if (!currentItemInfo) { Error(QStringLiteral("No valid algorithm is selected. ABORTING.")); return; } m_Controls.m_teAlgorithmDetails->updateInfo(currentItemInfo); m_Controls.m_lbSelectedAlgorithm->setText(QString::fromStdString( currentItemInfo->getAlgorithmUID().getName())); // enable loading m_CanLoadAlgorithm = true; this->AdaptFolderGUIElements(); } void QmitkMatchPoint::OnLoadAlgorithmButtonPushed() { map::deployment::DLLInfo::ConstPointer dllInfo = GetSelectedAlgorithmDLL(); if (!dllInfo) { Error(QStringLiteral("No valid algorithm is selected. Cannot load algorithm. ABORTING.")); return; } ::map::deployment::DLLHandle::Pointer tempDLLHandle = ::map::deployment::openDeploymentDLL( dllInfo->getLibraryFilePath()); ::map::algorithm::RegistrationAlgorithmBase::Pointer tempAlgorithm = ::map::deployment::getRegistrationAlgorithm(tempDLLHandle); if (tempAlgorithm.IsNull()) { Error(QStringLiteral("Error. Cannot load selected algorithm.")); return; } this->m_LoadedAlgorithm = tempAlgorithm; this->m_LoadedDLLHandle = tempDLLHandle; this->m_Controls.m_AlgoConfigurator->setAlgorithm(m_LoadedAlgorithm); typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskRegInterface; const MaskRegInterface* pMaskReg = dynamic_cast (m_LoadedAlgorithm.GetPointer()); if (!pMaskReg) { m_spSelectedTargetMaskData = nullptr; m_spSelectedTargetMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; m_spSelectedMovingMaskNode = nullptr; m_Controls.targetMaskNodeSelector->SetCurrentSelection(QmitkAbstractNodeSelectionWidget::NodeList()); m_Controls.movingMaskNodeSelector->SetCurrentSelection(QmitkAbstractNodeSelectionWidget::NodeList()); } this->AdaptFolderGUIElements(); this->ConfigureNodeSelectors(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); this->m_Controls.m_tabs->setCurrentIndex(1); this->m_Controls.m_teLog->clear(); } void QmitkMatchPoint::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); m_Controls.m_teLog->append(QStringLiteral("") + msg + QStringLiteral("")); } void QmitkMatchPoint::AdaptFolderGUIElements() { m_Controls.m_pbLoadSelected->setEnabled(m_CanLoadAlgorithm); } void QmitkMatchPoint::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; m_Controls.m_tabs->setCurrentIndex(0); m_Controls.movingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.movingNodeSelector->SetSelectionIsOptional(false); m_Controls.targetNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.targetNodeSelector->SetSelectionIsOptional(false); m_Controls.movingMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.movingMaskNodeSelector->SetSelectionIsOptional(true); m_Controls.targetMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.targetMaskNodeSelector->SetSelectionIsOptional(true); m_AlgorithmSelectionListener.reset(new berry::SelectionChangedAdapter(this, &QmitkMatchPoint::OnAlgorithmSelectionChanged)); // register selection listener GetSite()->GetWorkbenchWindow()->GetSelectionService()->AddSelectionListener( m_AlgorithmSelectionListener.data()); this->CreateConnections(); this->AdaptFolderGUIElements(); this->CheckInputs(); this->ConfigureProgressInfos(); this->ConfigureRegistrationControls(); this->ConfigureNodeSelectors(); berry::ISelection::ConstPointer selection = GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection("org.mitk.views.matchpoint.algorithm.browser"); this->UpdateAlgorithmSelection(selection); } 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(); } bool QmitkMatchPoint::CheckInputs() { if (m_LoadedAlgorithm.IsNull()) { m_spSelectedMovingNode = nullptr; m_spSelectedMovingData = nullptr; m_spSelectedTargetNode = nullptr; m_spSelectedTargetData = nullptr; m_spSelectedMovingMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; m_spSelectedTargetMaskNode = nullptr; m_spSelectedTargetMaskData = nullptr; } else { if (m_Controls.movingNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedMovingNode = nullptr; m_spSelectedMovingData = nullptr; } else { m_spSelectedMovingNode = m_Controls.movingNodeSelector->GetSelectedNode(); m_spSelectedMovingData = m_spSelectedMovingNode->GetData(); auto movingImage = dynamic_cast(m_spSelectedMovingNode->GetData()); if (movingImage && movingImage->GetDimension() - 1 == m_LoadedAlgorithm->getMovingDimensions() && movingImage->GetTimeSteps() > 1) { m_spSelectedMovingData = ExtractFirstFrame(movingImage).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected moving image has multiple time steps. First time step is used as moving image.")); } } if (m_Controls.targetNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedTargetNode = nullptr; m_spSelectedTargetData = nullptr; } else { m_spSelectedTargetNode = m_Controls.targetNodeSelector->GetSelectedNode(); m_spSelectedTargetData = m_spSelectedTargetNode->GetData(); auto targetImage = dynamic_cast(m_spSelectedTargetNode->GetData()); if (targetImage && targetImage->GetDimension() - 1 == m_LoadedAlgorithm->getTargetDimensions() && targetImage->GetTimeSteps() > 1) { m_spSelectedTargetData = ExtractFirstFrame(targetImage).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected target image has multiple time steps. First time step is used as target image.")); } } if (m_Controls.movingMaskNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedMovingMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; } else { m_spSelectedMovingMaskNode = m_Controls.movingMaskNodeSelector->GetSelectedNode(); m_spSelectedMovingMaskData = dynamic_cast(m_spSelectedMovingMaskNode->GetData()); if (m_spSelectedMovingMaskData->GetDimension() - 1 == m_LoadedAlgorithm->getMovingDimensions() && m_spSelectedMovingMaskData->GetTimeSteps() > 1) { m_spSelectedMovingMaskData = ExtractFirstFrame(m_spSelectedMovingMaskData).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected moving mask has multiple time steps. First time step is used as moving mask.")); } } if (m_Controls.targetMaskNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedTargetMaskNode = nullptr; m_spSelectedTargetMaskData = nullptr; } else { m_spSelectedTargetMaskNode = m_Controls.targetMaskNodeSelector->GetSelectedNode(); m_spSelectedTargetMaskData = dynamic_cast(m_spSelectedTargetMaskNode->GetData()); if (m_spSelectedTargetMaskData->GetDimension() - 1 == m_LoadedAlgorithm->getTargetDimensions() && m_spSelectedTargetMaskData->GetTimeSteps() > 1) { m_spSelectedTargetMaskData = ExtractFirstFrame(m_spSelectedTargetMaskData).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected target mask has multiple time steps. First time step is used as target mask.")); } } } m_ValidInputs = m_spSelectedMovingData.IsNotNull() && m_spSelectedTargetData.IsNotNull(); return m_ValidInputs; } std::string QmitkMatchPoint::GetInputNodeDisplayName(const mitk::DataNode* node) const { std::string result = "UNDEFINED/nullptr"; if (node) { result = node->GetName(); const mitk::PointSet* pointSet = dynamic_cast(node->GetData()); if (pointSet) { mitk::DataStorage::SetOfObjects::ConstPointer sources = this->GetDataStorage()->GetSources(node); if (sources.IsNotNull() && sources->Size() > 0) { result = result + " (" + sources->GetElement(0)->GetName() + ")"; } } } return result; } mitk::DataStorage::SetOfObjects::Pointer QmitkMatchPoint::GetRegNodes() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetDataStorage()->GetAll(); mitk::DataStorage::SetOfObjects::Pointer result = mitk::DataStorage::SetOfObjects::New(); for (mitk::DataStorage::SetOfObjects::const_iterator pos = nodes->begin(); pos != nodes->end(); ++pos) { if (mitk::MITKRegistrationHelper::IsRegNode(*pos)) { result->push_back(*pos); } } return result; } std::string QmitkMatchPoint::GetDefaultRegJobName() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetRegNodes().GetPointer(); mitk::DataStorage::SetOfObjects::ElementIdentifier estimatedIndex = nodes->Size(); bool isUnique = false; std::string result = "Unnamed Reg"; while (!isUnique) { ++estimatedIndex; result = "Reg #" +::map::core::convert::toStr(estimatedIndex); isUnique = this->GetDataStorage()->GetNamedNode(result) == nullptr; } return result; } void QmitkMatchPoint::ConfigureRegistrationControls() { m_Controls.m_tabSelection->setEnabled(!m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); m_Controls.groupMasks->setEnabled(!m_Working); m_Controls.m_pbStartReg->setEnabled(false); m_Controls.m_pbStopReg->setEnabled(false); m_Controls.m_pbStopReg->setVisible(false); if (m_LoadedAlgorithm.IsNotNull()) { m_Controls.m_tabSettings->setEnabled(!m_Working); m_Controls.m_tabExecution->setEnabled(true); m_Controls.m_pbStartReg->setEnabled(m_ValidInputs && !m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); m_Controls.m_checkMapEntity->setEnabled(!m_Working); m_Controls.targetNodeSelector->setEnabled(!m_Working); m_Controls.movingNodeSelector->setEnabled(!m_Working); m_Controls.targetMaskNodeSelector->setEnabled(!m_Working); m_Controls.movingMaskNodeSelector->setEnabled(!m_Working); const IStoppableAlgorithm* pIterativ = dynamic_cast (m_LoadedAlgorithm.GetPointer()); if (pIterativ) { m_Controls.m_pbStopReg->setVisible(pIterativ->isStoppable()); } typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskRegInterface; const MaskRegInterface* pMaskReg = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.groupMasks->setVisible(pMaskReg != nullptr); //if the stop button is set to visible and the algorithm is working -> //then the algorithm is stoppable, thus enable the button. m_Controls.m_pbStopReg->setEnabled(m_Controls.m_pbStopReg->isVisible() && m_Working); this->m_Controls.m_lbLoadedAlgorithmName->setText( QString::fromStdString(m_LoadedAlgorithm->getUID()->toStr())); } else { m_Controls.m_tabSettings->setEnabled(false); m_Controls.m_tabExecution->setEnabled(false); this->m_Controls.m_lbLoadedAlgorithmName->setText( QStringLiteral("no algorithm loaded!")); m_Controls.groupMasks->setVisible(false); } if (!m_Working) { this->m_Controls.m_leRegJobName->setText(QString::fromStdString(this->GetDefaultRegJobName())); } } void QmitkMatchPoint::ConfigureNodeSelectors() { auto isImage = mitk::MITKRegistrationHelper::ImageNodePredicate(); auto isPointSet = mitk::MITKRegistrationHelper::PointSetNodePredicate(); auto isMask = mitk::MITKRegistrationHelper::MaskNodePredicate(); mitk::NodePredicateBase::Pointer dimensionPredicate = mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)).GetPointer(); m_Controls.movingNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.targetNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.movingMaskNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.targetMaskNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); if (m_LoadedAlgorithm.IsNotNull()) { mitk::NodePredicateBase::ConstPointer dataPredicate; if (m_LoadedAlgorithm->getMovingDimensions() == 2) { dimensionPredicate = mitk::NodePredicateDimension::New(2); } if (mitk::MAPAlgorithmHelper::HasImageAlgorithmInterface(m_LoadedAlgorithm)) { dataPredicate = mitk::NodePredicateAnd::New(isImage, dimensionPredicate); m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving image."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving image."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving image that should be registered onto the target image."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target image."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target image."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target image that should be used as reference for the registration."); } if (mitk::MAPAlgorithmHelper::HasPointSetAlgorithmInterface(m_LoadedAlgorithm)) { if (dataPredicate.IsNull()) { dataPredicate = isPointSet; m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving point set."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving point set."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving point set that should be registered onto the target point set."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target point set."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target point set."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target point set that should be used as reference for the registration."); } else { dataPredicate = mitk::NodePredicateOr::New(dataPredicate, isPointSet); m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving data."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving data."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving data that should be registered onto the target data. The algorithm supports images as well as point sets."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target data."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target data."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target data that should be used as reference for the registration. The algorithm supports images as well as point sets."); } } mitk::NodePredicateBase::ConstPointer nodePredicate = dataPredicate; m_Controls.movingNodeSelector->SetNodePredicate(nodePredicate); m_Controls.targetNodeSelector->SetNodePredicate(nodePredicate); nodePredicate = mitk::NodePredicateAnd::New(isMask, dimensionPredicate); m_Controls.movingMaskNodeSelector->SetEmptyInfo("Select moving mask. (optional)"); m_Controls.movingMaskNodeSelector->SetPopUpTitel("Select moving mask"); m_Controls.movingMaskNodeSelector->SetPopUpHint("Select a segmentation that serves as moving mask for the registration."); m_Controls.targetMaskNodeSelector->SetEmptyInfo("Select target mask. (optional)"); m_Controls.targetMaskNodeSelector->SetPopUpTitel("Select target mask"); m_Controls.targetMaskNodeSelector->SetPopUpHint("Select a segmentation that serves as target mask for the registration."); m_Controls.movingMaskNodeSelector->SetNodePredicate(nodePredicate); m_Controls.targetMaskNodeSelector->SetNodePredicate(nodePredicate); } } void QmitkMatchPoint::ConfigureProgressInfos() { const IIterativeAlgorithm* pIterative = dynamic_cast (m_LoadedAlgorithm.GetPointer()); const IMultiResAlgorithm* pMultiRes = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.m_progBarIteration->setVisible(pIterative); m_Controls.m_lbProgBarIteration->setVisible(pIterative); if (pIterative) { QString format = "%p% (%v/%m)"; if (!pIterative->hasMaxIterationCount()) { format = "%v"; m_Controls.m_progBarIteration->setMaximum(0); } else { m_Controls.m_progBarIteration->setMaximum(pIterative->getMaxIterations()); } m_Controls.m_progBarIteration->setFormat(format); } m_Controls.m_progBarLevel->setVisible(pMultiRes); m_Controls.m_lbProgBarLevel->setVisible(pMultiRes); if (pMultiRes) { m_Controls.m_progBarLevel->setMaximum(pMultiRes->getResolutionLevels()); } else { m_Controls.m_progBarLevel->setMaximum(1); } m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); } void QmitkMatchPoint::OnNodeSelectionChanged(QList /*nodes*/) { if (!m_Working) { CheckInputs(); ConfigureRegistrationControls(); } } void QmitkMatchPoint::OnStartRegBtnPushed() { this->m_Working = true; //////////////////////////////// //configure GUI this->ConfigureProgressInfos(); m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); this->ConfigureRegistrationControls(); if (m_Controls.m_checkClearLog->checkState() == Qt::Checked) { this->m_Controls.m_teLog->clear(); } ///////////////////////// //create job and put it into the thread pool QmitkRegistrationJob* pJob = new QmitkRegistrationJob(m_LoadedAlgorithm); pJob->setAutoDelete(true); pJob->m_spTargetData = m_spSelectedTargetData; pJob->m_spMovingData = m_spSelectedMovingData; pJob->m_TargetDataUID = mitk::EnsureUID(this->m_spSelectedTargetNode->GetData()); pJob->m_MovingDataUID = mitk::EnsureUID(this->m_spSelectedMovingNode->GetData()); if (m_spSelectedTargetMaskData.IsNotNull()) { pJob->m_spTargetMask = m_spSelectedTargetMaskData; pJob->m_TargetMaskDataUID = mitk::EnsureUID(this->m_spSelectedTargetMaskNode->GetData()); } if (m_spSelectedMovingMaskData.IsNotNull()) { pJob->m_spMovingMask = m_spSelectedMovingMaskData; pJob->m_MovingMaskDataUID = mitk::EnsureUID(this->m_spSelectedMovingMaskNode->GetData()); } pJob->m_JobName = m_Controls.m_leRegJobName->text().toStdString(); pJob->m_StoreReg = true; connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnRegJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnRegJobFinished())); connect(pJob, SIGNAL(RegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer, const QmitkRegistrationJob*)), this, SLOT(OnRegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer, const QmitkRegistrationJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(AlgorithmInfo(QString)), this, SLOT(OnAlgorithmInfo(QString))); connect(pJob, SIGNAL(AlgorithmStatusChanged(QString)), this, SLOT(OnAlgorithmStatusChanged(QString))); connect(pJob, SIGNAL(AlgorithmIterated(QString, bool, unsigned long)), this, SLOT(OnAlgorithmIterated(QString, bool, unsigned long))); connect(pJob, SIGNAL(LevelChanged(QString, bool, unsigned long)), this, SLOT(OnLevelChanged(QString, bool, unsigned long))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } void QmitkMatchPoint::OnStopRegBtnPushed() { if (m_LoadedAlgorithm.IsNotNull()) { IStoppableAlgorithm* pIterativ = dynamic_cast(m_LoadedAlgorithm.GetPointer()); if (pIterativ && pIterativ->isStoppable()) { if (pIterativ->stopAlgorithm()) { } else { } m_Controls.m_pbStopReg->setEnabled(false); } else { } } } void QmitkMatchPoint::OnSaveLogBtnPushed() { QDateTime currentTime = QDateTime::currentDateTime(); QString fileName = tr("registration_log_") + currentTime.toString(tr("yyyy-MM-dd_hh-mm-ss")) + tr(".txt"); fileName = QFileDialog::getSaveFileName(nullptr, tr("Save registration log"), fileName, tr("Text files (*.txt)")); if (fileName.isEmpty()) { QMessageBox::critical(nullptr, tr("No file selected!"), tr("Cannot save registration log file. Please selected a file.")); } else { std::ofstream file; std::ios_base::openmode iOpenFlag = std::ios_base::out | std::ios_base::trunc; file.open(fileName.toStdString().c_str(), iOpenFlag); if (!file.is_open()) { mitkThrow() << "Cannot open or create specified file to save. File path: " << fileName.toStdString(); } file << this->m_Controls.m_teLog->toPlainText().toStdString() << std::endl; file.close(); } } void QmitkMatchPoint::OnRegJobError(QString err) { Error(err); } void QmitkMatchPoint::OnRegJobFinished() { this->m_Working = false; this->GetRenderWindowPart()->RequestUpdate(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); } void QmitkMatchPoint::OnRegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer spResultRegistration, const QmitkRegistrationJob* pRegJob) { mitk::DataNode::Pointer spResultRegistrationNode = mitk::generateRegistrationResultNode( pRegJob->m_JobName, spResultRegistration, pRegJob->GetLoadedAlgorithm()->getUID()->toStr(), pRegJob->m_MovingDataUID, pRegJob->m_TargetDataUID); if (pRegJob->m_StoreReg) { m_Controls.m_teLog->append( QStringLiteral(" Storing registration object in data manager ... ")); this->GetDataStorage()->Add(spResultRegistrationNode); this->GetRenderWindowPart()->RequestUpdate(); } if (m_Controls.m_checkMapEntity->checkState() == Qt::Checked) { QmitkMappingJob* pMapJob = new QmitkMappingJob(); pMapJob->setAutoDelete(true); pMapJob->m_spInputData = pRegJob->m_spMovingData; pMapJob->m_InputDataUID = pRegJob->m_MovingDataUID; pMapJob->m_spRegNode = spResultRegistrationNode; pMapJob->m_doGeometryRefinement = false; pMapJob->m_spRefGeometry = pRegJob->m_spTargetData->GetGeometry()->Clone().GetPointer(); pMapJob->m_MappedName = pRegJob->m_JobName + std::string(" mapped moving data"); pMapJob->m_allowUndefPixels = true; pMapJob->m_paddingValue = 100; pMapJob->m_allowUnregPixels = true; pMapJob->m_errorValue = 200; pMapJob->m_InterpolatorLabel = "Linear Interpolation"; pMapJob->m_InterpolatorType = mitk::ImageMappingInterpolator::Linear; connect(pMapJob, SIGNAL(Error(QString)), this, SLOT(OnMapJobError(QString))); connect(pMapJob, SIGNAL(MapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), this, SLOT(OnMapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), Qt::BlockingQueuedConnection); connect(pMapJob, SIGNAL(AlgorithmInfo(QString)), this, SLOT(OnAlgorithmInfo(QString))); m_Controls.m_teLog->append( QStringLiteral("Started mapping input data...")); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pMapJob); } } void QmitkMatchPoint::OnMapJobError(QString err) { Error(err); } void QmitkMatchPoint::OnMapResultIsAvailable(mitk::BaseData::Pointer spMappedData, const QmitkMappingJob* job) { m_Controls.m_teLog->append(QStringLiteral("Mapped entity stored. Name: ") + QString::fromStdString(job->m_MappedName) + QStringLiteral("")); mitk::DataNode::Pointer spMappedNode = mitk::generateMappedResultNode(job->m_MappedName, spMappedData, job->GetRegistration()->getRegistrationUID(), job->m_InputDataUID, job->m_doGeometryRefinement, job->m_InterpolatorLabel); this->GetDataStorage()->Add(spMappedNode); - this->GetRenderWindowPart()->RequestUpdate(); + + if (nullptr != this->GetRenderWindowPart()) + { + this->GetRenderWindowPart()->RequestUpdate(); + } } void QmitkMatchPoint::OnAlgorithmIterated(QString info, bool hasIterationCount, unsigned long currentIteration) { if (hasIterationCount) { m_Controls.m_progBarIteration->setValue(currentIteration); } m_Controls.m_teLog->append(info); } void QmitkMatchPoint::OnLevelChanged(QString info, bool hasLevelCount, unsigned long currentLevel) { if (hasLevelCount) { m_Controls.m_progBarLevel->setValue(currentLevel); } m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral("")); } void QmitkMatchPoint::OnAlgorithmStatusChanged(QString info) { m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral(" ")); } void QmitkMatchPoint::OnAlgorithmInfo(QString info) { m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral("")); } void QmitkMatchPoint::OnAlgorithmSelectionChanged(const berry::IWorkbenchPart::Pointer& sourcepart, const berry::ISelection::ConstPointer& selection) { // check for null selection if (selection.IsNull()) { return; } if (sourcepart != this) { UpdateAlgorithmSelection(selection); } } void QmitkMatchPoint::UpdateAlgorithmSelection(berry::ISelection::ConstPointer selection) { mitk::MAPAlgorithmInfoSelection::ConstPointer currentSelection = selection.Cast(); if (currentSelection) { mitk::MAPAlgorithmInfoSelection::AlgorithmInfoVectorType infoVector = currentSelection->GetSelectedAlgorithmInfo(); if (!infoVector.empty()) { // only the first selection is of interest, the rest will be skipped. this->m_SelectedAlgorithmInfo = infoVector[0]; } } this->OnSelectedAlgorithmChanged(); } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp index 2d38d0f4f1..5da7f6c349 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp @@ -1,306 +1,309 @@ /*============================================================================ 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. ============================================================================*/ // Blueberry #include #include // Mitk #include #include #include #include "mitkRegVisPropertyTags.h" #include "mitkMatchPointPropertyTags.h" #include "mitkRegEvaluationObject.h" #include "mitkRegistrationHelper.h" #include "mitkRegEvaluationMapper2D.h" #include // Qmitk #include "QmitkRenderWindow.h" #include "QmitkMatchPointRegistrationEvaluator.h" // Qt #include #include #include const std::string QmitkMatchPointRegistrationEvaluator::VIEW_ID = "org.mitk.views.matchpoint.registration.evaluator"; const std::string QmitkMatchPointRegistrationEvaluator::HelperNodeName = "RegistrationEvaluationHelper"; QmitkMatchPointRegistrationEvaluator::QmitkMatchPointRegistrationEvaluator() : m_Parent(nullptr), m_activeEvaluation(false), m_currentSelectedTimePoint(0.) { m_currentSelectedPosition.Fill(0.0); } QmitkMatchPointRegistrationEvaluator::~QmitkMatchPointRegistrationEvaluator() { if (this->m_selectedEvalNode.IsNotNull() && this->GetDataStorage().IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } } void QmitkMatchPointRegistrationEvaluator::SetFocus() { } void QmitkMatchPointRegistrationEvaluator::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); } void QmitkMatchPointRegistrationEvaluator::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; this->m_Controls.registrationNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.registrationNodeSelector->SetSelectionIsOptional(true); this->m_Controls.movingNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.movingNodeSelector->SetSelectionIsOptional(false); this->m_Controls.targetNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.targetNodeSelector->SetSelectionIsOptional(false); this->m_Controls.registrationNodeSelector->SetInvalidInfo("Select valid registration."); this->m_Controls.registrationNodeSelector->SetEmptyInfo("Assuming identity. Select registration to change."); this->m_Controls.registrationNodeSelector->SetPopUpTitel("Select registration."); this->m_Controls.registrationNodeSelector->SetPopUpHint("Select a registration object that should be evaluated. If no registration is selected, identity will be assumed for evaluation."); this->m_Controls.movingNodeSelector->SetInvalidInfo("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpTitel("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpHint("Select the moving image for the evaluation. This is the image that will be mapped by the registration."); this->m_Controls.targetNodeSelector->SetInvalidInfo("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpTitel("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpHint("Select the target image for the evaluation."); this->m_Controls.checkAutoSelect->setChecked(true); this->ConfigureNodePredicates(); connect(m_Controls.pbEval, SIGNAL(clicked()), this, SLOT(OnEvalBtnPushed())); connect(m_Controls.pbStop, SIGNAL(clicked()), this, SLOT(OnStopBtnPushed())); connect(m_Controls.evalSettings, SIGNAL(SettingsChanged(mitk::DataNode*)), this, SLOT(OnSettingsChanged(mitk::DataNode*))); connect(m_Controls.registrationNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); connect(m_Controls.movingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); connect(m_Controls.targetNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); m_selectedEvalNode = this->GetDataStorage()->GetNamedNode(HelperNodeName); this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkMatchPointRegistrationEvaluator::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkMatchPointRegistrationEvaluator::ConfigureNodePredicates() { this->m_Controls.registrationNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::RegNodePredicate()); this->m_Controls.movingNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); this->m_Controls.targetNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); } void QmitkMatchPointRegistrationEvaluator::CheckInputs() { if (!m_activeEvaluation) { bool autoSelectInput = m_Controls.checkAutoSelect->isChecked() && this->m_spSelectedRegNode != this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_spSelectedRegNode = this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_spSelectedMovingNode = this->m_Controls.movingNodeSelector->GetSelectedNode(); this->m_spSelectedTargetNode = this->m_Controls.targetNodeSelector->GetSelectedNode(); if (this->m_spSelectedRegNode.IsNotNull() && (this->m_spSelectedMovingNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_spSelectedRegNode->GetData()->GetProperty(mitk::Prop_RegAlgMovingData); if (uidProp) { //search for the moving node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer movingNode = this->GetDataStorage()->GetNode(predicate); if (movingNode.IsNotNull()) { this->m_spSelectedMovingNode = movingNode; QmitkSingleNodeSelectionWidget::NodeList selection({ movingNode }); this->m_Controls.movingNodeSelector->SetCurrentSelection(selection); } } } if (this->m_spSelectedRegNode.IsNotNull() && (this->m_spSelectedTargetNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_spSelectedRegNode->GetData()->GetProperty(mitk::Prop_RegAlgTargetData); if (uidProp) { //search for the target node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer targetNode = this->GetDataStorage()->GetNode(predicate); if (targetNode.IsNotNull()) { this->m_spSelectedTargetNode = targetNode; QmitkSingleNodeSelectionWidget::NodeList selection({ targetNode }); this->m_Controls.targetNodeSelector->SetCurrentSelection(selection); } } } } } void QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged(QList /*nodes*/) { this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::NodeRemoved(const mitk::DataNode* node) { if (node == this->m_spSelectedMovingNode || node == this->m_spSelectedRegNode || node == this->m_spSelectedTargetNode || node == this->m_selectedEvalNode) { if (node == this->m_selectedEvalNode) { this->m_selectedEvalNode = nullptr; } this->OnStopBtnPushed(); MITK_INFO << "Stopped current MatchPoint evaluation session, because at least one relevant node was removed from storage."; } } void QmitkMatchPointRegistrationEvaluator::ConfigureControls() { //config settings widget this->m_Controls.evalSettings->setVisible(m_activeEvaluation); this->m_Controls.pbEval->setEnabled(this->m_spSelectedMovingNode.IsNotNull() && this->m_spSelectedTargetNode.IsNotNull()); this->m_Controls.pbEval->setVisible(!m_activeEvaluation); this->m_Controls.pbStop->setVisible(m_activeEvaluation); this->m_Controls.registrationNodeSelector->setEnabled(!m_activeEvaluation); this->m_Controls.movingNodeSelector->setEnabled(!m_activeEvaluation); this->m_Controls.targetNodeSelector->setEnabled(!m_activeEvaluation); } void QmitkMatchPointRegistrationEvaluator::OnSliceChanged() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); auto currentTimePoint = GetRenderWindowPart()->GetSelectedTimePoint(); if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimePoint != currentTimePoint || m_selectedNodeTime > m_currentPositionTime) { //the current position has been changed or the selected node has been changed since the last position validation -> check position m_currentSelectedPosition = currentSelectedPosition; m_currentSelectedTimePoint = currentTimePoint; m_currentPositionTime.Modified(); if (this->m_selectedEvalNode.IsNotNull()) { this->m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalCurrentPosition, mitk::GenericProperty::New(currentSelectedPosition)); } } } void QmitkMatchPointRegistrationEvaluator::OnSettingsChanged(mitk::DataNode*) { this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationEvaluator::OnEvalBtnPushed() { //reinit view mitk::RenderingManager::GetInstance()->InitializeViews(m_spSelectedTargetNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); mitk::RegEvaluationObject::Pointer regEval = mitk::RegEvaluationObject::New(); mitk::MAPRegistrationWrapper::Pointer reg; if (m_spSelectedRegNode.IsNotNull()) { reg = dynamic_cast(this->m_spSelectedRegNode->GetData()); } else { //generate a dymme reg to use reg = mitk::GenerateIdentityRegistration3D(); } regEval->SetRegistration(reg); regEval->SetTargetNode(this->m_spSelectedTargetNode); regEval->SetMovingNode(this->m_spSelectedMovingNode); if (this->m_selectedEvalNode.IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } this->m_selectedEvalNode = mitk::DataNode::New(); this->m_selectedEvalNode->SetData(regEval); mitk::RegEvaluationMapper2D::SetDefaultProperties(this->m_selectedEvalNode); this->m_selectedEvalNode->SetName(HelperNodeName); this->m_selectedEvalNode->SetBoolProperty("helper object", true); this->GetDataStorage()->Add(this->m_selectedEvalNode); this->m_Controls.evalSettings->SetNode(this->m_selectedEvalNode); this->OnSliceChanged(); this->GetRenderWindowPart()->RequestUpdate(); this->m_activeEvaluation = true; this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::OnStopBtnPushed() { this->m_activeEvaluation = false; if (this->m_selectedEvalNode.IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } this->m_selectedEvalNode = nullptr; this->m_Controls.evalSettings->SetNode(this->m_selectedEvalNode); this->CheckInputs(); this->ConfigureControls(); - this->GetRenderWindowPart()->RequestUpdate(); + if (nullptr != this->GetRenderWindowPart()) + { + this->GetRenderWindowPart()->RequestUpdate(); + } } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/Manual.dox index 76b20f6419..c3f3652037 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/Manual.dox @@ -1,85 +1,79 @@ /** -\page org_mitk_gui_qt_matchpoint_mapper The MatchPoint Image Mapper View +\page org_mitk_views_matchpoint_mapper The MatchPoint Image Mapper View \imageMacro{map_mapper_icon_doc.svg, "Icon of the MatchPoint Image Mapper",3} \tableofcontents \section MAP_MAPPER_Introduction Introduction -This view offers the possibility to map any image or point set in the data manager using a user selected registration object. -Using the Mapper to map images the user can control the field of view (image geometry) the image should be mapped into, as well as -interpolation strategy that should be used.\n -It is one of several MatchPoint registration plugins.\n -Typical usage scenarios\n -\li You have registered image I1 onto image I2. Now you want to transfer the segmentation of I1 to I2 in order to evaluate I2 within this mapped segmentation using \ref org_mitk_views_imagestatistics . -\li You have registered image I1 onto image I2. Now you want to map I3 (e.g. an other MRI sequence of the same session) also onto I2 with the same registration. -\li You have registered image I1 onto image I2. Now you want to map a segmentation done on I1 also onto I2 with the same registration. -\li You have registered image I1 onto image I2. Now you want to map a point set of image I1 also onto I2 with the same registration. +This view offers the possibility to map any loaded image or point set using a user selected registration object. +When mapping images the user can control the field of view (image geometry) the image should be mapped into, as well as the interpolation strategy and padding values that should be used. + +It is one of several MatchPoint registration plugins. -\section MAP_MAPPER_Contact Contact information -This plug-in is being developed by the SIDT group (Software development for Integrated Diagnostics -and Therapy) at the German Cancer Research Center (DKFZ). If you have any questions, need support, -find a bug or have a feature request, feel free to contact us at www.mitk.org. +Typical usage scenarios\n +You have registered image I1 onto image I2. Now you want to +\li (Most obvious) map I1 onto I2 with the registration, e.g. to make a joint statistical analysis. +\li map image I3 (e.g. an other MRI sequence of the same session) also onto I2 with the same registration. +\li map a segmentation created on I1 also onto I2 with the same registration. +\li map a point set of image I1 also onto I2 with the same registration. \section MAP_MAPPER_Usage Usage -\imageMacro{map_mapper-examplescreen.png, "Example screenshot showing the Mapper plugin in use.", 14} -To use the mapper at least an input data (image or point set) must be selected. Additionally you may select a registration object and a reference image. -Registration objects are marked with a small blue icon (e.g. the data "Registration" in the data manager of the screen shot above). -The Reference image defines the geometry (field of view, orientation, spacing) that should be used for the result image. -By default the view will try to automatically determine the reference image (by default it is the target image of the selected registration). -If auto selection cannot determine the reference it will choose the input image as reference. -The reference image can be also defined by the user explicitly by activating manual selection.\n -REMARK: If you map point sets you can ignore the reference image slot. It has no affect.\n -You can multi select registration and data (press the CTRL-key while selecting the nodes in the data manager). -The Mapper will automatically sort the selections in the correct "slots" of the view.\n -REMARK: The mapping results will be added as child nodes to the used input node.\n +To use the mapper at least the input (image or point set) must be selected. Additionally, you may select a registration object and, in case the input is an image, an optional reference image. + +The reference image defines the geometry (field of view, orientation, spacing) that should be used for the result image. +The view will try to automatically determine the reference image. By default it is the target image that was used to determine the selected registration. +If auto selection cannot determine the reference (e.g. because it was not specified or it is currently not loaded), the input image will be selected as reference. +The reference image can be also defined by the user explicitly by activating manual selection. + +REMARK: If you map point sets you can ignore the reference image slot. It has no effect. + +REMARK: The mapping results will be added as child nodes to the used input node. + REMARK: If you do not select an registration the view will assume that you make an identity transform. This is a convenient way if you just want to resample an image into the geometry of an other image (when no registration is needed). Also in this use case you can take advantage of the different interpolation and sub/super sampling strategies. -\imageMacro{map_mapper.png, "Details of the mapper view.", 8} -(1) The currently selected registration, that will be used for mapping.\n -(2) The currently selected input data, that will be mapped.\n -(3) The currently (automatically or by user) selected reference image, that defines the geometry of the result.\n -(4) The name of the result data in the data manger.\n -(5) The start button(s) to commence the mapping process. For details regarding the two options see \ref MAP_MAPPER_Refine.\n -(6) Log windows with messages regarding the mapping process.\n\n +\imageMacro{map_mapper_instructions.png, "Main elements of the mapper view.", 7} +(1) The currently selected registration that will be used for mapping. Click to change.\n +(2) Reset button that will remove the current selected registration and switch back to an identity transform.\n +(3) The currently selected input data, that will be mapped. Click to change.\n +(4) The currently (automatically or by user) selected reference image, that defines the geometry of the result. Click to change.\n +(5) The name of the result data in the data manager.\n +(6) The start button(s) to commence the mapping process. For details regarding the two options see \ref MAP_MAPPER_Refine.\n +(7) Log windows with messages regarding the mapping process.\n -Every "slot" has the ability to be locked. If locked the last selection will be kept, regardless the current selection in the data manager. -You can use this for example to lock the registration, if you want to map multiple images. Doing so it is enough to just select the next image -in the data manager. To lock a slot, click at the "lock" button at the right side (see example images below). -\imageMacro{map_node-unlocked.png, "Unlocked slot/node (default state). Changes with the selections in the data manager.",6} -\imageMacro{map_node-locked.png, "Locked slot/node. Stays, regardless the selections in the data manager.",6} \section MAP_MAPPER_Refine Mapping or geometry refinement The mapper view offers two options to map images:\n \li "Map" (default) \li "Refine geometry" -For images "Map" fills the pixels of the output image by interpolating input image pixels using the registration object. This option always works. -But may take longer and introduces interpolation errors, because a new image is resampled.\n -The second option "Refine geometry" is only offered, if the registration (more precise its inverse kernel) is matrix based and the selected data is an image. +For images "Map" fills the pixels of the output image by interpolating input image pixels using the registration object. This option always works, +but may take longer and introduces interpolation errors, because a new image is resampled.\n +The second option "Refine geometry" is only offered, if the registration (more precisely its inverse kernel) is matrix based and the selected data is an image. In this case it just clones the image and refines its image geometry (origin and orientation) to project it to the position indicated by the registration; thus no interpolation artefacts are introduced. -\remark If you want to use a mapped image in conjunction with the statistic plugin and an mask of the reference image (or you want to proceed any other computation that expects the voxel to be in the same grid for direct numeric comparison), you must use "Map" to ensure the same geometry -(including the same image grid; including same spacing and resolution). Otherwise operations like the statistic plugin will fail. +\remark If you want to use a mapped image in conjunction with the image statistics plugin and a mask of the reference image (or you want to proceed any other computation that expects the voxel to be in the same grid for direct numeric comparison), you must use "Map" to ensure the same geometry +(including the same image grid; including same spacing and resolution). Otherwise operations like the images statistics plugin will fail. \section MAP_MAPPER_Settings Settings +\imageMacro{map_mapper-settings.png, "Available settings for mapping images.", 7} If you map the image (and not just refine the geometry), you have several settings available:\n -\li "Allow undefined pixels": Activate to handle pixels of the result image that are not in the field of view of the input image. This pixel will get the "padding value". -\li "Allow error pixels": Activate to handle pixels of the result image that can not be mapped because the registration does not support this part of the output image. This pixel will get the "error value". +\li "Allow undefined pixels": Activate to handle pixels of the result image that are not in the field of view of the input image. These pixels will get the "padding value". +\li "Allow unregistered pixels": Activate to handle pixels of the result image that can not be mapped because the registration does not support this part of the output image. These pixels will get the "error value". \li "Interpolator": Set to choose the interpolation strategy that should be used for mapping. \li "Activate super/sub sampling": Activate if you want to use origin and orientation of the reference image but want to alter the spacing. \section MAP_MAPPER_Interpolation Interpolation You can choose from the following interpolation strategies:\n -\li "nearest neighbor": Use the value of the nearest pixel. Fastest, but high interpolation errors for gray value images. Right choice for label images or masks. +\li "Nearest Neighbor": Use the value of the nearest pixel. Fastest, but high interpolation errors for gray value images. Right choice for label images or masks. \li "Linear": Fast linear interpolation with often sufficient quality. Tends to blur edges. \li "BSpline (3rd order)": Good trade off between time and quality. \li "Windowed Sinc (Hamming)": Good interpolation quality but very time consuming. \li "Windowed Sinc (Welch)": Good interpolation quality but very time consuming. \section MAP_MAPPER_Masks Handling of masks/segmentations If you select an mask as input image, the plugin will be automatically reconfigured to settings that are suitable for the task of mapping masks. -Most importantly the interpolator will be set to "nearest neighbor". +Most importantly the interpolator will be set to "Nearest Neighbor". */ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-examplescreen.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-examplescreen.png deleted file mode 100644 index 438d48fc90..0000000000 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-examplescreen.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-settings.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-settings.png index fb7ba5f47b..5e1e2d5d6f 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-settings.png and b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper-settings.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper.png index 2ddec78f8f..bbb2054937 100644 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper.png and b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructions.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructions.png new file mode 100644 index 0000000000..9c83eb21f1 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructions.png differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructionsassets.svg b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructionsassets.svg new file mode 100644 index 0000000000..10fa218739 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_mapper_instructionsassets.svg @@ -0,0 +1,365 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (1) Registration slot + (6) Start buttons + (2) Registration reset + (3) Input slot + (4) Reference slot + (5) Result name + (7) Log + diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-locked.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-locked.png deleted file mode 100644 index abc66c27c4..0000000000 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-locked.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-unlocked.png b/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-unlocked.png deleted file mode 100644 index 4ec502eec3..0000000000 Binary files a/Plugins/org.mitk.gui.qt.matchpoint.mapper/documentation/UserManual/map_node-unlocked.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.matchpoint.mapper/src/internal/QmitkMatchPointMapper.ui b/Plugins/org.mitk.gui.qt.matchpoint.mapper/src/internal/QmitkMatchPointMapper.ui index fe48fae142..a1da741298 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.mapper/src/internal/QmitkMatchPointMapper.ui +++ b/Plugins/org.mitk.gui.qt.matchpoint.mapper/src/internal/QmitkMatchPointMapper.ui @@ -1,565 +1,565 @@ MatchPointMapperControls 0 0 392 816 5 5 5 5 5 Selected registration: 3 0 40 Input data (images or point sets): 3 0 40 Select reference manually instat of determined by registration... Select reference image manually: 3 0 40 true 0 false false false Execution 5 5 5 5 5 Mapped data name: Name of the resulting mapped image mappedImage <html><head/><body><p>Starts the mapping of the input image with the current settings.</p></body></html> Map false <html><head/><body><p>Applys the registration by refining the geometry of the data and not by resampling it. This option can only be selected if the chosen registration is 3D and can be decomposed in a rotation matrix and offset.</p></body></html> Refine geometry Log: 9 true QTextEdit::NoWrap true Clear log on mapping start Settings 5 5 5 5 5 0 0 50 false <html><head/><body><p>Allows that pixels may not be defined in the mapped image because they are outside of the field of view of the used input image.</p><p>The pixels will be marked with the given padding value.</p><p>If unchecked the mapping will be aborted in a case of undefined pixels.</p></body></html> Allow undefined pixels false true 5 9 5 9 5 Padding value: 0 0 Pixel value that indicates pixels that are outside of the input image -5000 5000 0 0 50 false <html><head/><body><p>Allows that pixels may not be registred because they are outside of the field of view of the used registration. The location in the correlated input image pixel(s) are therefore unkown. The pixels will be marked witrh the given error value.</p><p>If unchecked the mapping will be aborted in a case of unregistered pixels.</p></body></html> - Allow unregistred pixels + Allow unregistered pixels false true 5 5 5 Error value: 0 0 <html><head/><body><p>Value of pixels that cannot be registered because of an unsufficient field of view of the selected registration instance.</p></body></html> -5000 5000 0 0 Interpolator: true <html><head/><body><p>Interpolation function that should be used to map the pixel values from the input image into the result image.</p></body></html> 1 Nearest Neighbor Linear BSpline (3rd order) Windowed Sinc (Hamming) Windowed Sinc (Welch) 0 0 Activate super/sub sampling true false 5 <html><head/><body><p>Check to ensure that x, y and z dimension use the same sampling factor.</p></body></html> linked factors true 0 0 x: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>Indicate the sampling factor to change the resolution.</p><p>2.0: doubled resolution; e.g. 100 pixels -&gt; 200 pixels and spacing 1 -&gt; spacing 0.5</p><p>0.5: half resolution; e.g. 100 pixels -&gt; 50 pixels and spacing 1 -&gt; spacing 2</p></body></html> y: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter z: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical QSizePolicy::Preferred 20 700 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
5 5 true true true
diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox index e7ea6bd729..b71b4bc844 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox @@ -1,126 +1,90 @@ /** \page org_mitk_views_measurement The Measurement View \imageMacro{measurement.svg,"Icon of the Measurement View",2.00} -\section QmitkMeasurementUserManualOverview Overview +

Overview

-The Measurement view enables the user to interact with 2D images or single slices of 3D image stacks and planar figure data types. It allows to measure distances, angels, pathes and several geometric figures on a dataset. +The Measurement view allows you to measure distances, angels, paths and several geometric figures on 2D images or image slices of 3D and 4D images. The measurement view is repeatedly useable with the same or different measurement figures, which are correlated to the chosen image and can be saved together with it for future use. -\tableofcontents -The workflow to use this view is: +\imageMacro{QmitkMeasurementToolbox_BasicScreenEdited.jpg,"Image with measurements",16.00} -\imageMacro{QmitkMeasurementToolbox_Workflow.png,"",16.00} -The workflow is repeatedly useable with the same or different measurement figures, which are correlated to the choosen image and can be saved together with it for future use. On pressing the Measurement icon (see picture below the page title) in the view button line the basic appearance of the view is as follws. +

Usage

-\imageMacro{QmitkMeasurementToolbox_BasicScreenEdited.jpg,"",16.00} +The first step to perform measurements is to select a reference image on top of the view. All resulting measurements will be assigned as child nodes of the image in the data manager. The standard working plane is 'Axial' but the other standard view planes ('Saggital' and 'Coronal') are also valid for measurements. -The standard working plane is "Axial" but the other standard viewplanes ("Saggital" and "Coronal") are also valid for measurements. To swap between the view planes refer to the application user manual. -\section QmitkMeasurementUserManualFeatures Features +\imageMacro{QmitkMeasurementToolbox_MeasurementView.jpg,"Measurement View",7.60} -The view as it is depicted below offers the following features in the order of apperance on the image from top to bottom: +After selecting an image, you can click on any of the geometrical symbols. The respective measurement will appear as soon as you click on the image location you want to measure. -\imageMacro{QmitkMeasurementToolbox_MeasurementView.jpg,"",7.60} +The view offers a variety of measurement options that are introduced in the following: -The first information is the selected image's name (here: DICOM-MRI-Image) followed by the measurement figures button line with the seven measurement figures. From left to right the buttons are connected with the following functions: +
    +
  • Line: -\subsection SubOne Draw Line Draws a line between two set points and returns the distance between these points. -\subsection SubTwo Draw Path -Draws a path between several set points (two and more) and calculates the circumference, that is all line's length summed up. Add the final point by double left click. +
  • Path: -\subsection SubThree Draw Angle -Draws two lines from three set points connected in the second set point and returns the inner angle at the second point. +Draws a line between several set points (two and more) and calculates the overall length, which is all line's length summed up. Add the final point by double left-click. -\subsection SubFour Draw Four Point Angle -Draws two lines that may but must not intersect from four set points. The returned angle is the one depicted in the icon. +
  • Angle: -\subsection SubFive Draw Circle -Draws a circle by setting two points, whereas the first set point is the center and the second the radius of the circle. The measured values are the radius and the included area. +Draws two lines from three set points connected in the second set point and return the inner angle at the second point. -\subsection SubSix Draw Rectangle -Draws a rectangle by setting two points at the opposing edges of the rectangle starting with the upper left edge. The measured values are the circumference and the included area. +
  • Four Point / Double Angle: -\subsection SubSeven Draw Polygon -Draws a polygon by setting three or more points. The measured values are the circumference and the included area. Add the final point by double left click. +Draws two lines that may but must not intersect from four set points. The returned angle is the one depicted in the icon. -Below the buttonline the statistics window is situated, it displays the results of the actual measurements from the selected measurement figures. The content of the statistics window can be copied to the clipboard with the correspondig button for further use in a table calculation programm (e.g. Open Office Calc etc.). +
  • Circle: -\imageMacro{QmitkMeasurementToolbox_ImageProcessed.jpg,"",7.56} +Draws a circle by setting two points, whereas the first set point is the center and the second the radius of the circle. The measured values are the radius and the included area. -The last row contains again a button line to swap from the measurement perspective (activated in the image) to other supported MITK perspectives. +
  • Ellipse: -\section QmitkMeasurementUserManualUsage Usage +Draws an ellipse that can be modified by three points. The middle point can be used to move the whole measurement. The lower point modifies the ellipse axes. The right point can be used to modify the radius of the ellipse. The measured values are the major and minor axes and the area. -This Section is subdivided into four subsections: -
      -
    1. Add an image -
    2. Work with measurement figures -
    3. Save the image with measurement information -
    4. Remove measurement figures or image -
    +
  • Double Ellipse: -Let's start with subsection 1 -\subsection One Add an image +Draws two ellipses by adjusting four points. The middle point can be used to move the whole measurement. The left point is used to adjust the distance between the two ellipses. The lower point modifies the ellipse axes. The right point can be used to modify the radius of the ellipse. Can be used for measuring e.g. the wall thickness of a vessel. The measured values are the major and minor axes and the thickness. -There are two possible ways to add an image to the programm. One is to grap the image with left mouse click from your prefered file browser and simply drag&drop it to the View Plane field. The other way is to use the -\imageMacro{QmitkMeasurementToolbox_OpenButton.png,"",2.01} -button in the upper left corner of the application. A dialog window appears showing the file tree of the computer. Navigate to the wanted file and select it with the left mouse click. Afterwards just use the dialog's open button. +
  • Rectangle: -The wanted image appears in the View Plane and in the Data Manager the images name appears as a new tree node. Now the image is loaded it can be adjusted in the usual way ( zoom in/out: right mouse button + moving the mouse up and down, moving the image: press mouse wheel and move the mouse to the wished direction, scroll through the slices( only on 3D images): scroll mouse wheel up and down). +Draws a rectangle by setting two points at the opposing edges of the rectangle starting with the upper left edge. The measured values are the circumference and the included area. -\imageMacro{QmitkMeasurementToolbox_ImageLoadedScreen.jpg,"",16.00} +
  • Polygon: -After the image is loaded the image's name appears in the Data Manager. By left-clicking on the image name the buttonline becomes activated. +Draws a closed polygon that can have an arbitrary number of points. New points are added by left mouse-click. To finish the drawing the user can double click on the last control point. The measured values are circumference and area. -\subsection Two Work with measurement figures -The measurement view comes with seven measurement figures(see picture below), that can be applied to the images. +
  • Bezier curve: -\imageMacro{QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg,"",7.22} +Draws a bezier curve by adding some control points with left mouse-click. To finish the drawing the user can double-click on the last control point. The measured value is the length of the bezier curve. -The results of the measurement with each of these figures is shown in the statistics window and in the lower right corner of the view plane. +
  • Subdivision Polygon: -\imageMacro{QmitkMeasurementToolbox_ImageProcessedScreen.jpg,"",6.96} +Draws a closed subdivision polygon by adding some control points with left mouse-click. To finish the drawing the user can double-click on the last control point. The measured value is the circumference of the polygon. +
-When applying more then one measurement figure to the image the actual measurement figure is depicted in red and the displayed values belong to this measurement figure. All measurement figures become part of the Data Manager as a node of the image tree. +

Fixed sizes of measurement figures

+The measurement view offers a fixed size for circle and double ellipses to preset a radius and a thickness. This is useful e.g. for disagnostic studies where you want to derive gray value statistics from a well defined region. -\subsection Three Save the image with measurement information -After applying the wanted measurement figures the entire scene consisting of the image and the measurement figures can be saved for future use. Therefore just click the right mouse button when over the image item in the Data Manager and choose the item "Save" in the opening item list. Following to that a save dialog appears where the path to the save folder can be set. Afterwards just accept your choice with the save button. +

Modify measurements

-\subsection Four Remove measurement figures or image -If the single measurement figures or the image is not needed any longer, it can be removed solely or as an entire group. The image can't be removed without simultaneously removing all the dependent measurement figures that belong to the image tree in the Data Manager. To remove just select the wanted items in the data manager list by left-click on it or if several items wanted to be removed left click on all wanted by simultaneously holding the ctrl-button pressed. +All measurements can be modified later on by moving the respective control points. Note that they can only be modified if the view is open. -For more detailed usage of the save/remove functionality refer to the Data Manager User Manual. +

Multiple measurement figures

-",16.00} +

Save the measurement information

+The entire scene containing the image and the measurement figures can be saved for future use. Scenes are saved with a '.mitk' extension by pressing 'Save Project' and contain all nodes and relevant information. Alternatively, you can just save the measurement solely (with file extension '.pf') by right-click on the node in the data manager. -*/ +The content of the measurement widget can be copied to the clipboard with the corresponding button for further use in a table calculation program (e.g. Open Office Calc etc.). +

Remove measurement figures or image

+If the single measurement figures or the image is not needed any longer, it can be removed solely or as an entire group. The image can't be removed without simultaneously removing all the dependent measurement figures that belong to the image tree in the data manager. To remove just select the wanted items in the data manager list by left-clicking on the respective node or, if several items wanted to be removed, left-click on all wanted by simultaneously holding the ctrl-button pressed. +*/ \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox index 811c479e3e..c3f1ed2e58 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox @@ -1,12 +1,12 @@ /** \page org_mitk_gui_qt_measurementtoolbox The Measurement Toolbox -\section QmitkmeasurementToolbox Manual +

QmitkmeasurementToolbox Manual

This plugin contains all views that provide measurement and statistics functionality.
  • \subpage org_mitk_views_measurement
  • \subpage org_mitk_views_imagestatistics
*/ \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg index 13446ab25f..708aa039d1 100644 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg and b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg deleted file mode 100644 index 71abc0236e..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg deleted file mode 100644 index 88514a9d30..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg deleted file mode 100644 index 01c5de3526..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg deleted file mode 100644 index 02dc8951f8..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png deleted file mode 100644 index d80789bf41..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg index 12b94152bd..db04c46943 100644 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg and b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png deleted file mode 100644 index 1ec53342b1..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png deleted file mode 100644 index 32fbfc5cf2..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index f471a8a318..1007251dc0 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,871 +1,874 @@ /*============================================================================ 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 "QmitkMeasurementView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctkDoubleSpinBox.h" #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), m_EndInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SingleNodeSelectionWidget(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), m_Layout(nullptr), m_Radius(nullptr), m_Thickness(nullptr), m_FixedParameterBox(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; QmitkSingleNodeSelectionWidget* m_SingleNodeSelectionWidget; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; ctkDoubleSpinBox* m_Radius; ctkDoubleSpinBox* m_Thickness; QGroupBox* m_FixedParameterBox; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; d->m_SingleNodeSelectionWidget = new QmitkSingleNodeSelectionWidget(); d->m_SingleNodeSelectionWidget->SetDataStorage(GetDataStorage()); d->m_SingleNodeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); d->m_SingleNodeSelectionWidget->SetSelectionIsOptional(true); d->m_SingleNodeSelectionWidget->SetEmptyInfo(QStringLiteral("Please select a reference image")); d->m_SingleNodeSelectionWidget->SetPopUpTitel(QStringLiteral("Select a reference image")); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; d->m_DrawActionsToolBar->setEnabled(false); // fixed parameter section auto fixedLayout = new QGridLayout(); d->m_FixedParameterBox = new QGroupBox(); d->m_FixedParameterBox->setCheckable(true); d->m_FixedParameterBox->setChecked(false); d->m_FixedParameterBox->setTitle("Fixed sized circle/double ellipse"); d->m_FixedParameterBox->setToolTip("If activated, circles and double ellipses (as rings) figures will always be created with the set parameters as fixed size."); d->m_FixedParameterBox->setAlignment(Qt::AlignLeft); auto labelRadius1 = new QLabel(QString("Radius")); d->m_Radius = new ctkDoubleSpinBox(); d->m_Radius->setMinimum(0); d->m_Radius->setValue(10); d->m_Radius->setSuffix(" mm"); d->m_Radius->setAlignment(Qt::AlignLeft); d->m_Radius->setToolTip("Sets the radius for following planar figures: circle, double ellipse (as ring)."); auto labelThickness = new QLabel(QString("Thickness")); d->m_Thickness = new ctkDoubleSpinBox(); d->m_Thickness->setMinimum(0); d->m_Thickness->setMaximum(10); d->m_Thickness->setValue(5); d->m_Thickness->setSuffix(" mm"); d->m_Thickness->setAlignment(Qt::AlignLeft); d->m_Thickness->setToolTip("Sets the thickness for following planar figures: double ellipse (as ring)."); fixedLayout->addWidget(labelRadius1,0,0); fixedLayout->addWidget(d->m_Radius,0,1); fixedLayout->addWidget(labelThickness,1,0); fixedLayout->addWidget(d->m_Thickness,1,1); d->m_FixedParameterBox->setLayout(fixedLayout); // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 0, 1, 2); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); + + // placed after CreateConnections to trigger update of the current selection + d->m_SingleNodeSelectionWidget->SetAutoSelectNewNodes(true); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); } } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); // add observer for event when interaction with figure starts auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { auto it = std::find(d->m_CurrentSelection.begin(), d->m_CurrentSelection.end(), node); if (it != d->m_CurrentSelection.end()) { this->UpdateMeasurementText(); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { mitk::CoreServicePointer propertyFilters(mitk::CoreServices::GetPropertyFilters()); mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png index 567bee6f1d..1c625dfb42 100644 Binary files a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox index 71a6a17ed4..10230cc3cb 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox +++ b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox @@ -1,19 +1,18 @@ /** \page org_mitk_views_screenshotmaker The Screenshot Maker This view provides the functionality to create and save screenshots of the data. -Available sections: - - \ref QmitkScreenshotMakerUserManualUse - \imageMacro{QmitkMovieMaker_ScreenshotMakerInterface.png,"The Screenshot Maker User Interface",7.09} \section QmitkScreenshotMakerUserManualUse Usage -The first section offers the option of creating a screenshot of the last activated render window (thus the one, which was last clicked into). Upon clicking the button, the Screenshot Maker asks for a filename in which the screenshot is to be stored. The multiplanar Screenshot button asks for a folder, where screenshots of the three 2D views will be stored with default names. +The first section offers the option to create a screenshot of selected view plane (axial, sagittal or coronal). Upon clicking the 'Single' button in the '2D Screenshot' section, the Screenshot Maker asks for a filename in which the screenshot is to be stored. The 'Multiplanar' button asks for a folder, in which screenshots of the three 2D views will be stored with default names. + +If you tick the 'All Components' checkbox, you can take a screenshot of every channel of your multi-channel image. Otherwise, only the visible channel is captured. -The high resolution screenshot section works the same as the simple screenshot section, aside from the fact, that the user can choose a magnification factor. +The '3D Screenshots (High-res)' section works the same as the simple screenshot section, aside from the fact, that the user can choose a magnification ('Upsampling') factor. The 'Single' button creates a screenshot of the current 3D render window and the 'Multiplanar' button creates three screenshots from different perspectives. -In the option section one can choose the background color for the screenshots, default is black. +In the 'Options' section, one can choose the background color for the screenshots, default is black. */ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox index 1a32f2174a..46579d046d 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox @@ -1,91 +1,104 @@ /** -\page org_mitk_gui_qt_pharmacokinetics_mri DCE MR Perfusion Datafit View +\page org_mitk_views_pharmacokinetics_mri DCE MR Perfusion Datafit View \imageMacro{pharmacokinetics_mri_doc.svg,"Icon of the DCE MR Perfusion View",3.0} \tableofcontents \section FIT_DCE_Introduction Introduction -For pharmacokinetic analysis of DCE MRI/CT data using compartment models in non-linear least square fitting the DCE MR Perfusion Datafit plugin can be used. +In dynamic contrast-enhanced (DCE) MRI, pharmacokinetic (PK) modeling can be used to quantify tissue physiology. +Parameters describing the tissue microvasculature can be derived by fitting a pharmacokinetic model, e.g. a compartment model, to the dynamic data. +This view offers a comprehensive set of tools to perform pharmacokinetic analysis. \section FIT_DCE_Contact Contact information -This plug-in is being developed by Charlotte Debus and the SIDT group (Software development for Integrated Diagnostics -and Therapy) at the German Cancer Research Center (DKFZ). If you have any questions, need support, find a bug or have a feature request, feel free to contact us at www.mitk.org. + \subsection FIT_DCE_Cite Citation information If you use the view for your research please cite our work as reference:\n\n -Debus C and Floca R, Ingrisch M, Kompan I, Maier-Hein K, Abdollahi A, Nolden M, MITK-ModelFit: generic open-source framework for model fits and their exploration in medical imaging – design, implementation and application on the example of DCE-MRI (arXiv:1807.07353) +Debus C and Floca R, Ingrisch M, Kompan I, Maier-Hein K, Abdollahi A, Nolden M, MITK-ModelFit: generic open-source framework for model fits and their exploration in medical imaging – design, implementation and application on the example of DCE-MRI. https://doi.org/10.1186/s12859-018-2588-1 (BMC Bioinformatics 2019 20:31) -\section FIT_DCE_General General information -\imageMacro{dce_mri_init.png, "Example screen shot showing the view at first start.", 10} -For pharmacokinetic analysis of DCE MRI/CT data using compartment models in non-linear least square fitting the DCE MR Perfusion Datafit view can be used. +\section FIT_DCE_Data_and_ROI_Selection Time series and mask selection +\imageMacro{dce_mri_maskAndFittingStrategy.png, "Time series and mask selection.", 10} In principle, every model can be fitted on the entire image. However, for model configuration reasons (e.g. AIF required) and computational time cost, this is often not advisable. -Therefore, apart from the image to be fitted (Selected Time Series), a ROI segmentation can be defined (Selected Mask), within which model fitting is performed. -The view currently offers voxel wise and/or ROI averaged fits of intensity-time curves with different quantitative and semi-quantitative models. -If a mask is selected, ROI-based fitting (fit average curve within ROI) is enabled (radio button Fitting Strategy – Pixel based / ROI based). +Therefore, apart from the image to be fitted (Selected Time Series), a ROI segmentation can be defined (Selected Mask), within which model fitting is performed. +The view currently offers Pixel based and/or ROI based averaged fits of time-varying curves. The ROI based fitting option becomes enabled, if a mask is selected. + -\subsection FIT_DCE_General_models Supported models +\section FIT_DCE_General_models Supported models Currently the following pharmacokinetic models for gadolinium-based contrast agent are available: - The Descriptive Brix model \ref FIT_DCE_lit_ref1 "[1]" -- A semi-quantitative three segment linear model (3SL) +- A semi-quantitative two/three segment linear model (2SL/3SL) - The standard tofts model \ref FIT_DCE_lit_ref2 "[2]" - The extended Tofts model \ref FIT_DCE_lit_ref3 "[3]" - The two compartment exchange model (2CXM) \ref FIT_DCE_lit_ref4 "[4, 5]" -\section FIT_DCE_Settings Model Settings -\imageMacro{dce_mri_config.png, "Example screenshot showing the config settings of the view.", 10} +\section FIT_DCE_Settings Model settings +\imageMacro{dce_mri_modelSettings.png, "Model settings of the view for the standard Tofts model.", 10} \subsection FIT_DCE_Settings_model Model specific settings Selecting one of the \ref FIT_DCE_General_models "supported models" will open below tabs for further configuration of the model. -- The descriptive Brix model requires only definition of the duration of the bolus, i.e. the overall time of the injection (Injection Time [min]) -- The 3SL is a semi-quantitative descriptive model that distinguishes three different segments of the signal: A constant baseline, the initial fast rise (wash-in) and the final slow rise / signal decrease (washout). Each of these segments is approximated by a linear curve, with change points in-between. It requires no further configuration -- The standard Tofts model, the extended Tofts model and the 2CXM are all three compartment models that require the input of the concentration time curve in the tissue feeding artery, the AIF. In the DCE MRI Model fitting plugin, the arterial input function can be defined in several ways. For patient individual image derived AIFs, select the radio button "Select AIF from Image". In that case, a segmentation ROI for the artery has to be given to the tool (Drop-down menu AIF Mask from Data Manager). In cases, where the respective artery does not lie in the same image as the investigated tissue (e.g. in animal experiments, where a slice through the heart is used for AIF extraction), a dedicated AIF image can be selected from the Data Manager. -The other option is to define the AIF via an external file (e.g. for population derived AIFs or AIFs from blood sampling). By clicking the Browse button, one can select a csv file that holds the arterial intensity values and corresponding timepoints (in tuple format (Time, Value)). Caution: the file may not contain a header line, but the first line must start with Time and Intensity values. -Furthermore, the hematocrit level has to be set (from 0 to 1) for conversion from whole blood to plasma concentration. It is set to the literature default value of 0.45. +- The descriptive Brix model requires only definition of the duration of the bolus, i.e. the overall time of the injection (Injection Time [min]). +- The 3SL is a semi-quantitative descriptive model that distinguishes three different segments of the signal: A constant baseline, the initial fast rise (wash-in) and the final slow rise / signal decrease (washout). Each of these segments is approximated by a linear curve, with change points in-between. It requires no further configuration. +- The standard Tofts model, the extended Tofts model and the 2CXM are compartment models that require the input of the concentration time curve in the tissue feeding artery, the arterial input function (AIF). +In the DCE MR Perfusion Datafit View, the arterial input function can be defined in several ways. For patient individual image derived AIFs, select the radio button Select AIF from Image. +In that case, a segmentation ROI for the artery has to be selected. This can be done by clicking on the AIF Mask selection widget and selecting a suitable AIF segmentation from the data loaded in the Data Manager. In cases where the respective artery does not lie in the same image as the investigated tissue (e.g. in animal experiments, where a slice through the heart is used for AIF extraction), a dedicated AIF image can be selected using the corresponding Dedicated AIF image selection widget. +An alternative option is to define the AIF via an external file by selecting Select AIF from File (e.g. for population derived AIFs or AIFs from blood sampling). By clicking the Browse button, one can select a csv file that holds the AIF values and corresponding timepoints (in tuple format (Time, Value)). +Caution: the file must not contain a header line, but the first line must start with Time and Intensity values. +Furthermore, the Hematocrit Level has to be set (from 0 to 1) for conversion from whole blood to plasma concentration. It is set as default to the literature value of 0.45. \subsection FIT_DCE_Settings_start Start parameter -\imageMacro{dce_mri_start.png, "Example screen shot for start parameter settings.", 10} +\imageMacro{dce_mri_start.png, "Example screenshot for start parameter settings.", 10} In cases of noisy data it can be useful to define the initial starting values of the parameter estimates, at which optimization starts, in order to prevent optimization results in local optima. Each model has default scalar values (applied to every voxel) for initial values of each parameter, however these can be adjusted. -Moreover, initial values can also be defined locally for each individual voxel via starting value images. +Moreover, initial values can also be defined locally for each individual voxel via starting value images. To load a starting value image, change the Type from scalar to image. This can be done by double-clicking on the type cell. +In the Value column, selection of a starting value image will be available. -\subsection FIT_DCE_Settings_constraint Constraint settings -\imageMacro{dce_mri_constraint.png, "Example screen shot for constraint settings.", 10} +\subsection FIT_DCE_Settings_constraint Constraints settings +\imageMacro{dce_mri_constraints.png, "Example screenshot for constraints settings.", 10} To limit the fitting search space and to exclude unphysical/illogical results for model parameter estimates, constraints to individual parameters as well as combinations can be imposed. Each model has default constraints, however, new ones can be defined or removed by the + and – buttons in the table. -The first column specifies the parameter(s) involved in the constraint (if multiple selected, their sum will be used) by selection in the drop down menu. -The second column defines whether the constraints defines an upper or lower boundary. -Value and Width define the actual constraint value, that should not be crossed, and a certain tolerance width. +The first column specifies the parameter(s) involved in the constraint (if multiple parameters are selected, their sum will be used) by selection in the drop down menu. +The second column Type defines whether the constraint defines an upper or lower boundary. +Value defines the actual constraint value, that should not be crossed, and Width allows for a certain tolerance width. \subsection FIT_DCE_Settings_concentration Signal to concentration conversion settings -\imageMacro{dce_mri_concentration.png, "Example screen shot for concentration conversion settings.", 10} -Most models require concentration values as input rather than raw signal intensities (i.e. all compartment models). -The DCE MR Perfusion view offers conversion to concentration by means of relative and absolute signal enhancement as well as a special conversion for turbo flash sequences. -For the conversion methods, a baseline image prior to contrast agent arrival is required. In many data sets, multiple baseline images are available. The "Baseline Range Selection" allows for selection of a range of time frames, from which the average image (along the time dimension) is calculated and set as baseline input image. Remark: The number of the first time frame is 0. +\imageMacro{dce_mri_concentration.png, "Example screenshot for concentration conversion settings.", 10} +Most models require contrast agent concentration values as input rather than raw signal intensities (i.e. all compartment models). +The DCE MR Perfusion DataFit View offers a variety of tools for the conversion from signal to concentration: +by means of relative and absolute signal enhancement, via a T1-map calculated by the variable flip angle method, as well as a special conversion for turbo flash sequences. +For the conversion methods, a baseline image prior to contrast agent arrival is required. +In many data sets, multiple baseline images are available. The Baseline Range Selection allows for selection of a range of time frames, from which the average image (along the time dimension) is calculated and set as baseline input image. +Remark: The number of the first time frame is 0. \section FIT_DCE_Fitting Executing a fit -After configuration of the entire fit routine, the respective time series to be fitted and eventually the ROI mask have to be selected. -If only an image is needed, selection of the respective time series in the data manager is sufficient. -If a mask is to be selected as well, image and mask have to be selected by holding the shift key and selecting them in this order from the Data manager.\n\n -In order to distinguish results from different model fits to the data, a Fitting name can be defined in the bottom field. +In order to distinguish results from different model fits to the data, a Fitting name can be defined. As default, the name of the model and the fitting strategy (pixel/ROI) are given. This name will then be appended by the respective parameter name.\n\n -For development purposes and evaluation of the fits, the option "Generate debug parameter images" is available. -Enabling this option will result in additional parameter maps displaying the status of the optimizer at fit termination, like needed optimization time, number of iterations, constraint violations and reasons for fit termination (criterion reached, maximum number of iterations, etc.).\n\n +For development purposes and evaluation of the fits, the option Generate debug parameter images is available. +Enabling this option will result in additional parameter maps displaying the status of the optimizer at fit termination. +In the following definitions, an evaluation describes the process of cost function calculation and evaluation by the optimizer for a given parameter set. + +- Stop condition: Reasons for the fit termination, i.e. criterion reached, maximum number of iterations,... +- Optimization time: The overall time from fitting start to termination. +- Number of iterations: The number of iterations from fitting start to termination. +- Constraint penalty ratio: Ratio between evaluations that were penalized and all evaluations. 0.0 means no evaluation was penalized; 1.0 all evaluations were. Evaluations that hit the failure threshold count as penalized, too. +- Constraint last failed parameter: Ratio between evaluations that were beyond the failure threshold. 0.0 means no evaluation was a failure (but some may be penalized). +- Constraint failure ratio: Index of the first (in terms of index position) parameter, which failed the constraints in the last evaluation. -After all necessary configurations are set, the button "Start Modelling" is enabled, which starts the fitting routine. Progress can be seen in the message box on the bottom. Resulting parameter maps will afterwards be added to the data manager as sub-nodes to the analyzed 4D image. +After all necessary configurations are set, the button Start Modelling is enabled, which starts the fitting routine. +Progress can be seen in the message box on the bottom. Resulting parameter maps will afterwards be added to the Data Manager as sub-nodes of the analyzed 4D image. \section FIT_DCE_lit References/Literature - \anchor FIT_DCE_lit_ref1 [1] Brix G, Semmler W, Port R, Schad LR, Layer G, Lorenz WJ. Pharmacokinetic parameters in CNS Gd-DTPA enhanced MR imaging. J Comput Assist Tomogr. 1991;15:621–8. - \anchor FIT_DCE_lit_ref2 [2] Tofts PS, Kermode AG. Measurement of the blood-brain barrier permeability and leakage space using dynamic MR imaging. 1. Fundamental concepts. Magn Reson Med. 1991;17:357–67. - \anchor FIT_DCE_lit_ref3 [3] Sourbron SP, Buckley DL. On the scope and interpretation of the Tofts models for DCE-MRI. Magn Reson Med. 2011;66:735–45. - \anchor FIT_DCE_lit_ref4 [4] Brix G, Kiessling F, Lucht R, Darai S, Wasser K, Delorme S, et al. Microcirculation and microvasculature in breast tumors: Pharmacokinetic analysis of dynamic MR image series. Magn Reson Med. 2004;52:420–9. - \anchor FIT_DCE_lit_ref5 [5] Sourbron, Buckley. Tracer kinetic modelling in MRI: estimating perfusion and capillary permeability - pdf. Phys Med Biol. 2012. http://iopscience.iop.org/article/10.1088/0031-9155/57/2/R1/pdf. Accessed 1 May 2016. */ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png index 5ba06abe29..bcf2d51953 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png index b2ac30d6da..c7ca984653 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png deleted file mode 100644 index 1848b5fb8f..0000000000 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png new file mode 100644 index 0000000000..200837c59b Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png new file mode 100644 index 0000000000..65f7604cd6 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png new file mode 100644 index 0000000000..45d1ac1ab0 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png index cc77a83553..b7c2275720 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml index a724f1c486..bfb82f6dd2 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml @@ -1,12 +1,12 @@ - diff --git a/Plugins/org.mitk.gui.qt.radiomics/documentation/UserManual/QmitkPhenotypingPortalPage.dox b/Plugins/org.mitk.gui.qt.radiomics/documentation/UserManual/QmitkPhenotypingPortalPage.dox index a2cc0e7284..69c9dce5b6 100644 --- a/Plugins/org.mitk.gui.qt.radiomics/documentation/UserManual/QmitkPhenotypingPortalPage.dox +++ b/Plugins/org.mitk.gui.qt.radiomics/documentation/UserManual/QmitkPhenotypingPortalPage.dox @@ -1,41 +1,41 @@ /** \page org_mitk_gui_qt_mitkphenotyping The Phenotyping \tableofcontents MITK Phenotyping is a selection of algorithms that can be used to extract image-based phenotypes, for example using a radiomics approach. The software is part of the research of the Division of Medical Image Computing of the German Cancer Research Center (DKFZ). MITK Phenotyping is not intended to be a single application, it is rather a collection of the necessary plugins within the offical MITK releases. The functionality of MITK Phenotyping can be accessed in different ways: Using the graphical interface using the Plugins listed below, using command line applications, or using one of the programming interfaces. \section org_mitk_gui_qt_mitkphenotyping_Tutorials Tutorials \li \subpage org_mitk_views_radiomicstutorial_gui_portal A tutorial on how to use the grapical interface of MITK Phenotying \section org_mitk_gui_qt_mitkphenotyping_Views Views \subsection sub2 Specific Views: Views that were developed with the main focus on Radiomics. They still might be used in other use-cases as well: \li \subpage org_mitk_views_radiomicstransformationview : Image transformations like Resampling, Laplacian of Gaussian, and Wavelet Transformations \li \subpage org_mitk_views_radiomicsmaskprocessingview : Processing and Cleaning of Masks \li \subpage org_mitk_views_radiomicsarithmetricview : Processing images using mathematical operations \li \subpage org_mitk_views_radiomicsstatisticview : Calculate Radiomics Features \subsection sub1 Non-Specific Views: This section contains views that are included within MITK Phenotyping, but were developed with a broader application in mind. \li \subpage org_mitk_views_basicimageprocessing : Deprecated plugin for performing different image-related tasks like subtraction, mutliplaction, filtering etc. -\li \subpage org_mitk_gui_qt_matchpoint_algorithm_browser : Selection of MatchPoint (Registration) Algorithm -\li \subpage org_mitk_gui_qt_matchpoint_algorithm_control : Configuring and Controlling MatchPoint (Registration) Algorithm -\li \subpage org_mitk_gui_qt_matchpoint_evaluator : Evaluate the Registration performance using MatchPoint -\li \subpage org_mitk_gui_qt_matchpoint_manipulator : Adapt a registration calculated using MatchPoint -\li \subpage org_mitk_gui_qt_matchpoint_mapper : Apply a MatchPoint Registration to a specific image -\li \subpage org_mitk_gui_qt_matchpoint_visualizer : Visualize a Registration obtained with MatchPoint -\li \subpage org_mitk_gui_qt_matchpoint_algorithm_batch : Running MatchPoint over multiple images (BatchMode) +\li \subpage org_mitk_views_matchpoint_algorithm_browser : Selection of MatchPoint (Registration) Algorithm +\li \subpage org_mitk_views_matchpoint_algorithm_control : Configuring and Controlling MatchPoint (Registration) Algorithm +\li \subpage org_mitk_views_matchpoint_evaluator : Evaluate the Registration performance using MatchPoint +\li \subpage org_mitk_views_matchpoint_manipulator : Adapt a registration calculated using MatchPoint +\li \subpage org_mitk_views_matchpoint_mapper : Apply a MatchPoint Registration to a specific image +\li \subpage org_mitk_views_matchpoint_visualizer : Visualize a Registration obtained with MatchPoint +\li \subpage org_mitk_views_matchpoint_algorithm_batch : Running MatchPoint over multiple images (BatchMode) \li \subpage org_mitk_views_multilabelsegmentation : Create and editing of Multilabel-Segmentations. \li \subpage org_mitk_views_segmentation : Create simple segmentations \li \subpage org_mitk_views_segmentationutilities : Utilities for the processing of simple segmentations. \section radiomics_miniapps MiniApps (Command line Tools) \li \subpage MiniAppExplainPage Explanation of the Command Line App concept in MITK \li \subpage mitkBasicImageProcessingMiniAppsPortalPage : List of common preprocessing MiniApps \li \subpage mitkClassificationMiniAppsPortalPage : (Incomplete) list of MITK Classification MiniApps */ diff --git a/Plugins/org.mitk.gui.qt.radiomics/documentation/doxygen/tutorial_gui/RadiomicsTutorial_GUI_03_Preprocessing.dox b/Plugins/org.mitk.gui.qt.radiomics/documentation/doxygen/tutorial_gui/RadiomicsTutorial_GUI_03_Preprocessing.dox index 52194c1bcd..33b6af06c5 100644 --- a/Plugins/org.mitk.gui.qt.radiomics/documentation/doxygen/tutorial_gui/RadiomicsTutorial_GUI_03_Preprocessing.dox +++ b/Plugins/org.mitk.gui.qt.radiomics/documentation/doxygen/tutorial_gui/RadiomicsTutorial_GUI_03_Preprocessing.dox @@ -1,23 +1,23 @@ /** \page org_mitk_views_radiomicstutorial_gui_03_preprocessing GUI based Radiomics Tutorial - Preprocessing the data \subsection Preprocessing the data The first step we take is to resample the data. To do so, we open the "Radiomics Transformation" View and select the "Resample Image" panel. We start by resampling the original image and therefore select the original picture (for us, Pic3D). Right-clicking on the image in the "Data Manager" and selecting the option "Details" gives us more information on the image. As we can see, our image has a spacing of [1, 1, 3], with an inplane resolution of 1x1mm and a out-of-plane resolution of 3 mm. We therefore decide to resample the image to an isotropic resolution of 1x1x1 mm. \imageMacro{RadiomicsTutorial_GUI_Step3_01_DetailView.png,"Details showing the spacing of the original image.",1} -To resample the image, we de-select "Dimension X" and "Dimension Y" option and set the "Dimension Z" option to 1, as indiciated by the image above. This tells the resampling algorithm to change only the last dimension to the value we specified. We further select to have the output image as double and chose B-Spline as resampling algorithm. This is a fast and still accurate option for resampling. To learn more about the other interpolation modes, refer to \ref org_mitk_gui_qt_matchpoint_mapper . +To resample the image, we de-select "Dimension X" and "Dimension Y" option and set the "Dimension Z" option to 1, as indiciated by the image above. This tells the resampling algorithm to change only the last dimension to the value we specified. We further select to have the output image as double and chose B-Spline as resampling algorithm. This is a fast and still accurate option for resampling. To learn more about the other interpolation modes, refer to \ref org_mitk_views_matchpoint_mapper . After resampling the original image, we also need to resample the segmentation. For this, we select the segmentation, leave the dimensions unchanged. Remove the "Output as double" option, as segmentations are not double values and choose a linear interpolation, which seems to be a better solution for resampling masks. We also check the option that we are resampling a mask. After performing those two steps, there should be two additional, resampled images in the "Data Manager". As a second step, we calculate some Laplacian of Gaussian images of the resampled image that allow us to capture more detailed information. For this, we select the panel "Laplacian of Gaussian" of the "Radiomics Transformation"-view and perform the algorithm three times with different sigma values (we chose 1,2, and 4). Make sure that you selected the right image to calculate the image, i.e. the resampled image. Finally, we clear the mask to obtain a clear segmentation of the target structure and remove possible resampling artifacts. To do so, we open the "Radiomics Mask Processing" View and select the resampled image and the resampled mask. We then select a lower limit only and set it to 160. Since we are working with MR, this is not a fixed value but something we manually determined. With this set, we perform the mask reducing by cklicking "Clean Mask based on Intervall". After this, we have the resampled image, three LoG images and a resampled and cleaned mask. The result should look similar to the next picture. You can also see the final image structure we obtained from our processing. It might help you to compare your results, although it is not necessary to obtain the same structure as long as you have all necessary images. \imageMacro{RadiomicsTutorial_GUI_Step3_02_FinishedPreprocessing.png,"Final results with a completed resampling",1} */ \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.cpp index 6dbca7e573..095153cb34 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.cpp @@ -1,619 +1,584 @@ /*============================================================================ 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 "QmitkDeformableClippingPlaneView.h" -#include "mitkClippingPlaneInteractor3D.h" -#include "mitkHeightFieldSurfaceClipImageFilter.h" -#include -#include "mitkImageToSurfaceFilter.h" -#include "mitkInteractionConst.h" -#include "mitkLabeledImageLookupTable.h" -#include "mitkLabeledImageVolumeCalculator.h" -#include "mitkLevelWindowProperty.h" -#include "mitkLookupTableProperty.h" -#include "mitkNodePredicateProperty.h" -#include "mitkNodePredicateDataType.h" -#include "mitkRenderingModeProperty.h" -#include "mitkRotationOperation.h" -#include "mitkSurfaceDeformationDataInteractor3D.h" -#include "mitkSurfaceVtkMapper3D.h" -#include "mitkVtkRepresentationProperty.h" -#include "mitkVtkResliceInterpolationProperty.h" -#include "usModuleRegistry.h" - -#include "vtkFloatArray.h" -#include "vtkPointData.h" -#include "vtkProperty.h" +#include + +// mitk core +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include #include +#include + +#include +#include const std::string QmitkDeformableClippingPlaneView::VIEW_ID = "org.mitk.views.deformableclippingplane"; QmitkDeformableClippingPlaneView::QmitkDeformableClippingPlaneView() : QmitkAbstractView() - , m_ReferenceNode(nullptr) + , m_Controls(new Ui::QmitkDeformableClippingPlaneViewControls) , m_WorkingNode(nullptr) { + auto isImage = mitk::TNodePredicateDataType::New(); + auto isNotHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true))); + + m_IsImagePredicate = mitk::NodePredicateAnd::New(isImage, isNotHelperObject); + + m_IsClippingPlanePredicate = mitk::NodePredicateProperty::New("clippingPlane", mitk::BoolProperty::New(true)); } QmitkDeformableClippingPlaneView::~QmitkDeformableClippingPlaneView() { if (m_WorkingNode.IsNotNull()) m_WorkingNode->SetDataInteractor(nullptr); } +void QmitkDeformableClippingPlaneView::SetFocus() +{ + m_Controls->createNewPlanePushButton->setFocus(); +} + void QmitkDeformableClippingPlaneView::CreateQtPartControl(QWidget *parent) { - m_Controls.setupUi(parent); + m_Controls->setupUi(parent); - auto isClippingPlane = mitk::NodePredicateProperty::New("clippingPlane", mitk::BoolProperty::New(true)); + m_Controls->imageSelectionWidget->SetDataStorage(GetDataStorage()); + m_Controls->imageSelectionWidget->SetNodePredicate(m_IsImagePredicate); + m_Controls->imageSelectionWidget->SetSelectionIsOptional(false); + m_Controls->imageSelectionWidget->SetInvalidInfo("Select an image or segmentation."); + m_Controls->imageSelectionWidget->SetPopUpTitel("Select an image or segmentation."); - m_Controls.clippingPlaneSelector->SetDataStorage(this->GetDataStorage()); - m_Controls.clippingPlaneSelector->SetPredicate(isClippingPlane); + m_Controls->clippingPlaneSelector->SetDataStorage(this->GetDataStorage()); + m_Controls->clippingPlaneSelector->SetPredicate(m_IsClippingPlanePredicate); - m_Controls.volumeGroupBox->setEnabled(false); - m_Controls.interactionSelectionBox->setEnabled(false); - m_Controls.noSelectedImageLabel->show(); - m_Controls.planesWarningLabel->hide(); + m_Controls->volumeGroupBox->setEnabled(false); + m_Controls->interactionSelectionBox->setEnabled(false); + m_Controls->planesWarningLabel->hide(); this->CreateConnections(); -} -void QmitkDeformableClippingPlaneView::SetFocus() -{ - m_Controls.createNewPlanePushButton->setFocus(); + m_Controls->imageSelectionWidget->SetAutoSelectNewNodes(true); } -void QmitkDeformableClippingPlaneView::CreateConnections() +void QmitkDeformableClippingPlaneView::OnCurrentSelectionChanged(const QList& /*nodes*/) { - connect(m_Controls.translationPushButton, SIGNAL(toggled(bool)), this, SLOT(OnTranslationMode(bool))); - connect(m_Controls.rotationPushButton, SIGNAL(toggled(bool)), this, SLOT(OnRotationMode(bool))); - connect(m_Controls.deformationPushButton, SIGNAL(toggled(bool)), this, SLOT(OnDeformationMode(bool))); - connect(m_Controls.createNewPlanePushButton, SIGNAL(clicked()), this, SLOT(OnCreateNewClippingPlane())); - connect(m_Controls.updateVolumePushButton, SIGNAL(clicked()), this, SLOT(OnCalculateClippingVolume())); - connect(m_Controls.clippingPlaneSelector, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), - this, SLOT(OnComboBoxSelectionChanged(const mitk::DataNode*))); + this->UpdateView(); } void QmitkDeformableClippingPlaneView::OnComboBoxSelectionChanged(const mitk::DataNode* node) { this->DeactivateInteractionButtons(); auto selectedNode = const_cast(node); - - if(selectedNode != nullptr) + if(nullptr != selectedNode) { if(m_WorkingNode.IsNotNull()) selectedNode->SetDataInteractor(m_WorkingNode->GetDataInteractor()); m_WorkingNode = selectedNode; } this->UpdateView(); } -void QmitkDeformableClippingPlaneView::OnSelectionChanged(mitk::DataNode* node) -{ - berry::IWorkbenchPart::Pointer nullPart; - QList nodes; - nodes.push_back(node); - - this->OnSelectionChanged(nullPart, nodes); -} - -void QmitkDeformableClippingPlaneView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) -{ - bool isClippingPlane = false; - - for (auto node : nodes) - { - node->GetBoolProperty("clippingPlane", isClippingPlane); - - if (isClippingPlane) - { - m_Controls.clippingPlaneSelector->setCurrentIndex(m_Controls.clippingPlaneSelector->Find(node)); - } - else - { - if(node.IsNotNull() && dynamic_cast(node->GetData()) != nullptr) - { - if(m_ReferenceNode.IsNotNull() && node->GetData() == m_ReferenceNode->GetData()) - return; - - m_ReferenceNode = node; - } - } - } - - this->UpdateView(); -} - -void::QmitkDeformableClippingPlaneView::NodeChanged(const mitk::DataNode*) -{ - this->UpdateView(); -} - -void QmitkDeformableClippingPlaneView::NodeRemoved(const mitk::DataNode* node) -{ - bool isClippingPlane(false); - node->GetBoolProperty("clippingPlane", isClippingPlane); - - if (isClippingPlane) - { - if (this->GetAllClippingPlanes()->Size() <= 1) - { - m_WorkingNode = nullptr; - this->UpdateView(); - } - else - { - this->OnSelectionChanged(this->GetAllClippingPlanes()->front() == node - ? this->GetAllClippingPlanes()->ElementAt(1) - : this->GetAllClippingPlanes()->front()); - } - } - else - { - if(m_ReferenceNode.IsNotNull()) - { - if(node->GetData() == m_ReferenceNode->GetData()) - { - m_ReferenceNode = nullptr; - m_Controls.volumeList->clear(); - } - - this->UpdateView(); - } - } -} - -void QmitkDeformableClippingPlaneView::UpdateView() -{ - if (m_ReferenceNode.IsNotNull()) - { - m_Controls.noSelectedImageLabel->hide(); - - m_Controls.selectedImageLabel->setText(QString::fromUtf8(m_ReferenceNode->GetName().c_str())); - - if (m_WorkingNode.IsNotNull()) - { - bool isSegmentation = false; - m_ReferenceNode->GetBoolProperty("binary", isSegmentation); - m_Controls.interactionSelectionBox->setEnabled(true); - - m_Controls.volumeGroupBox->setEnabled(isSegmentation); - - //clear list --> than search for all shown clipping plans (max 7 planes) - m_Controls.selectedVolumePlanesLabel->setText(""); - m_Controls.planesWarningLabel->hide(); - int volumePlanes=0; - - mitk::DataStorage::SetOfObjects::ConstPointer allClippingPlanes = this->GetAllClippingPlanes(); - for (mitk::DataStorage::SetOfObjects::ConstIterator itPlanes = allClippingPlanes->Begin(); itPlanes != allClippingPlanes->End(); itPlanes++) - { - bool isVisible = false; - itPlanes.Value()->GetBoolProperty("visible", isVisible); - if (isVisible) - { - if (volumePlanes < 7) - { - ++volumePlanes; - m_Controls.selectedVolumePlanesLabel->setText(m_Controls.selectedVolumePlanesLabel->text().append(QString::fromStdString(itPlanes.Value()->GetName()+"\n"))); - } - else - { - m_Controls.planesWarningLabel->show(); - return; - } - } - } - } - else - { - m_Controls.volumeGroupBox->setEnabled(false); - m_Controls.interactionSelectionBox->setEnabled(false); - m_Controls.selectedVolumePlanesLabel->setText(""); - m_Controls.volumeList->clear(); - } - } - else - { - m_Controls.volumeGroupBox->setEnabled(false); - m_Controls.noSelectedImageLabel->show(); - m_Controls.selectedImageLabel->setText(""); - m_Controls.selectedVolumePlanesLabel->setText(""); - m_Controls.planesWarningLabel->hide(); - - if (m_WorkingNode.IsNull()) - { - m_Controls.interactionSelectionBox->setEnabled(false); - } - else - { - m_Controls.interactionSelectionBox->setEnabled(true); - } - } -} - void QmitkDeformableClippingPlaneView::OnCreateNewClippingPlane() { this->DeactivateInteractionButtons(); auto plane = mitk::Surface::New(); - auto referenceImage = mitk::Image::New(); auto planeSource = vtkSmartPointer::New(); planeSource->SetOrigin(-32.0, -32.0, 0.0); - planeSource->SetPoint1( 32.0, -32.0, 0.0); - planeSource->SetPoint2(-32.0, 32.0, 0.0); + planeSource->SetPoint1(32.0, -32.0, 0.0); + planeSource->SetPoint2(-32.0, 32.0, 0.0); planeSource->SetResolution(128, 128); planeSource->Update(); plane->SetVtkPolyData(planeSource->GetOutput()); - double imageDiagonal = 200; + mitk::ScalarType imageDiagonal = 200.0; - if (m_ReferenceNode.IsNotNull()) + auto selectedNode = m_Controls->imageSelectionWidget->GetSelectedNode(); + if (selectedNode.IsNotNull()) { - referenceImage = dynamic_cast(m_ReferenceNode->GetData()); - - if (referenceImage.IsNotNull()) + auto selectedImage = dynamic_cast(selectedNode->GetData()); + if (nullptr != selectedImage) { // check if user wants a surface model - if(m_Controls.surfaceModelCheckBox->isChecked()) + if (m_Controls->surfaceModelCheckBox->isChecked()) { //Check if there is a surface node from the image. If not, create one - bool createSurfaceFromImage(true); - mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); - mitk::NodePredicateDataType::Pointer isSurface = mitk::NodePredicateDataType::New("Surface"); - mitk::DataStorage::SetOfObjects::ConstPointer childNodes = GetDataStorage()->GetDerivations(m_ReferenceNode,isSurface, true); + bool createSurfaceFromImage = true; + auto isSurface = mitk::NodePredicateDataType::New("Surface"); + auto childNodes = GetDataStorage()->GetDerivations(selectedNode, isSurface, true); - for (mitk::DataStorage::SetOfObjects::ConstIterator itChildNodes = childNodes->Begin(); - itChildNodes != childNodes->End(); itChildNodes++) + for (mitk::DataStorage::SetOfObjects::ConstIterator it = childNodes->Begin(); + it != childNodes->End(); it++) { - if (itChildNodes.Value().IsNotNull() && itChildNodes->Value()->GetName().compare(m_ReferenceNode->GetName()) == 0) + if (it.Value().IsNotNull() && it->Value()->GetName() == selectedNode->GetName()) { createSurfaceFromImage = false; - itChildNodes.Value()->SetVisibility(true); + it.Value()->SetVisibility(true); } } - if(createSurfaceFromImage) + if (createSurfaceFromImage) { //Lsg 2: Surface for the 3D-perspective - mitk::ImageToSurfaceFilter::Pointer surfaceFilter = mitk::ImageToSurfaceFilter::New(); - surfaceFilter->SetInput(referenceImage); + auto surfaceFilter = mitk::ImageToSurfaceFilter::New(); + surfaceFilter->SetInput(selectedImage); surfaceFilter->SetThreshold(1); surfaceFilter->SetSmooth(true); //Downsampling surfaceFilter->SetDecimate(mitk::ImageToSurfaceFilter::DecimatePro); - mitk::DataNode::Pointer surfaceNode = mitk::DataNode::New(); + auto surfaceNode = mitk::DataNode::New(); surfaceNode->SetData(surfaceFilter->GetOutput()); - surfaceNode->SetProperty("color", m_ReferenceNode->GetProperty("color")); + surfaceNode->SetProperty("color", selectedNode->GetProperty("color")); surfaceNode->SetOpacity(0.5); - surfaceNode->SetName(m_ReferenceNode->GetName()); - GetDataStorage()->Add(surfaceNode, m_ReferenceNode); + surfaceNode->SetName(selectedNode->GetName()); + this->GetDataStorage()->Add(surfaceNode, selectedNode); } } //If an image is selected trim the plane to this. - imageDiagonal = referenceImage->GetGeometry()->GetDiagonalLength(); - plane->SetOrigin( referenceImage->GetGeometry()->GetCenter()); + imageDiagonal = selectedImage->GetGeometry()->GetDiagonalLength(); + plane->SetOrigin(selectedImage->GetGeometry()->GetCenter()); // Rotate plane mitk::Vector3D rotationAxis; mitk::FillVector3D(rotationAxis, 0.0, 1.0, 0.0); - mitk::RotationOperation op(mitk::OpROTATE, referenceImage->GetGeometry()->GetCenter(), rotationAxis, 90.0); + mitk::RotationOperation op(mitk::OpROTATE, selectedImage->GetGeometry()->GetCenter(), rotationAxis, 90.0); plane->GetGeometry()->ExecuteOperation(&op); } } - //set some properties for the clipping plane - // plane->SetExtent(imageDiagonal * 0.9, imageDiagonal * 0.9); - // plane->SetResolution(64, 64); - - // eequivalent to the extent and resolution function of the clipping plane - const double x = imageDiagonal * 0.9; - planeSource->SetOrigin( -x / 2.0, -x / 2.0, 0.0 ); - planeSource->SetPoint1( x / 2.0, -x / 2.0, 0.0 ); - planeSource->SetPoint2( -x / 2.0, x / 2.0, 0.0 ); - planeSource->SetResolution( 64, 64 ); + // equivalent to the extent and resolution function of the clipping plane + const auto x = imageDiagonal * 0.9; + planeSource->SetOrigin(-x / 2.0, -x / 2.0, 0.0); + planeSource->SetPoint1(x / 2.0, -x / 2.0, 0.0); + planeSource->SetPoint2(-x / 2.0, x / 2.0, 0.0); + planeSource->SetResolution(64, 64); planeSource->Update(); plane->SetVtkPolyData(planeSource->GetOutput()); // Set scalars (for colorization of plane) vtkFloatArray *scalars = vtkFloatArray::New(); scalars->SetName("Distance"); scalars->SetNumberOfComponents(1); - for ( unsigned int i = 0; i < plane->GetVtkPolyData(0)->GetNumberOfPoints(); ++i) + const auto numerOfPoints = plane->GetVtkPolyData(0)->GetNumberOfPoints(); + for (std::remove_const_t i = 0; i < plane->GetVtkPolyData(0)->GetNumberOfPoints(); ++i) { scalars->InsertNextValue(-1.0); } plane->GetVtkPolyData(0)->GetPointData()->SetScalars(scalars); plane->GetVtkPolyData(0)->GetPointData()->Update(); - mitk::DataNode::Pointer planeNode = mitk::DataNode::New(); + auto planeNode = mitk::DataNode::New(); planeNode->SetData(plane); std::stringstream planeName; planeName << "ClippingPlane "; planeName << this->GetAllClippingPlanes()->Size() + 1; planeNode->SetName(planeName.str()); - planeNode->AddProperty("clippingPlane",mitk::BoolProperty::New(true)); + planeNode->AddProperty("clippingPlane", mitk::BoolProperty::New(true)); // Make plane pickable planeNode->SetBoolProperty("pickable", true); mitk::SurfaceVtkMapper3D::SetDefaultProperties(planeNode); // Don't include plane in bounding box! planeNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); // Set lookup table for plane surface visualization - vtkSmartPointer lookupTable = vtkSmartPointer::New(); - lookupTable->SetHueRange(0.6, 0.0); - lookupTable->SetSaturationRange(1.0, 1.0); - lookupTable->SetValueRange(1.0, 1.0); - lookupTable->SetTableRange(-1.0, 1.0); - lookupTable->Build(); + auto lookupTablevtk = vtkSmartPointer::New(); + lookupTablevtk->SetHueRange(0.6, 0.0); + lookupTablevtk->SetSaturationRange(1.0, 1.0); + lookupTablevtk->SetValueRange(1.0, 1.0); + lookupTablevtk->SetTableRange(-1.0, 1.0); + lookupTablevtk->Build(); - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - lut->SetVtkLookupTable(lookupTable); + auto lookupTable = mitk::LookupTable::New(); + lookupTable->SetVtkLookupTable(lookupTablevtk); - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); + auto prop = mitk::LookupTableProperty::New(lookupTable); planeNode->SetProperty("LookupTable", prop); planeNode->SetBoolProperty("scalar visibility", true); planeNode->SetBoolProperty("color mode", true); planeNode->SetFloatProperty("ScalarsRangeMinimum", -1.0); planeNode->SetFloatProperty("ScalarsRangeMaximum", 1.0); // Configure material so that only scalar colors are shown - planeNode->SetColor(0.0f,0.0f,0.0f); + planeNode->SetColor(0.0f, 0.0f, 0.0f); planeNode->SetOpacity(1.0f); - planeNode->SetFloatProperty("material.wireframeLineWidth",2.0f); + planeNode->SetFloatProperty("material.wireframeLineWidth", 2.0f); //Set view of plane to wireframe planeNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); - //Set the plane as working data for the tools and selected it - this->OnSelectionChanged (planeNode); - //Add the plane to data storage this->GetDataStorage()->Add(planeNode); //Change the index of the selector to the new generated node - m_Controls.clippingPlaneSelector->setCurrentIndex( m_Controls.clippingPlaneSelector->Find(planeNode) ); + m_Controls->clippingPlaneSelector->setCurrentIndex(m_Controls->clippingPlaneSelector->Find(planeNode)); - m_Controls.interactionSelectionBox->setEnabled(true); + m_Controls->interactionSelectionBox->setEnabled(true); if (auto renderWindowPart = dynamic_cast(this->GetRenderWindowPart())) { renderWindowPart->EnableSlicingPlanes(false); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkDeformableClippingPlaneView::OnCalculateClippingVolume() { - bool isSegmentation(false); - m_ReferenceNode->GetBoolProperty("binary", isSegmentation); + auto selectedNode = m_Controls->imageSelectionWidget->GetSelectedNode(); + if (selectedNode.IsNull()) + { + MITK_ERROR << "No segmentation selected! Can't calculate volume"; + return; + } - if(m_ReferenceNode.IsNull() || !isSegmentation) + bool isSegmentation = false; + selectedNode->GetBoolProperty("binary", isSegmentation); + if (!isSegmentation) { MITK_ERROR << "No segmentation selected! Can't calculate volume"; return; } std::vector clippingPlanes; mitk::DataStorage::SetOfObjects::ConstPointer allClippingPlanes = this->GetAllClippingPlanes(); for (mitk::DataStorage::SetOfObjects::ConstIterator itPlanes = allClippingPlanes->Begin(); itPlanes != allClippingPlanes->End(); itPlanes++) { - bool isVisible(false); - itPlanes.Value()->GetBoolProperty("visible",isVisible); - mitk::Surface* plane = dynamic_cast(itPlanes.Value()->GetData()); + bool isVisible = false; + itPlanes.Value()->GetBoolProperty("visible", isVisible); + auto plane = dynamic_cast(itPlanes.Value()->GetData()); - if (isVisible && plane) + if (isVisible && nullptr != plane) clippingPlanes.push_back(plane); } if (clippingPlanes.empty()) { MITK_ERROR << "No clipping plane selected! Can't calculate volume"; return; } // deactivate Tools this->DeactivateInteractionButtons(); //Clear the list of volumes, before calculating the new values - m_Controls.volumeList->clear(); + m_Controls->volumeList->clear(); - m_ReferenceNode->SetBoolProperty("visible", false); + selectedNode->SetBoolProperty("visible", false); //set some properties for clipping the image-->Output: labled Image mitk::HeightFieldSurfaceClipImageFilter::Pointer surfaceClipFilter = mitk::HeightFieldSurfaceClipImageFilter::New(); - surfaceClipFilter->SetInput(dynamic_cast (m_ReferenceNode->GetData())); + surfaceClipFilter->SetInput(dynamic_cast(selectedNode->GetData())); surfaceClipFilter->SetClippingModeToMultiPlaneValue(); surfaceClipFilter->SetClippingSurfaces(clippingPlanes); surfaceClipFilter->Update(); //delete the old clipped image node mitk::DataStorage::SetOfObjects::ConstPointer oldClippedNode = this->GetDataStorage()->GetSubset(mitk::NodePredicateProperty::New("name", mitk::StringProperty::New("Clipped Image"))); if (oldClippedNode.IsNotNull()) this->GetDataStorage()->Remove(oldClippedNode); //add the new clipped image node - mitk::DataNode::Pointer clippedNode = mitk::DataNode::New(); + auto clippedNode = mitk::DataNode::New(); mitk::Image::Pointer clippedImage = surfaceClipFilter->GetOutput(); clippedImage->DisconnectPipeline(); clippedNode->SetData(clippedImage); - //clippedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); clippedNode->SetName("Clipped Image"); - clippedNode->SetColor(1.0,1.0,1.0); // color property will not be used, labeled image lookuptable will be used instead - clippedNode->SetProperty ("use color", mitk::BoolProperty::New(false)); + clippedNode->SetColor(1.0, 1.0, 1.0); // color property will not be used, labeled image lookuptable will be used instead + clippedNode->SetProperty("use color", mitk::BoolProperty::New(false)); clippedNode->SetProperty("reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST)); clippedNode->SetOpacity(0.4); this->GetDataStorage()->Add(clippedNode); - mitk::LabeledImageVolumeCalculator::Pointer volumeCalculator = mitk::LabeledImageVolumeCalculator::New(); + auto volumeCalculator = mitk::LabeledImageVolumeCalculator::New(); volumeCalculator->SetImage(clippedImage); volumeCalculator->Calculate(); - std::vector volumes = volumeCalculator->GetVolumes(); + auto volumes = volumeCalculator->GetVolumes(); - mitk::LabeledImageLookupTable::Pointer lut = mitk::LabeledImageLookupTable::New(); - int lablesWithVolume=0; - - for(unsigned int i = 1; i < volumes.size(); ++i) + auto lookupTable = mitk::LabeledImageLookupTable::New(); + int lablesWithVolume = 0; + const auto numberOfVolumes = volumes.size(); + for (std::remove_const_t i = 1; i < numberOfVolumes; ++i) { - if(volumes.at(i)!=0) + if (0 != volumes[0]) { lablesWithVolume++; - mitk::Color color (GetLabelColor(lablesWithVolume)); - lut->SetColorForLabel(i,color.GetRed(), color.GetGreen(), color.GetBlue(), 1.0); + mitk::Color color(GetLabelColor(lablesWithVolume)); + lookupTable->SetColorForLabel(i, color.GetRed(), color.GetGreen(), color.GetBlue(), 1.0); QColor qcolor; qcolor.setRgbF(color.GetRed(), color.GetGreen(), color.GetBlue(), 0.7); //output volume as string "x.xx ml" std::stringstream stream; - stream<< std::fixed << std::setprecision(2)<setText(QString::fromStdString(stream.str())); item->setBackgroundColor(qcolor); - m_Controls.volumeList->addItem(item); + m_Controls->volumeList->addItem(item); } } //set the rendering mode to use the lookup table and level window clippedNode->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR)); - mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(lut.GetPointer()); + mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(lookupTable.GetPointer()); clippedNode->SetProperty("LookupTable", lutProp); // it is absolutely important, to use the LevelWindow settings provided by // the LUT generator, otherwise, it is not guaranteed, that colors show // up correctly. - clippedNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(lut->GetLevelWindow())); -} - -mitk::DataStorage::SetOfObjects::ConstPointer QmitkDeformableClippingPlaneView::GetAllClippingPlanes() -{ - mitk::NodePredicateProperty::Pointer clipPredicate= mitk::NodePredicateProperty::New("clippingPlane",mitk::BoolProperty::New(true)); - mitk::DataStorage::SetOfObjects::ConstPointer allPlanes = GetDataStorage()->GetSubset(clipPredicate); - return allPlanes; -} - -mitk::Color QmitkDeformableClippingPlaneView::GetLabelColor(int label) -{ - float red, green, blue; - switch ( label % 6 ) - { - case 0: - {red = 1.0; green = 0.0; blue = 0.0; break;} - case 1: - {red = 0.0; green = 1.0; blue = 0.0; break;} - case 2: - {red = 0.0; green = 0.0; blue = 1.0;break;} - case 3: - {red = 1.0; green = 1.0; blue = 0.0;break;} - case 4: - {red = 1.0; green = 0.0; blue = 1.0;break;} - case 5: - {red = 0.0; green = 1.0; blue = 1.0;break;} - default: - {red = 0.0; green = 0.0; blue = 0.0;} - } - - float tmp[3] = { red, green, blue }; - - double factor; - - int outerCycleNr = label / 6; - int cycleSize = pow(2.0,(int)(log((double)(outerCycleNr))/log( 2.0 ))); - if (cycleSize==0) - cycleSize = 1; - int insideCycleCounter = outerCycleNr % cycleSize; - - if ( outerCycleNr == 0) - factor = 255; - else - factor = ( 256 / ( 2 * cycleSize ) ) + ( insideCycleCounter * ( 256 / cycleSize ) ); - - tmp[0]= tmp[0]/256*factor; - tmp[1]= tmp[1]/256*factor; - tmp[2]= tmp[2]/256*factor; - - return mitk::Color(tmp); + clippedNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(lookupTable->GetLevelWindow())); } void QmitkDeformableClippingPlaneView::OnTranslationMode(bool check) { - if(check) + if (check) { //uncheck all other buttons - m_Controls.rotationPushButton->setChecked(false); - m_Controls.deformationPushButton->setChecked(false); + m_Controls->rotationPushButton->setChecked(false); + m_Controls->deformationPushButton->setChecked(false); mitk::ClippingPlaneInteractor3D::Pointer affineDataInteractor = mitk::ClippingPlaneInteractor3D::New(); affineDataInteractor->LoadStateMachine("ClippingPlaneInteraction3D.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); affineDataInteractor->SetEventConfig("ClippingPlaneTranslationConfig.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); affineDataInteractor->SetDataNode(m_WorkingNode); } else m_WorkingNode->SetDataInteractor(nullptr); } void QmitkDeformableClippingPlaneView::OnRotationMode(bool check) { - if(check) + if (check) { //uncheck all other buttons - m_Controls.translationPushButton->setChecked(false); - m_Controls.deformationPushButton->setChecked(false); + m_Controls->translationPushButton->setChecked(false); + m_Controls->deformationPushButton->setChecked(false); mitk::ClippingPlaneInteractor3D::Pointer affineDataInteractor = mitk::ClippingPlaneInteractor3D::New(); affineDataInteractor->LoadStateMachine("ClippingPlaneInteraction3D.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); affineDataInteractor->SetEventConfig("ClippingPlaneRotationConfig.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); affineDataInteractor->SetDataNode(m_WorkingNode); } else m_WorkingNode->SetDataInteractor(nullptr); } void QmitkDeformableClippingPlaneView::OnDeformationMode(bool check) { - if(check) + if (check) { //uncheck all other buttons - m_Controls.translationPushButton->setChecked(false); - m_Controls.rotationPushButton->setChecked(false); + m_Controls->translationPushButton->setChecked(false); + m_Controls->rotationPushButton->setChecked(false); mitk::SurfaceDeformationDataInteractor3D::Pointer surfaceDataInteractor = mitk::SurfaceDeformationDataInteractor3D::New(); surfaceDataInteractor->LoadStateMachine("ClippingPlaneInteraction3D.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); surfaceDataInteractor->SetEventConfig("ClippingPlaneDeformationConfig.xml", us::ModuleRegistry::GetModule("MitkDataTypesExt")); surfaceDataInteractor->SetDataNode(m_WorkingNode); } else m_WorkingNode->SetDataInteractor(nullptr); } +void QmitkDeformableClippingPlaneView::CreateConnections() +{ + connect(m_Controls->imageSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkDeformableClippingPlaneView::OnCurrentSelectionChanged); + connect(m_Controls->translationPushButton, &QPushButton::toggled, + this, &QmitkDeformableClippingPlaneView::OnTranslationMode); + connect(m_Controls->rotationPushButton, &QPushButton::toggled, + this, &QmitkDeformableClippingPlaneView::OnRotationMode); + connect(m_Controls->deformationPushButton, &QPushButton::toggled, + this, &QmitkDeformableClippingPlaneView::OnDeformationMode); + connect(m_Controls->createNewPlanePushButton, &QPushButton::clicked, + this, &QmitkDeformableClippingPlaneView::OnCreateNewClippingPlane); + connect(m_Controls->updateVolumePushButton, &QPushButton::clicked, + this, &QmitkDeformableClippingPlaneView::OnCalculateClippingVolume); + connect(m_Controls->clippingPlaneSelector, &QmitkDataStorageComboBox::OnSelectionChanged, + this, &QmitkDeformableClippingPlaneView::OnComboBoxSelectionChanged); +} + +void QmitkDeformableClippingPlaneView::NodeRemoved(const mitk::DataNode* node) +{ + if (m_IsClippingPlanePredicate->CheckNode(node)) + { + if (this->GetAllClippingPlanes()->Size() <= 1) + { + m_WorkingNode = nullptr; + this->UpdateView(); + } + } +} + +void::QmitkDeformableClippingPlaneView::NodeChanged(const mitk::DataNode*) +{ + this->UpdateView(); +} + +void QmitkDeformableClippingPlaneView::UpdateView() +{ + auto selectedNode = m_Controls->imageSelectionWidget->GetSelectedNode(); + if (selectedNode.IsNotNull()) + { + m_Controls->selectedReferenceImageLabel->setText(QString::fromUtf8(selectedNode->GetName().c_str())); + if (m_WorkingNode.IsNotNull()) + { + bool isSegmentation = false; + selectedNode->GetBoolProperty("binary", isSegmentation); + m_Controls->interactionSelectionBox->setEnabled(true); + + m_Controls->volumeGroupBox->setEnabled(isSegmentation); + + //clear list --> than search for all shown clipping plans (max 7 planes) + m_Controls->selectedClippingPlanesLabel->setText(""); + m_Controls->planesWarningLabel->hide(); + int volumePlanes = 0; + + auto allClippingPlanes = this->GetAllClippingPlanes(); + for (mitk::DataStorage::SetOfObjects::ConstIterator itPlanes = allClippingPlanes->Begin(); itPlanes != allClippingPlanes->End(); itPlanes++) + { + bool isVisible = false; + itPlanes.Value()->GetBoolProperty("visible", isVisible); + if (isVisible) + { + if (volumePlanes < 7) + { + ++volumePlanes; + m_Controls->selectedClippingPlanesLabel->setText(m_Controls->selectedClippingPlanesLabel->text().append(QString::fromStdString(itPlanes.Value()->GetName()+"\n"))); + } + else + { + m_Controls->planesWarningLabel->show(); + return; + } + } + } + } + else + { + m_Controls->volumeGroupBox->setEnabled(false); + m_Controls->interactionSelectionBox->setEnabled(false); + m_Controls->selectedClippingPlanesLabel->setText(""); + m_Controls->volumeList->clear(); + } + } + else + { + m_Controls->volumeGroupBox->setEnabled(false); + m_Controls->selectedReferenceImageLabel->setText(""); + m_Controls->selectedClippingPlanesLabel->setText(""); + m_Controls->planesWarningLabel->hide(); + + m_Controls->interactionSelectionBox->setEnabled(m_WorkingNode.IsNotNull()); + } +} + +mitk::DataStorage::SetOfObjects::ConstPointer QmitkDeformableClippingPlaneView::GetAllClippingPlanes() +{ + auto allPlanes = GetDataStorage()->GetSubset(m_IsClippingPlanePredicate); + return allPlanes; +} + +mitk::Color QmitkDeformableClippingPlaneView::GetLabelColor(int label) +{ + std::array color = { 0.0f, 0.0f, 0.0f }; + + switch (label % 6) + { + case 0: // red + color[0] = 1.0f; + break; + case 1: // green + color[1] = 1.0f; + break; + case 2: // blue + color[2] = 1.0f; + break; + case 3: // yellow + color[0] = 1.0f; + color[1] = 1.0f; + break; + case 4: // magenta + color[0] = 1.0f; + color[2] = 1.0f; + break; + case 5: // cyan + color[1] = 1.0f; + color[2] = 1.0f; + default: // black + break; + } + + int outerCycleNr = label / 6; + int cycleSize = std::min(1, static_cast(std::pow(2.0, std::log(outerCycleNr) / std::log(2.0)))); + int insideCycleCounter = outerCycleNr % cycleSize; + + float factor; + if (0 == outerCycleNr) + { + factor = 255.0f; + } + else + { + factor = 256.0f / (2.0f * cycleSize) + insideCycleCounter * (256.0f / cycleSize); + } + + color = { + std::min(1.0f, color[0] / 256.0f * factor), + std::min(1.0f, color[1] / 256.0f * factor), + std::min(1.0f, color[2] / 256.0f * factor) + }; + + return mitk::Color(color.data()); +} + void QmitkDeformableClippingPlaneView::DeactivateInteractionButtons() { - m_Controls.translationPushButton->setChecked(false); - m_Controls.rotationPushButton->setChecked(false); - m_Controls.deformationPushButton->setChecked(false); + m_Controls->translationPushButton->setChecked(false); + m_Controls->rotationPushButton->setChecked(false); + m_Controls->deformationPushButton->setChecked(false); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.h index 6bad16198b..5cc022e7f4 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneView.h @@ -1,81 +1,75 @@ /*============================================================================ 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. ============================================================================*/ -#ifndef _QMITKDEFORMABLECLIPPINGPLANEVIEW_H_INCLUDED -#define _QMITKDEFORMABLECLIPPINGPLANEVIEW_H_INCLUDED - +#ifndef QMITKDEFORMABLECLIPPINGPLANEVIEW_H +#define QMITKDEFORMABLECLIPPINGPLANEVIEW_H #include + #include +#include +#include #include typedef itk::RGBPixel< float > Color; -/*! -* \ingroup org_mitk_gui_qt_deformableSurface -* -* \brief QmitkDeformableClippingPlaneView -* -* Document your class here. -*/ +/** + * @brief + */ class QmitkDeformableClippingPlaneView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; QmitkDeformableClippingPlaneView(); ~QmitkDeformableClippingPlaneView() override; - void CreateQtPartControl(QWidget *parent) override; - - /// \brief Creation of the connections of main and control widget - virtual void CreateConnections(); - - /// - /// Sets the focus to an internal widget. - /// void SetFocus() override; -protected slots: +private Q_SLOTS: + void OnCurrentSelectionChanged(const QList& nodes); void OnComboBoxSelectionChanged(const mitk::DataNode* node); void OnCreateNewClippingPlane(); void OnCalculateClippingVolume(); void OnTranslationMode(bool check); void OnRotationMode(bool check); void OnDeformationMode(bool check); -protected: +private: + + void CreateQtPartControl(QWidget *parent) override; + virtual void CreateConnections(); - virtual void OnSelectionChanged(mitk::DataNode* node); - void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList& nodes) override; void NodeRemoved(const mitk::DataNode* node) override; void NodeChanged(const mitk::DataNode* node) override; void UpdateView(); - Ui::QmitkDeformableClippingPlaneViewControls m_Controls; - -private: mitk::DataStorage::SetOfObjects::ConstPointer GetAllClippingPlanes(); mitk::Color GetLabelColor(int label); void DeactivateInteractionButtons(); - mitk::DataNode::Pointer m_ReferenceNode; + Ui::QmitkDeformableClippingPlaneViewControls* m_Controls; + + mitk::NodePredicateAnd::Pointer m_IsImagePredicate; + mitk::NodePredicateProperty::Pointer m_IsClippingPlanePredicate; + mitk::DataNode::Pointer m_WorkingNode; + }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneViewControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneViewControls.ui index 8507259989..0130bd6153 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneViewControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkDeformableClippingPlaneViewControls.ui @@ -1,611 +1,431 @@ QmitkDeformableClippingPlaneViewControls 0 0 - 422 - 642 + 300 + 600 0 0 - QmitkDeformableSurface + Deformable surface - + - - - - 0 - 0 - - - - - - - - - 197 - 0 - 0 - - - - - - - 191 - 0 - 0 - - - - - - - 189 - 0 - 0 - - - - - - - - - 197 - 0 - 0 - - - - - - - 191 - 0 - 0 - - - - - - - 189 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - - - Please select an image! - - + + + + + Selected image + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + - - - false false - + 0 0 Create new clipping plane Qt::RightToLeft ...with surface model true - + 0 0 0 0 16777215 16777215 Plane 0 0 QComboBox::AdjustToMinimumContentsLength true 50 false 0 0 20 50 Translation - + :/org.mitk.gui.qt.segmentation/resources/clipping_plane_translate_48x48.png:/org.mitk.gui.qt.segmentation/resources/clipping_plane_translate_48x48.png 32 32 true false false 0 0 20 50 Rotation - + :/org.mitk.gui.qt.segmentation/resources/clipping_plane_rotate48x48.png:/org.mitk.gui.qt.segmentation/resources/clipping_plane_rotate48x48.png 32 32 true false 0 0 20 50 Deformation - + :/org.mitk.gui.qt.segmentation/resources/clipping_plane_deform48x48.png:/org.mitk.gui.qt.segmentation/resources/clipping_plane_deform48x48.png 32 32 true false Qt::Vertical QSizePolicy::Preferred 20 20 0 0 - - - - - - - 197 - 0 - 0 - - - - - - - 191 - 0 - 0 - - - - - - - 189 - 0 - 0 - - - - - - - - - 197 - 0 - 0 - - - - - - - 191 - 0 - 0 - - - - - - - 189 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - - 120 - 120 - 120 - - - - - - Please select less or equal 6 clipping planes! 0 0 - + 0 0 Referenced image - + 0 0 - Clipping planes + Clipping planes Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - + true 0 0 - - - MS Shell Dlg 2 - 8 - 50 - false - - - - - - + 8 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Update Volumina Qt::Vertical QSizePolicy::Preferred 20 20 true 0 0 + + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+
QmitkDataStorageComboBox QComboBox
QmitkDataStorageComboBox.h