diff --git a/CMake/mitkFunctionCreatePlugin.cmake b/CMake/mitkFunctionCreatePlugin.cmake index c80446d1d3..72fa8012d9 100644 --- a/CMake/mitkFunctionCreatePlugin.cmake +++ b/CMake/mitkFunctionCreatePlugin.cmake @@ -1,356 +1,365 @@ #! \brief Creates a MITK CTK plugin. #! #! This function should be called from the plugins CMakeLists.txt file. #! The target name is available after the macro call as ${PLUGIN_TARGET} #! to add additional libraries in your CMakeLists.txt. Include paths and link #! libraries are set depending on the value of the Required-Plugins header #! in your manifest_headers.cmake file. #! #! This function internally calls ctkMacroBuildPlugin() and adds support #! for Qt Help files and installers. #! #! Options: #! \param TEST_PLUGIN Mark this plug-in as a testing plug-in. #! \param NO_INSTALL Don't install this plug-in. #! #! Parameters: #! #! \param EXPORT_DIRECTIVE (required) The export directive to use in the generated #! _Exports.h file. #! #! Multi-value parameters (all optional): #! #! \param EXPORTED_INCLUDE_SUFFIXES A list of sub-directories which should #! be added to the current source directory. The resulting directories #! will be available in the set of include directories of depending plug-ins. #! \param MODULE_DEPENDS (optional) A list of Modules this plug-in depends on. #! \param PACKAGE_DEPENDS (optional) A list of external packages this plug-in depends on. #! \param DOXYGEN_TAGFILES (optional) Which external tag files should be available for the plugin documentation #! \param MOC_OPTIONS (optional) Additional options to pass to the Qt MOC compiler #! \param WARNINGS_NO_ERRORS (optional) Do not handle compiler warnings as errors function(mitk_create_plugin) # options set(arg_options TEST_PLUGIN # Mark this plug-in as a testing plug-in NO_INSTALL # Don't install this plug-in NO_QHP_TRANSFORM WARNINGS_NO_ERRORS ) # single value arguments set(arg_single EXPORT_DIRECTIVE # (required) TODO: could be generated via CMake as it is done for MITK modules already ) # multiple value arguments set(arg_multiple EXPORTED_INCLUDE_SUFFIXES # (optional) additional public include directories MODULE_DEPENDS # (optional) PACKAGE_DEPENDS DOXYGEN_TAGFILES MOC_OPTIONS SUBPROJECTS ) cmake_parse_arguments(_PLUGIN "${arg_options}" "${arg_single}" "${arg_multiple}" ${ARGN}) if(_PLUGIN_TEST_PLUGIN) set(_PLUGIN_NO_INSTALL 1) set(is_test_plugin "TEST_PLUGIN") else() set(is_test_plugin) endif() set(_PLUGIN_MOC_OPTIONS "-DBOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION -DBOOST_TT_HAS_OPERATOR_HPP_INCLUDED ${_PLUGIN_MOC_OPTIONS}") set(PLUGIN_TARGET ${PROJECT_NAME}) mitk_check_module_dependencies(MODULES ${_PLUGIN_MODULE_DEPENDS} PACKAGES ${_PLUGIN_PACKAGE_DEPENDS} MISSING_DEPENDENCIES_VAR _missing_deps MODULE_DEPENDENCIES_VAR _module_deps PACKAGE_DEPENDENCIES_VAR _package_deps) if(_missing_deps) if(NOT MITK_BUILD_ALL_PLUGINS) message(SEND_ERROR "${PROJECT_NAME} is missing requirements and won't be built. Missing: ${_missing_deps}") else() message(STATUS "${PROJECT_NAME} is missing requirements and won't be built. Missing: ${_missing_deps}") endif() return() endif() + foreach(_module_dep ${_PLUGIN_MODULE_DEPENDS}) + if(TARGET ${_module_dep}) + get_target_property(AUTLOAD_DEP ${_module_dep} MITK_AUTOLOAD_DIRECTORY) + if (AUTLOAD_DEP) + message(SEND_ERROR "Plugin \"${PROJECT_NAME}\" has an invalid dependency on autoload module \"${_module_dep}\". Check MITK_CREATE_PLUGIN usage for \"${PROJECT_NAME}\".") + endif() + endif() + endforeach() + # -------------- All dependencies are resolved ------------------ message(STATUS "Creating CTK plugin ${PROJECT_NAME}") include(files.cmake) set(_PLUGIN_CPP_FILES ${CPP_FILES}) set(_PLUGIN_MOC_H_FILES ${MOC_H_FILES}) set(_PLUGIN_UI_FILES ${UI_FILES}) set(_PLUGIN_CACHED_RESOURCE_FILES ${CACHED_RESOURCE_FILES}) set(_PLUGIN_TRANSLATION_FILES ${TRANSLATION_FILES}) set(_PLUGIN_QRC_FILES ${QRC_FILES}) set(_PLUGIN_H_FILES ${H_FILES}) set(_PLUGIN_TXX_FILES ${TXX_FILES}) set(_PLUGIN_DOX_FILES ${DOX_FILES}) set(_PLUGIN_CMAKE_FILES ${CMAKE_FILES} files.cmake) set(_PLUGIN_FILE_DEPENDENCIES ${FILE_DEPENDENCIES}) if(CTK_PLUGINS_OUTPUT_DIR) set(_output_dir "${CTK_PLUGINS_OUTPUT_DIR}") else() set(_output_dir "") endif() # Compute the plugin dependencies ctkFunctionGetTargetLibraries(_PLUGIN_target_libraries "") #------------------------------------------------------------# #------------------ Qt Help support -------------------------# set(PLUGIN_GENERATED_QCH_FILES ) if(BLUEBERRY_USE_QT_HELP AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/documentation/UserManual") set(PLUGIN_DOXYGEN_INPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/documentation/UserManual") set(PLUGIN_DOXYGEN_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/documentation/UserManual") # Create a list of Doxygen tag files from the plug-in dependencies set(PLUGIN_DOXYGEN_TAGFILES) foreach(_dep_target ${_PLUGIN_target_libraries}) string(REPLACE _ . _dep ${_dep_target}) get_target_property(_is_imported ${_dep_target} IMPORTED) if(_is_imported) get_target_property(_import_loc_debug ${_dep_target} IMPORTED_LOCATION_DEBUG) get_target_property(_import_loc_release ${_dep_target} IMPORTED_LOCATION_RELEASE) # There is not necessarily a debug and release build if(_import_loc_release) set(_import_loc ${_import_loc_release}) else() set(_import_loc ${_import_loc_debug}) endif() get_filename_component(_target_filename "${_import_loc}" NAME) # on windows there might be a Debug or Release subdirectory string(REGEX REPLACE "/bin/plugins/(Debug/|Release/)?${_target_filename}" "/Plugins/${_dep}/documentation/UserManual" plugin_tag_dir "${_import_loc}" ) else() set(plugin_tag_dir "${CMAKE_BINARY_DIR}/Plugins/${_dep}/documentation/UserManual") endif() set(_tag_file "${plugin_tag_dir}/${_dep_target}.tag") if(EXISTS ${_tag_file}) set(PLUGIN_DOXYGEN_TAGFILES "${PLUGIN_DOXYGEN_TAGFILES} \"${_tag_file}=qthelp://${_dep}/bundle/\"") endif() endforeach() if(_PLUGIN_DOXYGEN_TAGFILES) set(PLUGIN_DOXYGEN_TAGFILES "${PLUGIN_DOXYGEN_TAGFILES} ${_PLUGIN_DOXYGEN_TAGFILES}") endif() #message("PLUGIN_DOXYGEN_TAGFILES: ${PLUGIN_DOXYGEN_TAGFILES}") if(_PLUGIN_NO_QHP_TRANSFORM) set(_use_qhp_xsl 0) else() set(_use_qhp_xsl 1) endif() _FUNCTION_CREATE_CTK_QT_COMPRESSED_HELP(PLUGIN_GENERATED_QCH_FILES ${_use_qhp_xsl}) list(APPEND _PLUGIN_CACHED_RESOURCE_FILES ${PLUGIN_GENERATED_QCH_FILES}) endif() #------------------------------------------------------------# #------------------ Create Plug-in --------------------------# mitkFunctionOrganizeSources( SOURCE ${_PLUGIN_CPP_FILES} HEADER ${_PLUGIN_H_FILES} TXX ${_PLUGIN_TXX_FILES} DOC ${_PLUGIN_DOX_FILES} UI ${_PLUGIN_UI_FILES} QRC ${_PLUGIN_QRC_FILES} ${_PLUGIN_CACHED_RESOURCE_FILES} META ${_PLUGIN_META_FILES} MOC ${MY_MOC_CPP} GEN_UI ${MY_UI_CPP} GEN_QRC ${MY_QRC_SRCS} ) ctkMacroBuildPlugin( NAME ${PLUGIN_TARGET} EXPORT_DIRECTIVE ${_PLUGIN_EXPORT_DIRECTIVE} SRCS ${_PLUGIN_CPP_FILES} ${_PLUGIN_H_FILES} ${CORRESPONDING__H_FILES} ${GLOBBED__H_FILES} MOC_SRCS ${_PLUGIN_MOC_H_FILES} MOC_OPTIONS ${_PLUGIN_MOC_OPTIONS} UI_FORMS ${_PLUGIN_UI_FILES} EXPORTED_INCLUDE_SUFFIXES ${_PLUGIN_EXPORTED_INCLUDE_SUFFIXES} RESOURCES ${_PLUGIN_QRC_FILES} TARGET_LIBRARIES ${_PLUGIN_target_libraries} CACHED_RESOURCEFILES ${_PLUGIN_CACHED_RESOURCE_FILES} TRANSLATIONS ${_PLUGIN_TRANSLATION_FILES} OUTPUT_DIR ${_output_dir} NO_INSTALL # we install the plug-in ourselves ${is_test_plugin} ) mitk_use_modules(TARGET ${PLUGIN_TARGET} MODULES ${_PLUGIN_MODULE_DEPENDS} PACKAGES ${_PLUGIN_PACKAGE_DEPENDS} ) set_property(TARGET ${PLUGIN_TARGET} APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=${PLUGIN_TARGET}) set_property(TARGET ${PLUGIN_TARGET} PROPERTY US_MODULE_NAME ${PLUGIN_TARGET}) if(NOT CMAKE_CURRENT_SOURCE_DIR MATCHES "^${CMAKE_SOURCE_DIR}.*") foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) if(CMAKE_CURRENT_SOURCE_DIR MATCHES "^${MITK_EXTENSION_DIR}.*") get_filename_component(MITK_EXTENSION_ROOT_FOLDER ${MITK_EXTENSION_DIR} NAME) set_property(TARGET ${PLUGIN_TARGET} PROPERTY FOLDER "${MITK_EXTENSION_ROOT_FOLDER}/Plugins") break() endif() endforeach() else() set_property(TARGET ${PLUGIN_TARGET} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/Plugins") endif() set(plugin_c_flags) set(plugin_cxx_flags) if(NOT _PLUGIN_WARNINGS_NO_ERRORS) if(MSVC_VERSION) mitkFunctionCheckCAndCXXCompilerFlags("/WX" plugin_c_flags plugin_cxx_flags) else() mitkFunctionCheckCAndCXXCompilerFlags(-Werror plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=c++0x-static-nonintegral-init" plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=static-member-init" plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=unknown-warning" plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=gnu" plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=cast-function-type" plugin_c_flags plugin_cxx_flags) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=inconsistent-missing-override" plugin_c_flags plugin_cxx_flags) endif() endif() if(plugin_c_flags) string(REPLACE " " ";" plugin_c_flags "${plugin_c_flags}") target_compile_options(${PLUGIN_TARGET} PRIVATE ${plugin_c_flags}) endif() if(plugin_cxx_flags) string(REPLACE " " ";" plugin_cxx_flags "${plugin_cxx_flags}") target_compile_options(${PLUGIN_TARGET} PRIVATE ${plugin_cxx_flags}) endif() if(NOT MY_SUBPROJECTS) if(MITK_DEFAULT_SUBPROJECTS) set(MY_SUBPROJECTS ${MITK_DEFAULT_SUBPROJECTS}) elseif(TARGET MITK-Plugins) set(MY_SUBPROJECTS MITK-Plugins) endif() endif() if(MY_SUBPROJECTS) set_property(TARGET ${PLUGIN_TARGET} PROPERTY LABELS ${MY_SUBPROJECTS}) foreach(subproject ${MY_SUBPROJECTS}) add_dependencies(${subproject} ${PLUGIN_TARGET}) endforeach() endif() if(_PLUGIN_TEST_PLUGIN) find_package(CppUnit REQUIRED) target_include_directories(${PLUGIN_TARGET} PRIVATE ${CppUnit_INCLUDE_DIRS}) target_link_libraries(${PLUGIN_TARGET} PRIVATE ${CppUnit_LIBRARIES}) endif() if(mbilog_FOUND) target_link_libraries(${PLUGIN_TARGET} PRIVATE mbilog) endif() set(_PLUGIN_META_FILES "${CMAKE_CURRENT_SOURCE_DIR}/manifest_headers.cmake") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/plugin.xml") list(APPEND _PLUGIN_META_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin.xml") endif() set(PLUGIN_TARGET ${PLUGIN_TARGET} PARENT_SCOPE) #------------------------------------------------------------# #------------------ Installer support -----------------------# if(NOT _PLUGIN_NO_INSTALL) set(install_directories "") if(NOT MACOSX_BUNDLE_NAMES) set(install_directories bin/plugins) else(NOT MACOSX_BUNDLE_NAMES) foreach(bundle_name ${MACOSX_BUNDLE_NAMES}) list(APPEND install_directories ${bundle_name}.app/Contents/MacOS/plugins) endforeach(bundle_name) endif(NOT MACOSX_BUNDLE_NAMES) foreach(install_subdir ${install_directories}) mitkFunctionInstallCTKPlugin(TARGETS ${PLUGIN_TARGET} DESTINATION ${install_subdir}) endforeach() set(_autoload_targets ) foreach(_dependency ${_module_deps}) get_target_property(_dep_autoloads ${_dependency} MITK_AUTOLOAD_TARGETS) if (_dep_autoloads) list(APPEND _autoload_targets ${_dep_autoloads}) endif() endforeach() # The MITK_AUTOLOAD_TARGETS property is used in the mitkFunctionInstallAutoLoadModules # macro which expects a list of plug-in targets. if (_autoload_targets) list(REMOVE_DUPLICATES _autoload_targets) set_target_properties(${PLUGIN_TARGET} PROPERTIES MITK_AUTOLOAD_TARGETS "${_autoload_targets}") endif() endif() endfunction() function(_FUNCTION_CREATE_CTK_QT_COMPRESSED_HELP qch_file use_xsl) set(_manifest_path "${CMAKE_CURRENT_SOURCE_DIR}/manifest_headers.cmake") if(NOT EXISTS ${_manifest_path}) message(FATAL_ERROR "${_manifest_path} not found") endif() include(${_manifest_path}) string(REPLACE "_" "." Plugin-SymbolicName "${PLUGIN_TARGET}") configure_file(${MITK_SOURCE_DIR}/Documentation/doxygen_plugin_manual.conf.in ${PLUGIN_DOXYGEN_OUTPUT_DIR}/doxygen.conf ) set(_qhp_xsl_file "${MITK_SOURCE_DIR}/Documentation/qhp_toc.xsl") set(_generated_qhp_file "${PLUGIN_DOXYGEN_OUTPUT_DIR}/html/index.qhp") set(_transformed_qhp_file "${PLUGIN_DOXYGEN_OUTPUT_DIR}/html/${PLUGIN_TARGET}.qhp") set(${qch_file} "${CMAKE_CURRENT_BINARY_DIR}/resources/${PLUGIN_TARGET}.qch") set(_xsl_command ) if(use_xsl) set(_xsl_command COMMAND ${QT_XMLPATTERNS_EXECUTABLE} ${_qhp_xsl_file} ${_generated_qhp_file} -output ${_transformed_qhp_file}) endif() file(GLOB _file_dependencies "${PLUGIN_DOXYGEN_INPUT_DIR}/*") add_custom_command(OUTPUT ${${qch_file}} # Generate a Qt help project (index.qhp) with doxygen COMMAND ${DOXYGEN_EXECUTABLE} ${PLUGIN_DOXYGEN_OUTPUT_DIR}/doxygen.conf # Use a XSL transformation to get rid of the top-level entry ${_xsl_command} # Generate the final Qt compressed help file (.qch) COMMAND ${QT_HELPGENERATOR_EXECUTABLE} ${_transformed_qhp_file} -o ${${qch_file}} DEPENDS ${PLUGIN_DOXYGEN_OUTPUT_DIR}/doxygen.conf ${_file_dependencies} ) #set_source_files_properties(${qch_file} PROPERTIES GENERATED 1) set(${qch_file} ${${qch_file}} PARENT_SCOPE) endfunction() function(MACRO_CREATE_MITK_CTK_PLUGIN) message(SEND_ERROR "The function MACRO_CREATE_MITK_CTK_PLUGIN was renamed to mitk_create_plugin in MITK 2015.05.") endfunction() diff --git a/CMake/mitkInstallRules.cmake b/CMake/mitkInstallRules.cmake index ec850c1efc..d63fd4bdfd 100644 --- a/CMake/mitkInstallRules.cmake +++ b/CMake/mitkInstallRules.cmake @@ -1,152 +1,156 @@ # Install MITK icon and logo MITK_INSTALL(FILES "${MITK_SOURCE_DIR}/mitk.ico" "${MITK_SOURCE_DIR}/mitk.bmp") # Helper vars if(WIN32) set(_prefix "") set(_ext ".dll") elseif(UNIX) set(_prefix "lib") if(APPLE) set(_ext ".dylib") else() set(_ext ".so") endif() endif() # Install MITK executables including auto-load modules get_property(_mitk_executable_targets GLOBAL PROPERTY MITK_EXECUTABLE_TARGETS) if(_mitk_executable_targets) get_property(_mitk_module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) foreach(_mitk_module_target ${_mitk_module_targets}) if(TARGET ${_mitk_module_target}) get_target_property(_mitk_autoload_targets ${_mitk_module_target} MITK_AUTOLOAD_TARGETS) if (_mitk_autoload_targets) foreach(_mitk_autoload_target ${_mitk_autoload_targets}) get_target_property(_mitk_autoload_directory ${_mitk_autoload_target} MITK_AUTOLOAD_DIRECTORY) if (_mitk_autoload_directory) if(WIN32) get_target_property(_target_location ${_mitk_autoload_target} RUNTIME_OUTPUT_DIRECTORY) else() get_target_property(_target_location ${_mitk_autoload_target} LIBRARY_OUTPUT_DIRECTORY) endif() if(NOT CMAKE_CFG_INTDIR STREQUAL ".") set(_target_location "${_target_location}/Release") endif() set(_mitk_autoload_target_filename "${_prefix}${_mitk_autoload_target}${_ext}") set(_mitk_autoload_target_filepath "${_target_location}/${_mitk_autoload_target_filename}") set(_install_DESTINATION "${_mitk_autoload_directory}") MITK_INSTALL(FILES ${_mitk_autoload_target_filepath}) if(UNIX AND NOT APPLE) install(CODE "file(RPATH_REMOVE FILE \"\${CMAKE_INSTALL_PREFIX}/bin/${_mitk_autoload_directory}/${_mitk_autoload_target_filename}\")") endif() endif() endforeach() endif() endif() endforeach() set(_install_DESTINATION "") foreach(_mitk_executable_target ${_mitk_executable_targets}) + get_target_property(_no_install ${_mitk_executable_target} NO_INSTALL) + if(_no_install) + continue() + endif() MITK_INSTALL_TARGETS(EXECUTABLES ${_mitk_executable_target} GLOB_PLUGINS) if(UNIX AND NOT APPLE) install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/RunInstalledApp.sh" DESTINATION "." RENAME ${_mitk_executable_target}.sh) endif() endforeach() endif() # Install PythonQt if(MITK_USE_Python3 AND PythonQt_DIR) set(_python_qt_lib "${PythonQt_DIR}/") if(WIN32) set(_python_qt_lib "${_python_qt_lib}bin") else() set(_python_qt_lib "${_python_qt_lib}lib") endif() set(_python_qt_lib "${_python_qt_lib}/${_prefix}PythonQt${_ext}") MITK_INSTALL(FILES ${_python_qt_lib}) endif() # Install Qt plugins if(MITK_USE_Qt5) get_property(_qmake_location TARGET ${Qt5Core_QMAKE_EXECUTABLE} PROPERTY IMPORT_LOCATION) get_filename_component(_qmake_path ${_qmake_location} DIRECTORY) set(_install_DESTINATION "plugins/sqldrivers") MITK_INSTALL(FILES "${_qmake_path}/../plugins/sqldrivers/${_prefix}qsqlite${_ext}") set(_install_DESTINATION "plugins/imageformats") MITK_INSTALL(FILES "${_qmake_path}/../plugins/imageformats/${_prefix}qsvg${_ext}") set(_install_DESTINATION "plugins/iconengines") MITK_INSTALL(FILES "${_qmake_path}/../plugins/iconengines/${_prefix}qsvgicon${_ext}") # Install platform-specific Qt plugins set(_install_DESTINATION "plugins/platforms") if(WIN32) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/qwindows.dll") elseif(APPLE) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/libqcocoa.dylib") elseif(UNIX) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/libqxcb.so") set(_install_DESTINATION "plugins/xcbglintegrations") MITK_INSTALL(FILES "${_qmake_path}/../plugins/xcbglintegrations/libqxcb-glx-integration.so") endif() # Install platform-specific Qt styles set(_install_DESTINATION "plugins/styles") if(WIN32) MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/qwindowsvistastyle.dll") elseif(APPLE) MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/libqmacstyle.dylib") endif() # Install Qt WebEngine if(APPLE) set(_install_DESTINATION "../Frameworks/QtWebEngineCore.framework") get_filename_component(_real_path "${_qmake_path}/../lib/QtWebEngineCore.framework/Helpers" REALPATH) MITK_INSTALL(DIRECTORY ${_real_path} USE_SOURCE_PERMISSIONS) # Translations are included in the Resources directory of # QtWebEngineCore.framework and are installed by default. else() set(_install_DESTINATION "") if(WIN32) MITK_INSTALL(PROGRAMS "${_qmake_path}/QtWebEngineProcess.exe") elseif(UNIX) MITK_INSTALL(PROGRAMS "${_qmake_path}/../libexec/QtWebEngineProcess") endif() MITK_INSTALL(DIRECTORY "${_qmake_path}/../resources") set(_install_DESTINATION "translations") MITK_INSTALL(DIRECTORY "${_qmake_path}/../translations/qtwebengine_locales") endif() endif() set(_install_DESTINATION "") # Install MatchPoint binaries that are not auto-detected if(MITK_USE_MatchPoint) MITK_INSTALL(DIRECTORY "${MITK_EXTERNAL_PROJECT_PREFIX}/bin/" FILES_MATCHING PATTERN "MapUtilities*") MITK_INSTALL(DIRECTORY "${MITK_EXTERNAL_PROJECT_PREFIX}/bin/" FILES_MATCHING PATTERN "MapAlgorithms*") endif() # IMPORTANT: Restore default install destination! Do not edit this file beyond this line! set(_install_DESTINATION "") diff --git a/CMake/mitkMacroCreateExecutable.cmake b/CMake/mitkMacroCreateExecutable.cmake index 6f3a3048b0..c9c9d130ed 100644 --- a/CMake/mitkMacroCreateExecutable.cmake +++ b/CMake/mitkMacroCreateExecutable.cmake @@ -1,112 +1,117 @@ ################################################################## # # MITK_CREATE_EXECUTABLE # #! Creates an executable with MITK dependencies and batch files #! for proper application start-up. #! #! USAGE: #! #! \code #! MITK_CREATE_EXECUTABLE( [] #! [DEPENDS ] #! [PACKAGE_DEPENDS ] #! [INCLUDE_DIRS ] #! [TARGET_DEPENDS #! [WARNINGS_NO_ERRORS] +#! [NO_INSTALL] #! \endcode #! #! \param EXECUTABLE_NAME The name for the new executable target ################################################################## macro(mitk_create_executable) set(_macro_params VERSION # version number, e.g. "1.2.0" FILES_CMAKE # file name of a CMake file setting source list variables # (defaults to files.cmake) DESCRIPTION # a description for the executable ) set(_macro_multiparams SUBPROJECTS # list of CDash labels INCLUDE_DIRS # additional include dirs DEPENDS # list of modules this module depends on PACKAGE_DEPENDS # list of "packages" this module depends on (e.g. Qt, VTK, etc.) TARGET_DEPENDS # list of CMake targets this executable should depend on ADDITIONAL_LIBS # list of additional libraries linked to this executable CPP_FILES # (optional) list of cpp files ) set(_macro_options NO_INIT # do not create CppMicroServices initialization code NO_FEATURE_INFO # do not create a feature info by calling add_feature_info() NO_BATCH_FILE # do not create batch files on Windows WARNINGS_NO_ERRORS # do not treat compiler warnings as errors + NO_INSTALL ) cmake_parse_arguments(EXEC "${_macro_options}" "${_macro_params}" "${_macro_multiparams}" ${ARGN}) set(_EXEC_OPTIONS EXECUTABLE) if(EXEC_NO_INIT) list(APPEND _EXEC_OPTIONS NO_INIT) endif() if(EXEC_WARNINGS_NO_ERRORS) list(APPEND _EXEC_OPTIONS WARNINGS_NO_ERRORS) endif() if(EXEC_NO_FEATURE_INFO) list(APPEND _EXEC_OPTIONS NO_FEATURE_INFO) endif() mitk_create_module(${EXEC_UNPARSED_ARGUMENTS} SUBPROJECTS ${EXEC_SUBPROJECTS} VERSION ${EXEC_VERSION} INCLUDE_DIRS ${EXEC_INCLUDE_DIRS} DEPENDS ${EXEC_DEPENDS} PACKAGE_DEPENDS ${EXEC_PACKAGE_DEPENDS} TARGET_DEPENDS ${EXEC_TARGET_DEPENDS} ADDITIONAL_LIBS ${EXEC_ADDITIONAL_LIBS} FILES_CMAKE ${EXEC_FILES_CMAKE} CPP_FILES ${EXEC_CPP_FILES} DESCRIPTION "${DESCRIPTION}" ${_EXEC_OPTIONS} ) set(EXECUTABLE_IS_ENABLED ${MODULE_IS_ENABLED}) set(EXECUTABLE_TARGET ${MODULE_TARGET}) if(EXECUTABLE_IS_ENABLED) set_property(GLOBAL APPEND PROPERTY MITK_EXECUTABLE_TARGETS ${EXECUTABLE_TARGET}) + if(EXEC_NO_INSTALL) + set_target_properties(${EXECUTABLE_TARGET} PROPERTIES NO_INSTALL TRUE) + endif() # Add meta dependencies (e.g. on auto-load modules from depending modules) if(TARGET ${CMAKE_PROJECT_NAME}-autoload) add_dependencies(${MODULE_TARGET} ${CMAKE_PROJECT_NAME}-autoload) endif() # Create batch and VS user files for Windows platforms include(mitkFunctionCreateWindowsBatchScript) if(WIN32) set(_batch_file_in "${CMAKE_CURRENT_SOURCE_DIR}/${MODULE_TARGET}.bat.in") if(NOT EXISTS "${_batch_file_in}") set(_batch_file_in "${MITK_CMAKE_DIR}/StartApp.bat.in") endif() if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(_batch_file_out_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") else() set(_batch_file_out_dir "${CMAKE_CURRENT_BINARY_DIR}") endif() if(NOT EXEC_NO_BATCH_FILE) if(NOT EXEC_NAME) set(EXEC_NAME ${MODULE_TARGET}) endif() foreach(BUILD_TYPE debug release) mitkFunctionCreateWindowsBatchScript( ${_batch_file_in} ${_batch_file_out_dir}/${MODULE_TARGET}_${BUILD_TYPE}.bat ${BUILD_TYPE} ) endforeach() endif() mitkFunctionConfigureVisualStudioUserProjectFile( NAME ${MODULE_TARGET} ) endif() endif() endmacro() diff --git a/CMake/mitkMacroCreateModuleTests.cmake b/CMake/mitkMacroCreateModuleTests.cmake index 9cd2e0a889..9c6c81a7f3 100644 --- a/CMake/mitkMacroCreateModuleTests.cmake +++ b/CMake/mitkMacroCreateModuleTests.cmake @@ -1,106 +1,109 @@ # # Create tests and testdriver for this module # # Usage: MITK_CREATE_MODULE_TESTS( [EXTRA_DRIVER_INIT init_code] ) # # EXTRA_DRIVER_INIT is inserted as c++ code in the testdriver and will be executed before each test # macro(MITK_CREATE_MODULE_TESTS) cmake_parse_arguments(MODULE_TEST "US_MODULE;NO_INIT" "EXTRA_DRIVER_INIT;EXTRA_DRIVER_INCLUDE" "EXTRA_DEPENDS;DEPENDS;PACKAGE_DEPENDS" ${ARGN}) if(BUILD_TESTING AND MODULE_IS_ENABLED) include(files.cmake) include_directories(.) set(TESTDRIVER ${MODULE_NAME}TestDriver) set(MODULE_TEST_EXTRA_DRIVER_INIT "${MODULE_TEST_EXTRA_DRIVER_INIT}") if(MITK_XVFB_TESTING) set(xvfb_run "xvfb-run" "--auto-servernum") else() set(xvfb_run ) endif() if(MODULE_TEST_US_MODULE) message(WARNING "The US_MODULE argument is deprecated and should be removed") endif() if(MODULE_TEST_US_MODULE AND MODULE_TEST_NO_INIT) message(WARNING "Conflicting arguments US_MODULE and NO_INIT: NO_INIT wins.") endif() set(_no_init) if(MODULE_TEST_NO_INIT) set(_no_init NO_INIT) endif() set(MITK_MODULE_NAME_REGEX_MATCH ) set(MITK_MODULE_NAME_REGEX_NOT_MATCH ) set(_testdriver_file_list ${CMAKE_CURRENT_BINARY_DIR}/testdriver_files.cmake) configure_file(${MITK_CMAKE_DIR}/mitkTestDriverFiles.cmake.in ${_testdriver_file_list} @ONLY) mitk_create_executable(${TESTDRIVER} DEPENDS ${MODULE_NAME} ${MODULE_TEST_DEPENDS} ${MODULE_TEST_EXTRA_DEPENDS} MitkTestingHelper PACKAGE_DEPENDS ${MODULE_TEST_PACKAGE_DEPENDS} SUBPROJECTS ${MODULE_SUBPROJECTS} FILES_CMAKE ${_testdriver_file_list} - NO_FEATURE_INFO NO_BATCH_FILE ${_no_init}) + NO_FEATURE_INFO + NO_BATCH_FILE + NO_INSTALL + ${_no_init}) set_property(TARGET ${EXECUTABLE_TARGET} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/Modules/Tests") # # Now tell CMake which tests should be run. This is done automatically # for all tests in ${KITNAME}_TESTS and ${KITNAME}_IMAGE_TESTS. The IMAGE_TESTS # are run for each image in the TESTIMAGES list. # include(files.cmake) foreach( test ${MODULE_TESTS} ) get_filename_component(TName ${test} NAME_WE) add_test(NAME ${TName} COMMAND ${xvfb_run} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} ${TName}) # Add labels for CDash subproject support if(MODULE_SUBPROJECTS) set_property(TEST ${TName} PROPERTY LABELS ${MODULE_SUBPROJECTS} MITK) endif() mitkFunctionGetLibrarySearchPaths(MITK_RUNTIME_PATH_RELEASE release RELEASE) mitkFunctionGetLibrarySearchPaths(MITK_RUNTIME_PATH_DEBUG debug DEBUG) set(test_env_path ${MITK_RUNTIME_PATH_RELEASE} ${MITK_RUNTIME_PATH_DEBUG} $ENV{PATH}) list(REMOVE_DUPLICATES test_env_path) string (REGEX REPLACE "\;" "\\\;" test_env_path "${test_env_path}") set_property(TEST ${TName} PROPERTY ENVIRONMENT "PATH=${test_env_path}" APPEND) set_property(TEST ${TName} PROPERTY SKIP_RETURN_CODE 77) endforeach() set(TEST_TYPES IMAGE SURFACE POINTSET) # add other file types here foreach(test_type ${TEST_TYPES}) foreach(test_data ${MODULE_TEST${test_type}} ${ADDITIONAL_TEST_${test_type}}) if(EXISTS ${test_data}) set(TEST_DATA_FULL_PATH ${test_data}) else() # todo: maybe search other paths as well # yes, please in mitk/Testing/Data, too set(TEST_DATA_FULL_PATH ${MITK_DATA_DIR}/${test_data}) endif() if(EXISTS ${TEST_DATA_FULL_PATH}) foreach( test ${MODULE_${test_type}_TESTS}) get_filename_component(TName ${test} NAME_WE) get_filename_component(DName ${TEST_DATA_FULL_PATH} NAME) add_test(NAME ${TName}_${DName} COMMAND ${xvfb_run} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} ${TName} ${TEST_DATA_FULL_PATH}) # Add labels for CDash subproject support if(MODULE_SUBPROJECTS) set_property(TEST ${TName}_${DName} PROPERTY LABELS ${MODULE_SUBPROJECTS} MITK) endif() set_property(TEST ${TName}_${DName} PROPERTY ENVIRONMENT "PATH=${test_env_path}" APPEND) set_property(TEST ${TName}_${DName} PROPERTY SKIP_RETURN_CODE 77) endforeach() else() message("!!!!! No such file: ${TEST_DATA_FULL_PATH} !!!!!") endif() endforeach() endforeach() endif() endmacro() diff --git a/CMakeLists.txt b/CMakeLists.txt index 48e767fb67..0431e192ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1470 +1,1473 @@ set(MITK_CMAKE_MINIMUM_REQUIRED_VERSION 3.14.5) cmake_minimum_required(VERSION ${MITK_CMAKE_MINIMUM_REQUIRED_VERSION}) #----------------------------------------------------------------------------- # See https://cmake.org/cmake/help/v3.14/manual/cmake-policies.7.html for details #----------------------------------------------------------------------------- set(project_policies ) foreach(policy ${project_policies}) if(POLICY ${policy}) cmake_policy(SET ${policy} NEW) endif() endforeach() #----------------------------------------------------------------------------- # Superbuild Option - Enabled by default #----------------------------------------------------------------------------- option(MITK_USE_SUPERBUILD "Build MITK and the projects it depends on via SuperBuild.cmake." ON) if(MITK_USE_SUPERBUILD) project(MITK-superbuild) set(MITK_SOURCE_DIR ${PROJECT_SOURCE_DIR}) set(MITK_BINARY_DIR ${PROJECT_BINARY_DIR}) else() project(MITK VERSION 2018.04.99) include_directories(SYSTEM ${MITK_SUPERBUILD_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # MITK Extension Feature #----------------------------------------------------------------------------- set(MITK_EXTENSION_DIRS "" CACHE STRING "") set(MITK_DIR_PLUS_EXTENSION_DIRS ${MITK_SOURCE_DIR} ${MITK_EXTENSION_DIRS}) #----------------------------------------------------------------------------- # Update CMake module path #----------------------------------------------------------------------------- set(MITK_CMAKE_DIR ${MITK_SOURCE_DIR}/CMake) set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake) get_filename_component(MITK_CMAKE_EXTENSION_DIR ${MITK_CMAKE_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTENSION_DIR}) list(APPEND CMAKE_MODULE_PATH ${MITK_CMAKE_EXTENSION_DIR}) endif() endforeach() #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- # Standard CMake macros include(FeatureSummary) include(CTestUseLaunchers) include(CMakeParseArguments) include(FindPackageHandleStandardArgs) # MITK macros include(mitkFunctionGetGccVersion) include(mitkFunctionCheckCompilerFlags) include(mitkFunctionSuppressWarnings) # includes several functions include(mitkMacroEmptyExternalProject) include(mitkFunctionGenerateProjectXml) include(mitkFunctionEnableBuildConfiguration) include(mitkFunctionWhitelists) include(mitkFunctionAddExternalProject) include(mitkFunctionAddLibrarySearchPaths) SUPPRESS_VC_DEPRECATED_WARNINGS() #----------------------------------------------------------------------------- # Set a default build type if none was specified #----------------------------------------------------------------------------- if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified.") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() #----------------------------------------------------------------------------- # Check miminum macOS version #----------------------------------------------------------------------------- # The minimum supported macOS version is 10.13. If you use a version less than 10.13, there is no guarantee that the build still works. if(APPLE) exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE macos_version) if (macos_version VERSION_LESS "10.13") message(WARNING "Detected macOS version \"${macos_version}\" is not supported anymore. Minimum required macOS version is at least 10.13.") endif() if (CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.13) message(WARNING "Detected macOS deployment target \"${CMAKE_OSX_DEPLOYMENT_TARGET}\" is not supported anymore. Minimum required macOS version is at least 10.13.") endif() endif() #----------------------------------------------------------------------------- # Check miminum compiler versions #----------------------------------------------------------------------------- if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # require at least gcc 4.9 as provided by ppa:ubuntu-toolchain-r/test for Ubuntu 14.04 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) message(FATAL_ERROR "GCC version must be at least 4.9 If you are using Ubuntu 14.04, you can easily install gcc and g++ 4.9 (or any later version available) in addition to your version ${CMAKE_CXX_COMPILER_VERSION}: sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install gcc-4.9 g++-4.9 Make sure to explicitly specify these compilers when configuring MITK: CMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc-4.9 CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++-4.9 For more information on the proposed PPA see the Toolchain Updates section of https://wiki.ubuntu.com/ToolChain.") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # require at least clang 3.4 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4) message(FATAL_ERROR "Clang version must be at least 3.4") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") # require at least clang 5.0 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) message(FATAL_ERROR "Apple Clang version must be at least 5.0") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # require at least Visual Studio 2017 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10) message(FATAL_ERROR "Microsoft Visual Studio 2017 or newer required") endif() else() message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang (Linux or Apple), GCC and MSVC.") endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionGetGccVersion(${CMAKE_CXX_COMPILER} GCC_VERSION) else() set(GCC_VERSION 0) endif() set(MITK_CXX_STANDARD 14) set(CMAKE_CXX_EXTENSIONS 0) set(CMAKE_CXX_STANDARD ${MITK_CXX_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED 1) # This is necessary to avoid problems with compile feature checks. # CMAKE_CXX_STANDARD seems to only set the -std=c++14 flag for targets. # However, compile flag checks also need to be done with -std=c++14. # The MITK_CXX14_FLAG variable is also used for external projects # build during the MITK super-build. mitkFunctionCheckCompilerFlags("-std=c++14" MITK_CXX14_FLAG) #----------------------------------------------------------------------------- # Warn if source or build path is too long #----------------------------------------------------------------------------- if(WIN32) set(_src_dir_length_max 50) set(_bin_dir_length_max 50) if(MITK_USE_SUPERBUILD) set(_src_dir_length_max 34) # _src_dir_length_max - strlen(ep/src/ITK-build) set(_bin_dir_length_max 40) # _bin_dir_length_max - strlen(MITK-build) endif() string(LENGTH "${MITK_SOURCE_DIR}" _src_n) string(LENGTH "${MITK_BINARY_DIR}" _bin_n) # The warnings should be converted to errors if(_src_n GREATER _src_dir_length_max) message(WARNING "MITK source code directory path length is too long (${_src_n} > ${_src_dir_length_max})." "Please move the MITK source code directory to a directory with a shorter path." ) endif() if(_bin_n GREATER _bin_dir_length_max) message(WARNING "MITK build directory path length is too long (${_bin_n} > ${_bin_dir_length_max})." "Please move the MITK build directory to a directory with a shorter path." ) endif() endif() #----------------------------------------------------------------------------- # Additional MITK Options (also shown during superbuild) #----------------------------------------------------------------------------- # ----------------------------------------- # General build options option(BUILD_SHARED_LIBS "Build MITK with shared libraries" ON) option(WITH_COVERAGE "Enable/Disable coverage" OFF) option(BUILD_TESTING "Test the project" ON) option(MITK_FAST_TESTING "Disable long-running tests like packaging" OFF) option(MITK_XVFB_TESTING "Execute test drivers through xvfb-run" OFF) option(MITK_BUILD_ALL_APPS "Build all MITK applications" OFF) option(MITK_BUILD_EXAMPLES "Build the MITK Examples" OFF) option(MITK_ENABLE_PIC_READER "Enable support for reading the DKFZ pic file format." ON) mark_as_advanced( MITK_XVFB_TESTING MITK_FAST_TESTING MITK_BUILD_ALL_APPS MITK_ENABLE_PIC_READER ) # ----------------------------------------- # Other options set(MITK_CUSTOM_REVISION_DESC "" CACHE STRING "Override MITK revision description") mark_as_advanced(MITK_CUSTOM_REVISION_DESC) set_property(GLOBAL PROPERTY MITK_EXTERNAL_PROJECTS "") include(CMakeExternals/ExternalProjectList.cmake) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_CMAKE_EXTERNALS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMakeExternals) get_filename_component(MITK_CMAKE_EXTERNALS_EXTENSION_DIR ${MITK_CMAKE_EXTERNALS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake) include(${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake) endif() endforeach() # ----------------------------------------- # Other MITK_USE_* options not related to # external projects build via the # MITK superbuild option(MITK_USE_BLUEBERRY "Build the BlueBerry platform" ON) option(MITK_USE_OpenCL "Use OpenCL GPU-Computing library" OFF) option(MITK_USE_OpenMP "Use OpenMP" OFF) option(MITK_USE_Python3 "Use Python 3" OFF) #----------------------------------------------------------------------------- # Build configurations #----------------------------------------------------------------------------- set(_buildConfigs "Custom") file(GLOB _buildConfigFiles CMake/BuildConfigurations/*.cmake) foreach(_buildConfigFile ${_buildConfigFiles}) get_filename_component(_buildConfigFile ${_buildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_buildConfigFile}) endforeach() foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) file(GLOB _extBuildConfigFiles ${MITK_EXTENSION_DIR}/CMake/BuildConfigurations/*.cmake) foreach(_extBuildConfigFile ${_extBuildConfigFiles}) get_filename_component(_extBuildConfigFile ${_extBuildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_extBuildConfigFile}) endforeach() list(REMOVE_DUPLICATES _buildConfigs) endforeach() set(MITK_BUILD_CONFIGURATION "Custom" CACHE STRING "Use pre-defined MITK configurations") set_property(CACHE MITK_BUILD_CONFIGURATION PROPERTY STRINGS ${_buildConfigs}) mitkFunctionEnableBuildConfiguration() mitkFunctionCreateWhitelistPaths(MITK) mitkFunctionFindWhitelists(MITK) # ----------------------------------------- # Qt version related variables option(MITK_USE_Qt5 "Use Qt 5 library" ON) if(MITK_USE_Qt5) set(MITK_QT5_MINIMUM_VERSION 5.12) set(MITK_QT5_COMPONENTS Concurrent OpenGL PrintSupport Script Sql Svg Widgets Xml XmlPatterns WebEngineWidgets UiTools Help LinguistTools) if(APPLE) list(APPEND MITK_QT5_COMPONENTS DBus) elseif(UNIX) list(APPEND MITK_QT5_COMPONENTS X11Extras) endif() # Hint at default install locations of Qt if(NOT Qt5_DIR) if(MSVC) set(_dir_candidates "C:/Qt") if(CMAKE_GENERATOR MATCHES "^Visual Studio [0-9]+ ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") elseif(CMAKE_GENERATOR MATCHES "Ninja") include(mitkFunctionGetMSVCVersion) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_PRODUCT_NAME MATCHES "^Visual Studio ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") endif() endif() if(_compilers MATCHES "[0-9]+") if (CMAKE_MATCH_0 EQUAL 2019) list(APPEND _compilers "msvc2017") # Binary compatible to 2019 endif() endif() else() set(_dir_candidates ~/Qt) if(APPLE) set(_compilers clang) else() list(APPEND _dir_candidates /opt/Qt) set(_compilers gcc) endif() endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) foreach(_compiler ${_compilers}) list(APPEND _compilers64 "${_compiler}_64") endforeach() set(_compilers ${_compilers64}) endif() foreach(_dir_candidate ${_dir_candidates}) get_filename_component(_dir_candidate ${_dir_candidate} REALPATH) foreach(_compiler ${_compilers}) set(_glob_expression "${_dir_candidate}/5.*/${_compiler}") file(GLOB _hints ${_glob_expression}) list(SORT _hints) list(APPEND MITK_QT5_HINTS ${_hints}) endforeach() endforeach() endif() find_package(Qt5 ${MITK_QT5_MINIMUM_VERSION} COMPONENTS ${MITK_QT5_COMPONENTS} REQUIRED HINTS ${MITK_QT5_HINTS}) if(${Qt5_VERSION} VERSION_GREATER_EQUAL 5.13) message(FATAL_ERROR "Qt version ${Qt5_VERSION_MAJOR}.${Qt5_VERSION_MINOR} is not yet supported. We recommend using the latest long-term support version 5.12.x.") endif() endif() # ----------------------------------------- # Custom dependency logic option(MITK_USE_SYSTEM_Boost "Use the system Boost" OFF) set(MITK_USE_Boost_LIBRARIES "" CACHE STRING "A semi-colon separated list of required Boost libraries") if(MITK_USE_cpprestsdk) find_package(OpenSSL QUIET) if(NOT OpenSSL_FOUND) set(openssl_message "Could not find OpenSSL (dependency of C++ REST SDK).\n") if(UNIX) if(APPLE) set(openssl_message "${openssl_message}Please install it using your favorite package management " "system (i.e. Homebrew or MacPorts).\n") else() set(openssl_message "${openssl_message}Please install the dev package of OpenSSL (i.e. libssl-dev).\n") endif() else() set(openssl_message "${openssl_message}Please install Win32 OpenSSL:\n" " https://slproweb.com/products/Win32OpenSSL.html\n") endif() set(openssl_message "${openssl_message}If it still cannot be found, you can hint CMake to find OpenSSL by " "adding/setting the OPENSSL_ROOT_DIR variable to the root directory of an " "OpenSSL installation. Make sure to clear variables of partly found " "versions of OpenSSL before, or they will be mixed up.") message(FATAL_ERROR ${openssl_message}) endif() list(APPEND MITK_USE_Boost_LIBRARIES date_time regex system) if(UNIX) list(APPEND MITK_USE_Boost_LIBRARIES atomic chrono filesystem random thread) endif() list(REMOVE_DUPLICATES MITK_USE_Boost_LIBRARIES) set(MITK_USE_Boost_LIBRARIES ${MITK_USE_Boost_LIBRARIES} CACHE STRING "A semi-colon separated list of required Boost libraries" FORCE) endif() if(MITK_USE_Python3) set(MITK_USE_ZLIB ON CACHE BOOL "" FORCE) if(APPLE AND CMAKE_FRAMEWORK_PATH AND CMAKE_FRAMEWORK_PATH MATCHES "python3\\.?([0-9]+)") find_package(Python3 3.${CMAKE_MATCH_1} EXACT REQUIRED COMPONENTS Interpreter Development NumPy) else() find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy) endif() if(WIN32) string(REPLACE "\\" "/" Python3_STDARCH "${Python3_STDARCH}") string(REPLACE "\\" "/" Python3_STDLIB "${Python3_STDLIB}") string(REPLACE "\\" "/" Python3_SITELIB "${Python3_SITELIB}") endif() endif() if(BUILD_TESTING AND NOT MITK_USE_CppUnit) message("> Forcing MITK_USE_CppUnit to ON because BUILD_TESTING=ON") set(MITK_USE_CppUnit ON CACHE BOOL "Use CppUnit for unit tests" FORCE) endif() if(MITK_USE_BLUEBERRY) option(MITK_BUILD_ALL_PLUGINS "Build all MITK plugins" OFF) mark_as_advanced(MITK_BUILD_ALL_PLUGINS) if(NOT MITK_USE_CTK) message("> Forcing MITK_USE_CTK to ON because of MITK_USE_BLUEBERRY") set(MITK_USE_CTK ON CACHE BOOL "Use CTK in MITK" FORCE) endif() endif() #----------------------------------------------------------------------------- # Pixel type multiplexing #----------------------------------------------------------------------------- # Customize the default pixel types for multiplex macros set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") mark_as_advanced(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES MITK_ACCESSBYITK_DIMENSIONS ) # consistency checks if(NOT MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES) set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES) set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES) set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) string(REPLACE "," ";" _integral_types ${MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES}) string(REPLACE "," ";" _floating_types ${MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES}) foreach(_scalar_type ${_integral_types} ${_floating_types}) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}itk::VariableLengthVector<${_scalar_type}>,") endforeach() string(LENGTH "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" _length) math(EXPR _length "${_length} - 1") string(SUBSTRING "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" 0 ${_length} MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES ${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES} CACHE STRING "List of vector pixel types used in AccessByItk and InstantiateAccessFunction macros for itk::VectorImage types" FORCE) endif() if(NOT MITK_ACCESSBYITK_DIMENSIONS) set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") endif() #----------------------------------------------------------------------------- # Project.xml #----------------------------------------------------------------------------- # A list of topologically ordered targets set(CTEST_PROJECT_SUBPROJECTS) list(APPEND CTEST_PROJECT_SUBPROJECTS MITK-Core MITK-CoreUI MITK-IGT MITK-ToF MITK-Modules # all modules not contained in a specific subproject MITK-Plugins # all plugins not contained in a specific subproject MITK-Examples Unlabeled # special "subproject" catching all unlabeled targets and tests ) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake) get_filename_component(MITK_CMAKE_EXTENSION_DIR ${MITK_CMAKE_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTENSION_DIR}/CTestSubprojectList.cmake) set(MITK_CTEST_SUBPROJECTS "") include(${MITK_CMAKE_EXTENSION_DIR}/CTestSubprojectList.cmake) if(MITK_CTEST_SUBPROJECTS) list(APPEND CTEST_PROJECT_SUBPROJECTS ${MITK_CTEST_SUBPROJECTS}) endif() endif() endforeach() # Configure CTestConfigSubProject.cmake that could be used by CTest scripts configure_file(${MITK_SOURCE_DIR}/CTestConfigSubProject.cmake.in ${MITK_BINARY_DIR}/CTestConfigSubProject.cmake) if(CTEST_PROJECT_ADDITIONAL_TARGETS) # those targets will be executed at the end of the ctest driver script # and they also get their own subproject label set(subproject_list "${CTEST_PROJECT_SUBPROJECTS};${CTEST_PROJECT_ADDITIONAL_TARGETS}") else() set(subproject_list "${CTEST_PROJECT_SUBPROJECTS}") endif() # Generate Project.xml file expected by the CTest driver script mitkFunctionGenerateProjectXml(${MITK_BINARY_DIR} MITK "${subproject_list}" ${MITK_USE_SUPERBUILD}) #----------------------------------------------------------------------------- # Superbuild script #----------------------------------------------------------------------------- if(MITK_USE_SUPERBUILD) include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake") # Print configuration summary message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL) return() endif() #***************************************************************************** #**************************** END OF SUPERBUILD **************************** #***************************************************************************** #----------------------------------------------------------------------------- # Organize MITK targets in folders #----------------------------------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(MITK_ROOT_FOLDER "MITK" CACHE STRING "") mark_as_advanced(MITK_ROOT_FOLDER) #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- include(WriteBasicConfigVersionFile) include(CheckCXXSourceCompiles) include(GenerateExportHeader) include(mitkFunctionAddCustomModuleTest) include(mitkFunctionCheckModuleDependencies) include(mitkFunctionCompileSnippets) include(mitkFunctionConfigureVisualStudioUserProjectFile) include(mitkFunctionConvertXPSchema) include(mitkFunctionCreateBlueBerryApplication) include(mitkFunctionCreateCommandLineApp) include(mitkFunctionCreateModule) include(mitkFunctionCreatePlugin) include(mitkFunctionCreateProvisioningFile) include(mitkFunctionGetLibrarySearchPaths) include(mitkFunctionGetVersion) include(mitkFunctionGetVersionDescription) include(mitkFunctionInstallAutoLoadModules) include(mitkFunctionInstallCTKPlugin) include(mitkFunctionInstallProvisioningFiles) include(mitkFunctionInstallThirdPartyCTKPlugins) include(mitkFunctionOrganizeSources) include(mitkFunctionUseModules) if( ${MITK_USE_MatchPoint} ) include(mitkFunctionCreateMatchPointDeployedAlgorithm) endif() include(mitkMacroConfigureItkPixelTypes) include(mitkMacroCreateExecutable) include(mitkMacroCreateModuleTests) include(mitkMacroGenerateToolsLibrary) include(mitkMacroGetLinuxDistribution) include(mitkMacroGetPMDPlatformString) include(mitkMacroInstall) include(mitkMacroInstallHelperApp) include(mitkMacroInstallTargets) include(mitkMacroMultiplexPicType) # Deprecated include(mitkMacroCreateCTKPlugin) #----------------------------------------------------------------------------- # Global CMake variables #----------------------------------------------------------------------------- # Required and enabled C++14 features for all MITK code. # These are added as PUBLIC compile features to all MITK modules. set(MITK_CXX_FEATURES cxx_auto_type cxx_decltype cxx_enum_forward_declarations cxx_extended_friend_declarations cxx_extern_templates cxx_final cxx_lambdas cxx_local_type_template_args cxx_long_long_type cxx_nullptr cxx_override cxx_range_for cxx_right_angle_brackets cxx_rvalue_references cxx_static_assert cxx_strong_enums cxx_template_template_parameters cxx_trailing_return_types cxx_variadic_macros ) if(NOT DEFINED CMAKE_DEBUG_POSTFIX) # We can't do this yet because the CTK Plugin Framework # cannot cope with a postfix yet. #set(CMAKE_DEBUG_POSTFIX d) endif() #----------------------------------------------------------------------------- # Output directories. #----------------------------------------------------------------------------- set(_default_LIBRARY_output_dir lib) set(_default_RUNTIME_output_dir bin) set(_default_ARCHIVE_output_dir lib) foreach(type LIBRARY RUNTIME ARCHIVE) # Make sure the directory exists if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY AND NOT EXISTS ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) message("Creating directory MITK_CMAKE_${type}_OUTPUT_DIRECTORY: ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") file(MAKE_DIRECTORY "${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") endif() if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY) set(CMAKE_${type}_OUTPUT_DIRECTORY ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) else() set(CMAKE_${type}_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${_default_${type}_output_dir}) set(MITK_CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY}) endif() set(CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY} CACHE INTERNAL "Output directory for ${type} files.") mark_as_advanced(CMAKE_${type}_OUTPUT_DIRECTORY) endforeach() #----------------------------------------------------------------------------- # Set MITK specific options and variables (NOT available during superbuild) #----------------------------------------------------------------------------- # Look for optional Doxygen package find_package(Doxygen) option(BLUEBERRY_DEBUG_SMARTPOINTER "Enable code for debugging smart pointers" OFF) mark_as_advanced(BLUEBERRY_DEBUG_SMARTPOINTER) # Ask the user to show the console window for applications option(MITK_SHOW_CONSOLE_WINDOW "Use this to enable or disable the console window when starting MITK GUI Applications" ON) mark_as_advanced(MITK_SHOW_CONSOLE_WINDOW) # TODO: check if necessary option(USE_ITKZLIB "Use the ITK zlib for pic compression." ON) mark_as_advanced(USE_ITKZLIB) if(NOT MITK_FAST_TESTING) if(DEFINED MITK_CTEST_SCRIPT_MODE AND (MITK_CTEST_SCRIPT_MODE STREQUAL "continuous" OR MITK_CTEST_SCRIPT_MODE STREQUAL "experimental") ) set(MITK_FAST_TESTING ON) endif() endif() if(NOT UNIX) set(MITK_WIN32_FORCE_STATIC "STATIC" CACHE INTERNAL "Use this variable to always build static libraries on non-unix platforms") endif() if(MITK_BUILD_ALL_PLUGINS) set(MITK_BUILD_ALL_PLUGINS_OPTION "FORCE_BUILD_ALL") endif() # Configure pixel types used for ITK image access multiplexing mitkMacroConfigureItkPixelTypes() # Configure module naming conventions set(MITK_MODULE_NAME_REGEX_MATCH "^[A-Z].*$") set(MITK_MODULE_NAME_REGEX_NOT_MATCH "^[Mm][Ii][Tt][Kk].*$") set(MITK_DEFAULT_MODULE_NAME_PREFIX "Mitk") set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) set(MITK_MODULE_NAME_DEFAULTS_TO_DIRECTORY_NAME 1) #----------------------------------------------------------------------------- # Get MITK version info #----------------------------------------------------------------------------- mitkFunctionGetVersion(${MITK_SOURCE_DIR} MITK) mitkFunctionGetVersionDescription(${MITK_SOURCE_DIR} MITK) # MITK_VERSION set(MITK_VERSION_STRING "${MITK_VERSION_MAJOR}.${MITK_VERSION_MINOR}.${MITK_VERSION_PATCH}") if(MITK_VERSION_PATCH STREQUAL "99") set(MITK_VERSION_STRING "${MITK_VERSION_STRING}-${MITK_REVISION_SHORTID}") endif() #----------------------------------------------------------------------------- # Installation preparation # # These should be set before any MITK install macros are used #----------------------------------------------------------------------------- # on macOS all BlueBerry plugins get copied into every # application bundle (.app directory) specified here if(MITK_USE_BLUEBERRY AND APPLE) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 1 option_name) list(GET target_info_list 0 app_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) set(MACOSX_BUNDLE_NAMES ${MACOSX_BUNDLE_NAMES} Mitk${app_name}) endif() endforeach() endif() endforeach() endif() #----------------------------------------------------------------------------- # Set coverage Flags #----------------------------------------------------------------------------- if(WITH_COVERAGE) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(coverage_flags "-g -fprofile-arcs -ftest-coverage -O0 -DNDEBUG") set(COVERAGE_CXX_FLAGS ${coverage_flags}) set(COVERAGE_C_FLAGS ${coverage_flags}) endif() endif() #----------------------------------------------------------------------------- # MITK C/CXX Flags #----------------------------------------------------------------------------- set(MITK_C_FLAGS "${COVERAGE_C_FLAGS}") set(MITK_C_FLAGS_DEBUG ) set(MITK_C_FLAGS_RELEASE ) set(MITK_CXX_FLAGS "${COVERAGE_CXX_FLAGS} ${MITK_CXX14_FLAG}") set(MITK_CXX_FLAGS_DEBUG ) set(MITK_CXX_FLAGS_RELEASE ) set(MITK_EXE_LINKER_FLAGS ) set(MITK_SHARED_LINKER_FLAGS ) if(WIN32) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DPOCO_NO_UNWINDOWS -DWIN32_LEAN_AND_MEAN -DNOMINMAX") mitkFunctionCheckCompilerFlags("/wd4005" MITK_CXX_FLAGS) # warning C4005: macro redefinition mitkFunctionCheckCompilerFlags("/wd4231" MITK_CXX_FLAGS) # warning C4231: nonstandard extension used : 'extern' before template explicit instantiation # the following line should be removed after fixing bug 17637 mitkFunctionCheckCompilerFlags("/wd4316" MITK_CXX_FLAGS) # warning C4316: object alignment on heap mitkFunctionCheckCompilerFlags("/wd4180" MITK_CXX_FLAGS) # warning C4180: qualifier applied to function type has no meaning mitkFunctionCheckCompilerFlags("/wd4251" MITK_CXX_FLAGS) # warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' endif() if(APPLE) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DGL_SILENCE_DEPRECATION") # Apple deprecated OpenGL in macOS 10.14 endif() if(NOT MSVC_VERSION) foreach(_flag -Wall -Wextra -Wpointer-arith -Winvalid-pch -Wcast-align -Wwrite-strings -Wno-error=gnu -Wno-error=unknown-pragmas # The strict-overflow warning is generated by ITK template code -Wno-error=strict-overflow -Woverloaded-virtual -Wstrict-null-sentinel #-Wold-style-cast #-Wsign-promo -Wno-error=deprecated-copy -Wno-array-bounds -fdiagnostics-show-option ) mitkFunctionCheckCAndCXXCompilerFlags(${_flag} MITK_C_FLAGS MITK_CXX_FLAGS) endforeach() endif() if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE) mitkFunctionCheckCompilerFlags("-Wl,--no-undefined" MITK_SHARED_LINKER_FLAGS) mitkFunctionCheckCompilerFlags("-Wl,--as-needed" MITK_SHARED_LINKER_FLAGS) endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionCheckCAndCXXCompilerFlags("-fstack-protector-all" MITK_C_FLAGS MITK_CXX_FLAGS) set(MITK_CXX_FLAGS_RELEASE "-U_FORTIFY_SOURCES -D_FORTIFY_SOURCE=2 ${MITK_CXX_FLAGS_RELEASE}") endif() set(MITK_MODULE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) set(MITK_EXE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) #----------------------------------------------------------------------------- # MITK Packages #----------------------------------------------------------------------------- set(MITK_MODULES_PACKAGE_DEPENDS_DIR ${MITK_SOURCE_DIR}/CMake/PackageDepends) set(MODULES_PACKAGE_DEPENDS_DIRS ${MITK_MODULES_PACKAGE_DEPENDS_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_PACKAGE_DEPENDS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake/PackageDepends) get_filename_component(MITK_PACKAGE_DEPENDS_EXTENSION_DIR ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}) list(APPEND MODULES_PACKAGE_DEPENDS_DIRS ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}) endif() endforeach() if(NOT MITK_USE_SYSTEM_Boost) set(Boost_NO_SYSTEM_PATHS 1) endif() set(Boost_USE_MULTITHREADED 1) set(Boost_USE_STATIC_LIBS 0) set(Boost_USE_STATIC_RUNTIME 0) set(Boost_ADDITIONAL_VERSIONS 1.68 1.68.0) # We need this later for a DCMTK workaround set(_dcmtk_dir_orig ${DCMTK_DIR}) # This property is populated at the top half of this file get_property(MITK_EXTERNAL_PROJECTS GLOBAL PROPERTY MITK_EXTERNAL_PROJECTS) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(MITK_USE_${ep} AND _package) if(_components) find_package(${_package} COMPONENTS ${_components} REQUIRED CONFIG) else() # Prefer config mode first because it finds external # Config.cmake files pointed at by _DIR variables. # Otherwise, existing Find.cmake files could fail. if(DEFINED ${_package}_DIR) #we store the information because it will be overwritten by find_package #and would get lost for all EPs that use on Find.cmake instead of config #files. set(_temp_EP_${_package}_dir ${${_package}_DIR}) endif(DEFINED ${_package}_DIR) find_package(${_package} QUIET CONFIG) string(TOUPPER "${_package}" _package_uc) if(NOT (${_package}_FOUND OR ${_package_uc}_FOUND)) if(DEFINED _temp_EP_${_package}_dir) set(${_package}_DIR ${_temp_EP_${_package}_dir} CACHE PATH "externaly set dir of the package ${_package}" FORCE) endif(DEFINED _temp_EP_${_package}_dir) find_package(${_package} REQUIRED) endif() endif() endif() endforeach() # Ensure that the MITK CMake module path comes first set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR} ${CMAKE_MODULE_PATH} ) if(MITK_USE_DCMTK) # Due to the preferred CONFIG mode in find_package calls above, # the DCMTKConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMTK. if(${_dcmtk_dir_orig} MATCHES "${MITK_EXTERNAL_PROJECT_PREFIX}.*") # Help our FindDCMTK.cmake script find our super-build DCMTK set(DCMTK_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) else() # Use the original value set(DCMTK_DIR ${_dcmtk_dir_orig}) endif() find_package(DCMTK REQUIRED MODULE) endif() if(MITK_USE_DCMQI) # Due to the preferred CONFIG mode in find_package calls above, # the DCMQIConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMQI. # Help our FindDCMQI.cmake script find our super-build DCMQI set(DCMQI_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) find_package(DCMQI REQUIRED) endif() link_directories(${Boost_LIBRARY_DIRS}) if(MITK_USE_OpenIGTLink) link_directories(${OpenIGTLink_LIBRARY_DIRS}) endif() if(MITK_USE_OpenCL) find_package(OpenCL REQUIRED) endif() if(MITK_USE_OpenMP) find_package(OpenMP REQUIRED COMPONENTS CXX) else() find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) elseif(APPLE AND OpenMP_libomp_LIBRARY AND NOT OpenMP_CXX_LIB_NAMES) set(OpenMP_CXX_LIB_NAMES libomp CACHE STRING "" FORCE) get_filename_component(openmp_lib_dir "${OpenMP_libomp_LIBRARY}" DIRECTORY) set(openmp_include_dir "${openmp_lib_dir}/../include") if(EXISTS "${openmp_include_dir}") get_filename_component(openmp_include_dir "${openmp_include_dir}" REALPATH) set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${openmp_include_dir}" CACHE STRING "" FORCE) find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) endif() endif() endif() endif() # Qt support if(MITK_USE_Qt5) find_package(Qt5Core ${MITK_QT5_MINIMUM_VERSION} REQUIRED) # at least Core required get_target_property(_qmake_exec Qt5::qmake LOCATION) execute_process(COMMAND ${_qmake_exec} -query QT_INSTALL_BINS RESULT_VARIABLE _result OUTPUT_VARIABLE QT_BINARY_DIR ERROR_VARIABLE _error ) string(STRIP "${QT_BINARY_DIR}" QT_BINARY_DIR) if(_result OR NOT EXISTS "${QT_BINARY_DIR}") message(FATAL_ERROR "Could not determine Qt binary directory: ${_result} ${QT_BINARY_DIR} ${_error}") endif() find_program(QT_HELPGENERATOR_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5 qhelpgenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_COLLECTIONGENERATOR_EXECUTABLE NAMES qcollectiongenerator qcollectiongenerator-qt5 qcollectiongenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_ASSISTANT_EXECUTABLE NAMES assistant assistant-qt5 assistant5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_XMLPATTERNS_EXECUTABLE NAMES xmlpatterns PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) mark_as_advanced(QT_HELPGENERATOR_EXECUTABLE QT_COLLECTIONGENERATOR_EXECUTABLE QT_ASSISTANT_EXECUTABLE QT_XMLPATTERNS_EXECUTABLE ) if(MITK_USE_BLUEBERRY) option(BLUEBERRY_USE_QT_HELP "Enable support for integrating plugin documentation into Qt Help" ${DOXYGEN_FOUND}) mark_as_advanced(BLUEBERRY_USE_QT_HELP) # Sanity checks for in-application BlueBerry plug-in help generation if(BLUEBERRY_USE_QT_HELP) set(_force_blueberry_use_qt_help_to_off 0) if(NOT DOXYGEN_FOUND) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen was not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(DOXYGEN_FOUND AND DOXYGEN_VERSION VERSION_LESS 1.8.7) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen version 1.8.7 or newer not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_HELPGENERATOR_EXECUTABLE) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because QT_HELPGENERATOR_EXECUTABLE is empty.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT MITK_USE_Qt5) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because MITK_USE_Qt5 is OFF.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_XMLPATTERNS_EXECUTABLE) message("You have enabled Qt Help support, but QT_XMLPATTERNS_EXECUTABLE is empty") set(_force_blueberry_use_qt_help_to_off 1) endif() if(_force_blueberry_use_qt_help_to_off) set(BLUEBERRY_USE_QT_HELP OFF CACHE BOOL "Enable support for integrating plugin documentation into Qt Help" FORCE) endif() endif() if(BLUEBERRY_QT_HELP_REQUIRED AND NOT BLUEBERRY_USE_QT_HELP) message(FATAL_ERROR "BLUEBERRY_USE_QT_HELP is required to be set to ON") endif() endif() endif() #----------------------------------------------------------------------------- # Testing #----------------------------------------------------------------------------- if(BUILD_TESTING) enable_testing() include(CTest) mark_as_advanced(TCL_TCLSH DART_ROOT) # Setup file for setting custom ctest vars configure_file( CMake/CTestCustom.cmake.in ${MITK_BINARY_DIR}/CTestCustom.cmake @ONLY ) # Initial cache for ProjectTemplate and PluginGenerator tests configure_file( CMake/mitkTestInitialCache.txt.in ${MITK_BINARY_DIR}/mitkTestInitialCache.txt @ONLY ) # Configuration for the CMake-generated test driver set(CMAKE_TESTDRIVER_EXTRA_INCLUDES "#include ") set(CMAKE_TESTDRIVER_BEFORE_TESTMAIN " try {") set(CMAKE_TESTDRIVER_AFTER_TESTMAIN " } catch ( const std::exception & excp ) { fprintf(stderr,\"%s\\n\",excp.what()); return EXIT_FAILURE; } catch( ... ) { printf(\"Exception caught in the test driver\\n\"); return EXIT_FAILURE; } ") set(MITK_TEST_OUTPUT_DIR "${MITK_BINARY_DIR}/test_output") if(NOT EXISTS ${MITK_TEST_OUTPUT_DIR}) file(MAKE_DIRECTORY ${MITK_TEST_OUTPUT_DIR}) endif() # Test the package target include(mitkPackageTest) endif() configure_file(mitkTestingConfig.h.in ${MITK_BINARY_DIR}/mitkTestingConfig.h) #----------------------------------------------------------------------------- # MITK_SUPERBUILD_BINARY_DIR #----------------------------------------------------------------------------- # If MITK_SUPERBUILD_BINARY_DIR isn't defined, it means MITK is *NOT* build using Superbuild. # In that specific case, MITK_SUPERBUILD_BINARY_DIR should default to MITK_BINARY_DIR if(NOT DEFINED MITK_SUPERBUILD_BINARY_DIR) set(MITK_SUPERBUILD_BINARY_DIR ${MITK_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # Set C/CXX and linker flags for MITK code #----------------------------------------------------------------------------- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MITK_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MITK_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MITK_CXX_FLAGS_RELEASE}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MITK_C_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${MITK_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${MITK_C_FLAGS_RELEASE}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MITK_EXE_LINKER_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${MITK_SHARED_LINKER_FLAGS}") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${MITK_MODULE_LINKER_FLAGS}") #----------------------------------------------------------------------------- # Add custom targets representing CDash subprojects #----------------------------------------------------------------------------- foreach(subproject ${CTEST_PROJECT_SUBPROJECTS}) if(NOT TARGET ${subproject} AND NOT subproject MATCHES "Unlabeled") add_custom_target(${subproject}) set_property(TARGET ${subproject} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CTestSubprojects") endif() endforeach() #----------------------------------------------------------------------------- # Add subdirectories #----------------------------------------------------------------------------- add_subdirectory(Utilities) add_subdirectory(Modules) include("${CMAKE_CURRENT_SOURCE_DIR}/Modules/ModuleList.cmake") mitkFunctionWhitelistModules(MITK MITK_MODULES) set(MITK_ROOT_FOLDER_BACKUP "${MITK_ROOT_FOLDER}") foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) get_filename_component(MITK_ROOT_FOLDER ${MITK_EXTENSION_DIR} NAME) set(MITK_MODULES_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Modules) get_filename_component(MITK_MODULES_EXTENSION_DIR ${MITK_MODULES_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake) set(MITK_MODULES "") include(${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake) foreach(mitk_module ${MITK_MODULES}) add_subdirectory(${MITK_MODULES_EXTENSION_DIR}/${mitk_module} Modules/${mitk_module}) endforeach() endif() set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) endforeach() set(MITK_ROOT_FOLDER "${MITK_ROOT_FOLDER_BACKUP}") add_subdirectory(Wrapping) +set(MITK_DOXYGEN_OUTPUT_DIR "${PROJECT_BINARY_DIR}/Documentation/Doxygen" CACHE PATH + "Output directory for doxygen generated documentation.") + if(MITK_USE_BLUEBERRY) set(BLUEBERRY_XPDOC_OUTPUT_DIR "${MITK_DOXYGEN_OUTPUT_DIR}/html/extension-points/html/") execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BLUEBERRY_XPDOC_OUTPUT_DIR}) include("${CMAKE_CURRENT_SOURCE_DIR}/Plugins/PluginList.cmake") mitkFunctionWhitelistPlugins(MITK MITK_PLUGINS) set(mitk_plugins_fullpath "") foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath Plugins/${mitk_plugin}) endforeach() set(MITK_PLUGIN_REGEX_LIST "") foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_PLUGINS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Plugins) get_filename_component(MITK_PLUGINS_EXTENSION_DIR ${MITK_PLUGINS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake) set(MITK_PLUGINS "") include(${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake) foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath ${MITK_PLUGINS_EXTENSION_DIR}/${mitk_plugin}) endforeach() endif() endforeach() if(EXISTS ${MITK_PRIVATE_MODULES}/PluginList.cmake) include(${MITK_PRIVATE_MODULES}/PluginList.cmake) foreach(mitk_plugin ${MITK_PRIVATE_PLUGINS}) list(APPEND mitk_plugins_fullpath ${MITK_PRIVATE_MODULES}/${mitk_plugin}) endforeach() endif() if(MITK_BUILD_EXAMPLES) include("${CMAKE_CURRENT_SOURCE_DIR}/Examples/Plugins/PluginList.cmake") set(mitk_example_plugins_fullpath ) foreach(mitk_example_plugin ${MITK_EXAMPLE_PLUGINS}) list(APPEND mitk_example_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) list(APPEND mitk_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) endforeach() endif() # Specify which plug-ins belong to this project macro(GetMyTargetLibraries all_target_libraries varname) set(re_ctkplugin_mitk "^org_mitk_[a-zA-Z0-9_]+$") set(re_ctkplugin_bb "^org_blueberry_[a-zA-Z0-9_]+$") set(_tmp_list) list(APPEND _tmp_list ${all_target_libraries}) ctkMacroListFilter(_tmp_list re_ctkplugin_mitk re_ctkplugin_bb MITK_PLUGIN_REGEX_LIST OUTPUT_VARIABLE ${varname}) endmacro() # Get infos about application directories and build options set(mitk_apps_fullpath "") foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) if(${option_name}) list(APPEND mitk_apps_fullpath "${MITK_APPLICATIONS_EXTENSION_DIR}/${directory_name}^^${option_name}") endif() endforeach() endif() endforeach() if (mitk_plugins_fullpath) ctkMacroSetupPlugins(${mitk_plugins_fullpath} BUILD_OPTION_PREFIX MITK_BUILD_ APPS ${mitk_apps_fullpath} BUILD_ALL ${MITK_BUILD_ALL_PLUGINS} COMPACT_OPTIONS) endif() set(MITK_PLUGIN_USE_FILE "${MITK_BINARY_DIR}/MitkPluginUseFile.cmake") if(${PROJECT_NAME}_PLUGIN_LIBRARIES) ctkFunctionGeneratePluginUseFile(${MITK_PLUGIN_USE_FILE}) else() file(REMOVE ${MITK_PLUGIN_USE_FILE}) set(MITK_PLUGIN_USE_FILE ) endif() endif() #----------------------------------------------------------------------------- # Documentation #----------------------------------------------------------------------------- if(DOXYGEN_FOUND) add_subdirectory(Documentation) endif() #----------------------------------------------------------------------------- # Installation #----------------------------------------------------------------------------- # set MITK cpack variables # These are the default variables, which can be overwritten ( see below ) include(mitkSetupCPack) set(use_default_config ON) set(ALL_MITK_APPS "") set(activated_apps_no 0) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) list(APPEND ALL_MITK_APPS "${MITK_EXTENSION_DIR}/Applications/${directory_name}^^${option_name}^^${executable_name}") if(${option_name} OR MITK_BUILD_ALL_APPS) MATH(EXPR activated_apps_no "${activated_apps_no} + 1") endif() endforeach() endif() endforeach() list(LENGTH ALL_MITK_APPS app_count) if(app_count EQUAL 1 AND (activated_apps_no EQUAL 1 OR MITK_BUILD_ALL_APPS)) # Corner case if there is only one app in total set(use_project_cpack ON) elseif(activated_apps_no EQUAL 1 AND NOT MITK_BUILD_ALL_APPS) # Only one app is enabled (no "build all" flag set) set(use_project_cpack ON) else() # Less or more then one app is enabled set(use_project_cpack OFF) endif() foreach(mitk_app ${ALL_MITK_APPS}) # extract target_dir and option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 target_dir) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) # check whether application specific configuration files will be used if(use_project_cpack) # use files if they exist if(EXISTS "${target_dir}/CPackOptions.cmake") include("${target_dir}/CPackOptions.cmake") endif() if(EXISTS "${target_dir}/CPackConfig.cmake.in") set(CPACK_PROJECT_CONFIG_FILE "${target_dir}/CPackConfig.cmake") configure_file(${target_dir}/CPackConfig.cmake.in ${CPACK_PROJECT_CONFIG_FILE} @ONLY) set(use_default_config OFF) endif() endif() # add link to the list list(APPEND CPACK_CREATE_DESKTOP_LINKS "${executable_name}") endif() endforeach() # if no application specific configuration file was used, use default if(use_default_config) configure_file(${MITK_SOURCE_DIR}/MITKCPackOptions.cmake.in ${MITK_BINARY_DIR}/MITKCPackOptions.cmake @ONLY) set(CPACK_PROJECT_CONFIG_FILE "${MITK_BINARY_DIR}/MITKCPackOptions.cmake") endif() # include CPack model once all variables are set include(CPack) # Additional installation rules include(mitkInstallRules) #----------------------------------------------------------------------------- # Last configuration steps #----------------------------------------------------------------------------- # ---------------- Export targets ----------------- set(MITK_EXPORTS_FILE "${MITK_BINARY_DIR}/MitkExports.cmake") file(REMOVE ${MITK_EXPORTS_FILE}) set(targets_to_export) get_property(module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) if(module_targets) list(APPEND targets_to_export ${module_targets}) endif() if(MITK_USE_BLUEBERRY) if(MITK_PLUGIN_LIBRARIES) list(APPEND targets_to_export ${MITK_PLUGIN_LIBRARIES}) endif() endif() export(TARGETS ${targets_to_export} APPEND FILE ${MITK_EXPORTS_FILE}) set(MITK_EXPORTED_TARGET_PROPERTIES ) foreach(target_to_export ${targets_to_export}) get_target_property(autoload_targets ${target_to_export} MITK_AUTOLOAD_TARGETS) if(autoload_targets) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_TARGETS \"${autoload_targets}\")") endif() get_target_property(autoload_dir ${target_to_export} MITK_AUTOLOAD_DIRECTORY) if(autoload_dir) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_DIRECTORY \"${autoload_dir}\")") endif() get_target_property(deprecated_module ${target_to_export} MITK_MODULE_DEPRECATED_SINCE) if(deprecated_module) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_MODULE_DEPRECATED_SINCE \"${deprecated_module}\")") endif() endforeach() # ---------------- External projects ----------------- get_property(MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS_CONFIG GLOBAL PROPERTY MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS) set(MITK_CONFIG_EXTERNAL_PROJECTS ) #string(REPLACE "^^" ";" _mitk_external_projects ${MITK_EXTERNAL_PROJECTS}) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} set(MITK_USE_${ep} ${MITK_USE_${ep}}) set(MITK_${ep}_DIR \"${${ep}_DIR}\") set(MITK_${ep}_COMPONENTS ${_components}) ") endforeach() foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(_components) set(_components_arg COMPONENTS \${_components}) else() set(_components_arg) endif() if(_package) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} if(MITK_USE_${ep}) set(${ep}_DIR \${MITK_${ep}_DIR}) if(MITK_${ep}_COMPONENTS) mitkMacroFindDependency(${_package} COMPONENTS \${MITK_${ep}_COMPONENTS}) else() mitkMacroFindDependency(${_package}) endif() endif()") endif() endforeach() # ---------------- Tools ----------------- configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactory.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactoryLoader.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactoryLoader.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolGUIExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolGUIExtensionITKFactory.cpp.in COPYONLY) # ---------------- Configure files ----------------- configure_file(mitkVersion.h.in ${MITK_BINARY_DIR}/mitkVersion.h) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) set(IPFUNC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ipFunc) set(UTILITIES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) configure_file(MITKConfig.cmake.in ${MITK_BINARY_DIR}/MITKConfig.cmake @ONLY) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake VERSION ${MITK_VERSION_STRING} COMPATIBILITY AnyNewerVersion) #----------------------------------------------------------------------------- # MITK Applications #----------------------------------------------------------------------------- # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Applications) if(MSVC AND TARGET MitkWorkbench) set_directory_properties(PROPERTIES VS_STARTUP_PROJECT MitkWorkbench) endif() foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/CMakeLists.txt) add_subdirectory(${MITK_APPLICATIONS_EXTENSION_DIR} Applications) endif() endforeach() #----------------------------------------------------------------------------- # MITK Examples #----------------------------------------------------------------------------- if(MITK_BUILD_EXAMPLES) # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Examples) endif() #----------------------------------------------------------------------------- # Print configuration summary #----------------------------------------------------------------------------- message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL ) diff --git a/Documentation/CMakeLists.txt b/Documentation/CMakeLists.txt index c0f2041897..7ab4c59c20 100644 --- a/Documentation/CMakeLists.txt +++ b/Documentation/CMakeLists.txt @@ -1,224 +1,216 @@ -# -# Variables: -# MITK_DOXYGEN_OUTPUT_DIR: doxygen output directory (optional) - -# Needed early on for redirecting the BlueBerry documentation output dir -set(MITK_DOXYGEN_OUTPUT_DIR ${PROJECT_BINARY_DIR}/Documentation/Doxygen CACHE PATH - "Output directory for doxygen generated documentation." ) - # Compile source code snippets add_subdirectory(Snippets) # Different doxygen versions produce significantly different behaviour in the MITK documentation # especially in regards to the MITK Qt assistant help files and markdown files. # The HTML documentation is supposed to be build with Doxygen 1.8.7 or newer, the # Qt assistant QCH files are supposed to be generated with Doxygen 1.8.7 or newer. # So we check for 1.8.7 here and QCH generation support is checked in # BlueBerry/CMakeLists.txt set(supported_doxygen_version "1.8.7") if(DOXYGEN_VERSION VERSION_LESS ${supported_doxygen_version}) MESSAGE(WARNING "Unsupported doxygen version ${DOXYGEN_VERSION}. The MITK HTML documentation has been tested to work with doxygen ${supported_doxygen_version} or newer.") endif() option(USE_DOT "Use dot program for generating graphical class diagrams with doxygen, if available" ON) option(MITK_DOXYGEN_BUILD_ALWAYS "Always build the MITK documentation when building the default target" OFF) option(MITK_DOXYGEN_GENERATE_QCH_FILES "Use doxygen to generate Qt compressed help files for MITK docs" OFF) mark_as_advanced(USE_DOT MITK_DOXYGEN_BUILD_ALWAYS MITK_DOXYGEN_GENERATE_QCH_FILES) if (MITK_DOXYGEN_GENERATE_QCH_FILES AND DOXYGEN_VERSION VERSION_LESS "1.8.7") message(WARNING "> Forcing MITK_DOXYGEN_GENERATE_QCH_FILES to OFF because Doxygen version 1.8.7 or newer not found.") set(MITK_DOXYGEN_GENERATE_QCH_FILES OFF CACHE BOOL "Use doxygen to generate Qt compressed help files for MITK docs" FORCE) endif() set(HAVE_DOT "NO") if(DOXYGEN_DOT_EXECUTABLE AND USE_DOT) set(HAVE_DOT "YES") endif() set(MITK_DOXYGEN_TAGFILE_NAME ${MITK_DOXYGEN_OUTPUT_DIR}/MITK.tag CACHE INTERNAL "MITK Doxygen tag file") # This is relative to the working directory of the doxygen command set(MITK_DOXYGEN_STYLESHEET mitk_doxygen_extra.css) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${MITK_DOXYGEN_STYLESHEET} ${CMAKE_CURRENT_BINARY_DIR}/${MITK_DOXYGEN_STYLESHEET} COPYONLY) # Create QCH files for MITK and external projects set(MITK_DOXYGEN_GENERATE_QHP "NO") if(MITK_DOXYGEN_GENERATE_QCH_FILES) find_program(QT_HELPGENERATOR_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5 qhelpgenerator5 PATHS ${QT_BINARY_DIR} DOC "The location of the the Qt help generator executable" NO_DEFAULT_PATH ) mark_as_advanced(QT_HELPGENERATOR_EXECUTABLE) if(NOT QT_HELPGENERATOR_EXECUTABLE) message(SEND_ERROR "The Qt help generator could not be found. Disabling qch generation") else() set(MITK_DOXYGEN_GENERATE_QHP "YES") endif() # The name of the generated MITK qch file, relative to the # Doxygen HTML output folder set(MITK_DOXYGEN_QCH_FILE "${MITK_BINARY_DIR}/MITK-${MITK_REVISION_ID}.qch") # Generating ITK and VTK docs it not done yet #option(MITK_DOXYGEN_GENERATE_VTK_QCH_FILE "Use doxygen to generate a Qt compressed help file for VTK docs" OFF) #option(MITK_DOXYGEN_GENERATE_ITK_QCH_FILE "Use doxygen to generate a Qt compressed help file for ITK docs" OFF) #mark_as_advanced(MITK_DOXYGEN_GENERATE_VTK_QCH_FILE MITK_DOXYGEN_GENERATE_ITK_QCH_FILE) endif() if(MITK_USE_BLUEBERRY) file(RELATIVE_PATH _blueberry_doxygen_path ${MITK_DOXYGEN_OUTPUT_DIR}/html ${BLUEBERRY_DOXYGEN_OUTPUT_DIR}/html) set(BLUEBERRY_DOXYGEN_TAGFILE "${BLUEBERRY_DOXYGEN_TAGFILE_NAME}=${_blueberry_doxygen_path}") set(BLUEBERRY_DOXYGEN_LINK "BlueBerry Documentation") set(MITK_XP_LINK "\\ref mitkExtPointsIndex") configure_file(schema.css ${MITK_DOXYGEN_OUTPUT_DIR}/html/schema.css) set(MITK_DOXYGEN_ENABLED_SECTIONS "${MITK_DOXYGEN_ENABLED_SECTIONS} BLUEBERRY") endif(MITK_USE_BLUEBERRY) # Compile a doxygen input filter for processing CMake scripts include(mitkFunctionCMakeDoxygenFilterCompile) mitkFunctionCMakeDoxygenFilterCompile(NAMESPACE "CMake") # Configure some doxygen options if(NOT MITK_DOXYGEN_INTERNAL_DOCS) set(MITK_DOXYGEN_INTERNAL_DOCS "NO") set(MITK_DOXYGEN_HIDE_FRIEND_COMPOUNDS "YES") set(MITK_DOXYGEN_EXCLUDE_PATTERNS "*_p.* *Private.h */internal/*") else() set(MITK_DOXYGEN_HIDE_FRIEND_COMPOUNDS "NO") set(MITK_DOXYGEN_EXCLUDE_PATTERNS "") endif() if(NOT MITK_DOXYGEN_GENERATE_TODOLIST) set(MITK_DOXYGEN_GENERATE_TODOLIST "NO") endif() if(NOT MITK_DOXYGEN_GENERATE_BUGLIST) set(MITK_DOXYGEN_GENERATE_BUGLIST "NO") endif() if(NOT MITK_DOXYGEN_HTML_DYNAMIC_SECTIONS) set(MITK_DOXYGEN_HTML_DYNAMIC_SECTIONS "NO") endif() if(NOT MITK_DOXYGEN_UML_LOOK) set(MITK_DOXYGEN_UML_LOOK "NO") endif() if(NOT MITK_DOXYGEN_GENERATE_DEPRECATEDLIST) set(MITK_DOXYGEN_GENERATE_DEPRECATEDLIST "YES") endif() if(NOT DEFINED MITK_DOXYGEN_DOT_NUM_THREADS) set(MITK_DOXYGEN_DOT_NUM_THREADS 0) endif() if(NOT DEFINED US_PLATFORM) if(UNIX) if(APPLE) set(US_PLATFORM "US_PLATFORM_APPLE=1") else() set(US_PLATFORM "US_PLATFORM_LINUX=1") endif() set(US_PLATFORM "${US_PLATFORM} \\\nUS_PLATFORM_POSIX=1") else() set(US_PLATFORM "US_PLATFORM_WINDOWS=1") endif() endif() # parse which plug-in documentation to activate set(USERS_GUIDE_INPUT "${MITK_SOURCE_DIR}/Documentation/Doxygen/UserManual/") if(MITK_USE_BLUEBERRY) if(MITK_BUILD_ALL_PLUGINS) set(USERS_GUIDE_INPUT "${USERS_GUIDE_INPUT} \\ ${MITK_SOURCE_DIR}/Plugins/") else() foreach(mitk_plugin ${${CMAKE_PROJECT_NAME}_PLUGIN_LIBRARIES}) # we want each line to end in " \" and each directory be on a separate line set(USERS_GUIDE_INPUT "${USERS_GUIDE_INPUT} \\ ${${mitk_plugin}_SOURCE_DIR}/") endforeach() endif() if(MITK_BUILD_EXAMPLES) set(USERS_GUIDE_INPUT "${USERS_GUIDE_INPUT} \\ ${MITK_SOURCE_DIR}/Examples/Plugins/") endif() endif() # create output directories for the guides file(MAKE_DIRECTORY ${MITK_DOXYGEN_OUTPUT_DIR}/Guides/Users_Guide/) file(MAKE_DIRECTORY ${MITK_DOXYGEN_OUTPUT_DIR}/Guides/Developers_Guide/) configure_file(doxygen.conf.in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) configure_file(doxygen_users_guide.conf.in ${CMAKE_CURRENT_BINARY_DIR}/doxygen_users_guide.conf) configure_file(doxygen_developers_guide.conf.in ${CMAKE_CURRENT_BINARY_DIR}/doxygen_developers_guide.conf) if(MITK_DOXYGEN_BUILD_ALWAYS) set(_doc_in_all "ALL") else() set(_doc_in_all "") endif() add_custom_target(doc ${_doc_in_all} ${DOXYGEN} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) add_custom_target(doc_usersguide ${DOXYGEN} ${CMAKE_CURRENT_BINARY_DIR}/doxygen_users_guide.conf WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) add_custom_target(doc_developersguide ${DOXYGEN} ${CMAKE_CURRENT_BINARY_DIR}/doxygen_developers_guide.conf WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) set_property(TARGET doc doc_usersguide doc_developersguide PROPERTY FOLDER "${MITK_ROOT_FOLDER}/Documentation") if(MITK_USE_BLUEBERRY) # convert the extension points schema files into html find_package(Ant) set(BLUEBERRY_DOC_TOOLS_DIR "" CACHE PATH "Directory containing additional tools needed for generating the documentation") if(ANT_FOUND AND BLUEBERRY_DOC_TOOLS_DIR) list(APPEND MITK_XP_GLOB_EXPRESSIONS ${MITK_SOURCE_DIR}/Plugins/plugin.xml) file(GLOB_RECURSE _plugin_xmls ${MITK_XP_GLOB_EXPRESSIONS}) mitkFunctionConvertXPSchema(INPUT ${_plugin_xmls} OUTPUT_DIR "${MITK_DOXYGEN_OUTPUT_DIR}/html/extension-points/html" TARGET_NAME mitkXPDoc ) add_dependencies(doc mitkXPDoc) else() message(STATUS "Extension-point schema documentation generation disabled due to missing Ant and / or missing BlueBerry doc tools.") endif() endif(MITK_USE_BLUEBERRY) #if(MITK_DOXYGEN_GENERATE_ITK_QCH_FILE) # # add the command to generate the ITK documentation # add_custom_target(doc-itk # COMMAND ${DOXYGEN} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.itk.conf) # add_dependencies(doc doc-itk) #endif() #if(MITK_DOXYGEN_GENERATE_VTK_QCH_FILE) # # add the command to generate the VTK documentation # add_custom_target(doc-vtk # COMMAND ${DOXYGEN} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.vtk.conf) # add_dependencies(doc doc-vtk) #endif() diff --git a/Modules/Core/include/mitkItkImageIO.h b/Modules/Core/include/mitkItkImageIO.h index 28e70484cf..a1c488f9fb 100644 --- a/Modules/Core/include/mitkItkImageIO.h +++ b/Modules/Core/include/mitkItkImageIO.h @@ -1,67 +1,77 @@ /*============================================================================ 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 MITKITKFILEIO_H #define MITKITKFILEIO_H #include "mitkAbstractFileIO.h" #include namespace mitk { /** * This class wraps ITK image IO objects as mitk::IFileReader and * mitk::IFileWriter objects. * * Instantiating this class with a given itk::ImageIOBase instance * will register corresponding MITK reader/writer services for that * ITK ImageIO object. */ class MITKCORE_EXPORT ItkImageIO : public AbstractFileIO { public: ItkImageIO(itk::ImageIOBase::Pointer imageIO); ItkImageIO(const CustomMimeType &mimeType, itk::ImageIOBase::Pointer imageIO, int rank); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; std::vector> Read() override; ConfidenceLevel GetReaderConfidenceLevel() const override; // -------------- AbstractFileWriter ------------- void Write() override; ConfidenceLevel GetWriterConfidenceLevel() const override; protected: virtual std::vector FixUpImageIOExtensions(const std::string &imageIOName); virtual void FixUpCustomMimeTypeName(const std::string &imageIOName, CustomMimeType &customMimeType); // Fills the m_DefaultMetaDataKeys vector with default values virtual void InitializeDefaultMetaDataKeys(); private: ItkImageIO(const ItkImageIO &other); ItkImageIO *IOClone() const override; itk::ImageIOBase::Pointer m_ImageIO; std::vector m_DefaultMetaDataKeys; }; + /**Helper function that converts the content of a meta data into a time point vector. + * If MetaData is not valid or cannot be converted an empty vector is returned.*/ + MITKCORE_EXPORT std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase* data); + + + /**Helper function that converts the time points of a passed time geometry to a time point list + and stores it in a itk::MetaDataObject. Use ConvertMetaDataObjectToTimePointList() to convert it back + to a time point list.*/ + MITKCORE_EXPORT itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry); + } // namespace mitk #endif /* MITKITKFILEIO_H */ diff --git a/Modules/Core/src/IO/mitkItkImageIO.cpp b/Modules/Core/src/IO/mitkItkImageIO.cpp index 41b85c58be..f482ea5f13 100644 --- a/Modules/Core/src/IO/mitkItkImageIO.cpp +++ b/Modules/Core/src/IO/mitkItkImageIO.cpp @@ -1,699 +1,708 @@ /*============================================================================ 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 "mitkItkImageIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { const char *const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; const char *const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; ItkImageIO::ItkImageIO(const ItkImageIO &other) : AbstractFileIO(other), m_ImageIO(dynamic_cast(other.m_ImageIO->Clone().GetPointer())) { this->InitializeDefaultMetaDataKeys(); } std::vector ItkImageIO::FixUpImageIOExtensions(const std::string &imageIOName) { std::vector extensions; // Try to fix-up some known ITK image IO classes if (imageIOName == "GiplImageIO") { extensions.push_back("gipl"); extensions.push_back("gipl.gz"); } else if (imageIOName == "GDCMImageIO") { extensions.push_back("gdcm"); extensions.push_back("dcm"); extensions.push_back("DCM"); extensions.push_back("dc3"); extensions.push_back("DC3"); extensions.push_back("ima"); extensions.push_back("img"); } else if (imageIOName == "PNGImageIO") { extensions.push_back("png"); extensions.push_back("PNG"); } else if (imageIOName == "StimulateImageIO") { extensions.push_back("spr"); } else if (imageIOName == "HDF5ImageIO") { extensions.push_back("hdf"); extensions.push_back("h4"); extensions.push_back("hdf4"); extensions.push_back("h5"); extensions.push_back("hdf5"); extensions.push_back("he4"); extensions.push_back("he5"); extensions.push_back("hd5"); } else if ("GE4ImageIO" == imageIOName || "GE5ImageIO" == imageIOName || "Bruker2dseqImageIO" == imageIOName) { extensions.push_back(""); } if (!extensions.empty()) { MITK_DEBUG << "Fixing up known extensions for " << imageIOName; } return extensions; } void ItkImageIO::FixUpCustomMimeTypeName(const std::string &imageIOName, CustomMimeType &customMimeType) { if ("GE4ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge4"); } else if ("GE5ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge5"); } else if ("Bruker2dseqImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "bruker2dseq"); } } ItkImageIO::ItkImageIO(itk::ImageIOBase::Pointer imageIO) : AbstractFileIO(Image::GetStaticNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); std::vector readExtensions = m_ImageIO->GetSupportedReadExtensions(); if (readExtensions.empty()) { std::string imageIOName = m_ImageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide read extensions"; readExtensions = FixUpImageIOExtensions(imageIOName); } CustomMimeType customReaderMimeType; customReaderMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = readExtensions.begin(), endIter = readExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customReaderMimeType.AddExtension(extension); } auto extensions = customReaderMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customReaderMimeType); } this->AbstractFileReader::SetMimeType(customReaderMimeType); std::vector writeExtensions = imageIO->GetSupportedWriteExtensions(); if (writeExtensions.empty()) { std::string imageIOName = imageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide write extensions"; writeExtensions = FixUpImageIOExtensions(imageIOName); } if (writeExtensions != readExtensions) { CustomMimeType customWriterMimeType; customWriterMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = writeExtensions.begin(), endIter = writeExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customWriterMimeType.AddExtension(extension); } auto extensions = customWriterMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customWriterMimeType); } this->AbstractFileWriter::SetMimeType(customWriterMimeType); } std::string description = std::string("ITK ") + imageIO->GetNameOfClass(); this->SetReaderDescription(description); this->SetWriterDescription(description); this->RegisterService(); } ItkImageIO::ItkImageIO(const CustomMimeType &mimeType, itk::ImageIOBase::Pointer imageIO, int rank) : AbstractFileIO(Image::GetStaticNameOfClass(), mimeType, std::string("ITK ") + imageIO->GetNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); if (rank) { this->AbstractFileReader::SetRanking(rank); this->AbstractFileWriter::SetRanking(rank); } this->RegisterService(); } - /**Helper function that converts the content of a meta data into a time point vector. - * If MetaData is not valid or cannot be converted an empty vector is returned.*/ - std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase *data) + std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase* data) { - const auto *timeGeometryTimeData = - dynamic_cast *>(data); + const auto* timeGeometryTimeData = + dynamic_cast*>(data); std::vector result; if (timeGeometryTimeData) { std::string dataStr = timeGeometryTimeData->GetMetaDataObjectValue(); std::stringstream stream(dataStr); TimePointType tp; while (stream >> tp) { result.push_back(tp); } } return result; }; + itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry) + { + std::stringstream stream; + stream << timeGeometry->GetTimeBounds(0)[0]; + const auto maxTimePoints = timeGeometry->CountTimeSteps(); + for (TimeStepType pos = 0; pos < maxTimePoints; ++pos) + { + stream << " " << timeGeometry->GetTimeBounds(pos)[1]; + } + auto result = itk::MetaDataObject::New(); + result->SetMetaDataObjectValue(stream.str()); + return result.GetPointer(); + }; + std::vector ItkImageIO::Read() { std::vector result; mitk::LocaleSwitch localeSwitch("C"); Image::Pointer image = Image::New(); const unsigned int MINDIM = 2; const unsigned int MAXDIM = 4; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << " via itk::ImageIOFactory... " << std::endl; // Check to see if we can read the file given the name or prefix if (path.empty()) { mitkThrow() << "Empty filename in mitk::ItkImageIO "; } // Got to allocate space for the image. Determine the characteristics of // the image. m_ImageIO->SetFileName(path); m_ImageIO->ReadImageInformation(); unsigned int ndim = m_ImageIO->GetNumberOfDimensions(); if (ndim < MINDIM || ndim > MAXDIM) { MITK_WARN << "Sorry, only dimensions 2, 3 and 4 are supported. The given file has " << ndim << " dimensions! Reading as 4D."; ndim = MAXDIM; } itk::ImageIORegion ioRegion(ndim); itk::ImageIORegion::SizeType ioSize = ioRegion.GetSize(); itk::ImageIORegion::IndexType ioStart = ioRegion.GetIndex(); unsigned int dimensions[MAXDIM]; dimensions[0] = 0; dimensions[1] = 0; dimensions[2] = 0; dimensions[3] = 0; ScalarType spacing[MAXDIM]; spacing[0] = 1.0f; spacing[1] = 1.0f; spacing[2] = 1.0f; spacing[3] = 1.0f; Point3D origin; origin.Fill(0); unsigned int i; for (i = 0; i < ndim; ++i) { ioStart[i] = 0; ioSize[i] = m_ImageIO->GetDimensions(i); if (i < MAXDIM) { dimensions[i] = m_ImageIO->GetDimensions(i); spacing[i] = m_ImageIO->GetSpacing(i); if (spacing[i] <= 0) spacing[i] = 1.0f; } if (i < 3) { origin[i] = m_ImageIO->GetOrigin(i); } } ioRegion.SetSize(ioSize); ioRegion.SetIndex(ioStart); MITK_INFO << "ioRegion: " << ioRegion << std::endl; m_ImageIO->SetIORegion(ioRegion); void *buffer = new unsigned char[m_ImageIO->GetImageSizeInBytes()]; m_ImageIO->Read(buffer); image->Initialize(MakePixelType(m_ImageIO), ndim, dimensions); image->SetImportChannel(buffer, 0, Image::ManageMemory); const itk::MetaDataDictionary &dictionary = m_ImageIO->GetMetaDataDictionary(); // access direction of itk::Image and include spacing mitk::Matrix3D matrix; matrix.SetIdentity(); unsigned int j, itkDimMax3 = (ndim >= 3 ? 3 : ndim); for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) matrix[i][j] = m_ImageIO->GetDirection(j)[i]; // re-initialize PlaneGeometry with origin and direction PlaneGeometry *planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D SlicedGeometry3D *slicedGeometry = image->GetSlicedGeometry(0); slicedGeometry->InitializeEvenlySpaced(planeGeometry, image->GetDimension(2)); slicedGeometry->SetSpacing(spacing); MITK_INFO << slicedGeometry->GetCornerPoint(false, false, false); MITK_INFO << slicedGeometry->GetCornerPoint(true, true, true); // re-initialize TimeGeometry TimeGeometry::Pointer timeGeometry; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE) || dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TYPE)) { // also check for the name because of backwards compatibility. Past code version stored with the name and not with // the key itk::MetaDataObject::ConstPointer timeGeometryTypeData = nullptr; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE)) { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TYPE)); } else { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TYPE)); } if (timeGeometryTypeData->GetMetaDataObjectValue() == ArbitraryTimeGeometry::GetStaticNameOfClass()) { - MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass() << std::endl; + MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass(); typedef std::vector TimePointVector; TimePointVector timePoints; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)); } else if (dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)); } - if (timePoints.size() - 1 != image->GetDimension(3)) + if (timePoints.empty()) + { + MITK_ERROR << "Stored timepoints are empty. Meta information seems to bee invalid. Switch to ProportionalTimeGeometry fallback"; + } + else if (timePoints.size() - 1 != image->GetDimension(3)) { MITK_ERROR << "Stored timepoints (" << timePoints.size() - 1 << ") and size of image time dimension (" - << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback" - << std::endl; + << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback"; } else { ArbitraryTimeGeometry::Pointer arbitraryTimeGeometry = ArbitraryTimeGeometry::New(); TimePointVector::const_iterator pos = timePoints.begin(); auto prePos = pos++; for (; pos != timePoints.end(); ++prePos, ++pos) { arbitraryTimeGeometry->AppendNewTimeStepClone(slicedGeometry, *prePos, *pos); } timeGeometry = arbitraryTimeGeometry; } } } if (timeGeometry.IsNull()) { // Fallback. If no other valid time geometry has been created, create a ProportionalTimeGeometry - MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass() << std::endl; + MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass(); ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); timeGeometry = propTimeGeometry; } image->SetTimeGeometry(timeGeometry); buffer = nullptr; - MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents() << std::endl; + MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; ++iter) { if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) { const std::string &key = iter->first; std::string assumedPropertyName = key; std::replace(assumedPropertyName.begin(), assumedPropertyName.end(), '_', '.'); std::string mimeTypeName = GetMimeType()->GetName(); // Check if there is already a info for the key and our mime type. - IPropertyPersistence::InfoResultType infoList = mitk::CoreServices::GetPropertyPersistence()->GetInfoByKey(key); + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); - auto predicate = [mimeTypeName](const PropertyPersistenceInfo::ConstPointer &x) { + auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer &x) { return x.IsNotNull() && x->GetMimeTypeName() == mimeTypeName; }; auto finding = std::find_if(infoList.begin(), infoList.end(), predicate); if (finding == infoList.end()) { auto predicateWild = [](const PropertyPersistenceInfo::ConstPointer &x) { return x.IsNotNull() && x->GetMimeTypeName() == PropertyPersistenceInfo::ANY_MIMETYPE_NAME(); }; finding = std::find_if(infoList.begin(), infoList.end(), predicateWild); } PropertyPersistenceInfo::ConstPointer info; if (finding != infoList.end()) { assumedPropertyName = (*finding)->GetName(); info = *finding; } else { // we have not found anything suitable so we generate our own info - PropertyPersistenceInfo::Pointer newInfo = PropertyPersistenceInfo::New(); + auto newInfo = PropertyPersistenceInfo::New(); newInfo->SetNameAndKey(assumedPropertyName, key); newInfo->SetMimeTypeName(PropertyPersistenceInfo::ANY_MIMETYPE_NAME()); info = newInfo; } std::string value = dynamic_cast *>(iter->second.GetPointer())->GetMetaDataObjectValue(); mitk::BaseProperty::Pointer loadedProp = info->GetDeserializationFunction()(value); image->SetProperty(assumedPropertyName.c_str(), loadedProp); // Read properties should be persisted unless they are default properties // which are written anyway bool isDefaultKey(false); for (const auto &defaultKey : m_DefaultMetaDataKeys) { if (defaultKey.length() <= assumedPropertyName.length()) { // does the start match the default key if (assumedPropertyName.substr(0, defaultKey.length()).find(defaultKey) != std::string::npos) { isDefaultKey = true; break; } } } if (!isDefaultKey) { - mitk::CoreServices::GetPropertyPersistence()->AddInfo(info); + propPersistenceService->AddInfo(info); } } } - MITK_INFO << "...finished!" << std::endl; + MITK_INFO << "...finished!"; result.push_back(image.GetPointer()); return result; } AbstractFileIO::ConfidenceLevel ItkImageIO::GetReaderConfidenceLevel() const { return m_ImageIO->CanReadFile(GetLocalFileName().c_str()) ? IFileReader::Supported : IFileReader::Unsupported; } void ItkImageIO::Write() { const auto *image = dynamic_cast(this->GetInput()); if (image == nullptr) { mitkThrow() << "Cannot write non-image data"; } // Switch the current locale to "C" LocaleSwitch localeSwitch("C"); // Clone the image geometry, because we might have to change it // for writing purposes BaseGeometry::Pointer geometry = image->GetGeometry()->Clone(); // Check if geometry information will be lost if (image->GetDimension() == 2 && !geometry->Is2DConvertable()) { MITK_WARN << "Saving a 2D image with 3D geometry information. Geometry information will be lost! You might " "consider using Convert2Dto3DImageFilter before saving."; // set matrix to identity mitk::AffineTransform3D::Pointer affTrans = mitk::AffineTransform3D::New(); affTrans->SetIdentity(); mitk::Vector3D spacing = geometry->GetSpacing(); mitk::Point3D origin = geometry->GetOrigin(); geometry->SetIndexToWorldTransform(affTrans); geometry->SetSpacing(spacing); geometry->SetOrigin(origin); } LocalFile localFile(this); const std::string path = localFile.GetFileName(); MITK_INFO << "Writing image: " << path << std::endl; try { // Implementation of writer using itkImageIO directly. This skips the use // of templated itkImageFileWriter, which saves the multiplexing on MITK side. const unsigned int dimension = image->GetDimension(); const unsigned int *const dimensions = image->GetDimensions(); const mitk::PixelType pixelType = image->GetPixelType(); const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); const mitk::Point3D mitkOrigin = geometry->GetOrigin(); // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, // though they are not supported in MITK itk::Vector spacing4D; spacing4D[0] = mitkSpacing[0]; spacing4D[1] = mitkSpacing[1]; spacing4D[2] = mitkSpacing[2]; spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here itk::Vector origin4D; origin4D[0] = mitkOrigin[0]; origin4D[1] = mitkOrigin[1]; origin4D[2] = mitkOrigin[2]; origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here // Set the necessary information for imageIO m_ImageIO->SetNumberOfDimensions(dimension); m_ImageIO->SetPixelType(pixelType.GetPixelType()); m_ImageIO->SetComponentType(pixelType.GetComponentType() < PixelComponentUserType ? static_cast(pixelType.GetComponentType()) : itk::ImageIOBase::UNKNOWNCOMPONENTTYPE); m_ImageIO->SetNumberOfComponents(pixelType.GetNumberOfComponents()); itk::ImageIORegion ioRegion(dimension); for (unsigned int i = 0; i < dimension; i++) { m_ImageIO->SetDimensions(i, dimensions[i]); m_ImageIO->SetSpacing(i, spacing4D[i]); m_ImageIO->SetOrigin(i, origin4D[i]); mitk::Vector3D mitkDirection; mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i)); itk::Vector direction4D; direction4D[0] = mitkDirection[0]; direction4D[1] = mitkDirection[1]; direction4D[2] = mitkDirection[2]; // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. if (i == 3) { direction4D[3] = 1; // homogenous component } else { direction4D[3] = 0; } vnl_vector axisDirection(dimension); for (unsigned int j = 0; j < dimension; j++) { axisDirection[j] = direction4D[j] / spacing4D[i]; } m_ImageIO->SetDirection(i, axisDirection); ioRegion.SetSize(i, image->GetLargestPossibleRegion().GetSize(i)); ioRegion.SetIndex(i, image->GetLargestPossibleRegion().GetIndex(i)); } // use compression if available m_ImageIO->UseCompressionOn(); m_ImageIO->SetIORegion(ioRegion); m_ImageIO->SetFileName(path); // Handle time geometry const auto *arbitraryTG = dynamic_cast(image->GetTimeGeometry()); if (arbitraryTG) { itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), PROPERTY_KEY_TIMEGEOMETRY_TYPE, ArbitraryTimeGeometry::GetStaticNameOfClass()); - std::stringstream stream; - stream << arbitraryTG->GetTimeBounds(0)[0]; - for (TimeStepType pos = 0; pos < arbitraryTG->CountTimeSteps(); ++pos) - { - stream << " " << arbitraryTG->GetTimeBounds(pos)[1]; - } - std::string data = stream.str(); - - itk::EncapsulateMetaData( - m_ImageIO->GetMetaDataDictionary(), PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, data); + auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); + m_ImageIO->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); } // Handle properties mitk::PropertyList::Pointer imagePropertyList = image->GetPropertyList(); for (const auto &property : *imagePropertyList->GetMap()) { - IPropertyPersistence::InfoResultType infoList = - mitk::CoreServices::GetPropertyPersistence()->GetInfo(property.first, GetMimeType()->GetName(), true); + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, GetMimeType()->GetName(), true); if (infoList.empty()) { continue; } std::string value = infoList.front()->GetSerializationFunction()(property.second); if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) { continue; } std::string key = infoList.front()->GetKey(); itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), key, value); } + ImageReadAccessor imageAccess(image); LocaleSwitch localeSwitch2("C"); m_ImageIO->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } } AbstractFileIO::ConfidenceLevel ItkImageIO::GetWriterConfidenceLevel() const { // Check if the image dimension is supported const auto *image = dynamic_cast(this->GetInput()); if (image == nullptr) { // We cannot write a null object, DUH! return IFileWriter::Unsupported; } if (!m_ImageIO->SupportsDimension(image->GetDimension())) { // okay, dimension is not supported. We have to look at a special case: // 3D-Image with one slice. We can treat that as a 2D image. if ((image->GetDimension() == 3) && (image->GetSlicedGeometry()->GetSlices() == 1)) return IFileWriter::Supported; else return IFileWriter::Unsupported; } // Check if geometry information will be lost if (image->GetDimension() == 2 && !image->GetGeometry()->Is2DConvertable()) { return IFileWriter::PartiallySupported; } return IFileWriter::Supported; } ItkImageIO *ItkImageIO::IOClone() const { return new ItkImageIO(*this); } void ItkImageIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); } } diff --git a/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp b/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp index b5ddd26d2e..e6c3e2ec9a 100644 --- a/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp +++ b/Modules/Core/src/Rendering/mitkPlaneGeometryDataMapper2D.cpp @@ -1,706 +1,706 @@ /*============================================================================ 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 "mitkPlaneGeometryDataMapper2D.h" // mitk includes #include "mitkVtkPropRenderer.h" #include #include #include #include #include #include #include #include #include // vtk includes #include #include #include #include #include #include #include #include #include #include /// #include #include #include #include namespace { /// Some simple interval arithmetic template class SimpleInterval { public: SimpleInterval(T start = T(), T end = T()) : m_LowerBoundary(std::min(start, end)), m_UpperBoundary(std::max(start, end)) { } T GetLowerBoundary() const { return m_LowerBoundary; } T GetUpperBoundary() const { return m_UpperBoundary; } bool empty() const { return m_LowerBoundary == m_UpperBoundary; } bool operator<(const SimpleInterval &otherInterval) const { return this->m_UpperBoundary < otherInterval.GetLowerBoundary(); } private: T m_LowerBoundary; T m_UpperBoundary; }; template class IntervalSet { public: typedef SimpleInterval IntervalType; IntervalSet(IntervalType startingInterval) { m_IntervalsContainer.insert(std::move(startingInterval)); } void operator-=(const IntervalType &interval) { // equal_range will find all the intervals in the interval set which intersect with the input interval // due to the nature of operator< of SimpleInterval auto range = m_IntervalsContainer.equal_range(interval); for (auto iter = range.first; iter != range.second;) { auto subtractionResult = SubtractIntervals(*iter, interval); // Remove the old interval from the set iter = m_IntervalsContainer.erase(iter); for (auto &&interval : subtractionResult) { if (!interval.empty()) { // Add the new interval to the set // emplace_hint adds the element at the closest valid place before the hint iterator, // which is exactly where the new interval should be iter = m_IntervalsContainer.insert(iter, std::move(interval)); ++iter; } } } } IntervalSet operator-(const IntervalType &interval) { IntervalSet result = *this; result -= interval; return result; } typedef std::set IntervalsContainer; const IntervalsContainer &getIntervals() const { return m_IntervalsContainer; } private: IntervalsContainer m_IntervalsContainer; std::array SubtractIntervals(const IntervalType &firstInterval, const IntervalType &secondInterval) { assert(secondInterval.GetUpperBoundary() >= firstInterval.GetLowerBoundary() && firstInterval.GetUpperBoundary() >= secondInterval.GetLowerBoundary()); // Non-intersecting intervals should never reach here if (secondInterval.GetLowerBoundary() < firstInterval.GetLowerBoundary()) { if (firstInterval.GetUpperBoundary() < secondInterval.GetUpperBoundary()) { std::array result = {{IntervalType(), IntervalType()}}; return result; // firstInterval completely enclosed } std::array result = { {IntervalType(firstInterval.GetUpperBoundary(), secondInterval.GetUpperBoundary()), IntervalType()}}; return result; // secondInterval removes the beginning of firstInterval } if (firstInterval.GetUpperBoundary() < secondInterval.GetUpperBoundary()) { std::array result = { {IntervalType(firstInterval.GetLowerBoundary(), secondInterval.GetLowerBoundary()), IntervalType()}}; return result; // secondInterval removes the end of firstInterval } std::array result = { {IntervalType(firstInterval.GetLowerBoundary(), secondInterval.GetLowerBoundary()), IntervalType(secondInterval.GetUpperBoundary(), firstInterval.GetUpperBoundary())}}; return result; // secondInterval is completely enclosed in firstInterval and removes the middle } }; } mitk::PlaneGeometryDataMapper2D::AllInstancesContainer mitk::PlaneGeometryDataMapper2D::s_AllInstances; // input for this mapper ( = PlaneGeometryData) const mitk::PlaneGeometryData *mitk::PlaneGeometryDataMapper2D::GetInput() const { return static_cast(GetDataNode()->GetData()); } mitk::PlaneGeometryDataMapper2D::PlaneGeometryDataMapper2D() : m_RenderOrientationArrows(false), m_ArrowOrientationPositive(true), m_DepthValue(1.0f) { s_AllInstances.insert(this); } mitk::PlaneGeometryDataMapper2D::~PlaneGeometryDataMapper2D() { s_AllInstances.erase(this); } vtkProp *mitk::PlaneGeometryDataMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); return ls->m_CrosshairAssembly; } void mitk::PlaneGeometryDataMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { BaseLocalStorage *ls = m_LSH.GetLocalStorage(renderer); // The PlaneGeometryDataMapper2D mapper is special in that the rendering of // OTHER PlaneGeometryDatas affects how we render THIS PlaneGeometryData // (for the gap at the point where they intersect). A change in any of the // other PlaneGeometryData nodes could mean that we render ourself // differently, so we check for that here. for (auto it = s_AllInstances.begin(); it != s_AllInstances.end(); ++it) { bool generateDataRequired = ls->IsGenerateDataRequired(renderer, this, (*it)->GetDataNode()); if (generateDataRequired) break; } ls->UpdateGenerateDataTime(); // Collect all other PlaneGeometryDatas that are being mapped by this mapper m_OtherPlaneGeometries.clear(); for (auto it = s_AllInstances.begin(); it != s_AllInstances.end(); ++it) { Self *otherInstance = *it; // Skip ourself if (otherInstance == this) continue; mitk::DataNode *otherNode = otherInstance->GetDataNode(); if (!otherNode) continue; // Skip other PlaneGeometryData nodes that are not visible on this renderer if (!otherNode->IsVisible(renderer)) continue; auto *otherData = dynamic_cast(otherNode->GetData()); if (!otherData) continue; auto *otherGeometry = dynamic_cast(otherData->GetPlaneGeometry()); if (otherGeometry && !dynamic_cast(otherData->GetPlaneGeometry())) { m_OtherPlaneGeometries.push_back(otherNode); } } CreateVtkCrosshair(renderer); ApplyAllProperties(renderer); } void mitk::PlaneGeometryDataMapper2D::CreateVtkCrosshair(mitk::BaseRenderer *renderer) { bool visible = true; LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ls->m_CrosshairActor->SetVisibility(0); ls->m_ArrowActor->SetVisibility(0); ls->m_CrosshairHelperLineActor->SetVisibility(0); GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { return; } PlaneGeometryData::ConstPointer input = this->GetInput(); mitk::DataNode *geometryDataNode = renderer->GetCurrentWorldPlaneGeometryNode(); const PlaneGeometryData *rendererWorldPlaneGeometryData = dynamic_cast(geometryDataNode->GetData()); // intersecting with ourself? if (input.IsNull() || input.GetPointer() == rendererWorldPlaneGeometryData) { return; // nothing to do in this case } const auto *inputPlaneGeometry = dynamic_cast(input->GetPlaneGeometry()); const auto *worldPlaneGeometry = dynamic_cast(rendererWorldPlaneGeometryData->GetPlaneGeometry()); if (worldPlaneGeometry && dynamic_cast(worldPlaneGeometry) == nullptr && inputPlaneGeometry && dynamic_cast(input->GetPlaneGeometry()) == nullptr) { const BaseGeometry *referenceGeometry = inputPlaneGeometry->GetReferenceGeometry(); // calculate intersection of the plane data with the border of the // world geometry rectangle Point3D point1, point2; Line3D crossLine; // Calculate the intersection line of the input plane with the world plane if (worldPlaneGeometry->IntersectionLine(inputPlaneGeometry, crossLine)) { bool hasIntersection = referenceGeometry ? CutCrossLineWithReferenceGeometry(referenceGeometry, crossLine) : CutCrossLineWithPlaneGeometry(inputPlaneGeometry, crossLine); if (!hasIntersection) { return; } point1 = crossLine.GetPoint1(); point2 = crossLine.GetPoint2(); vtkSmartPointer lines = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer linesPolyData = vtkSmartPointer::New(); // Now iterate through all other lines displayed in this window and // calculate the positions of intersection with the line to be // rendered; these positions will be stored in lineParams to form a // gap afterwards. auto otherPlanesIt = m_OtherPlaneGeometries.begin(); auto otherPlanesEnd = m_OtherPlaneGeometries.end(); int gapSize = 32; this->GetDataNode()->GetPropertyValue("Crosshair.Gap Size", gapSize, nullptr); auto intervals = IntervalSet(SimpleInterval(0, 1)); ScalarType lineLength = point1.EuclideanDistanceTo(point2); ScalarType gapInMM = gapSize * renderer->GetScaleFactorMMPerDisplayUnit(); float gapSizeParam = gapInMM / lineLength; if (gapSize != 0) { while (otherPlanesIt != otherPlanesEnd) { bool ignorePlane = false; (*otherPlanesIt)->GetPropertyValue("Crosshair.Ignore", ignorePlane); if (ignorePlane) { ++otherPlanesIt; continue; } auto *otherPlaneGeometry = static_cast( static_cast((*otherPlanesIt)->GetData())->GetPlaneGeometry()); if (otherPlaneGeometry != inputPlaneGeometry && otherPlaneGeometry != worldPlaneGeometry) { double intersectionParam; if (otherPlaneGeometry->IntersectionPointParam(crossLine, intersectionParam) && intersectionParam > 0 && intersectionParam < 1) { Point3D point = crossLine.GetPoint() + intersectionParam * crossLine.GetDirection(); bool intersectionPointInsideOtherPlane = otherPlaneGeometry->HasReferenceGeometry() ? TestPointInReferenceGeometry(otherPlaneGeometry->GetReferenceGeometry(), point) : TestPointInPlaneGeometry(otherPlaneGeometry, point); if (intersectionPointInsideOtherPlane) { intervals -= SimpleInterval(intersectionParam - gapSizeParam, intersectionParam + gapSizeParam); } } } ++otherPlanesIt; } } for (const auto &interval : intervals.getIntervals()) { this->DrawLine(crossLine.GetPoint(interval.GetLowerBoundary()), crossLine.GetPoint(interval.GetUpperBoundary()), lines, points); } // Add the points to the dataset linesPolyData->SetPoints(points); // Add the lines to the dataset linesPolyData->SetLines(lines); Vector3D orthogonalVector; orthogonalVector = inputPlaneGeometry->GetNormal(); worldPlaneGeometry->Project(orthogonalVector, orthogonalVector); orthogonalVector.Normalize(); // Visualize ls->m_Mapper->SetInputData(linesPolyData); ls->m_CrosshairActor->SetMapper(ls->m_Mapper); // Determine if we should draw the area covered by the thick slicing, default is false. // This will also show the area of slices that do not have thick slice mode enabled bool showAreaOfThickSlicing = false; GetDataNode()->GetBoolProperty("reslice.thickslices.showarea", showAreaOfThickSlicing); // determine the pixelSpacing in that direction double thickSliceDistance = SlicedGeometry3D::CalculateSpacing( referenceGeometry ? referenceGeometry->GetSpacing() : inputPlaneGeometry->GetSpacing(), orthogonalVector); IntProperty *intProperty = nullptr; if (GetDataNode()->GetProperty(intProperty, "reslice.thickslices.num") && intProperty) thickSliceDistance *= intProperty->GetValue() + 0.5; else showAreaOfThickSlicing = false; // not the nicest place to do it, but we have the width of the visible bloc in MM here // so we store it in this fancy property GetDataNode()->SetFloatProperty("reslice.thickslices.sizeinmm", thickSliceDistance * 2); ls->m_CrosshairActor->SetVisibility(1); vtkSmartPointer arrowPolyData = vtkSmartPointer::New(); ls->m_Arrowmapper->SetInputData(arrowPolyData); if (this->m_RenderOrientationArrows) { ScalarType triangleSizeMM = 7.0 * renderer->GetScaleFactorMMPerDisplayUnit(); vtkSmartPointer triangles = vtkSmartPointer::New(); vtkSmartPointer triPoints = vtkSmartPointer::New(); DrawOrientationArrow(triangles, triPoints, triangleSizeMM, orthogonalVector, point1, point2); DrawOrientationArrow(triangles, triPoints, triangleSizeMM, orthogonalVector, point2, point1); arrowPolyData->SetPoints(triPoints); arrowPolyData->SetPolys(triangles); ls->m_ArrowActor->SetVisibility(1); } // Visualize vtkSmartPointer helperlinesPolyData = vtkSmartPointer::New(); ls->m_HelperLinesmapper->SetInputData(helperlinesPolyData); if (showAreaOfThickSlicing) { vtkSmartPointer helperlines = vtkSmartPointer::New(); // vectorToHelperLine defines how to reach the helperLine from the mainLine // got the right direction, so we multiply the width Vector3D vecToHelperLine = orthogonalVector * thickSliceDistance; this->DrawLine(point1 - vecToHelperLine, point2 - vecToHelperLine, helperlines, points); this->DrawLine(point1 + vecToHelperLine, point2 + vecToHelperLine, helperlines, points); // Add the points to the dataset helperlinesPolyData->SetPoints(points); // Add the lines to the dataset helperlinesPolyData->SetLines(helperlines); ls->m_CrosshairActor->GetProperty()->SetLineStipplePattern(0xf0f0); ls->m_CrosshairActor->GetProperty()->SetLineStippleRepeatFactor(1); ls->m_CrosshairHelperLineActor->SetVisibility(1); } } } } bool mitk::PlaneGeometryDataMapper2D::TestPointInPlaneGeometry(const PlaneGeometry *planeGeometry, const Point3D &point) { Point2D mappedPoint; planeGeometry->Map(point, mappedPoint); planeGeometry->WorldToIndex(mappedPoint, mappedPoint); return (planeGeometry->GetBounds()[0] < mappedPoint[0] && mappedPoint[0] < planeGeometry->GetBounds()[1] && planeGeometry->GetBounds()[2] < mappedPoint[1] && mappedPoint[1] < planeGeometry->GetBounds()[3]); } bool mitk::PlaneGeometryDataMapper2D::TestPointInReferenceGeometry(const BaseGeometry *referenceGeometry, const Point3D &point) { return referenceGeometry->IsInside(point); } bool mitk::PlaneGeometryDataMapper2D::CutCrossLineWithPlaneGeometry(const PlaneGeometry *planeGeometry, Line3D &crossLine) { Point2D indexLinePoint; Vector2D indexLineDirection; planeGeometry->Map(crossLine.GetPoint(), indexLinePoint); planeGeometry->Map(crossLine.GetPoint(), crossLine.GetDirection(), indexLineDirection); planeGeometry->WorldToIndex(indexLinePoint, indexLinePoint); planeGeometry->WorldToIndex(indexLineDirection, indexLineDirection); mitk::Point2D intersectionPoints[2]; // Then, clip this line with the (transformed) bounding box of the // reference geometry. int nIntersections = Line3D::RectangleLineIntersection(planeGeometry->GetBounds()[0], planeGeometry->GetBounds()[2], planeGeometry->GetBounds()[1], planeGeometry->GetBounds()[3], indexLinePoint, indexLineDirection, intersectionPoints[0], intersectionPoints[1]); if (nIntersections < 2) { return false; } planeGeometry->IndexToWorld(intersectionPoints[0], intersectionPoints[0]); planeGeometry->IndexToWorld(intersectionPoints[1], intersectionPoints[1]); Point3D point1, point2; planeGeometry->Map(intersectionPoints[0], point1); planeGeometry->Map(intersectionPoints[1], point2); crossLine.SetPoints(point1, point2); return true; } bool mitk::PlaneGeometryDataMapper2D::CutCrossLineWithReferenceGeometry(const BaseGeometry *referenceGeometry, Line3D &crossLine) { Point3D boundingBoxMin, boundingBoxMax; boundingBoxMin = referenceGeometry->GetCornerPoint(0); boundingBoxMax = referenceGeometry->GetCornerPoint(7); Point3D indexLinePoint; Vector3D indexLineDirection; referenceGeometry->WorldToIndex(crossLine.GetPoint(), indexLinePoint); referenceGeometry->WorldToIndex(crossLine.GetDirection(), indexLineDirection); referenceGeometry->WorldToIndex(boundingBoxMin, boundingBoxMin); referenceGeometry->WorldToIndex(boundingBoxMax, boundingBoxMax); Point3D point1, point2; // Then, clip this line with the (transformed) bounding box of the // reference geometry. int nIntersections = Line3D::BoxLineIntersection(boundingBoxMin[0], boundingBoxMin[1], boundingBoxMin[2], boundingBoxMax[0], boundingBoxMax[1], boundingBoxMax[2], indexLinePoint, indexLineDirection, point1, point2); if (nIntersections < 2) { return false; } referenceGeometry->IndexToWorld(point1, point1); referenceGeometry->IndexToWorld(point2, point2); crossLine.SetPoints(point1, point2); return true; } void mitk::PlaneGeometryDataMapper2D::DrawLine(mitk::Point3D p0, mitk::Point3D p1, vtkCellArray *lines, vtkPoints *points) { vtkIdType pidStart = points->InsertNextPoint(p0[0], p0[1], p0[2]); vtkIdType pidEnd = points->InsertNextPoint(p1[0], p1[1], p1[2]); vtkSmartPointer lineVtk = vtkSmartPointer::New(); lineVtk->GetPointIds()->SetId(0, pidStart); lineVtk->GetPointIds()->SetId(1, pidEnd); lines->InsertNextCell(lineVtk); } void mitk::PlaneGeometryDataMapper2D::DrawOrientationArrow(vtkSmartPointer triangles, vtkSmartPointer triPoints, double triangleSizeMM, Vector3D &orthogonalVector, Point3D &point1, Point3D &point2) { // Draw arrows to indicate plane orientation // Vector along line Vector3D v1 = point2 - point1; v1.Normalize(); v1 *= triangleSizeMM; // Orthogonal vector Vector3D v2 = orthogonalVector; v2 *= triangleSizeMM; if (!this->m_ArrowOrientationPositive) v2 *= -1.0; // Initialize remaining triangle coordinates accordingly Point3D p1 = point1 + v1 * 2.0; Point3D p2 = point1 + v1 + v2; vtkIdType t0 = triPoints->InsertNextPoint(point1[0], point1[1], point1[2]); // start of the line vtkIdType t1 = triPoints->InsertNextPoint(p1[0], p1[1], p1[2]); // point on line vtkIdType t2 = triPoints->InsertNextPoint(p2[0], p2[1], p2[2]); // direction point vtkSmartPointer triangle = vtkSmartPointer::New(); triangle->GetPointIds()->SetId(0, t0); triangle->GetPointIds()->SetId(1, t1); triangle->GetPointIds()->SetId(2, t2); triangles->InsertNextCell(triangle); } int mitk::PlaneGeometryDataMapper2D::DetermineThickSliceMode(DataNode *dn, int &thickSlicesNum) { int thickSlicesMode = 0; // determine the state and the extend of the thick-slice mode mitk::ResliceMethodProperty *resliceMethodEnumProperty = nullptr; if (dn->GetProperty(resliceMethodEnumProperty, "reslice.thickslices") && resliceMethodEnumProperty) thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); IntProperty *intProperty = nullptr; if (dn->GetProperty(intProperty, "reslice.thickslices.num") && intProperty) { thickSlicesNum = intProperty->GetValue(); if (thickSlicesNum < 1) thickSlicesNum = 0; if (thickSlicesNum > 10) thickSlicesNum = 10; } if (thickSlicesMode == 0) thickSlicesNum = 0; return thickSlicesMode; } void mitk::PlaneGeometryDataMapper2D::ApplyAllProperties(BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ApplyColorAndOpacityProperties2D(renderer, ls->m_CrosshairActor); ApplyColorAndOpacityProperties2D(renderer, ls->m_CrosshairHelperLineActor); ApplyColorAndOpacityProperties2D(renderer, ls->m_ArrowActor); float thickness; this->GetDataNode()->GetFloatProperty("Line width", thickness, renderer); ls->m_CrosshairActor->GetProperty()->SetLineWidth(thickness); ls->m_CrosshairHelperLineActor->GetProperty()->SetLineWidth(thickness); PlaneOrientationProperty *decorationProperty; this->GetDataNode()->GetProperty(decorationProperty, "decoration", renderer); if (decorationProperty != nullptr) { if (decorationProperty->GetPlaneDecoration() == PlaneOrientationProperty::PLANE_DECORATION_POSITIVE_ORIENTATION) { m_RenderOrientationArrows = true; m_ArrowOrientationPositive = true; } else if (decorationProperty->GetPlaneDecoration() == PlaneOrientationProperty::PLANE_DECORATION_NEGATIVE_ORIENTATION) { m_RenderOrientationArrows = true; m_ArrowOrientationPositive = false; } else { m_RenderOrientationArrows = false; } } } void mitk::PlaneGeometryDataMapper2D::ApplyColorAndOpacityProperties2D(BaseRenderer *renderer, vtkActor2D *actor) { float rgba[4] = {1.0f, 1.0f, 1.0f, 1.0f}; DataNode *node = GetDataNode(); // check for color prop and use it for rendering if it exists node->GetColor(rgba, renderer, "color"); // check for opacity prop and use it for rendering if it exists node->GetOpacity(rgba[3], renderer, "opacity"); double drgba[4] = {rgba[0], rgba[1], rgba[2], rgba[3]}; actor->GetProperty()->SetColor(drgba); actor->GetProperty()->SetOpacity(drgba[3]); } void mitk::PlaneGeometryDataMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { - mitk::IPropertyAliases *aliases = mitk::CoreServices::GetPropertyAliases(); + mitk::CoreServicePointer aliases(mitk::CoreServices::GetPropertyAliases()); node->AddProperty("Line width", mitk::FloatProperty::New(1), renderer, overwrite); aliases->AddAlias("line width", "Crosshair.Line Width", ""); node->AddProperty("Crosshair.Gap Size", mitk::IntProperty::New(32), renderer, overwrite); node->AddProperty("decoration", mitk::PlaneOrientationProperty::New(PlaneOrientationProperty::PLANE_DECORATION_NONE), renderer, overwrite); aliases->AddAlias("decoration", "Crosshair.Orientation Decoration", ""); Superclass::SetDefaultProperties(node, renderer, overwrite); } void mitk::PlaneGeometryDataMapper2D::UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) { } mitk::PlaneGeometryDataMapper2D::LocalStorage::LocalStorage() { m_CrosshairAssembly = vtkSmartPointer::New(); m_CrosshairActor = vtkSmartPointer::New(); m_ArrowActor = vtkSmartPointer::New(); m_CrosshairHelperLineActor = vtkSmartPointer::New(); m_HelperLinesmapper = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Arrowmapper = vtkSmartPointer::New(); m_CrosshairActor->SetMapper(m_Mapper); m_ArrowActor->SetMapper(m_Arrowmapper); m_CrosshairHelperLineActor->SetMapper(m_HelperLinesmapper); m_CrosshairActor->SetVisibility(0); m_ArrowActor->SetVisibility(0); m_CrosshairHelperLineActor->SetVisibility(0); m_CrosshairAssembly->AddPart(m_CrosshairActor); m_CrosshairAssembly->AddPart(m_ArrowActor); m_CrosshairAssembly->AddPart(m_CrosshairHelperLineActor); vtkCoordinate *tcoord = vtkCoordinate::New(); tcoord->SetCoordinateSystemToWorld(); m_HelperLinesmapper->SetTransformCoordinate(tcoord); m_Mapper->SetTransformCoordinate(tcoord); // tcoord->SetCoordinateSystemToNormalizedDisplay(); m_Arrowmapper->SetTransformCoordinate(tcoord); tcoord->Delete(); } mitk::PlaneGeometryDataMapper2D::LocalStorage::~LocalStorage() { } diff --git a/Modules/Core/src/Rendering/mitkSurfaceVtkMapper2D.cpp b/Modules/Core/src/Rendering/mitkSurfaceVtkMapper2D.cpp index 87709dff10..3439ff2a22 100644 --- a/Modules/Core/src/Rendering/mitkSurfaceVtkMapper2D.cpp +++ b/Modules/Core/src/Rendering/mitkSurfaceVtkMapper2D.cpp @@ -1,402 +1,402 @@ /*============================================================================ 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 "mitkSurfaceVtkMapper2D.h" // MITK includes #include "mitkVtkPropRenderer.h" #include #include #include #include #include #include #include #include #include // VTK includes #include #include #include #include #include #include #include #include #include #include #include // constructor LocalStorage mitk::SurfaceVtkMapper2D::LocalStorage::LocalStorage() { m_Mapper = vtkSmartPointer::New(); m_Mapper->ScalarVisibilityOff(); m_Actor = vtkSmartPointer::New(); m_PropAssembly = vtkSmartPointer::New(); m_PropAssembly->AddPart(m_Actor); m_CuttingPlane = vtkSmartPointer::New(); m_Cutter = vtkSmartPointer::New(); m_Cutter->SetCutFunction(m_CuttingPlane); m_Mapper->SetInputConnection(m_Cutter->GetOutputPort()); m_NormalGlyph = vtkSmartPointer::New(); m_InverseNormalGlyph = vtkSmartPointer::New(); // Source for the glyph filter m_ArrowSource = vtkSmartPointer::New(); // set small default values for fast rendering m_ArrowSource->SetTipRadius(0.05); m_ArrowSource->SetTipLength(0.20); m_ArrowSource->SetTipResolution(5); m_ArrowSource->SetShaftResolution(5); m_ArrowSource->SetShaftRadius(0.01); m_NormalGlyph->SetSourceConnection(m_ArrowSource->GetOutputPort()); m_NormalGlyph->SetVectorModeToUseNormal(); m_NormalGlyph->OrientOn(); m_InverseNormalGlyph->SetSourceConnection(m_ArrowSource->GetOutputPort()); m_InverseNormalGlyph->SetVectorModeToUseNormal(); m_InverseNormalGlyph->OrientOn(); m_NormalMapper = vtkSmartPointer::New(); m_NormalMapper->SetInputConnection(m_NormalGlyph->GetOutputPort()); m_NormalMapper->ScalarVisibilityOff(); m_InverseNormalMapper = vtkSmartPointer::New(); m_InverseNormalMapper->SetInputConnection(m_NormalGlyph->GetOutputPort()); m_InverseNormalMapper->ScalarVisibilityOff(); m_NormalActor = vtkSmartPointer::New(); m_NormalActor->SetMapper(m_NormalMapper); m_InverseNormalActor = vtkSmartPointer::New(); m_InverseNormalActor->SetMapper(m_InverseNormalMapper); m_ReverseSense = vtkSmartPointer::New(); } // destructor LocalStorage mitk::SurfaceVtkMapper2D::LocalStorage::~LocalStorage() { } const mitk::Surface *mitk::SurfaceVtkMapper2D::GetInput() const { return static_cast(GetDataNode()->GetData()); } // constructor PointSetVtkMapper2D mitk::SurfaceVtkMapper2D::SurfaceVtkMapper2D() { } mitk::SurfaceVtkMapper2D::~SurfaceVtkMapper2D() { } // reset mapper so that nothing is displayed e.g. toggle visiblity of the propassembly void mitk::SurfaceVtkMapper2D::ResetMapper(BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ls->m_PropAssembly->VisibilityOff(); } vtkProp *mitk::SurfaceVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); return ls->m_PropAssembly; } void mitk::SurfaceVtkMapper2D::Update(mitk::BaseRenderer *renderer) { const mitk::DataNode *node = GetDataNode(); if (node == nullptr) return; bool visible = true; node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *surface = static_cast(node->GetData()); if (surface == nullptr) return; // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const mitk::TimeGeometry *dataTimeGeometry = surface->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } surface->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to rerender if ((localStorage->m_LastUpdateTime < node->GetMTime()) // was the node modified? || (localStorage->m_LastUpdateTime < surface->GetPipelineMTime()) // Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) // was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) // was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer(renderer); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } void mitk::SurfaceVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { const DataNode *node = GetDataNode(); auto *surface = static_cast(node->GetData()); const TimeGeometry *dataTimeGeometry = surface->GetTimeGeometry(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); ScalarType time = renderer->GetTime(); int timestep = 0; if (time > itk::NumericTraits::NonpositiveMin()) timestep = dataTimeGeometry->TimePointToTimeStep(time); vtkSmartPointer inputPolyData = surface->GetVtkPolyData(timestep); if ((inputPolyData == nullptr) || (inputPolyData->GetNumberOfPoints() < 1)) return; // apply color and opacity read from the PropertyList this->ApplyAllProperties(renderer); const PlaneGeometry *planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); if ((planeGeometry == nullptr) || (!planeGeometry->IsValid()) || (!planeGeometry->HasReferenceGeometry())) { return; } if (localStorage->m_Actor->GetMapper() == nullptr) localStorage->m_Actor->SetMapper(localStorage->m_Mapper); double origin[3]; origin[0] = planeGeometry->GetOrigin()[0]; origin[1] = planeGeometry->GetOrigin()[1]; origin[2] = planeGeometry->GetOrigin()[2]; double normal[3]; normal[0] = planeGeometry->GetNormal()[0]; normal[1] = planeGeometry->GetNormal()[1]; normal[2] = planeGeometry->GetNormal()[2]; localStorage->m_CuttingPlane->SetOrigin(origin); localStorage->m_CuttingPlane->SetNormal(normal); // Transform the data according to its geometry. // See UpdateVtkTransform documentation for details. vtkSmartPointer vtktransform = GetDataNode()->GetVtkTransform(this->GetTimestep()); vtkSmartPointer filter = vtkSmartPointer::New(); filter->SetTransform(vtktransform); filter->SetInputData(inputPolyData); localStorage->m_Cutter->SetInputConnection(filter->GetOutputPort()); localStorage->m_Cutter->Update(); bool generateNormals = false; node->GetBoolProperty("draw normals 2D", generateNormals); if (generateNormals) { localStorage->m_NormalGlyph->SetInputConnection(localStorage->m_Cutter->GetOutputPort()); localStorage->m_NormalGlyph->Update(); localStorage->m_NormalMapper->SetInputConnection(localStorage->m_NormalGlyph->GetOutputPort()); localStorage->m_PropAssembly->AddPart(localStorage->m_NormalActor); } else { localStorage->m_NormalGlyph->SetInputConnection(nullptr); localStorage->m_PropAssembly->RemovePart(localStorage->m_NormalActor); } bool generateInverseNormals = false; node->GetBoolProperty("invert normals", generateInverseNormals); if (generateInverseNormals) { localStorage->m_ReverseSense->SetInputConnection(localStorage->m_Cutter->GetOutputPort()); localStorage->m_ReverseSense->ReverseCellsOff(); localStorage->m_ReverseSense->ReverseNormalsOn(); localStorage->m_InverseNormalGlyph->SetInputConnection(localStorage->m_ReverseSense->GetOutputPort()); localStorage->m_InverseNormalGlyph->Update(); localStorage->m_InverseNormalMapper->SetInputConnection(localStorage->m_InverseNormalGlyph->GetOutputPort()); localStorage->m_PropAssembly->AddPart(localStorage->m_InverseNormalActor); } else { localStorage->m_ReverseSense->SetInputConnection(nullptr); localStorage->m_PropAssembly->RemovePart(localStorage->m_InverseNormalActor); } } void mitk::SurfaceVtkMapper2D::FixupLegacyProperties(PropertyList *properties) { // Before bug 18528, "line width" was an IntProperty, now it is a FloatProperty float lineWidth = 1.0f; if (!properties->GetFloatProperty("line width", lineWidth)) { int legacyLineWidth = lineWidth; if (properties->GetIntProperty("line width", legacyLineWidth)) { properties->ReplaceProperty("line width", FloatProperty::New(static_cast(legacyLineWidth))); } } } void mitk::SurfaceVtkMapper2D::ApplyAllProperties(mitk::BaseRenderer *renderer) { const DataNode *node = GetDataNode(); if (node == nullptr) { return; } FixupLegacyProperties(node->GetPropertyList(renderer)); FixupLegacyProperties(node->GetPropertyList()); float lineWidth = 1.0f; node->GetFloatProperty("line width", lineWidth, renderer); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check for color and opacity properties, use it for rendering if they exists float color[3] = {1.0f, 1.0f, 1.0f}; node->GetColor(color, renderer, "color"); float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); // Pass properties to VTK localStorage->m_Actor->GetProperty()->SetColor(color[0], color[1], color[2]); localStorage->m_Actor->GetProperty()->SetOpacity(opacity); localStorage->m_NormalActor->GetProperty()->SetOpacity(opacity); localStorage->m_InverseNormalActor->GetProperty()->SetOpacity(opacity); localStorage->m_Actor->GetProperty()->SetLineWidth(lineWidth); // By default, the cutter will also copy/compute normals of the cut // to the output polydata. The normals will influence the // vtkPolyDataMapper lightning. To view a clean cut the lighting has // to be disabled. localStorage->m_Actor->GetProperty()->SetLighting(false); // same block for scalar data rendering as in 3D mapper mitk::TransferFunctionProperty::Pointer transferFuncProp; this->GetDataNode()->GetProperty(transferFuncProp, "Surface.TransferFunction", renderer); if (transferFuncProp.IsNotNull()) { localStorage->m_Mapper->SetLookupTable(transferFuncProp->GetValue()->GetColorTransferFunction()); } mitk::LookupTableProperty::Pointer lookupTableProp; this->GetDataNode()->GetProperty(lookupTableProp, "LookupTable", renderer); if (lookupTableProp.IsNotNull()) { localStorage->m_Mapper->SetLookupTable(lookupTableProp->GetLookupTable()->GetVtkLookupTable()); } mitk::LevelWindow levelWindow; if (this->GetDataNode()->GetLevelWindow(levelWindow, renderer, "levelWindow")) { localStorage->m_Mapper->SetScalarRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); } else if (this->GetDataNode()->GetLevelWindow(levelWindow, renderer)) { localStorage->m_Mapper->SetScalarRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); } bool scalarVisibility = false; this->GetDataNode()->GetBoolProperty("scalar visibility", scalarVisibility); localStorage->m_Mapper->SetScalarVisibility((scalarVisibility ? 1 : 0)); if (scalarVisibility) { mitk::VtkScalarModeProperty *scalarMode; if (this->GetDataNode()->GetProperty(scalarMode, "scalar mode", renderer)) localStorage->m_Mapper->SetScalarMode(scalarMode->GetVtkScalarMode()); else localStorage->m_Mapper->SetScalarModeToDefault(); bool colorMode = false; this->GetDataNode()->GetBoolProperty("color mode", colorMode); localStorage->m_Mapper->SetColorMode((colorMode ? 1 : 0)); double scalarsMin = 0; this->GetDataNode()->GetDoubleProperty("ScalarsRangeMinimum", scalarsMin, renderer); double scalarsMax = 1.0; this->GetDataNode()->GetDoubleProperty("ScalarsRangeMaximum", scalarsMax, renderer); localStorage->m_Mapper->SetScalarRange(scalarsMin, scalarsMax); } // color for inverse normals float inverseNormalsColor[3] = {1.0f, 0.0f, 0.0f}; node->GetColor(inverseNormalsColor, renderer, "back color"); localStorage->m_InverseNormalActor->GetProperty()->SetColor( inverseNormalsColor[0], inverseNormalsColor[1], inverseNormalsColor[2]); // color for normals float normalsColor[3] = {0.0f, 1.0f, 0.0f}; node->GetColor(normalsColor, renderer, "front color"); localStorage->m_NormalActor->GetProperty()->SetColor(normalsColor[0], normalsColor[1], normalsColor[2]); // normals scaling float normalScaleFactor = 10.0f; node->GetFloatProperty("front normal lenth (px)", normalScaleFactor, renderer); localStorage->m_NormalGlyph->SetScaleFactor(normalScaleFactor); // inverse normals scaling float inverseNormalScaleFactor = 10.0f; node->GetFloatProperty("back normal lenth (px)", inverseNormalScaleFactor, renderer); localStorage->m_InverseNormalGlyph->SetScaleFactor(inverseNormalScaleFactor); } void mitk::SurfaceVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { - mitk::IPropertyAliases *aliases = mitk::CoreServices::GetPropertyAliases(); + mitk::CoreServicePointer aliases(mitk::CoreServices::GetPropertyAliases()); node->AddProperty("line width", FloatProperty::New(2.0f), renderer, overwrite); aliases->AddAlias("line width", "Surface.2D.Line Width", "Surface"); node->AddProperty("scalar mode", VtkScalarModeProperty::New(), renderer, overwrite); node->AddProperty("draw normals 2D", BoolProperty::New(false), renderer, overwrite); aliases->AddAlias("draw normals 2D", "Surface.2D.Normals.Draw Normals", "Surface"); node->AddProperty("invert normals", BoolProperty::New(false), renderer, overwrite); aliases->AddAlias("invert normals", "Surface.2D.Normals.Draw Inverse Normals", "Surface"); node->AddProperty("front color", ColorProperty::New(0.0, 1.0, 0.0), renderer, overwrite); aliases->AddAlias("front color", "Surface.2D.Normals.Normals Color", "Surface"); node->AddProperty("back color", ColorProperty::New(1.0, 0.0, 0.0), renderer, overwrite); aliases->AddAlias("back color", "Surface.2D.Normals.Inverse Normals Color", "Surface"); node->AddProperty("front normal lenth (px)", FloatProperty::New(10.0), renderer, overwrite); aliases->AddAlias("front normal lenth (px)", "Surface.2D.Normals.Normals Scale Factor", "Surface"); node->AddProperty("back normal lenth (px)", FloatProperty::New(10.0), renderer, overwrite); aliases->AddAlias("back normal lenth (px)", "Surface.2D.Normals.Inverse Normals Scale Factor", "Surface"); node->AddProperty("layer", IntProperty::New(100), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/Core/src/Rendering/mitkSurfaceVtkMapper3D.cpp b/Modules/Core/src/Rendering/mitkSurfaceVtkMapper3D.cpp index 25d57dd493..8d45e37923 100644 --- a/Modules/Core/src/Rendering/mitkSurfaceVtkMapper3D.cpp +++ b/Modules/Core/src/Rendering/mitkSurfaceVtkMapper3D.cpp @@ -1,518 +1,519 @@ /*============================================================================ 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 "mitkSurfaceVtkMapper3D.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // VTK #include #include #include #include #include #include #include #include const mitk::Surface *mitk::SurfaceVtkMapper3D::GetInput() { return static_cast(GetDataNode()->GetData()); } mitk::SurfaceVtkMapper3D::SurfaceVtkMapper3D() { m_GenerateNormals = false; } mitk::SurfaceVtkMapper3D::~SurfaceVtkMapper3D() { } void mitk::SurfaceVtkMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { ls->m_Actor->VisibilityOff(); return; } // // set the input-object at time t for the mapper // mitk::Surface::ConstPointer input = this->GetInput(); vtkSmartPointer polydata = input->GetVtkPolyData(this->GetTimestep()); if (polydata == nullptr) { ls->m_Actor->VisibilityOff(); return; } if (m_GenerateNormals) { ls->m_VtkPolyDataNormals->SetInputData(polydata); ls->m_VtkPolyDataMapper->SetInputConnection(ls->m_VtkPolyDataNormals->GetOutputPort()); } else { bool depthsorting = false; GetDataNode()->GetBoolProperty("Depth Sorting", depthsorting); if (depthsorting) { ls->m_DepthSort->SetInputData(polydata); ls->m_DepthSort->SetCamera(renderer->GetVtkRenderer()->GetActiveCamera()); ls->m_DepthSort->SetDirectionToBackToFront(); ls->m_DepthSort->Update(); ls->m_VtkPolyDataMapper->SetInputConnection(ls->m_DepthSort->GetOutputPort()); } else { ls->m_VtkPolyDataMapper->SetInputData(polydata); } } // // apply properties read from the PropertyList // ApplyAllProperties(renderer, ls->m_Actor); if (visible) ls->m_Actor->VisibilityOn(); } void mitk::SurfaceVtkMapper3D::ResetMapper(BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ls->m_Actor->VisibilityOff(); } void mitk::SurfaceVtkMapper3D::ApplyMitkPropertiesToVtkProperty(mitk::DataNode *node, vtkProperty *property, mitk::BaseRenderer *renderer) { // Backface culling { mitk::BoolProperty::Pointer p; node->GetProperty(p, "Backface Culling", renderer); bool useCulling = false; if (p.IsNotNull()) useCulling = p->GetValue(); property->SetBackfaceCulling(useCulling); } // Colors { double ambient[3] = {0.5, 0.5, 0.0}; double diffuse[3] = {0.5, 0.5, 0.0}; double specular[3] = {1.0, 1.0, 1.0}; float coeff_ambient = 0.5f; float coeff_diffuse = 0.5f; float coeff_specular = 0.5f; float power_specular = 10.0f; // Color { mitk::ColorProperty::Pointer p; node->GetProperty(p, "color", renderer); if (p.IsNotNull()) { mitk::Color c = p->GetColor(); ambient[0] = c.GetRed(); ambient[1] = c.GetGreen(); ambient[2] = c.GetBlue(); diffuse[0] = c.GetRed(); diffuse[1] = c.GetGreen(); diffuse[2] = c.GetBlue(); // Setting specular color to the same, make physically no real sense, however vtk rendering slows down, if these // colors are different. specular[0] = c.GetRed(); specular[1] = c.GetGreen(); specular[2] = c.GetBlue(); } } // Ambient { mitk::ColorProperty::Pointer p; node->GetProperty(p, "material.ambientColor", renderer); if (p.IsNotNull()) { mitk::Color c = p->GetColor(); ambient[0] = c.GetRed(); ambient[1] = c.GetGreen(); ambient[2] = c.GetBlue(); } } // Diffuse { mitk::ColorProperty::Pointer p; node->GetProperty(p, "material.diffuseColor", renderer); if (p.IsNotNull()) { mitk::Color c = p->GetColor(); diffuse[0] = c.GetRed(); diffuse[1] = c.GetGreen(); diffuse[2] = c.GetBlue(); } } // Specular { mitk::ColorProperty::Pointer p; node->GetProperty(p, "material.specularColor", renderer); if (p.IsNotNull()) { mitk::Color c = p->GetColor(); specular[0] = c.GetRed(); specular[1] = c.GetGreen(); specular[2] = c.GetBlue(); } } // Ambient coeff { node->GetFloatProperty("material.ambientCoefficient", coeff_ambient, renderer); } // Diffuse coeff { node->GetFloatProperty("material.diffuseCoefficient", coeff_diffuse, renderer); } // Specular coeff { node->GetFloatProperty("material.specularCoefficient", coeff_specular, renderer); } // Specular power { node->GetFloatProperty("material.specularPower", power_specular, renderer); } property->SetAmbient(coeff_ambient); property->SetDiffuse(coeff_diffuse); property->SetSpecular(coeff_specular); property->SetSpecularPower(power_specular); property->SetAmbientColor(ambient); property->SetDiffuseColor(diffuse); property->SetSpecularColor(specular); } // Render mode { // Opacity { float opacity = 1.0f; if (node->GetOpacity(opacity, renderer)) property->SetOpacity(opacity); } // Wireframe line width { float lineWidth = 1; node->GetFloatProperty("material.wireframeLineWidth", lineWidth, renderer); property->SetLineWidth(lineWidth); } // Point size { float pointSize = 1.0f; node->GetFloatProperty("material.pointSize", pointSize, renderer); property->SetPointSize(pointSize); } // Representation { mitk::VtkRepresentationProperty::Pointer p; node->GetProperty(p, "material.representation", renderer); if (p.IsNotNull()) property->SetRepresentation(p->GetVtkRepresentation()); } // Interpolation { mitk::VtkInterpolationProperty::Pointer p; node->GetProperty(p, "material.interpolation", renderer); if (p.IsNotNull()) property->SetInterpolation(p->GetVtkInterpolation()); } } } void mitk::SurfaceVtkMapper3D::ApplyAllProperties(mitk::BaseRenderer *renderer, vtkActor * /*actor*/) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); Superclass::ApplyColorAndOpacityProperties(renderer, ls->m_Actor); // VTK Properties ApplyMitkPropertiesToVtkProperty(this->GetDataNode(), ls->m_Actor->GetProperty(), renderer); mitk::TransferFunctionProperty::Pointer transferFuncProp; this->GetDataNode()->GetProperty(transferFuncProp, "Surface.TransferFunction", renderer); if (transferFuncProp.IsNotNull()) { ls->m_VtkPolyDataMapper->SetLookupTable(transferFuncProp->GetValue()->GetColorTransferFunction()); } mitk::LookupTableProperty::Pointer lookupTableProp; this->GetDataNode()->GetProperty(lookupTableProp, "LookupTable", renderer); if (lookupTableProp.IsNotNull()) { ls->m_VtkPolyDataMapper->SetLookupTable(lookupTableProp->GetLookupTable()->GetVtkLookupTable()); } mitk::LevelWindow levelWindow; if (this->GetDataNode()->GetLevelWindow(levelWindow, renderer, "levelWindow")) { ls->m_VtkPolyDataMapper->SetScalarRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); } else if (this->GetDataNode()->GetLevelWindow(levelWindow, renderer)) { ls->m_VtkPolyDataMapper->SetScalarRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); } bool scalarVisibility = false; this->GetDataNode()->GetBoolProperty("scalar visibility", scalarVisibility); ls->m_VtkPolyDataMapper->SetScalarVisibility((scalarVisibility ? 1 : 0)); if (scalarVisibility) { mitk::VtkScalarModeProperty *scalarMode; if (this->GetDataNode()->GetProperty(scalarMode, "scalar mode", renderer)) ls->m_VtkPolyDataMapper->SetScalarMode(scalarMode->GetVtkScalarMode()); else ls->m_VtkPolyDataMapper->SetScalarModeToDefault(); bool colorMode = false; this->GetDataNode()->GetBoolProperty("color mode", colorMode); ls->m_VtkPolyDataMapper->SetColorMode((colorMode ? 1 : 0)); double scalarsMin = 0; this->GetDataNode()->GetDoubleProperty("ScalarsRangeMinimum", scalarsMin, renderer); double scalarsMax = 1.0; this->GetDataNode()->GetDoubleProperty("ScalarsRangeMaximum", scalarsMax, renderer); ls->m_VtkPolyDataMapper->SetScalarRange(scalarsMin, scalarsMax); } mitk::SmartPointerProperty::Pointer imagetextureProp = dynamic_cast(GetDataNode()->GetProperty("Surface.Texture", renderer)); if (imagetextureProp.IsNotNull()) { mitk::Image *miktTexture = dynamic_cast(imagetextureProp->GetSmartPointer().GetPointer()); vtkSmartPointer vtkTxture = vtkSmartPointer::New(); // Either select the first slice of a volume if (miktTexture->GetDimension(2) > 1) { MITK_WARN << "3D Textures are not supported by VTK and MITK. The first slice of the volume will be used instead!"; mitk::ImageSliceSelector::Pointer sliceselector = mitk::ImageSliceSelector::New(); sliceselector->SetSliceNr(0); sliceselector->SetChannelNr(0); sliceselector->SetTimeNr(0); sliceselector->SetInput(miktTexture); sliceselector->Update(); vtkTxture->SetInputData(sliceselector->GetOutput()->GetVtkImageData()); } else // or just use the 2D image { vtkTxture->SetInputData(miktTexture->GetVtkImageData()); } // pass the texture to the actor ls->m_Actor->SetTexture(vtkTxture); if (ls->m_VtkPolyDataMapper->GetInput()->GetPointData()->GetTCoords() == nullptr) { MITK_ERROR << "Surface.Texture property was set, but there are no texture coordinates. Please provide texture " "coordinates for the vtkPolyData via vtkPolyData->GetPointData()->SetTCoords()."; } // if no texture is set, this will also remove a previously used texture // and reset the actor to it's default behaviour } else { ls->m_Actor->SetTexture(nullptr); } // deprecated settings bool deprecatedUseCellData = false; this->GetDataNode()->GetBoolProperty("deprecated useCellDataForColouring", deprecatedUseCellData); bool deprecatedUsePointData = false; this->GetDataNode()->GetBoolProperty("deprecated usePointDataForColouring", deprecatedUsePointData); if (deprecatedUseCellData) { ls->m_VtkPolyDataMapper->SetColorModeToDefault(); ls->m_VtkPolyDataMapper->SetScalarRange(0, 255); ls->m_VtkPolyDataMapper->ScalarVisibilityOn(); ls->m_VtkPolyDataMapper->SetScalarModeToUseCellData(); ls->m_Actor->GetProperty()->SetSpecular(1); ls->m_Actor->GetProperty()->SetSpecularPower(50); ls->m_Actor->GetProperty()->SetInterpolationToPhong(); } else if (deprecatedUsePointData) { float scalarsMin = 0; if (dynamic_cast(this->GetDataNode()->GetProperty("ScalarsRangeMinimum")) != nullptr) scalarsMin = dynamic_cast(this->GetDataNode()->GetProperty("ScalarsRangeMinimum"))->GetValue(); float scalarsMax = 0.1; if (dynamic_cast(this->GetDataNode()->GetProperty("ScalarsRangeMaximum")) != nullptr) scalarsMax = dynamic_cast(this->GetDataNode()->GetProperty("ScalarsRangeMaximum"))->GetValue(); ls->m_VtkPolyDataMapper->SetScalarRange(scalarsMin, scalarsMax); ls->m_VtkPolyDataMapper->SetColorModeToMapScalars(); ls->m_VtkPolyDataMapper->ScalarVisibilityOn(); ls->m_Actor->GetProperty()->SetSpecular(1); ls->m_Actor->GetProperty()->SetSpecularPower(50); ls->m_Actor->GetProperty()->SetInterpolationToPhong(); } int deprecatedScalarMode = VTK_COLOR_MODE_DEFAULT; if (this->GetDataNode()->GetIntProperty("deprecated scalar mode", deprecatedScalarMode, renderer)) { ls->m_VtkPolyDataMapper->SetScalarMode(deprecatedScalarMode); ls->m_VtkPolyDataMapper->ScalarVisibilityOn(); ls->m_Actor->GetProperty()->SetSpecular(1); ls->m_Actor->GetProperty()->SetSpecularPower(50); } // Check whether one or more ClippingProperty objects have been defined for // this node. Check both renderer specific and global property lists, since // properties in both should be considered. const PropertyList::PropertyMap *rendererProperties = this->GetDataNode()->GetPropertyList(renderer)->GetMap(); const PropertyList::PropertyMap *globalProperties = this->GetDataNode()->GetPropertyList(nullptr)->GetMap(); // Add clipping planes (if any) ls->m_ClippingPlaneCollection->RemoveAllItems(); PropertyList::PropertyMap::const_iterator it; for (it = rendererProperties->begin(); it != rendererProperties->end(); ++it) { this->CheckForClippingProperty(renderer, (*it).second.GetPointer()); } for (it = globalProperties->begin(); it != globalProperties->end(); ++it) { this->CheckForClippingProperty(renderer, (*it).second.GetPointer()); } if (ls->m_ClippingPlaneCollection->GetNumberOfItems() > 0) { ls->m_VtkPolyDataMapper->SetClippingPlanes(ls->m_ClippingPlaneCollection); } else { ls->m_VtkPolyDataMapper->RemoveAllClippingPlanes(); } } vtkProp *mitk::SurfaceVtkMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); return ls->m_Actor; } void mitk::SurfaceVtkMapper3D::CheckForClippingProperty(mitk::BaseRenderer *renderer, mitk::BaseProperty *property) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); auto *clippingProperty = dynamic_cast(property); if ((clippingProperty != nullptr) && (clippingProperty->GetClippingEnabled())) { const Point3D &origin = clippingProperty->GetOrigin(); const Vector3D &normal = clippingProperty->GetNormal(); vtkSmartPointer clippingPlane = vtkSmartPointer::New(); clippingPlane->SetOrigin(origin[0], origin[1], origin[2]); clippingPlane->SetNormal(normal[0], normal[1], normal[2]); ls->m_ClippingPlaneCollection->AddItem(clippingPlane); } } void mitk::SurfaceVtkMapper3D::SetDefaultPropertiesForVtkProperty(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // Shading { node->AddProperty("material.wireframeLineWidth", mitk::FloatProperty::New(1.0f), renderer, overwrite); node->AddProperty("material.pointSize", mitk::FloatProperty::New(1.0f), renderer, overwrite); node->AddProperty("material.ambientCoefficient", mitk::FloatProperty::New(0.05f), renderer, overwrite); node->AddProperty("material.diffuseCoefficient", mitk::FloatProperty::New(0.9f), renderer, overwrite); node->AddProperty("material.specularCoefficient", mitk::FloatProperty::New(1.0f), renderer, overwrite); node->AddProperty("material.specularPower", mitk::FloatProperty::New(16.0f), renderer, overwrite); node->AddProperty("material.representation", mitk::VtkRepresentationProperty::New(), renderer, overwrite); node->AddProperty("material.interpolation", mitk::VtkInterpolationProperty::New(), renderer, overwrite); } } void mitk::SurfaceVtkMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 1.0f), renderer, overwrite); node->AddProperty("opacity", mitk::FloatProperty::New(1.0), renderer, overwrite); mitk::SurfaceVtkMapper3D::SetDefaultPropertiesForVtkProperty(node, renderer, overwrite); // Shading node->AddProperty("scalar visibility", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("color mode", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("scalar mode", mitk::VtkScalarModeProperty::New(), renderer, overwrite); mitk::Surface::Pointer surface = dynamic_cast(node->GetData()); if (surface.IsNotNull()) { if ((surface->GetVtkPolyData() != nullptr) && (surface->GetVtkPolyData()->GetPointData() != nullptr) && (surface->GetVtkPolyData()->GetPointData()->GetScalars() != nullptr)) { node->AddProperty("scalar visibility", mitk::BoolProperty::New(true), renderer, overwrite); node->AddProperty("color mode", mitk::BoolProperty::New(true), renderer, overwrite); } } // Backface culling node->AddProperty("Backface Culling", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("Depth Sorting", mitk::BoolProperty::New(false), renderer, overwrite); - mitk::CoreServices::GetPropertyDescriptions()->AddDescription( + mitk::CoreServicePointer propDescService(mitk::CoreServices::GetPropertyDescriptions()); + propDescService->AddDescription( "Depth Sorting", "Enables correct rendering for transparent objects by ordering polygons according to the distance " "to the camera. It is not recommended to enable this property for large surfaces (rendering might " "be slow)."); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/DicomRT/autoload/IO/files.cmake b/Modules/DicomRT/autoload/IO/files.cmake index f3970e6991..e05e20b944 100644 --- a/Modules/DicomRT/autoload/IO/files.cmake +++ b/Modules/DicomRT/autoload/IO/files.cmake @@ -1,7 +1,6 @@ set(CPP_FILES mitkDicomRTIOActivator.cpp - mitkDicomRTIOMimeTypes.cpp mitkRTDoseReaderService.cpp mitkRTPlanReaderService.cpp mitkRTStructureSetReaderService.cpp ) diff --git a/Modules/DicomRT/autoload/IO/mitkDicomRTIOActivator.cpp b/Modules/DicomRT/autoload/IO/mitkDicomRTIOActivator.cpp index 9abbc079d7..4ac2ab74f3 100644 --- a/Modules/DicomRT/autoload/IO/mitkDicomRTIOActivator.cpp +++ b/Modules/DicomRT/autoload/IO/mitkDicomRTIOActivator.cpp @@ -1,74 +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. ============================================================================*/ + #include #include +#include #include #include #include -#include -#include -#include - -#include "mitkDicomRTIOMimeTypes.h" - namespace mitk { - /** - \brief Registers services for segmentation module. - */ class DicomRTIOActivator : public us::ModuleActivator { public: + DicomRTIOActivator() + { + } + + ~DicomRTIOActivator() = default; void Load(us::ModuleContext* context) override { us::ServiceProperties props; - props[ us::ServiceConstants::SERVICE_RANKING() ] = 100; + props[us::ServiceConstants::SERVICE_RANKING()] = 100; - m_MimeTypes = mitk::DicomRTIOMimeTypes::Get(); - for (std::vector::const_iterator mimeTypeIter = m_MimeTypes.begin(), - iterEnd = m_MimeTypes.end(); mimeTypeIter != iterEnd; ++mimeTypeIter) - { - context->RegisterService(*mimeTypeIter, props); - } + for (const auto& mimeType : DicomRTMimeTypes::Get()) + context->RegisterService(mimeType.get(), props); - m_RTDoseReader = new RTDoseReaderService(); - m_RTPlanReader = new RTPlanReaderService(); - m_RTStructureSetReader = new RTStructureSetReaderService(); + m_RTDoseReader = std::make_unique(); + m_RTPlanReader = std::make_unique(); + m_RTStructureSetReader = std::make_unique(); } void Unload(us::ModuleContext*) override { - for (auto& aMimeType : m_MimeTypes) - { - delete aMimeType; - } - - delete m_RTDoseReader; - delete m_RTPlanReader; - delete m_RTStructureSetReader; } private: - - RTDoseReaderService * m_RTDoseReader; - RTPlanReaderService * m_RTPlanReader; - RTStructureSetReaderService * m_RTStructureSetReader; - - std::vector m_MimeTypes; - + std::unique_ptr m_RTDoseReader; + std::unique_ptr m_RTPlanReader; + std::unique_ptr m_RTStructureSetReader; }; } US_EXPORT_MODULE_ACTIVATOR(mitk::DicomRTIOActivator) diff --git a/Modules/DicomRT/autoload/IO/mitkRTDoseReaderService.cpp b/Modules/DicomRT/autoload/IO/mitkRTDoseReaderService.cpp index 9bd6a9c05b..a1e45309f7 100644 --- a/Modules/DicomRT/autoload/IO/mitkRTDoseReaderService.cpp +++ b/Modules/DicomRT/autoload/IO/mitkRTDoseReaderService.cpp @@ -1,168 +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 "mitkRTDoseReaderService.h" +#include #include #include #include #include #include #include #include #include -#include "mitkDICOMDCMTKTagScanner.h" -#include "mitkDicomRTIOMimeTypes.h" +#include +#include #include -#include "dcmtk/dcmrt/drtdose.h" +#include #include #include namespace mitk { - RTDoseReaderService::RTDoseReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTIOMimeTypes::DICOMRT_DOSE_MIMETYPE_NAME()), mitk::DicomRTIOMimeTypes::DICOMRT_DOSE_MIMETYPE_DESCRIPTION()) { + RTDoseReaderService::RTDoseReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE_NAME()), mitk::DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE_DESCRIPTION()) { m_FileReaderServiceReg = RegisterService(); } RTDoseReaderService::RTDoseReaderService(const RTDoseReaderService& other) : mitk::AbstractFileReader(other) { } RTDoseReaderService::~RTDoseReaderService() {} template void RTDoseReaderService::MultiplyGridScaling(itk::Image* image, float gridscale) { typedef itk::Image OutputImageType; typedef itk::Image InputImageType; typedef itk::CastImageFilter CastFilterType; typedef itk::ShiftScaleImageFilter ScaleFilterType; typename CastFilterType::Pointer castFilter = CastFilterType::New(); typename ScaleFilterType::Pointer scaleFilter = ScaleFilterType::New(); castFilter->SetInput(image); scaleFilter->SetInput(castFilter->GetOutput()); scaleFilter->SetScale(gridscale); scaleFilter->Update(); typename OutputImageType::Pointer scaledOutput = scaleFilter->GetOutput(); this->scaledDoseImage = mitk::Image::New(); mitk::CastToMitkImage(scaledOutput, this->scaledDoseImage); } std::vector > RTDoseReaderService::Read() { std::vector > result; mitk::IDICOMTagsOfInterest* toiSrv = GetDicomTagsOfInterestService(); auto tagsOfInterest = toiSrv->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto& tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } std::string location = GetInputLocation(); mitk::DICOMFileReaderSelector::Pointer selector = mitk::DICOMFileReaderSelector::New(); selector->LoadBuiltIn3DConfigs(); selector->SetInputFiles({ location }); mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); reader->SetAdditionalTagsOfInterest(toiSrv->GetTagsOfInterest()); reader->SetInputFiles({ location }); reader->AnalyzeInputFiles(); reader->LoadImages(); if (reader->GetNumberOfOutputs() == 0) { MITK_ERROR << "Could not determine a DICOM reader for this file" << std::endl; return result; } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ location }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the RTDOSE file" << std::endl; return result; } const mitk::DICOMImageBlockDescriptor& desc = reader->GetOutput(0); mitk::Image::Pointer originalImage = desc.GetMitkImage(); if (originalImage.IsNull()) { MITK_ERROR << "Error reading the RTDOSE file in mitk::DicomFileReader" << std::endl; return result; } DcmFileFormat fileformat; OFCondition outp = fileformat.loadFile(location.c_str(), EXS_Unknown); if (outp.bad()) { MITK_ERROR << "Error reading the RTDOSE file in DCMTK" << std::endl; return result; } DcmDataset *dataset = fileformat.getDataset(); DRTDoseIOD doseObject; OFCondition DCMTKresult = doseObject.read(*dataset); if (DCMTKresult.bad()) { MITK_ERROR << "Error reading the RTDOSE file in DCMTK" << std::endl; return result; } auto findingsGridScaling = frames.front()->GetTagValueAsString(DICOMTagPath(0x3004, 0x000e)); //(0x3004, 0x000e) is grid scaling double gridScaling; if (findingsGridScaling.empty()) { MITK_ERROR << "Could not find DoseGridScaling tag" << std::endl; return result; } else { gridScaling = boost::lexical_cast(findingsGridScaling.front().value); } AccessByItk_1(originalImage, MultiplyGridScaling, gridScaling); auto statistics = this->scaledDoseImage->GetStatistics(); double maxDose = statistics->GetScalarValueMax(); this->scaledDoseImage->SetPropertyList(originalImage->GetPropertyList()); this->scaledDoseImage->SetProperty(mitk::RTConstants::PRESCRIBED_DOSE_PROPERTY_NAME.c_str(), mitk::GenericProperty::New(0.8*maxDose)); auto findings = ExtractPathsOfInterest(tagsOfInterestList, frames); SetProperties(this->scaledDoseImage, findings); result.push_back(this->scaledDoseImage.GetPointer()); return result; } RTDoseReaderService* RTDoseReaderService::Clone() const { return new RTDoseReaderService(*this); } } diff --git a/Modules/DicomRT/autoload/IO/mitkRTPlanReaderService.cpp b/Modules/DicomRT/autoload/IO/mitkRTPlanReaderService.cpp index ddebbbc6b6..d5d2879a6c 100644 --- a/Modules/DicomRT/autoload/IO/mitkRTPlanReaderService.cpp +++ b/Modules/DicomRT/autoload/IO/mitkRTPlanReaderService.cpp @@ -1,82 +1,82 @@ /*============================================================================ 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 "mitkRTPlanReaderService.h" +#include -#include "mitkImage.h" -#include "mitkDICOMDCMTKTagScanner.h" -#include "mitkIOMimeTypes.h" +#include +#include +#include #include -#include "mitkDICOMTagPath.h" -#include "mitkIDICOMTagsOfInterest.h" -#include "mitkDICOMDatasetAccessingImageFrameInfo.h" -#include "mitkDicomRTIOMimeTypes.h" +#include +#include +#include +#include namespace mitk { - RTPlanReaderService::RTPlanReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTIOMimeTypes::DICOMRT_PLAN_MIMETYPE_NAME()), mitk::DicomRTIOMimeTypes::DICOMRT_PLAN_MIMETYPE_DESCRIPTION()) { + RTPlanReaderService::RTPlanReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE_NAME()), mitk::DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE_DESCRIPTION()) { m_FileReaderServiceReg = RegisterService(); } RTPlanReaderService::RTPlanReaderService(const RTPlanReaderService& other) : mitk::AbstractFileReader(other) { } RTPlanReaderService::~RTPlanReaderService() {} std::vector > RTPlanReaderService::Read() { std::vector > result; auto DICOMTagsOfInterestService = GetDicomTagsOfInterestService(); auto tagsOfInterest = DICOMTagsOfInterestService->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto& tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } std::string location = GetInputLocation(); mitk::StringList files = { location }; mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles(files); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the RTPLAN file" << std::endl; return result; } auto findings = ExtractPathsOfInterest(tagsOfInterestList, frames); //just create empty image. No image information available in RTPLAN. But properties will be attached. Image::Pointer dummyImage = Image::New(); mitk::PixelType pt = mitk::MakeScalarPixelType(); unsigned int dim[] = { 1,1}; dummyImage->Initialize(pt, 2, dim); SetProperties(dummyImage, findings); result.push_back(dummyImage.GetPointer()); return result; } RTPlanReaderService* RTPlanReaderService::Clone() const { return new RTPlanReaderService(*this); } } diff --git a/Modules/DicomRT/autoload/IO/mitkRTStructureSetReaderService.cpp b/Modules/DicomRT/autoload/IO/mitkRTStructureSetReaderService.cpp index 5b08e25989..ef2a5132ef 100644 --- a/Modules/DicomRT/autoload/IO/mitkRTStructureSetReaderService.cpp +++ b/Modules/DicomRT/autoload/IO/mitkRTStructureSetReaderService.cpp @@ -1,288 +1,288 @@ /*============================================================================ 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 "mitkRTStructureSetReaderService.h" -#include "mitkDicomRTIOMimeTypes.h" +#include #include #include #include #include "dcmtk/dcmrt/drtstrct.h" namespace mitk { - RTStructureSetReaderService::RTStructureSetReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTIOMimeTypes::DICOMRT_STRUCT_MIMETYPE_NAME()), mitk::DicomRTIOMimeTypes::DICOMRT_STRUCT_MIMETYPE_DESCRIPTION()) { + RTStructureSetReaderService::RTStructureSetReaderService() : AbstractFileReader(CustomMimeType(mitk::DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE_NAME()), mitk::DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE_DESCRIPTION()) { m_FileReaderServiceReg = RegisterService(); } RTStructureSetReaderService::RTStructureSetReaderService(const RTStructureSetReaderService& other) : mitk::AbstractFileReader(other) { } RTStructureSetReaderService::~RTStructureSetReaderService() {} RTStructureSetReaderService::RoiEntry::RoiEntry() { Number = 0; DisplayColor[0] = 1.0; DisplayColor[1] = 0.0; DisplayColor[2] = 0.0; ContourModelSet = mitk::ContourModelSet::New(); } RTStructureSetReaderService::RoiEntry::RoiEntry(const RoiEntry& src) { Number = src.Number; Name = src.Name; Description = src.Description; DisplayColor[0] = src.DisplayColor[0]; DisplayColor[1] = src.DisplayColor[1]; DisplayColor[2] = src.DisplayColor[2]; ContourModelSet = mitk::ContourModelSet::New(); SetPolyData(src.ContourModelSet); } RTStructureSetReaderService::RoiEntry::~RoiEntry() {} RTStructureSetReaderService::RoiEntry& RTStructureSetReaderService:: RoiEntry::operator =(const RoiEntry& src) { Number = src.Number; Name = src.Name; Description = src.Description; DisplayColor[0] = src.DisplayColor[0]; DisplayColor[1] = src.DisplayColor[1]; DisplayColor[2] = src.DisplayColor[2]; SetPolyData(src.ContourModelSet); return (*this); } void RTStructureSetReaderService::RoiEntry:: SetPolyData(mitk::ContourModelSet::Pointer roiPolyData) { if (roiPolyData == this->ContourModelSet) { return; } this->ContourModelSet = roiPolyData; } size_t RTStructureSetReaderService::GetNumberOfROIs() const { return this->ROISequenceVector.size(); } RTStructureSetReaderService::RoiEntry* RTStructureSetReaderService:: FindRoiByNumber(unsigned int roiNum) { for (unsigned int i = 0; i < this->ROISequenceVector.size(); ++i) { if (this->ROISequenceVector[i].Number == roiNum) { return &this->ROISequenceVector[i]; } } return nullptr; } std::vector > RTStructureSetReaderService::Read() { std::vector > result; std::string location = GetInputLocation(); auto DICOMTagsOfInterestService = GetDicomTagsOfInterestService(); auto tagsOfInterest = DICOMTagsOfInterestService->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto& tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ location }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the RTSTRUCT file" << std::endl; return result; } auto findings = ExtractPathsOfInterest(tagsOfInterestList, frames); DcmFileFormat file; OFCondition output = file.loadFile(location.c_str(), EXS_Unknown); if (output.bad()) { MITK_ERROR << "Can't read the file" << std::endl; return result; } DcmDataset* dataset = file.getDataset(); DRTStructureSetIOD structureSetObject; OFCondition outp = structureSetObject.read(*dataset); if (!outp.good()) { MITK_ERROR << "Error reading the file" << std::endl; return result; } DRTStructureSetROISequence& roiSequence = structureSetObject.getStructureSetROISequence(); if (!roiSequence.gotoFirstItem().good()) { MITK_ERROR << "Error reading the structure sequence" << std::endl; return result; } do { DRTStructureSetROISequence::Item& currentSequence = roiSequence.getCurrentItem(); if (!currentSequence.isValid()) { continue; } OFString roiName; OFString roiDescription; Sint32 roiNumber; RoiEntry roi; currentSequence.getROIName(roiName); currentSequence.getROIDescription(roiDescription); currentSequence.getROINumber(roiNumber); roi.Name = roiName.c_str(); roi.Description = roiDescription.c_str(); roi.Number = roiNumber; this->ROISequenceVector.push_back(roi); } while (roiSequence.gotoNextItem().good()); Sint32 refRoiNumber; DRTROIContourSequence& roiContourSeqObject = structureSetObject.getROIContourSequence(); if (!roiContourSeqObject.gotoFirstItem().good()) { MITK_ERROR << "Error reading the contour sequence" << std::endl; return result; } do { mitk::ContourModelSet::Pointer contourSet = mitk::ContourModelSet::New(); DRTROIContourSequence::Item& currentRoiObject = roiContourSeqObject.getCurrentItem(); if (!currentRoiObject.isValid()) { continue; } currentRoiObject.getReferencedROINumber(refRoiNumber); DRTContourSequence& contourSeqObject = currentRoiObject.getContourSequence(); if (contourSeqObject.getNumberOfItems() > 0 && contourSeqObject.gotoFirstItem().good()) { do { DRTContourSequence::Item& contourItem = contourSeqObject.getCurrentItem(); if (!contourItem.isValid()) { continue; } OFString contourNumber; OFString numberOfPoints; OFVector contourData_LPS; mitk::ContourModel::Pointer contourSequence = mitk::ContourModel::New(); contourItem.getContourNumber(contourNumber); contourItem.getNumberOfContourPoints(numberOfPoints); contourItem.getContourData(contourData_LPS); for (unsigned int i = 0; i < contourData_LPS.size() / 3; i++) { mitk::Point3D point; point[0] = contourData_LPS.at(3 * i); point[1] = contourData_LPS.at(3 * i + 1); point[2] = contourData_LPS.at(3 * i + 2); contourSequence->AddVertex(point); } contourSequence->Close(); contourSet->AddContourModel(contourSequence); } while (contourSeqObject.gotoNextItem().good()); } else { MITK_WARN << "contourSeqObject has no items in sequence. Object is neglected and not read. Struct name: " << this->FindRoiByNumber(refRoiNumber)->Name << std::endl; } RoiEntry* refROI = this->FindRoiByNumber(refRoiNumber); if (refROI == nullptr) { MITK_ERROR << "Can not find references ROI" << std::endl; continue; } Sint32 roiColor; for (unsigned int j = 0; j < 3; j++) { currentRoiObject.getROIDisplayColor(roiColor, j); refROI->DisplayColor[j] = roiColor / 255.0; } refROI->ContourModelSet = contourSet; contourSet->SetProperty("name", mitk::StringProperty::New(refROI->Name)); contourSet->SetProperty("contour.color", mitk::ColorProperty::New( refROI->DisplayColor[0], refROI->DisplayColor[1], refROI->DisplayColor[2])); } while (roiContourSeqObject.gotoNextItem().good()); for (auto const& aROI : ROISequenceVector) { result.push_back(aROI.ContourModelSet.GetPointer()); result.at(result.size() - 1)->SetProperty("name", aROI.ContourModelSet->GetProperty("name")); result.at(result.size() - 1)->SetProperty("color", aROI.ContourModelSet->GetProperty("contour.color")); result.at(result.size() - 1)->SetProperty("contour.color", aROI.ContourModelSet->GetProperty("contour.color")); SetProperties(result.at(result.size() - 1).GetPointer(), findings); } return result; } RTStructureSetReaderService* RTStructureSetReaderService::Clone() const { return new RTStructureSetReaderService(*this); } } diff --git a/Modules/DicomRT/files.cmake b/Modules/DicomRT/files.cmake index 64210e3cf4..b7fe17c979 100644 --- a/Modules/DicomRT/files.cmake +++ b/Modules/DicomRT/files.cmake @@ -1,18 +1,19 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") SET(CPP_FILES mitkRTConstants.cpp mitkIsoDoseLevel.cpp mitkIsoDoseLevelCollections.cpp mitkIsoDoseLevelSetProperty.cpp mitkIsoDoseLevelVectorProperty.cpp mitkDoseImageVtkMapper2D.cpp mitkIsoLevelsGenerator.cpp mitkDoseNodeHelper.cpp + mitkDicomRTMimeTypes.cpp ) set(TPP_FILES ) set(MOC_H_FILES ) diff --git a/Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.h b/Modules/DicomRT/include/mitkDicomRTMimeTypes.h similarity index 73% rename from Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.h rename to Modules/DicomRT/include/mitkDicomRTMimeTypes.h index b58e11df9e..d57d821f0d 100644 --- a/Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.h +++ b/Modules/DicomRT/include/mitkDicomRTMimeTypes.h @@ -1,76 +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 MITKDicomRTIOMimeTypes_H -#define MITKDicomRTIOMimeTypes_H +#ifndef MITKDicomRTMimeTypes_H +#define MITKDicomRTMimeTypes_H -#include "mitkCustomMimeType.h" +#include #include +#include +#include +#include #include namespace mitk { -class DicomRTIOMimeTypes +class MITKDICOMRT_EXPORT DicomRTMimeTypes { public: - class RTDoseMimeType : public CustomMimeType + class MITKDICOMRT_EXPORT RTDoseMimeType : public CustomMimeType { public: RTDoseMimeType(); bool AppliesTo(const std::string &path) const override; RTDoseMimeType* Clone() const override; }; - class RTStructMimeType : public CustomMimeType + class MITKDICOMRT_EXPORT RTStructMimeType : public CustomMimeType { public: RTStructMimeType(); bool AppliesTo(const std::string &path) const override; RTStructMimeType* Clone() const override; }; - class RTPlanMimeType : public CustomMimeType + class MITKDICOMRT_EXPORT RTPlanMimeType : public CustomMimeType { public: RTPlanMimeType(); bool AppliesTo(const std::string &path) const override; RTPlanMimeType* Clone() const override; }; // Get all DicomRT Mime Types - static std::vector Get(); + static std::array, 3> Get(); static RTDoseMimeType DICOMRT_DOSE_MIMETYPE(); static RTStructMimeType DICOMRT_STRUCT_MIMETYPE(); static RTPlanMimeType DICOMRT_PLAN_MIMETYPE(); static std::string DICOMRT_DOSE_MIMETYPE_NAME(); static std::string DICOMRT_STRUCT_MIMETYPE_NAME(); static std::string DICOMRT_PLAN_MIMETYPE_NAME(); static std::string DICOMRT_DOSE_MIMETYPE_DESCRIPTION(); static std::string DICOMRT_STRUCT_MIMETYPE_DESCRIPTION(); static std::string DICOMRT_PLAN_MIMETYPE_DESCRIPTION(); - DicomRTIOMimeTypes() = delete; - DicomRTIOMimeTypes(const DicomRTIOMimeTypes&) = delete; + DicomRTMimeTypes() = delete; + DicomRTMimeTypes(const DicomRTMimeTypes&) = delete; static mitk::IDICOMTagsOfInterest* GetDicomTagsOfInterestService(); static bool canReadByDicomFileReader(const std::string & path); static std::string GetModality(const std::string & path); }; } -#endif // MITKDicomRTIOMimeTypes_H +#endif // MITKDicomRTMimeTypes_H diff --git a/Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.cpp b/Modules/DicomRT/src/mitkDicomRTMimeTypes.cpp similarity index 60% rename from Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.cpp rename to Modules/DicomRT/src/mitkDicomRTMimeTypes.cpp index 8fad1d262a..b8a26714b8 100644 --- a/Modules/DicomRT/autoload/IO/mitkDicomRTIOMimeTypes.cpp +++ b/Modules/DicomRT/src/mitkDicomRTMimeTypes.cpp @@ -1,261 +1,252 @@ /*============================================================================ 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 "mitkDicomRTIOMimeTypes.h" +#include -#include "mitkIOMimeTypes.h" +#include -#include "mitkDICOMDCMTKTagScanner.h" -#include "mitkDICOMTagPath.h" +#include +#include #include #include #include #include #include #include #include namespace mitk { -std::vector DicomRTIOMimeTypes::Get() +std::array, 3> DicomRTMimeTypes::Get() { - std::vector mimeTypes; - - // order matters here (descending rank for mime types) - mimeTypes.push_back(DICOMRT_DOSE_MIMETYPE().Clone()); - mimeTypes.push_back(DICOMRT_PLAN_MIMETYPE().Clone()); - mimeTypes.push_back(DICOMRT_STRUCT_MIMETYPE().Clone()); - - return mimeTypes; + return { + std::make_unique(), + std::make_unique(), + std::make_unique() + }; } // Mime Types -DicomRTIOMimeTypes::RTDoseMimeType::RTDoseMimeType() +DicomRTMimeTypes::RTDoseMimeType::RTDoseMimeType() : CustomMimeType(DICOMRT_DOSE_MIMETYPE_NAME()) { std::string category = "DICOMRT"; this->SetCategory(category); this->SetComment("RTDose"); this->AddExtension("dcm"); } -bool DicomRTIOMimeTypes::RTDoseMimeType::AppliesTo(const std::string &path) const +bool DicomRTMimeTypes::RTDoseMimeType::AppliesTo(const std::string &path) const { bool canRead( CustomMimeType::AppliesTo(path) ); if (!canRead) { return false; } if (!canReadByDicomFileReader(path)) { return false; } auto modality = GetModality(path); if (modality == "RTDOSE") { return true; } else { return false; } } -std::string DicomRTIOMimeTypes::GetModality(const std::string & path) +std::string DicomRTMimeTypes::GetModality(const std::string & path) { mitk::IDICOMTagsOfInterest* toiSrv = GetDicomTagsOfInterestService(); auto tagsOfInterest = toiSrv->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto& tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ path }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); std::string modality = ""; if (frames.empty()) return modality; auto findings = frames.front()->GetTagValueAsString(DICOMTagPath(0x0008, 0x0060)); modality = findings.front().value; return modality; } -bool DicomRTIOMimeTypes::canReadByDicomFileReader(const std::string & filename) +bool DicomRTMimeTypes::canReadByDicomFileReader(const std::string & filename) { mitk::DICOMFileReaderSelector::Pointer selector = mitk::DICOMFileReaderSelector::New(); selector->LoadBuiltIn3DConfigs(); selector->SetInputFiles({ filename }); mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNull()) { return false; } else { return true; } } -DicomRTIOMimeTypes::RTDoseMimeType* DicomRTIOMimeTypes::RTDoseMimeType::Clone() const +DicomRTMimeTypes::RTDoseMimeType* DicomRTMimeTypes::RTDoseMimeType::Clone() const { return new RTDoseMimeType(*this); } -DicomRTIOMimeTypes::RTStructMimeType::RTStructMimeType() +DicomRTMimeTypes::RTStructMimeType::RTStructMimeType() : CustomMimeType(DICOMRT_STRUCT_MIMETYPE_NAME()) { std::string category = "DICOMRT"; this->SetCategory(category); this->SetComment("RTStruct"); this->AddExtension("dcm"); } -bool DicomRTIOMimeTypes::RTStructMimeType::AppliesTo(const std::string &path) const +bool DicomRTMimeTypes::RTStructMimeType::AppliesTo(const std::string &path) const { bool canRead(CustomMimeType::AppliesTo(path)); if (!canRead) { return false; } auto modality = GetModality(path); if (modality == "RTSTRUCT") { return true; } else { return false; } } -DicomRTIOMimeTypes::RTStructMimeType* DicomRTIOMimeTypes::RTStructMimeType::Clone() const +DicomRTMimeTypes::RTStructMimeType* DicomRTMimeTypes::RTStructMimeType::Clone() const { return new RTStructMimeType(*this); } -DicomRTIOMimeTypes::RTPlanMimeType::RTPlanMimeType() +DicomRTMimeTypes::RTPlanMimeType::RTPlanMimeType() : CustomMimeType(DICOMRT_PLAN_MIMETYPE_NAME()) { std::string category = "DICOMRT"; this->SetCategory(category); this->SetComment("RTPLAN"); this->AddExtension("dcm"); } -bool DicomRTIOMimeTypes::RTPlanMimeType::AppliesTo(const std::string &path) const +bool DicomRTMimeTypes::RTPlanMimeType::AppliesTo(const std::string &path) const { bool canRead(CustomMimeType::AppliesTo(path)); if (!canRead) { return false; } auto modality = GetModality(path); if (modality == "RTPLAN") { return true; } else { return false; } } -DicomRTIOMimeTypes::RTPlanMimeType* DicomRTIOMimeTypes::RTPlanMimeType::Clone() const +DicomRTMimeTypes::RTPlanMimeType* DicomRTMimeTypes::RTPlanMimeType::Clone() const { return new RTPlanMimeType(*this); } -DicomRTIOMimeTypes::RTDoseMimeType DicomRTIOMimeTypes::DICOMRT_DOSE_MIMETYPE() +DicomRTMimeTypes::RTDoseMimeType DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE() { return RTDoseMimeType(); } -DicomRTIOMimeTypes::RTStructMimeType DicomRTIOMimeTypes::DICOMRT_STRUCT_MIMETYPE() +DicomRTMimeTypes::RTStructMimeType DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE() { return RTStructMimeType(); } -DicomRTIOMimeTypes::RTPlanMimeType DicomRTIOMimeTypes::DICOMRT_PLAN_MIMETYPE() +DicomRTMimeTypes::RTPlanMimeType DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE() { return RTPlanMimeType(); } // Names -std::string DicomRTIOMimeTypes::DICOMRT_DOSE_MIMETYPE_NAME() +std::string DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE_NAME() { - static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.dose"; - return name; + return IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.dose"; } -std::string DicomRTIOMimeTypes::DICOMRT_STRUCT_MIMETYPE_NAME() +std::string DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE_NAME() { - static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.struct"; - return name; + return IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.struct"; } -std::string DicomRTIOMimeTypes::DICOMRT_PLAN_MIMETYPE_NAME() +std::string DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE_NAME() { - static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.plan"; - return name; + return IOMimeTypes::DEFAULT_BASE_NAME() + ".dicomrt.plan"; } // Descriptions -std::string DicomRTIOMimeTypes::DICOMRT_DOSE_MIMETYPE_DESCRIPTION() +std::string DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE_DESCRIPTION() { - static std::string description = "RTDOSE reader"; - return description; + return "RTDOSE reader"; } -std::string DicomRTIOMimeTypes::DICOMRT_STRUCT_MIMETYPE_DESCRIPTION() +std::string DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE_DESCRIPTION() { - static std::string description = "RTSTRUCT reader"; - return description; + return "RTSTRUCT reader"; } -std::string DicomRTIOMimeTypes::DICOMRT_PLAN_MIMETYPE_DESCRIPTION() +std::string DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE_DESCRIPTION() { - static std::string description = "RTPLAN reader"; - return description; + return "RTPLAN reader"; } -mitk::IDICOMTagsOfInterest* DicomRTIOMimeTypes::GetDicomTagsOfInterestService() +mitk::IDICOMTagsOfInterest* DicomRTMimeTypes::GetDicomTagsOfInterestService() { mitk::IDICOMTagsOfInterest* result = nullptr; std::vector > toiRegisters = us::GetModuleContext()->GetServiceReferences(); if (!toiRegisters.empty()) { if (toiRegisters.size() > 1) { MITK_WARN << "Multiple DICOM tags of interest services found. Using just one."; } result = us::GetModuleContext()->GetService(toiRegisters.front()); } return result; } } diff --git a/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.cpp b/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.cpp index 413a67888f..930c473485 100644 --- a/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.cpp +++ b/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.cpp @@ -1,243 +1,256 @@ /*============================================================================ 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 "mitkNavigationDataObjectVisualizationFilter.h" #include "mitkDataStorage.h" - +#include mitk::NavigationDataObjectVisualizationFilter::NavigationDataObjectVisualizationFilter() -: NavigationDataToNavigationDataFilter(), -m_RepresentationList(), m_TransformPosition(), m_TransformOrientation(), m_RotationMode(RotationStandard) + : NavigationDataToNavigationDataFilter(), + m_RepresentationVectorMap(), + m_TransformPosition(), + m_TransformOrientation(), + m_RotationMode(RotationStandard) { - } - mitk::NavigationDataObjectVisualizationFilter::~NavigationDataObjectVisualizationFilter() { - m_RepresentationList.clear(); + m_RepresentationVectorMap.clear(); m_OffsetList.clear(); } +mitk::BaseData::Pointer mitk::NavigationDataObjectVisualizationFilter::GetRepresentationObject(unsigned int idx) const +{ + auto iter = m_RepresentationVectorMap.find(idx); + if (iter != m_RepresentationVectorMap.end()) + return iter->second.at(0); + + return nullptr; +} -const mitk::BaseData* mitk::NavigationDataObjectVisualizationFilter::GetRepresentationObject(unsigned int idx) +std::vector mitk::NavigationDataObjectVisualizationFilter::GetAllRepresentationObjects(unsigned int idx) const { - RepresentationPointerMap::const_iterator iter = m_RepresentationList.find(idx); - if (iter != m_RepresentationList.end()) + RepresentationVectorPointerMap::const_iterator iter = m_RepresentationVectorMap.find(idx); + if (iter != m_RepresentationVectorMap.end()) return iter->second; - return nullptr; + std::vector empty; + return empty; } mitk::AffineTransform3D::Pointer mitk::NavigationDataObjectVisualizationFilter::GetOffset(int index) { OffsetPointerMap::const_iterator iter = m_OffsetList.find(index); if (iter != m_OffsetList.end()) return iter->second; return nullptr; } +void mitk::NavigationDataObjectVisualizationFilter::SetRepresentationObject(unsigned int idx, BaseData::Pointer data) +{ + std::vector dataVector; + dataVector.push_back(data); + SetRepresentationObjects(idx, dataVector); +} -void mitk::NavigationDataObjectVisualizationFilter::SetRepresentationObject(unsigned int idx, BaseData* data) +void mitk::NavigationDataObjectVisualizationFilter::SetRepresentationObjects(unsigned int idx, const std::vector &data) { - m_RepresentationList[idx] = RepresentationPointer(data); + m_RepresentationVectorMap[idx] = data; } void mitk::NavigationDataObjectVisualizationFilter::SetOffset(int index, mitk::AffineTransform3D::Pointer offset) { -m_OffsetList[index] = offset; + m_OffsetList[index] = offset; } - void mitk::NavigationDataObjectVisualizationFilter::SetRotationMode(RotationMode r) { m_RotationMode = r; } - void mitk::NavigationDataObjectVisualizationFilter::GenerateData() { /*get each input, lookup the associated BaseData and transfer the data*/ - DataObjectPointerArray inputs = this->GetInputs(); //get all inputs - for (unsigned int index=0; index < inputs.size(); index++) + DataObjectPointerArray inputs = this->GetInputs(); // get all inputs + for (unsigned int index = 0; index < inputs.size(); index++) { - //get the needed variables - const mitk::NavigationData* nd = this->GetInput(index); + // get the needed variables + const mitk::NavigationData *nd = this->GetInput(index); assert(nd); - mitk::NavigationData* output = this->GetOutput(index); + mitk::NavigationData *output = this->GetOutput(index); assert(output); - //check if the data is valid + // check if the data is valid if (!nd->IsDataValid()) { output->SetDataValid(false); continue; } output->Graft(nd); // copy all information from input to output - const mitk::BaseData* data = this->GetRepresentationObject(index); - if (data == nullptr) - { - MITK_WARN << "No BaseData associated with input " << index; - continue; - } - //get the transform from data - mitk::AffineTransform3D::Pointer affineTransform = data->GetGeometry()->GetIndexToWorldTransform(); - if (affineTransform.IsNull()) + const std::vector data = + this->GetAllRepresentationObjects(index); + + for (unsigned int dataIdx = 0; dataIdx < data.size(); dataIdx++) { - MITK_WARN << "AffineTransform IndexToWorldTransform not initialized!"; - continue; - } + if (data.at(dataIdx) == nullptr) + { + MITK_WARN << "No BaseData associated with input " << index; + continue; + } + + // get the transform from data + mitk::AffineTransform3D::Pointer affineTransform = data.at(dataIdx)->GetGeometry()->GetIndexToWorldTransform(); + if (affineTransform.IsNull()) + { + MITK_WARN << "AffineTransform IndexToWorldTransform not initialized!"; + continue; + } - //check for offset - mitk::AffineTransform3D::Pointer offset = this->GetOffset(index); + // check for offset + mitk::AffineTransform3D::Pointer offset = this->GetOffset(index); - //store the current scaling to set it after transformation - mitk::Vector3D spacing = data->GetGeometry()->GetSpacing(); - //clear spacing of data to be able to set it again afterwards - ScalarType scale[] = {1.0, 1.0, 1.0}; - data->GetGeometry()->SetSpacing(scale); + // store the current scaling to set it after transformation + mitk::Vector3D spacing = data.at(dataIdx)->GetGeometry()->GetSpacing(); + // clear spacing of data to be able to set it again afterwards + ScalarType scale[] = {1.0, 1.0, 1.0}; + data.at(dataIdx)->GetGeometry()->SetSpacing(scale); - /*now bring quaternion to affineTransform by using vnl_Quaternion*/ - affineTransform->SetIdentity(); + /*now bring quaternion to affineTransform by using vnl_Quaternion*/ + affineTransform->SetIdentity(); - if (this->GetTransformOrientation(index) == true) - { - mitk::NavigationData::OrientationType orientation = nd->GetOrientation(); + if (this->GetTransformOrientation(index) == true) + { + mitk::NavigationData::OrientationType orientation = nd->GetOrientation(); - /* because of an itk bug, the transform can not be calculated with float data type. - To use it in the mitk geometry classes, it has to be transfered to mitk::ScalarType which is float */ - static AffineTransform3D::MatrixType m; + /* because of an itk bug, the transform can not be calculated with float data type. + To use it in the mitk geometry classes, it has to be transfered to mitk::ScalarType which is float */ + static AffineTransform3D::MatrixType m; - //convert quaternion to rotation matrix depending on the rotation mode - if(m_RotationMode == RotationStandard) + // convert quaternion to rotation matrix depending on the rotation mode + if (m_RotationMode == RotationStandard) { - //calculate the transform from the quaternions - static itk::QuaternionRigidTransform::Pointer quatTransform = itk::QuaternionRigidTransform::New(); - // convert mitk::ScalarType quaternion to double quaternion because of itk bug - vnl_quaternion doubleQuaternion(orientation.x(), orientation.y(), orientation.z(), orientation.r()); - quatTransform->SetIdentity(); - quatTransform->SetRotation(doubleQuaternion); - quatTransform->Modified(); - mitk::TransferMatrix(quatTransform->GetMatrix(), m); + // calculate the transform from the quaternions + static itk::QuaternionRigidTransform::Pointer quatTransform = + itk::QuaternionRigidTransform::New(); + // convert mitk::ScalarType quaternion to double quaternion because of itk bug + vnl_quaternion doubleQuaternion(orientation.x(), orientation.y(), orientation.z(), orientation.r()); + quatTransform->SetIdentity(); + quatTransform->SetRotation(doubleQuaternion); + quatTransform->Modified(); + mitk::TransferMatrix(quatTransform->GetMatrix(), m); } - else if(m_RotationMode == RotationTransposed) + else if (m_RotationMode == RotationTransposed) { - vnl_matrix_fixed rot = orientation.rotation_matrix_transpose(); - for(int i=0; i<3; i++) for (int j=0; j<3; j++) m[i][j] = rot[i][j]; + vnl_matrix_fixed rot = orientation.rotation_matrix_transpose(); + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + m[i][j] = rot[i][j]; } - affineTransform->SetMatrix(m); - - } - if (this->GetTransformPosition(index) == true) - { - ///*set the offset by convert from itkPoint to itkVector and setting offset of transform*/ - mitk::Vector3D pos; - pos.SetVnlVector(nd->GetPosition().GetVnlVector()); - affineTransform->SetOffset(pos); - } - affineTransform->Modified(); - + affineTransform->SetMatrix(m); + } + if (this->GetTransformPosition(index) == true) + { + ///*set the offset by convert from itkPoint to itkVector and setting offset of transform*/ + mitk::Vector3D pos; + pos.SetVnlVector(nd->GetPosition().GetVnlVector()); + affineTransform->SetOffset(pos); + } + affineTransform->Modified(); - //set the transform to data - if(offset.IsNotNull()) //first use offset if there is one. + // set the transform to data + if (offset.IsNotNull()) // first use offset if there is one. { - mitk::AffineTransform3D::Pointer overallTransform = mitk::AffineTransform3D::New(); - overallTransform->SetIdentity(); - overallTransform->Compose(offset); - overallTransform->Compose(affineTransform); - data->GetGeometry()->SetIndexToWorldTransform(overallTransform); + mitk::AffineTransform3D::Pointer overallTransform = mitk::AffineTransform3D::New(); + overallTransform->SetIdentity(); + overallTransform->Compose(offset); + overallTransform->Compose(affineTransform); + data.at(dataIdx)->GetGeometry()->SetIndexToWorldTransform(overallTransform); } - else + else { - data->GetGeometry()->SetIndexToWorldTransform(affineTransform); + data.at(dataIdx)->GetGeometry()->SetIndexToWorldTransform(affineTransform); } - //set the original spacing to keep scaling of the geometrical object - data->GetGeometry()->SetSpacing(spacing); - data->GetGeometry()->Modified(); - data->Modified(); - output->SetDataValid(true); // operation was successful, therefore data of output is valid. + // set the original spacing to keep scaling of the geometrical object + data.at(dataIdx)->GetGeometry()->SetSpacing(spacing); + data.at(dataIdx)->GetGeometry()->Modified(); + data.at(dataIdx)->Modified(); + output->SetDataValid(true); // operation was successful, therefore data of output is valid. + } } } - -void mitk::NavigationDataObjectVisualizationFilter::SetTransformPosition( unsigned int index, bool applyTransform ) +void mitk::NavigationDataObjectVisualizationFilter::SetTransformPosition(unsigned int index, bool applyTransform) { itkDebugMacro("setting TransformPosition for index " << index << " to " << applyTransform); BooleanInputMap::const_iterator it = this->m_TransformPosition.find(index); if ((it != this->m_TransformPosition.end()) && (it->second == applyTransform)) return; this->m_TransformPosition[index] = applyTransform; this->Modified(); } - -bool mitk::NavigationDataObjectVisualizationFilter::GetTransformPosition( unsigned int index ) const +bool mitk::NavigationDataObjectVisualizationFilter::GetTransformPosition(unsigned int index) const { itkDebugMacro("returning TransformPosition for index " << index); BooleanInputMap::const_iterator it = this->m_TransformPosition.find(index); if (it != this->m_TransformPosition.end()) return it->second; else return true; // default to true } - -void mitk::NavigationDataObjectVisualizationFilter::TransformPositionOn( unsigned int index ) +void mitk::NavigationDataObjectVisualizationFilter::TransformPositionOn(unsigned int index) { this->SetTransformPosition(index, true); } - -void mitk::NavigationDataObjectVisualizationFilter::TransformPositionOff( unsigned int index ) +void mitk::NavigationDataObjectVisualizationFilter::TransformPositionOff(unsigned int index) { this->SetTransformPosition(index, false); } - -void mitk::NavigationDataObjectVisualizationFilter::SetTransformOrientation( unsigned int index, bool applyTransform ) +void mitk::NavigationDataObjectVisualizationFilter::SetTransformOrientation(unsigned int index, bool applyTransform) { itkDebugMacro("setting TransformOrientation for index " << index << " to " << applyTransform); BooleanInputMap::const_iterator it = this->m_TransformOrientation.find(index); if ((it != this->m_TransformOrientation.end()) && (it->second == applyTransform)) return; this->m_TransformOrientation[index] = applyTransform; - this->Modified(); \ + this->Modified(); } - -bool mitk::NavigationDataObjectVisualizationFilter::GetTransformOrientation( unsigned int index ) const +bool mitk::NavigationDataObjectVisualizationFilter::GetTransformOrientation(unsigned int index) const { itkDebugMacro("returning TransformOrientation for index " << index); BooleanInputMap::const_iterator it = this->m_TransformOrientation.find(index); if (it != this->m_TransformOrientation.end()) return it->second; else return true; // default to true } - -void mitk::NavigationDataObjectVisualizationFilter::TransformOrientationOn( unsigned int index ) +void mitk::NavigationDataObjectVisualizationFilter::TransformOrientationOn(unsigned int index) { this->SetTransformOrientation(index, true); } - -void mitk::NavigationDataObjectVisualizationFilter::TransformOrientationOff( unsigned int index ) +void mitk::NavigationDataObjectVisualizationFilter::TransformOrientationOff(unsigned int index) { this->SetTransformOrientation(index, false); } diff --git a/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.h b/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.h index bbfd2df5ba..42871e0337 100644 --- a/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.h +++ b/Modules/IGT/Rendering/mitkNavigationDataObjectVisualizationFilter.h @@ -1,153 +1,176 @@ /*============================================================================ 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 MITKNAVIGATIONDATAOBJECTVISUALIZATIONFILTER_H_HEADER_INCLUDED_ #define MITKNAVIGATIONDATAOBJECTVISUALIZATIONFILTER_H_HEADER_INCLUDED_ #include "mitkNavigationDataToNavigationDataFilter.h" #include "mitkNavigationData.h" #include "mitkBaseData.h" namespace mitk { /** * \brief Class that reads NavigationData from input and transfers the information to the geometry of the associated BaseData * * Derived from NavigationDataToNavigationDataFilter * * \ingroup IGT */ class MITKIGT_EXPORT NavigationDataObjectVisualizationFilter : public NavigationDataToNavigationDataFilter { public: mitkClassMacro(NavigationDataObjectVisualizationFilter, NavigationDataToNavigationDataFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** Defines the rotation modes of this tracking device which results in different representations * of quaternions. * * - Standard: normal representation, rawdata from the device is not changed (DEFAULT) * * - Transposed: the rotation is stored transposed, which is (by mistake!) expected by some older MITK classes due * to an ambigious method naming in VNL. * * CAUTION: The rotation mode can only be changed for backward compatibility of old WRONG code. * PLEASE DO NOT CHANGE THE ROTATION MODE UNLESS YOU ARE KNOWING EXACTLY WHAT YOU ARE DOING! * * use SetRotationMode to change the mode. */ enum RotationMode {RotationStandard, RotationTransposed}; /** * \brief Smart Pointer type to a BaseData. */ - typedef BaseData::ConstPointer RepresentationPointer; + typedef BaseData::Pointer RepresentationPointer; /** - * \brief STL map of index to BaseData . Using map to be able to set non continuous indices - */ + * \brief STL map of index to BaseData . Using map to be able to set non continuous indices + */ typedef std::map RepresentationPointerMap; /** - * \brief Size type of an std::vector + * \brief STL vector map of index to BaseData . Using map to be able to set non continuous indices */ - typedef RepresentationPointerMap::size_type RepresentationPointerMapSizeType; + typedef std::map> RepresentationVectorPointerMap; + + /** + * \brief Size type of an std::vector + */ + typedef RepresentationVectorPointerMap::size_type RepresentationPointerMapSizeType; /** * \brief Set the representation object of the input * * \param data The BaseData to be associated to the index * \param index the index with which data will be associated */ - void SetRepresentationObject(unsigned int index, BaseData* data); + void SetRepresentationObject(unsigned int index, BaseData::Pointer data); + /** + * \brief Set the representation objects vector of the input + * + * \param data The BaseData vector to be associated to the index + * \param index the index with which data will be associated + */ + void SetRepresentationObjects(unsigned int index, const std::vector &data); + /** * \brief Get the representation object associated with the index idx * * \param idx the corresponding input number with which the BaseData is associated * \return Returns the desired BaseData if it exists for the given input; Returns nullptr * if no BaseData was found. */ - const BaseData* GetRepresentationObject(unsigned int idx); + BaseData::Pointer GetRepresentationObject(unsigned int idx) const; + + /** + * \brief Get all the representation objects associated with the index idx + * + * \param idx the corresponding input number with which the BaseData is associated + * \return Returns the desired BaseData if it exists for the given input; Returns nullptr + * if no BaseData was found. + */ + std::vector GetAllRepresentationObjects(unsigned int idx) const; virtual void SetTransformPosition(unsigned int index, bool applyTransform); ///< if set to true, the filter will use the position part of the input navigation data at the given index to transform the representation object. If set to false, it will not. If no value is set, it defaults to true. virtual bool GetTransformPosition(unsigned int index) const; ///< returns whether position part of the input navigation data at the given index is used for the transformation of the representation object. virtual void TransformPositionOn(unsigned int index); ///< sets the TransformPosition flag to true for the given index virtual void TransformPositionOff(unsigned int index); ///< sets the TransformPosition flag to false for the given index virtual void SetTransformOrientation(unsigned int index, bool applyTransform); ///< if set to true, the filter will use the orientation part of the input navigation data at the given index to transform the representation object. If set to false, it will not. If no value is set, it defaults to true. virtual bool GetTransformOrientation(unsigned int index) const; ///< returns whether orientation part of the input navigation data at the given index is used for the transformation of the representation object. virtual void TransformOrientationOn(unsigned int index); ///< sets the TransformOrientation flag to true for the given index virtual void TransformOrientationOff(unsigned int index); ///< sets the TransformOrientation flag to false for the given index /** @brief Defines an offset for a representation object. This offset is applied before the object is visualized. * If no offset is given, no offset will be used. To deactivate the offset just set it to nullptr. The offset is deactivated by default. * @param offset The new offset which will be set. Set to nullptr to deactivate the offset. */ void SetOffset(int index, mitk::AffineTransform3D::Pointer offset); /** Sets the rotation mode of this class. See documentation of enum RotationMode for details * on the different modes. * CAUTION: The rotation mode can only be changed for backward compatibility of old WRONG code. * PLEASE DO NOT CHANGE THE ROTATION MODE UNLESS YOU ARE KNOWING EXACTLY WHAT YOU ARE DOING! */ virtual void SetRotationMode(RotationMode r); /** @return Returns the offset of a represenation object. Returns nullptr if there is no offset. */ mitk::AffineTransform3D::Pointer GetOffset(int index); /** *\brief Get the number of added BaseData associated to NavigationData * \return Returns the size of the internal map */ RepresentationPointerMapSizeType GetNumberOfToolRepresentations() const { - return m_RepresentationList.size(); + return m_RepresentationVectorMap.size(); } /* * \brief Transfer the information from the input to the associated BaseData */ void GenerateData() override; protected: typedef std::map BooleanInputMap; typedef std::map OffsetPointerMap; /** * \brief Constructor **/ NavigationDataObjectVisualizationFilter(); /** * \brief Destructor **/ ~NavigationDataObjectVisualizationFilter() override; /** * \brief An array of the BaseData which represent the tools. */ - RepresentationPointerMap m_RepresentationList; + + RepresentationVectorPointerMap m_RepresentationVectorMap; BooleanInputMap m_TransformPosition; ///< if set to true, the filter will use the position part of the input navigation data at the given index for the calculation of the transform. If no entry for the index exists, it defaults to true. BooleanInputMap m_TransformOrientation; ///< if set to true, the filter will use the orientation part of the input navigation data at the given index for the calculation of the transform. If no entry for the index exists, it defaults to true. OffsetPointerMap m_OffsetList; private: RotationMode m_RotationMode; ///< defines the rotation mode Standard or Transposed, Standard is default }; } // namespace mitk #endif /* MITKNAVIGATIONDATAOBJECTVISUALIZATIONFILTER_H_HEADER_INCLUDED_ */ diff --git a/Modules/IGT/Testing/mitkNavigationDataObjectVisualizationFilterTest.cpp b/Modules/IGT/Testing/mitkNavigationDataObjectVisualizationFilterTest.cpp index ef5726e4bc..2163f6531a 100644 --- a/Modules/IGT/Testing/mitkNavigationDataObjectVisualizationFilterTest.cpp +++ b/Modules/IGT/Testing/mitkNavigationDataObjectVisualizationFilterTest.cpp @@ -1,433 +1,458 @@ /*============================================================================ 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 "mitkNavigationDataObjectVisualizationFilter.h" #include "mitkNavigationData.h" #include "mitkTestingMacros.h" #include #include #include #include #include "mitkSurface.h" /**Documentation * test for the class "NavigationDataObjectVisualizationFilter". */ class mitkNavigationDataObjectVisualizationFilterTestSuite : public mitk::TestFixture { // List of Tests CPPUNIT_TEST_SUITE(mitkNavigationDataObjectVisualizationFilterTestSuite); //Constructor MITK_TEST(TestInitialize); MITK_TEST(TestInput); MITK_TEST(TestOutput); MITK_TEST(TestRepresentationObjects); MITK_TEST(TestTransforms); MITK_TEST(TestMessWithRepresentationObjects); MITK_TEST(TestThirdInput); MITK_TEST(TestSetTransformPosition); MITK_TEST(TestSetTransformOrientation); MITK_TEST(TestConvenienceSetTransformOrientation); MITK_TEST(TestUpdateOrientation); CPPUNIT_TEST_SUITE_END(); // Used Variables private: mitk::NavigationDataObjectVisualizationFilter::Pointer myFilter; mitk::NavigationData::PositionType initialPos1, initialPos2; mitk::NavigationData::OrientationType initialOri1; mitk::NavigationData::OrientationType initialOri2; mitk::ScalarType initialError1; mitk::ScalarType initialError2; bool initialValid1; bool initialValid2; mitk::NavigationData::Pointer nd1; mitk::NavigationData::Pointer nd2; // Test setting BaseData - mitk::Surface::Pointer mitkToolData1 ; - - mitk::Surface::Pointer mitkToolData2 ; + mitk::Surface::Pointer mitkToolData1; + std::vector mitkToolVectorData1; + mitk::Surface::Pointer mitkToolData2; + std::vector mitkToolVectorData2; //dummy for test; will not be set but used to test find - mitk::Surface::Pointer mitkToolDataDummy ; + mitk::Surface::Pointer mitkToolDataDummy; + std::vector mitkToolVectorDataDummy; //and the Dummy NavigationData for this mitk::NavigationData::OrientationType initialOriDummy; mitk::NavigationData::PositionType initialPosDummy; mitk::ScalarType initialErrorDummy; bool initialValidDummy; mitk::NavigationData::Pointer ndDummy ; mitk::NavigationData* output; mitk::AffineTransform3D::Pointer affineTransform1; mitk::AffineTransform3D::OutputVectorType offset1; mitk::AffineTransform3D::Pointer affineTransform2; mitk::AffineTransform3D::OutputVectorType offset2; mitk::AffineTransform3D::MatrixType::InternalMatrixType m1; mitk::AffineTransform3D::MatrixType::InternalMatrixType m2; public: // Set up for variables void setUp() override { // -------------- Setup for Filter --------------------------- /* create helper objects: navigation data with position as origin, zero quaternion, zero error and data valid */ myFilter = mitk::NavigationDataObjectVisualizationFilter::New(); mitk::FillVector3D(initialPos1, 1.1, 2.2, 3.3); mitk::FillVector3D(initialPos2, 5.0, 6.0, 7.0); mitk::FillVector4D(initialOri1, 0.1, 0.2, 0.3, 0.4); mitk::FillVector4D(initialOri2,0.5, 0.6, 0.7, 0.8); initialError1=0.0; initialError2=5.0; initialValid1 = true; initialValid2 = true; //Set Navigation Data nd1 = mitk::NavigationData::New(); nd1->SetPosition(initialPos1); nd1->SetOrientation(initialOri1); nd1->SetPositionAccuracy(initialError1); nd1->SetDataValid(initialValid1); nd2 = mitk::NavigationData::New(); nd2->SetPosition(initialPos2); nd2->SetOrientation(initialOri2); nd2->SetPositionAccuracy(initialError2); nd2->SetDataValid(initialValid2); //Set Input myFilter->SetInput(nd1); myFilter->SetInput(1, nd2); output = myFilter->GetOutput(); // -------------------------- Setup for TestData ---------------------- // Test setting BaseData mitkToolData1 = mitk::Surface::New(); mitkToolData2 = mitk::Surface::New(); + // Test setting BaseData vectors + mitkToolVectorData1.push_back(mitkToolData1.GetPointer()); + mitkToolVectorData2.push_back(mitkToolData2.GetPointer()); + //dummy for test; will not be set but used to test find mitkToolDataDummy = mitk::Surface::New(); + mitkToolVectorDataDummy.push_back(mitkToolDataDummy.GetPointer()); //and the Dummy NavigationData for this mitk::FillVector3D(initialPosDummy, 8.8, 9.9, 10.10); mitk::FillVector4D(initialOriDummy,1.1, 2.2, 3.3, 4.4); initialErrorDummy=10.0; initialValidDummy=true; ndDummy = mitk::NavigationData::New(); ndDummy->SetPosition(initialPosDummy); ndDummy->SetOrientation(initialOriDummy); ndDummy->SetPositionAccuracy(initialErrorDummy); ndDummy->SetDataValid(initialValidDummy); //now we have ndDummy and mitkToolDataDummy to test with } void tearDown() override { } // Test functions void TestInitialize(){ // first test: did this work? CPPUNIT_ASSERT_MESSAGE("Testing instantiation", myFilter.IsNotNull()); } void TestInput(){ //testing the input CPPUNIT_ASSERT_MESSAGE("Testing Set-/GetInput() input 1 without index",myFilter->GetInput() == nd1); CPPUNIT_ASSERT_MESSAGE("Testing Set-/GetInput() input 1", myFilter->GetInput(0) == nd1); CPPUNIT_ASSERT_MESSAGE( "Testing Set-/GetInput() input 2", myFilter->GetInput(1) == nd2); CPPUNIT_ASSERT_MESSAGE("Testing GetNumberOfToolRepresentations()", myFilter->GetNumberOfToolRepresentations() == 0); } void TestOutput(){ //testing getting the output CPPUNIT_ASSERT_MESSAGE("Testing GetOutput()", output != nullptr); CPPUNIT_ASSERT_MESSAGE("Testing GetOutput() == GetOutput()", output == myFilter->GetOutput()); CPPUNIT_ASSERT_MESSAGE("Testing GetOutput() != GetOutput(1)", output != myFilter->GetOutput(1)); } void TestRepresentationObjects(){ //setting nodes - myFilter->SetRepresentationObject(0, mitkToolData1); + myFilter->SetRepresentationObject(0, mitkToolData1.GetPointer()); CPPUNIT_ASSERT_MESSAGE( "Testing SetRepresentationObject()/GetRepresentationObject() node 1", myFilter->GetRepresentationObject(0) == mitkToolData1); CPPUNIT_ASSERT_MESSAGE("Testing GetNumberOfToolRepresentations() after adding first tool", myFilter->GetNumberOfToolRepresentations() == 1); - myFilter->SetRepresentationObject(1, mitkToolData2); + myFilter->SetRepresentationObject(1, mitkToolData2.GetPointer()); CPPUNIT_ASSERT_MESSAGE( "Testing SetRepresentationObject() node 2", myFilter->GetRepresentationObject(1) == mitkToolData2); CPPUNIT_ASSERT_MESSAGE( "Testing GetNumberOfToolRepresentations() after adding second tool", myFilter->GetNumberOfToolRepresentations() == 2); //getting nodes CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() node 1", myFilter->GetRepresentationObject(0) == mitkToolData1); CPPUNIT_ASSERT_MESSAGE( "Testing GetRepresentationObject() != Dummy node", myFilter->GetRepresentationObject(0) != mitkToolDataDummy); CPPUNIT_ASSERT_MESSAGE( "Testing GetRepresentationObject() node 2", myFilter->GetRepresentationObject(1) == mitkToolData2); CPPUNIT_ASSERT_MESSAGE( "Testing GetRepresentationObject() != Dummy node", myFilter->GetRepresentationObject(1) != mitkToolDataDummy); CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() with out of range parameter", myFilter->GetRepresentationObject(111) == nullptr); } + void TestAllRepresentationObjects() + { + // setting nodes + myFilter->SetRepresentationObjects(0, mitkToolVectorData1); + CPPUNIT_ASSERT_MESSAGE("Testing SetRepresentationObject()/GetRepresentationObject() node 1", myFilter->GetAllRepresentationObjects(0) == mitkToolVectorData1); + CPPUNIT_ASSERT_MESSAGE("Testing GetNumberOfToolRepresentations() after adding first tool", myFilter->GetNumberOfToolRepresentations() == 1); + + myFilter->SetRepresentationObjects(1, mitkToolVectorData2); + CPPUNIT_ASSERT_MESSAGE("Testing SetRepresentationObject() node 2", myFilter->GetAllRepresentationObjects(1) == mitkToolVectorData2); + CPPUNIT_ASSERT_MESSAGE("Testing GetNumberOfToolRepresentations() after adding second tool", myFilter->GetNumberOfToolRepresentations() == 2); + // getting nodes + CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() node 1", myFilter->GetAllRepresentationObjects(0) == mitkToolVectorData1); + CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() != Dummy node", myFilter->GetAllRepresentationObjects(0) != mitkToolVectorDataDummy); + CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() node 2", myFilter->GetAllRepresentationObjects(1) == mitkToolVectorData2); + CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() != Dummy node", myFilter->GetAllRepresentationObjects(1) != mitkToolVectorDataDummy); + CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() with out of range parameter", myFilter->GetAllRepresentationObjects(111).empty() == true); + } + void TestTransforms(){ - myFilter->SetRepresentationObject(0, mitkToolData1); - myFilter->SetRepresentationObject(1, mitkToolData2); + myFilter->SetRepresentationObject(0, mitkToolData1.GetPointer()); + myFilter->SetRepresentationObject(1, mitkToolData2.GetPointer()); //Process myFilter->Update(); affineTransform1 = mitkToolData1->GetGeometry()->GetIndexToWorldTransform(); offset1 = affineTransform1->GetOffset(); affineTransform2 = mitkToolData2->GetGeometry()->GetIndexToWorldTransform(); offset2 = affineTransform2->GetOffset(); m1 = affineTransform1->GetMatrix().GetVnlMatrix(); m2 = affineTransform2->GetMatrix().GetVnlMatrix(); //now check it there are data connected to the nodes with the according orientation and offsets CPPUNIT_ASSERT_MESSAGE("Testing Offset position 1", offset1.GetVnlVector()==initialPos1.GetVnlVector()); CPPUNIT_ASSERT_MESSAGE("Testing Offset position 2", offset2.GetVnlVector()==initialPos2.GetVnlVector()); MITK_TEST_OUTPUT( << "\n initOrient1="<GetVnlMatrix():\n "<< m1); MITK_TEST_OUTPUT( << "\n initOrient2=" << initialOri2 << " affineTransform2->GetVnlMatrix():\n " << m2); } void TestMessWithRepresentationObjects(){ - myFilter->SetRepresentationObject(0, mitkToolData1); - myFilter->SetRepresentationObject(1, mitkToolData2); + myFilter->SetRepresentationObject(0, mitkToolData1.GetPointer()); + myFilter->SetRepresentationObject(1, mitkToolData2.GetPointer()); //Process myFilter->Update(); affineTransform1 = mitkToolData1->GetGeometry()->GetIndexToWorldTransform(); offset1 = affineTransform1->GetOffset(); affineTransform2 = mitkToolData2->GetGeometry()->GetIndexToWorldTransform(); offset2 = affineTransform2->GetOffset(); m1 = affineTransform1->GetMatrix().GetVnlMatrix(); m2 = affineTransform2->GetMatrix().GetVnlMatrix(); //messing with SetRepresentationObject //setting nodes - myFilter->SetRepresentationObject(0, mitkToolData2); + myFilter->SetRepresentationObject(0, mitkToolData2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Twisting mitkToolData by using SetRepresentationObject() NavigationData 1 with ToolData 2", myFilter->GetRepresentationObject(0) == mitkToolData2); CPPUNIT_ASSERT_MESSAGE( "Testing GetNumberOfToolRepresentations() == 1", myFilter->GetNumberOfToolRepresentations() == 2); - myFilter->SetRepresentationObject(1, mitkToolData1); + myFilter->SetRepresentationObject(1, mitkToolData1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Twisting mitkToolData by using SetRepresentationObject() NavigationData 2 with ToolData 1", myFilter->GetRepresentationObject(1) == mitkToolData1); CPPUNIT_ASSERT_MESSAGE( "Testing GetNumberOfToolRepresentations() == 2", myFilter->GetNumberOfToolRepresentations() == 2); //getting nodes CPPUNIT_ASSERT_MESSAGE("Testing switched BaseData of NavigationData 1 ", myFilter->GetRepresentationObject(0) == mitkToolData2); CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() != Dummy node", myFilter->GetRepresentationObject(0) != mitkToolDataDummy); CPPUNIT_ASSERT_MESSAGE("Testing switched BaseData NavigationData 2", myFilter->GetRepresentationObject(1) == mitkToolData1); CPPUNIT_ASSERT_MESSAGE("Testing GetRepresentationObject() != Dummy node", myFilter->GetRepresentationObject(1) != mitkToolDataDummy); //processing update through pipeline myFilter->Update(); //now check it there are data connected to the nodes with the according orientation and offsets mitk::AffineTransform3D::Pointer affineTransform1Second = mitkToolData1->GetGeometry()->GetIndexToWorldTransform(); CPPUNIT_ASSERT_MESSAGE( "Testing affineTransform1 after second update", affineTransform1 == affineTransform1Second); mitk::AffineTransform3D::OutputVectorType offset1Second = affineTransform1->GetOffset(); CPPUNIT_ASSERT_MESSAGE( "Testing offset1 after second update", offset1 == offset1Second); CPPUNIT_ASSERT_MESSAGE("Testing offset1 equals first update", offset1Second.GetVnlVector()==offset1.GetVnlVector()); mitk::AffineTransform3D::Pointer affineTransform2Second = mitkToolData2->GetGeometry()->GetIndexToWorldTransform(); CPPUNIT_ASSERT_MESSAGE("Testing affineTransform2 after second update", affineTransform2 == affineTransform2Second); mitk::AffineTransform3D::OutputVectorType offset2Second = affineTransform2->GetOffset(); CPPUNIT_ASSERT_MESSAGE("Testing offset2 after second update", offset2 == offset2Second); CPPUNIT_ASSERT_MESSAGE("Testing offset2 equals first update", offset2Second.GetVnlVector()==offset2.GetVnlVector()); mitk::AffineTransform3D::MatrixType::InternalMatrixType m1Second= affineTransform1Second->GetMatrix().GetVnlMatrix(); MITK_TEST_OUTPUT( <<"\n after second update initOrient1="<GetVnlMatrix():\n "<< m1Second); mitk::AffineTransform3D::MatrixType::InternalMatrixType m2Second= affineTransform2Second->GetMatrix().GetVnlMatrix(); MITK_TEST_OUTPUT( << "\n after second update initOrient2="<GetVnlMatrix():\n "<< m2Second); } void TestThirdInput(){ - myFilter->SetRepresentationObject(0, mitkToolData1); - myFilter->SetRepresentationObject(1, mitkToolData2); + myFilter->SetRepresentationObject(0, mitkToolData1.GetPointer()); + myFilter->SetRepresentationObject(1, mitkToolData2.GetPointer()); //Process myFilter->Update(); //testing adding a third input myFilter->SetInput(2,ndDummy); CPPUNIT_ASSERT_MESSAGE("Adding new input and testing GetNumberOfInputs == 3", myFilter->GetNumberOfInputs() == 3); CPPUNIT_ASSERT_MESSAGE("testing GetNumberOfOutputs == 3", myFilter->GetNumberOfOutputs() == 3); CPPUNIT_ASSERT_MESSAGE("Testing Input == newly added input", myFilter->GetInput(2) == ndDummy); CPPUNIT_ASSERT_MESSAGE("Testing GetOutput(2) != nullptr", myFilter->GetOutput(2) != nullptr); CPPUNIT_ASSERT_MESSAGE( "Testing GetOutput(2) != GetOutput(1)", myFilter->GetOutput(2) != myFilter->GetOutput(1)); - myFilter->SetRepresentationObject(2, mitkToolDataDummy); + myFilter->SetRepresentationObject(2, mitkToolDataDummy.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Testing GetNumberOfToolRepresentations() after adding latest tool", myFilter->GetNumberOfToolRepresentations() == 3); CPPUNIT_ASSERT_MESSAGE("Testing Set-/GetRepresentationObject() equals was set", myFilter->GetRepresentationObject(2) == mitkToolDataDummy); //last time processing update through pipeline myFilter->Update(); //now check for the new values mitk::AffineTransform3D::Pointer affineTransformDummy = mitkToolDataDummy->GetGeometry()->GetIndexToWorldTransform(); mitk::AffineTransform3D::OutputVectorType offsetDummy = affineTransformDummy->GetOffset(); CPPUNIT_ASSERT_MESSAGE("Testing Offset latest added tool", offsetDummy.GetVnlVector()==initialPosDummy.GetVnlVector()); mitk::AffineTransform3D::MatrixType::InternalMatrixType m1Latest= affineTransformDummy->GetMatrix().GetVnlMatrix(); MITK_TEST_OUTPUT( << "\n latest initOrient="<GetVnlMatrix():\n "<< m1Latest); mitk::Surface::Pointer anotherSurface = mitk::Surface::New(); - myFilter->SetRepresentationObject(0, anotherSurface); + myFilter->SetRepresentationObject(0, anotherSurface.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Overwriting BaseData index 0", myFilter->GetRepresentationObject(0) == anotherSurface); } void TestSetTransformPosition(){ // test Set/GetTransformPosition() myFilter->SetTransformPosition(0,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(0,true)", myFilter->GetTransformPosition(0)==true); myFilter->SetTransformPosition(1,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(1,true)", myFilter->GetTransformPosition(1)==true); myFilter->SetTransformPosition(2,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(2,true)", myFilter->GetTransformPosition(2)==true); myFilter->SetTransformPosition(3,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(3,true)", myFilter->GetTransformPosition(3)==true); myFilter->SetTransformPosition(0,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(0,false)", myFilter->GetTransformPosition(0)==false); myFilter->SetTransformPosition(1,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(1,false)", myFilter->GetTransformPosition(1)==false); myFilter->SetTransformPosition(2,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(2,false)", myFilter->GetTransformPosition(2)==false); myFilter->SetTransformPosition(3,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformPosition(3,false)", myFilter->GetTransformPosition(3)==false); } void TestSetTransformOrientation(){ // test Set/GetTransformOrientation() myFilter->SetTransformOrientation(0,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(0,true)", myFilter->GetTransformOrientation(0)==true); myFilter->SetTransformOrientation(1,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(1,true)", myFilter->GetTransformOrientation(1)==true); myFilter->SetTransformOrientation(2,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(2,true)", myFilter->GetTransformOrientation(2)==true); myFilter->SetTransformOrientation(3,true); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(3,true)", myFilter->GetTransformOrientation(3)==true); myFilter->SetTransformOrientation(0,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(0,false)", myFilter->GetTransformOrientation(0)==false); myFilter->SetTransformOrientation(1,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(1,false)", myFilter->GetTransformOrientation(1)==false); myFilter->SetTransformOrientation(2,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(2,false)", myFilter->GetTransformOrientation(2)==false); myFilter->SetTransformOrientation(3,false); CPPUNIT_ASSERT_MESSAGE("test Set/GetTransformOrientation(3,false)", myFilter->GetTransformOrientation(3)==false); } void TestConvenienceSetTransformOrientation(){ // test the convenience methods to set/getTransformOrientation/Position myFilter->TransformOrientationOn(0); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOn()", myFilter->GetTransformOrientation(0)==true); myFilter->TransformOrientationOff(0); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOff()", myFilter->GetTransformOrientation(0)==false); myFilter->TransformOrientationOff(1); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOff()", myFilter->GetTransformOrientation(1)==false); myFilter->TransformOrientationOn(1); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOn()", myFilter->GetTransformOrientation(1)==true); myFilter->TransformOrientationOn(2); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOn()", myFilter->GetTransformOrientation(2)==true); myFilter->TransformOrientationOff(2); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOff()", myFilter->GetTransformOrientation(2)==false); myFilter->TransformOrientationOn(3); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOn()", myFilter->GetTransformOrientation(3)==true); myFilter->TransformOrientationOff(3); CPPUNIT_ASSERT_MESSAGE("test TransformOrientationOff()", myFilter->GetTransformOrientation(3)==false); myFilter->TransformPositionOn(0); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOn()", myFilter->GetTransformPosition(0)==true); myFilter->TransformPositionOff(0); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOff()", myFilter->GetTransformPosition(0)==false); myFilter->TransformPositionOff(1); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOff()", myFilter->GetTransformPosition(1)==false); myFilter->TransformPositionOn(1); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOn()", myFilter->GetTransformPosition(1)==true); myFilter->TransformPositionOn(2); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOn()", myFilter->GetTransformPosition(2)==true); myFilter->TransformPositionOff(2); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOff()", myFilter->GetTransformPosition(2)==false); myFilter->TransformPositionOn(3); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOn()", myFilter->GetTransformPosition(3)==true); myFilter->TransformPositionOff(3); CPPUNIT_ASSERT_MESSAGE("test TransformPositionOff()", myFilter->GetTransformPosition(3)==false); } void TestUpdateOrientation(){ // update position and orientation mitk::NavigationData::PositionType updatedPos1, updatedPos2, zero; mitk::FillVector3D(updatedPos1, 3.2, 1.5, 2.8); mitk::FillVector3D(updatedPos2, 4.3, 5.2, 6.0); mitk::FillVector3D(zero, 0.0, 0.0, 0.0); mitk::NavigationData::OrientationType updatedOri1(0.7, 0.5, 0.1, 0.4); mitk::NavigationData::OrientationType updatedOri2(0.2, 0.7, 0.6, 0.1); updatedOri1.normalize(); updatedOri2.normalize(); nd1->SetPosition(updatedPos1); nd1->SetOrientation(updatedOri1); nd2->SetPosition(updatedPos2); nd2->SetOrientation(updatedOri2); - myFilter->SetRepresentationObject(0,mitkToolData1); - myFilter->SetRepresentationObject(1,mitkToolData2); + myFilter->SetRepresentationObject(0, mitkToolData1.GetPointer()); + myFilter->SetRepresentationObject(1, mitkToolData2.GetPointer()); myFilter->TransformPositionOn(0); myFilter->TransformOrientationOff(0); myFilter->TransformPositionOff(1); myFilter->TransformOrientationOn(1); myFilter->Update(); // test positions and orientations mitk::AffineTransform3D::Pointer updatedAffineTransform1 = mitkToolData1->GetGeometry()->GetIndexToWorldTransform(); mitk::AffineTransform3D::OutputVectorType updatedOffset1 = updatedAffineTransform1->GetOffset(); CPPUNIT_ASSERT_MESSAGE("Testing updated position 1", mitk::Equal(updatedOffset1.GetVnlVector(),updatedPos1.GetVnlVector())); mitk::AffineTransform3D::Pointer updatedAffineTransform2 = mitkToolData2->GetGeometry()->GetIndexToWorldTransform(); mitk::AffineTransform3D::OutputVectorType updatedOffset2 = updatedAffineTransform2->GetOffset(); CPPUNIT_ASSERT_MESSAGE( "Testing updated position 2", mitk::Equal(updatedOffset2.GetVnlVector(),zero.GetVnlVector())); mitk::AffineTransform3D::Pointer identityTransform = mitk::AffineTransform3D::New(); identityTransform->SetIdentity(); mitk::AffineTransform3D::MatrixType identityMatrix = identityTransform->GetMatrix(); mitk::AffineTransform3D::MatrixType uM1 = updatedAffineTransform1->GetMatrix(); CPPUNIT_ASSERT_MESSAGE( "Testing updated orientation 1", mitk::MatrixEqualElementWise(uM1, identityMatrix)); mitk::AffineTransform3D::MatrixType::InternalMatrixType uM2 = updatedAffineTransform2->GetMatrix().GetVnlMatrix(); mitk::AffineTransform3D::MatrixType::InternalMatrixType updatedOriTransform2 = updatedOri2.rotation_matrix_transpose().transpose(); CPPUNIT_ASSERT_MESSAGE("Testing updated orientation 2", mitk::MatrixEqualElementWise(uM2,updatedOriTransform2)); // Test that the second RepresentationObject is updated properly even when // the first RepresentationObject is invalid nd2->Modified(); myFilter->SetRepresentationObject(0, nullptr); mitkToolData2->GetGeometry()->SetIdentity(); myFilter->Update(); CPPUNIT_ASSERT_MESSAGE( "Test that the second repr object is updated correctly when the first repr object is invalid", mitk::MatrixEqualElementWise(mitkToolData2->GetGeometry()->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix(), updatedOri2.rotation_matrix_transpose().transpose())); } };//end class mitkNavigationDataObjectVisualizationFilterTestSuite MITK_TEST_SUITE_REGISTRATION(mitkNavigationDataObjectVisualizationFilter) diff --git a/Modules/IGT/Tutorial/mitkIGTTutorialStep2.cpp b/Modules/IGT/Tutorial/mitkIGTTutorialStep2.cpp index dd74393157..60b968ecfb 100644 --- a/Modules/IGT/Tutorial/mitkIGTTutorialStep2.cpp +++ b/Modules/IGT/Tutorial/mitkIGTTutorialStep2.cpp @@ -1,179 +1,179 @@ /*============================================================================ 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 #include #include #include #include #include #include #include #include #include //The next line starts a snippet to display this code in the documentation. If you don't revise the documentation, don't remove it! //! [What we will do] //************************************************************************* // What we will do... //************************************************************************* // This tutorial shows how to compose navigation datas. Therefore we render two objects. //The first object is a cone that is tracked. The second object is a cylinder at a fixed position //relative to the cone. At the end of the tracking, the cylinder is moved to its new relative position //according to the last output of the tracking device. //In addition to IGT tutorial step 1, the objects are added to a datastorage. Furthermore, a renderwindow //is used for visual output. //! [What we will do] int main(int, char**) { //************************************************************************* // Set up Render Window and Tracking Device //************************************************************************* //! [Render Window] //General code rendering the data in a renderwindow. See MITK Tutorial Step1 for more details. mitk::StandaloneDataStorage::Pointer dataStorage = mitk::StandaloneDataStorage::New(); mitk::RenderWindow::Pointer renderWindow = mitk::RenderWindow::New(); mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); //Here, we want a 3D renderwindow renderWindow->GetRenderer()->SetMapperID(mitk::BaseRenderer::Standard3D); renderWindow->GetVtkRenderWindow()->SetSize(500, 500); renderWindow->GetRenderer()->Resize(500, 500); //Connect datastorage and renderwindow renderWindow->GetRenderer()->SetDataStorage(dataStorage); //! [Render Window] //! [Setup Tracking Device] //Virtual tracking device to generate random positions mitk::VirtualTrackingDevice::Pointer tracker = mitk::VirtualTrackingDevice::New(); //Bounds (within the random numbers are generated) must be set before the tools are added double bound = 10.0; mitk::ScalarType bounds[] = { -bound, bound, -bound, bound, -bound, bound }; tracker->SetBounds(bounds); tracker->AddTool("tool1"); //Tracking device source to get the data mitk::TrackingDeviceSource::Pointer source = mitk::TrackingDeviceSource::New(); source->SetTrackingDevice(tracker); source->Connect(); //! [Setup Tracking Device] //************************************************************************* // Create Objects //************************************************************************* //! [Moving Object] //Cone representation for rendering of the moving object mitk::Cone::Pointer cone = mitk::Cone::New(); dataNode->SetData(cone); dataNode->SetName("My tracked object"); dataNode->SetColor(0.0, 1.0, 1.0); dataStorage->Add(dataNode); //Filter for rendering the cone at correct postion and orientation mitk::NavigationDataObjectVisualizationFilter::Pointer visualizer = mitk::NavigationDataObjectVisualizationFilter::New(); visualizer->SetInput(0, source->GetOutput()); - visualizer->SetRepresentationObject(0, cone); + visualizer->SetRepresentationObject(0, cone.GetPointer()); //! [Moving Object] //! [Fixed Object] //Cylinder representation for rendering of the fixed object mitk::DataNode::Pointer cylinderNode = mitk::DataNode::New(); mitk::Cylinder::Pointer cylinder = mitk::Cylinder::New(); cylinderNode->SetData(cylinder); cylinderNode->SetName("My fixed object"); cylinderNode->SetColor(1.0, 0.0, 0.0); dataStorage->Add(cylinderNode); //Define a rotation and a translation for the fixed object mitk::Matrix3D rotationMatrix; rotationMatrix.SetIdentity(); double alpha = 0.3; rotationMatrix[1][1] = cos(alpha); rotationMatrix[1][2] = -sin(alpha); rotationMatrix[2][1] = sin(alpha); rotationMatrix[2][2] = cos(alpha); mitk::Vector3D offset; offset.Fill(5.0); //Add rotation and translation to affine transform mitk::AffineTransform3D::Pointer affineTransform3D = mitk::AffineTransform3D::New(); affineTransform3D->SetOffset(offset); affineTransform3D->SetMatrix(rotationMatrix); //apply rotation and translation mitk::NavigationData::Pointer fixedNavigationData = mitk::NavigationData::New(affineTransform3D); cylinder->GetGeometry()->SetIndexToWorldTransform(fixedNavigationData->GetAffineTransform3D()); //! [Fixed Object] //************************************************************************* // The Tracking loop //************************************************************************* //! [Initialize views] // Global reinit with the bounds of the virtual tracking device auto timeGeometry = dataStorage->ComputeBoundingGeometry3D(dataStorage->GetAll()); mitk::BaseGeometry::Pointer geometry = timeGeometry->GetGeometryForTimeStep(0); geometry->SetBounds(bounds); mitk::RenderingManager::GetInstance()->InitializeViews(geometry); source->StartTracking(); //! [Initialize views] //! [Tracking] //Generate and render 75 time steps to move the tracked object for (int i = 0; i < 75; ++i) { //Update the cone position visualizer->Update(); //Update rendering renderWindow->GetVtkRenderWindow()->Render(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); MITK_INFO << "Position " << source->GetOutput()->GetPosition(); //Slight delay for the random numbers itksys::SystemTools::Delay(100); } //Stop the tracking device and disconnect it //The tracking is done, now we want to move the fixed object to its correct relative position regarding the tracked object. source->StopTracking(); source->Disconnect(); //! [Tracking] //************************************************************************* // Final Transform //************************************************************************* //! [Calculate Transform] //Now the tracking is finished and we can use the transformation to move //the fixed object to its correct position relative to the new position //of the moving/tracked object. Therefore, we compose the navigation datas. fixedNavigationData->Compose(source->GetOutput(), false); //Update the transformation matrix of the cylinder cylinder->GetGeometry()->SetIndexToWorldTransform(fixedNavigationData->GetAffineTransform3D()); //Update the rendering renderWindow->GetVtkRenderWindow()->Render(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); //Wait a little before closing the renderwindow itksys::SystemTools::Delay(2000); //! [Calculate Transform] } diff --git a/Modules/ImageStatistics/CMakeLists.txt b/Modules/ImageStatistics/CMakeLists.txt index 850733b3bd..73a49bc11d 100644 --- a/Modules/ImageStatistics/CMakeLists.txt +++ b/Modules/ImageStatistics/CMakeLists.txt @@ -1,12 +1,10 @@ MITK_CREATE_MODULE( DEPENDS MitkImageExtraction MitkPlanarFigure MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|ITKIOXML PRIVATE ITK|ITKVTK+ITKConvolution ) if(BUILD_TESTING) - add_subdirectory(Testing) - -endif(BUILD_TESTING) +endif() diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp index 69081a273e..69e4f7505f 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp @@ -1,252 +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. ============================================================================*/ // Testing #include "mitkTestingMacros.h" #include "mitkTestFixture.h" //MITK includes #include #include #include #include #include #include #include #include #include +#include class mitkImageStatisticsContainerManagerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkImageStatisticsContainerManagerTestSuite); MITK_TEST(GetImageStatisticsNoRules); MITK_TEST(GetImageStatisticsWithImageConnected); MITK_TEST(GetImageStatisticsWithImageNotConnected); MITK_TEST(GetImageStatisticsWithImageAndMaskConnected); MITK_TEST(GetImageStatisticsWithImageAndMaskNotConnected); MITK_TEST(GetImageStatisticsInvalid); + MITK_TEST(GetImageStatisticsProperties); + MITK_TEST(GetImageStatisticsUpToDate); CPPUNIT_TEST_SUITE_END(); private: mitk::ImageStatisticsContainer::Pointer m_statisticsContainer, m_statisticsContainer2, m_statisticsContainer3; mitk::Image::Pointer m_image, m_image2; mitk::Image::Pointer m_mask, m_mask2; mitk::PlanarFigure::Pointer m_planarFigure, m_planarFigure2; public: void setUp() override { - m_statisticsContainer = mitk::ImageStatisticsContainer::New(); - m_statisticsContainer2 = mitk::ImageStatisticsContainer::New(); - m_statisticsContainer3 = mitk::ImageStatisticsContainer::New(); m_image = mitk::Image::New(); m_image2 = mitk::Image::New(); m_mask = mitk::Image::New(); m_mask2 = mitk::Image::New(); m_planarFigure = mitk::PlanarCircle::New().GetPointer(); m_planarFigure2 = mitk::PlanarCircle::New().GetPointer(); + + m_statisticsContainer = mitk::ImageStatisticsContainer::New(); + m_statisticsContainer->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(100)); + m_statisticsContainer->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(false)); + + m_statisticsContainer2 = mitk::ImageStatisticsContainer::New(); + m_statisticsContainer2->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(100)); + m_statisticsContainer2->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(false)); + + m_statisticsContainer3 = mitk::ImageStatisticsContainer::New(); + m_statisticsContainer3->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(100)); + m_statisticsContainer3->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(false)); } void tearDown() override { } void CreateNodeRelationImage(mitk::BaseData::Pointer statistics, mitk::BaseData::ConstPointer image) { auto rule = mitk::StatisticsToImageRelationRule::New(); rule->Connect(statistics, image); } void CreateNodeRelationMask(mitk::BaseData::Pointer statistics, mitk::BaseData::ConstPointer image) { auto rule = mitk::StatisticsToMaskRelationRule::New(); rule->Connect(statistics, image); } void GetImageStatisticsNoRules() { auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); standaloneDataStorage->Add(statisticsNode); //no rules + 1 image --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer emptyStatistic; CPPUNIT_ASSERT_NO_THROW(emptyStatistic = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); //no rules + 1 image + 1 mask --> test return nullptr CPPUNIT_ASSERT_NO_THROW(emptyStatistic = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); //no rules + 1 image + 1 planarFigure --> test return nullptr CPPUNIT_ASSERT_NO_THROW(emptyStatistic = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_planarFigure.GetPointer())); CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); } + void GetImageStatisticsProperties() + { + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + + statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer2, "testStatistics"); + m_statisticsContainer2->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(50)); + CreateNodeRelationImage(m_statisticsContainer2.GetPointer(), m_image.GetPointer()); + standaloneDataStorage->Add(statisticsNode); + + statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer3, "testStatistics"); + m_statisticsContainer3->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(true)); + CreateNodeRelationImage(m_statisticsContainer3.GetPointer(), m_image.GetPointer()); + standaloneDataStorage->Add(statisticsNode); + + mitk::ImageStatisticsContainer::ConstPointer foundStatistics; + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), nullptr, false, 50)); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer2->GetUID()); + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), nullptr, true,100)); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer3->GetUID()); + } + + void GetImageStatisticsUpToDate() + { + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + CreateNodeRelationMask(m_statisticsContainer.GetPointer(), m_mask.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + + mitk::ImageStatisticsContainer::ConstPointer foundStatistics; + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(),false, 100)); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + + m_image->Modified(); + + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100, true)); + CPPUNIT_ASSERT_MESSAGE("Error. Statistics was found even though it is outdated.",foundStatistics.IsNull()); + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100, false)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error. Statistics was not found, even if outdated results are allowed.", foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + + m_statisticsContainer->Modified(); + + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100)); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + + m_mask->Modified(); + + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100, true)); + CPPUNIT_ASSERT_MESSAGE("Error. Statistics was found even though it is outdated.", foundStatistics.IsNull()); + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100, false)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error. Statistics was not found, even if outdated results are allowed.", foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + + m_statisticsContainer->Modified(); + + CPPUNIT_ASSERT_NO_THROW(foundStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer(), false, 100)); + CPPUNIT_ASSERT_EQUAL(foundStatistics->GetUID(), m_statisticsContainer->GetUID()); + } + void GetImageStatisticsWithImageConnected() { //create rules connection auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); standaloneDataStorage->Add(statisticsNode); //rule: (image-->statistics), 1 connected image --> test return image statistics mitk::ImageStatisticsContainer::ConstPointer statisticsWithImage; CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImage->GetUID(), m_statisticsContainer->GetUID()); //new rule: (image2-->statistics2 AND mask --> statistics2) CreateNodeRelationImage(m_statisticsContainer2.GetPointer(), m_image2.GetPointer()); CreateNodeRelationMask(m_statisticsContainer2.GetPointer(), m_mask.GetPointer()); auto statisticsNode2 = mitk::CreateImageStatisticsNode(m_statisticsContainer2, "testStatistics2"); standaloneDataStorage->Add(statisticsNode2); //--> test return (still) image statistics (!= statistics2) mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAgain; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAgain = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID(), m_statisticsContainer->GetUID()); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID() != m_statisticsContainer2->GetUID(), true); //--> test return image statistics 2 CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAgain = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image2.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID(), m_statisticsContainer2->GetUID()); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID() != m_statisticsContainer->GetUID(), true); //add another newer statistic: should return this newer one auto statisticsContainerNew = mitk::ImageStatisticsContainer::New(); + statisticsContainerNew->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(100)); + statisticsContainerNew->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(false)); CreateNodeRelationImage(statisticsContainerNew.GetPointer(), m_image.GetPointer()); auto statisticsNodeNew = mitk::CreateImageStatisticsNode(statisticsContainerNew, "testStatisticsNew"); standaloneDataStorage->Add(statisticsNodeNew); statisticsContainerNew->Modified(); mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageNew; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageNew = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageNew->GetUID(), statisticsContainerNew->GetUID()); CPPUNIT_ASSERT_EQUAL(statisticsWithImageNew->GetUID() != m_statisticsContainer->GetUID(), true); } void GetImageStatisticsWithImageNotConnected() { //create rules connection auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); standaloneDataStorage->Add(statisticsNode); //rule: (image-->statistics), 1 unconnected image --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImage; CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image2.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImage.IsNull(), true); //rule: (image-->statistics), 1 connected image + 1 unconnected mask --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndMask; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMask = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask.IsNull(), true); //rule: (image-->statistics), 1 connected image + 1 unconnected planar figure --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndPlanarFigure; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigure = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_planarFigure.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigure.IsNull(), true); } void GetImageStatisticsWithImageAndMaskConnected() { //create rules connection + add statistics to dataStorage auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); CreateNodeRelationMask(m_statisticsContainer.GetPointer(), m_mask.GetPointer()); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); standaloneDataStorage->Add(statisticsNode); //rule: (image-->statistics, mask-->statistics), 1 connected image, 1 connected mask --> test return statistics mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndMask; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMask = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask.IsNull(), false); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask->GetUID(), m_statisticsContainer->GetUID()); //new rule: (image-->statistics2) --> returns statistic because statistic2 has no mask connection CreateNodeRelationImage(m_statisticsContainer2.GetPointer(), m_image.GetPointer()); auto statisticsNode2 = mitk::CreateImageStatisticsNode(m_statisticsContainer2, "testStatistics2"); standaloneDataStorage->Add(statisticsNode2); mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndMaskAgain; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskAgain = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskAgain->GetUID(), m_statisticsContainer->GetUID()); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskAgain->GetUID() != m_statisticsContainer2->GetUID(), true); //add another newer statistic: should return this newer one auto statisticsContainerNew = mitk::ImageStatisticsContainer::New(); - + statisticsContainerNew->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(100)); + statisticsContainerNew->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(false)); mitk::PropertyRelations::RuleResultVectorType rules4; CreateNodeRelationImage(statisticsContainerNew.GetPointer(), m_image.GetPointer()); CreateNodeRelationMask(statisticsContainerNew.GetPointer(), m_mask.GetPointer()); auto statisticsNodeNew = mitk::CreateImageStatisticsNode(statisticsContainerNew, "testStatisticsNew"); standaloneDataStorage->Add(statisticsNodeNew); mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndMaskNew; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskNew = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsContainerNew->GetUID(), statisticsWithImageAndMaskNew->GetUID()); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskNew->GetUID() != m_statisticsContainer->GetUID(), true); } void GetImageStatisticsWithImageAndMaskNotConnected() { //create rules connection + add statistics to dataStorage auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); CreateNodeRelationMask(m_statisticsContainer.GetPointer(), m_mask.GetPointer()); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); standaloneDataStorage->Add(statisticsNode); //rule: (image-->statistics, mask-->statistics), 1 connected image --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImage; CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImage.IsNull(), true); //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 unconnected mask --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageNotConnectedAndMaskNotConnected; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageNotConnectedAndMaskNotConnected = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image2.GetPointer(), m_mask2.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageNotConnectedAndMaskNotConnected.IsNull(), true); //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 connected mask --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndMaskNotConnected; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskNotConnected = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image2.GetPointer(), m_mask.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskNotConnected.IsNull(), true); //rule: (image-->statistics, mask-->statistics), 1 connected image, 1 unconnected planarFigure --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageAndPlanarFigureNotConnected; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigureNotConnected = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image.GetPointer(), m_planarFigure.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigureNotConnected.IsNull(), true); //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 unconnected planarFigure --> test return nullptr mitk::ImageStatisticsContainer::ConstPointer statisticsWithImageNotConnectedAndPlanarFigureNotConnected; CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigureNotConnected = mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), m_image2.GetPointer(), m_planarFigure.GetPointer())); CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigureNotConnected.IsNull(), true); } void GetImageStatisticsInvalid() { CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); CPPUNIT_ASSERT_THROW(mitk::ImageStatisticsContainerManager::GetImageStatistics(nullptr, m_image.GetPointer()), mitk::Exception); auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); CPPUNIT_ASSERT_THROW(mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), nullptr), mitk::Exception); CPPUNIT_ASSERT_THROW(mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), nullptr, m_mask.GetPointer()), mitk::Exception); CPPUNIT_ASSERT_THROW(mitk::ImageStatisticsContainerManager::GetImageStatistics(standaloneDataStorage.GetPointer(), nullptr, m_planarFigure.GetPointer()), mitk::Exception); } }; MITK_TEST_SUITE_REGISTRATION(mitkImageStatisticsContainerManager) diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp index 4339973779..34f21704af 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp @@ -1,234 +1,240 @@ /*============================================================================ 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 #include namespace mitk { ImageStatisticsContainer::ImageStatisticsContainer() { this->Reset(); } // The order is derived from the old (<2018) image statistics plugin. const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::m_DefaultNames = {ImageStatisticsConstants::MEAN(), ImageStatisticsConstants::MEDIAN(), ImageStatisticsConstants::STANDARDDEVIATION(), ImageStatisticsConstants::RMS(), ImageStatisticsConstants::MAXIMUM(), ImageStatisticsConstants::MAXIMUMPOSITION(), ImageStatisticsConstants::MINIMUM(), ImageStatisticsConstants::MINIMUMPOSITION(), ImageStatisticsConstants::NUMBEROFVOXELS(), ImageStatisticsConstants::VOLUME(), ImageStatisticsConstants::SKEWNESS(), ImageStatisticsConstants::KURTOSIS(), ImageStatisticsConstants::UNIFORMITY(), ImageStatisticsConstants::ENTROPY(), ImageStatisticsConstants::MPP(), ImageStatisticsConstants::UPP()}; ImageStatisticsContainer::ImageStatisticsObject::ImageStatisticsObject() { Reset(); } void ImageStatisticsContainer::ImageStatisticsObject::AddStatistic(const std::string &key, StatisticsVariantType value) { m_Statistics.emplace(key, value); if (std::find(m_DefaultNames.cbegin(), m_DefaultNames.cend(), key) == m_DefaultNames.cend()) { if (std::find(m_CustomNames.cbegin(), m_CustomNames.cend(), key) == m_CustomNames.cend()) { m_CustomNames.emplace_back(key); } } } const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector & ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames() { return m_DefaultNames; } const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector & ImageStatisticsContainer::ImageStatisticsObject::GetCustomStatisticNames() const { return m_CustomNames; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::GetAllStatisticNames() const { StatisticNameVector names = GetDefaultStatisticNames(); names.insert(names.cend(), m_CustomNames.cbegin(), m_CustomNames.cend()); return names; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::GetExistingStatisticNames() const { StatisticNameVector names; std::transform(m_Statistics.begin(), m_Statistics.end(), std::back_inserter(names), [](const auto &pair) { return pair.first; }); return names; } bool ImageStatisticsContainer::ImageStatisticsObject::HasStatistic(const std::string &name) const { return m_Statistics.find(name) != m_Statistics.cend(); } ImageStatisticsContainer::StatisticsVariantType ImageStatisticsContainer::ImageStatisticsObject::GetValueNonConverted( const std::string &name) const { if (HasStatistic(name)) { return m_Statistics.find(name)->second; } else { mitkThrow() << "invalid statistic key, could not find"; } } void ImageStatisticsContainer::ImageStatisticsObject::Reset() { m_Statistics.clear(); m_CustomNames.clear(); } bool ImageStatisticsContainer::TimeStepExists(TimeStepType timeStep) const { return m_TimeStepMap.find(timeStep) != m_TimeStepMap.end(); } + const ImageStatisticsContainer::HistogramType* + ImageStatisticsContainer::GetHistogramForTimeStep(TimeStepType timeStep) const + { + return this->GetStatisticsForTimeStep(timeStep).m_Histogram; + } + const ImageStatisticsContainer::ImageStatisticsObject &ImageStatisticsContainer::GetStatisticsForTimeStep( TimeStepType timeStep) const { auto it = m_TimeStepMap.find(timeStep); if (it != m_TimeStepMap.end()) { return it->second; } mitkThrow() << "StatisticsObject for timeStep " << timeStep << " not found!"; } void ImageStatisticsContainer::SetStatisticsForTimeStep(TimeStepType timeStep, ImageStatisticsObject statistics) { if (timeStep < this->GetTimeSteps()) { m_TimeStepMap.emplace(timeStep, statistics); this->Modified(); } else { mitkThrow() << "Given timeStep " << timeStep << " out of timeStep geometry bounds. TimeSteps in geometry: " << this->GetTimeSteps(); } } void ImageStatisticsContainer::PrintSelf(std::ostream &os, itk::Indent indent) const { Superclass::PrintSelf(os, indent); for (unsigned int i = 0; i < this->GetTimeSteps(); i++) { auto statisticsValues = GetStatisticsForTimeStep(i); os << std::endl << indent << "Statistics instance for timeStep " << i << ":"; auto statisticKeys = statisticsValues.GetExistingStatisticNames(); os << std::endl << indent << "Number of entries: " << statisticKeys.size(); for (const auto &aKey : statisticKeys) { os << std::endl << indent.GetNextIndent() << aKey << ": " << statisticsValues.GetValueNonConverted(aKey); } } } unsigned int ImageStatisticsContainer::GetNumberOfTimeSteps() const { return this->GetTimeSteps(); } void ImageStatisticsContainer::Reset() { for (auto iter = m_TimeStepMap.begin(); iter != m_TimeStepMap.end(); iter++) { iter->second.Reset(); } } itk::LightObject::Pointer ImageStatisticsContainer::InternalClone() const { itk::LightObject::Pointer ioPtr = Superclass::InternalClone(); Self::Pointer rval = dynamic_cast(ioPtr.GetPointer()); if (rval.IsNull()) { itkExceptionMacro(<< "downcast to type " << "StatisticsContainer" << " failed."); } rval->SetTimeStepMap(m_TimeStepMap); rval->SetTimeGeometry(this->GetTimeGeometry()->Clone()); return ioPtr; } void ImageStatisticsContainer::SetTimeStepMap(TimeStepMapType map) { m_TimeStepMap = map; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames( const ImageStatisticsContainer *container) { ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector names = ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames(); if (container) { std::set customKeys; for (unsigned int i = 0; i < container->GetTimeSteps(); i++) { auto statisticKeys = container->GetStatisticsForTimeStep(i).GetCustomStatisticNames(); customKeys.insert(statisticKeys.cbegin(), statisticKeys.cend()); } names.insert(names.cend(), customKeys.cbegin(), customKeys.cend()); } return names; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames( std::vector containers) { ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector names = ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames(); std::set customKeys; for (auto container : containers) { for (unsigned int i = 0; i < container->GetTimeSteps(); i++) { if(container->TimeStepExists(i)) { auto statisticKeys = container->GetStatisticsForTimeStep(i).GetCustomStatisticNames(); customKeys.insert(statisticKeys.cbegin(), statisticKeys.cend()); } } } names.insert(names.end(), customKeys.begin(), customKeys.end()); return names; }; } // namespace mitk diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.h b/Modules/ImageStatistics/mitkImageStatisticsContainer.h index f9a3842d04..2bceefc675 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainer.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainer.h @@ -1,162 +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. ============================================================================*/ #ifndef MITKIMAGESTATISTICSCONTAINER #define MITKIMAGESTATISTICSCONTAINER #include #include #include #include #include namespace mitk { + /** @brief Container class for storing a StatisticsObject for each timestep. Stored statistics are: - for the defined statistics, see GetAllStatisticNames - Histogram of Pixel Values */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer : public mitk::BaseData { public: mitkClassMacro(ImageStatisticsContainer, mitk::BaseData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); using HistogramType = itk::Statistics::Histogram; using RealType = double; using LabelIndex = unsigned int; using IndexType = vnl_vector; using VoxelCountType = unsigned long; using StatisticsVariantType = boost::variant; using StatisticsMapType = std::map < std::string, StatisticsVariantType>; using StatisticsKeyType = std::string; void SetRequestedRegionToLargestPossibleRegion() override {} bool RequestedRegionIsOutsideOfTheBufferedRegion() override { return false; } bool VerifyRequestedRegion() override { return true; } void SetRequestedRegion(const itk::DataObject*) override {} /** @brief Container class for storing the computed image statistics. @details The statistics are stored in a map with value as boost::variant. The type used to create the boost::variant is important as only this type can be recovered lateron. */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsObject { public: ImageStatisticsObject(); /** @brief Adds a statistic to the statistics object @details if already a statistic with that name is included, it is overwritten */ void AddStatistic(const std::string& key, StatisticsVariantType value); using StatisticNameVector = std::vector; /** @brief Returns the names of the default statistics @details The order is derived from the image statistics plugin. */ static const StatisticNameVector& GetDefaultStatisticNames(); /** @brief Returns the names of all custom statistics (defined at runtime and no default names). */ const StatisticNameVector& GetCustomStatisticNames() const; /** @brief Returns the names of all statistics (default and custom defined) Additional custom keys are added at the end in a sorted order. */ StatisticNameVector GetAllStatisticNames() const; StatisticNameVector GetExistingStatisticNames() const; bool HasStatistic(const std::string& name) const; /** @brief Converts the requested value to the defined type @param name defined string on creation (AddStatistic) @exception if no statistics with key name was found. */ template TType GetValueConverted(const std::string& name) const { auto value = GetValueNonConverted(name); return boost::get(value); } /** @brief Returns the requested value @exception if no statistics with key name was found. */ StatisticsVariantType GetValueNonConverted(const std::string& name) const; void Reset(); HistogramType::ConstPointer m_Histogram=nullptr; private: StatisticsMapType m_Statistics; StatisticNameVector m_CustomNames; static const StatisticNameVector m_DefaultNames; }; using TimeStepMapType = std::map; unsigned int GetNumberOfTimeSteps() const; /** @brief Deletes all stored values*/ void Reset(); /** @brief Returns the statisticObject for the given Timestep @pre timeStep must be valid */ const ImageStatisticsObject& GetStatisticsForTimeStep(TimeStepType timeStep) const; /** @brief Sets the statisticObject for the given Timestep @pre timeStep must be valid */ void SetStatisticsForTimeStep(TimeStepType timeStep, ImageStatisticsObject statistics); /** @brief Checks if the Time step exists @pre timeStep must be valid */ bool TimeStepExists(TimeStepType timeStep) const; + /** + /brief Returns the histogram of the passed time step. + @pre timeStep must be valid*/ + const HistogramType* GetHistogramForTimeStep(TimeStepType timeStep) const; + protected: ImageStatisticsContainer(); void PrintSelf(std::ostream &os, itk::Indent indent) const override; private: itk::LightObject::Pointer InternalClone() const override; void SetTimeStepMap(TimeStepMapType map); TimeStepMapType m_TimeStepMap; }; MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames(const ImageStatisticsContainer* container); MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames(std::vector containers); - } #endif // MITKIMAGESTATISTICSCONTAINER diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp index 2710c49a63..8465e18579 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -1,90 +1,130 @@ /*============================================================================ 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 "mitkImageStatisticsContainerManager.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateNot.h" +#include "mitkNodePredicateFunction.h" +#include "mitkNodePredicateDataProperty.h" +#include "mitkProperties.h" + #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" -mitk::ImageStatisticsContainer::ConstPointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask) +mitk::ImageStatisticsContainer::Pointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) +{ + auto node = GetImageStatisticsNode(dataStorage, image, mask, ignoreZeroVoxel, histogramNBins, onlyIfUpToDate, noWIP); + + mitk::ImageStatisticsContainer::Pointer result; + + if (node.IsNotNull()) + { + result = dynamic_cast(node->GetData()); + } + + return result; +} + +mitk::DataNode::Pointer mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) { if (!dataStorage) { mitkThrow() << "data storage is nullptr!"; } if (!image) { mitkThrow() << "Image is nullptr"; } - mitk::NodePredicateBase::ConstPointer predicate = GetPredicateForSources(image, mask);; + mitk::NodePredicateBase::ConstPointer predicate = GetStatisticsPredicateForSources(image, mask); - if (predicate) { - auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New(ImageStatisticsContainer::GetStaticNameOfClass()); - predicate = mitk::NodePredicateAnd::New(predicate, nodePredicateImageStatisticsContainer); + mitk::DataNode::Pointer result; - auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); - mitk::DataStorage::SetOfObjects::Pointer statisticContainerCandidateNodesFiltered; + if (predicate) + { + auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(histogramNBins)); + auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(ignoreZeroVoxel)); - statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); - for (const auto& node : *statisticContainerCandidateNodes) { - statisticContainerCandidateNodesFiltered->push_back(node); + predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); + + if (noWIP) + { + auto noWIPPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); + predicate = mitk::NodePredicateAnd::New(predicate, noWIPPredicate).GetPointer(); } - if (statisticContainerCandidateNodesFiltered->empty()) { - return nullptr; + auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); + + auto statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); + + for (const auto& node : *statisticContainerCandidateNodes) + { + auto isUpToDate = image->GetMTime() < node->GetData()->GetMTime() + && (mask == nullptr || mask->GetMTime() < node->GetData()->GetMTime()); + + if (!onlyIfUpToDate || isUpToDate) + { + statisticContainerCandidateNodesFiltered->push_back(node); + } } - auto newestElement = statisticContainerCandidateNodesFiltered->front(); - if (statisticContainerCandidateNodesFiltered->size() > 1) { + if (!statisticContainerCandidateNodesFiltered->empty()) + { + auto newestElement = statisticContainerCandidateNodesFiltered->front(); + if (statisticContainerCandidateNodesFiltered->size() > 1) + { //in case of multiple found statistics, return only newest one auto newestIter = std::max_element(std::begin(*statisticContainerCandidateNodesFiltered), std::end(*statisticContainerCandidateNodesFiltered), [](mitk::DataNode::Pointer a, mitk::DataNode::Pointer b) { return a->GetData()->GetMTime() < b->GetData()->GetMTime(); }); newestElement = *newestIter; - MITK_WARN << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; - for (const auto& node : *statisticContainerCandidateNodesFiltered) { + MITK_DEBUG << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; + for (const auto& node : *statisticContainerCandidateNodesFiltered) + { MITK_DEBUG << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); } } - return dynamic_cast(newestElement->GetData()); - } - else { - return nullptr; + result = newestElement; + } } + + return result; } -mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::GetPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask) +mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask) { if (!image) { mitkThrow() << "Image is nullptr"; } + auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New(ImageStatisticsContainer::GetStaticNameOfClass()); + auto imageRule = mitk::StatisticsToImageRelationRule::New(); - mitk::NodePredicateBase::ConstPointer predicate = imageRule->GetSourcesDetector(image); + auto imagePredicate = imageRule->GetSourcesDetector(image); + + mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(nodePredicateImageStatisticsContainer, imagePredicate).GetPointer(); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); if (mask) { auto maskPredicate = maskRule->GetSourcesDetector(mask); - predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); + predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } else { auto maskPredicate = mitk::NodePredicateNot::New(maskRule->GetConnectedSourcesDetector()); - predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); + predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } return predicate; } diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h index 403724fbe5..cab786019e 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h @@ -1,45 +1,60 @@ /*============================================================================ 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 QmitkImageStatisticsContainerManager_H__INCLUDED #define QmitkImageStatisticsContainerManager_H__INCLUDED #include "MitkImageStatisticsExports.h" #include #include #include #include #include #include namespace mitk { + + static const std::string STATS_HISTOGRAM_BIN_PROPERTY_NAME = "MITK.statistic.histogram_bins"; + static const std::string STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME = "MITK.statistic.ignore_zero_voxel"; + static const std::string STATS_GENERATION_STATUS_PROPERTY_NAME = "MITK.statistic.generation.status"; + static const std::string STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS = "workInProgress"; + static const std::string STATS_GENERATION_STATUS_VALUE_PENDING = "pending"; + static const std::string STATS_GENERATION_STATUS_VALUE_BASE_DATA_FAILED = "failed"; + /** \brief Returns the StatisticsContainer that was computed on given input (image/mask/planar figure) and is added as DataNode in a DataStorage */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainerManager { public: /**Documentation - @brief Returns the StatisticContainer for the given image and mask - @return a valid StatisticsContainer or nullptr if no StatisticContainer is found or no rules have been defined + @brief Returns the StatisticsContainer for the given image and mask from the storage- + @return a valid StatisticsContainer or nullptr if no StatisticsContainer is found. @details if more than one StatisticsContainer is found, only the newest (ModifiedTime) is returned @pre Datastorage must point to a valid instance. @pre image must Point to a valid instance. + @param onlyIfUpToDate Indicates if results should only be returned if the are up to date, thus not older then image and ROI. + @param noWIP If noWIP is true, the function only returns valid final result and not just its placeholder (WIP). + If noWIP equals false it might also return a WIP, thus the valid result is currently processed/ordered but might not be ready yet. + @param ignoreZeroVoxel indicates the wanted statistics are calculated with or w/o zero voxels. + @param histogramNBins Number of bins the statistics should have that are searched for. */ - static mitk::ImageStatisticsContainer::ConstPointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr); + static mitk::ImageStatisticsContainer::Pointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); + static mitk::DataNode::Pointer GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask = nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); - protected: - static mitk::NodePredicateBase::ConstPointer GetPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask = nullptr); + /** Returns the predicate that can be used to search for statistic containers of + the given image (and mask) in the passed data storage.*/ + static mitk::NodePredicateBase::ConstPointer GetStatisticsPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask = nullptr); }; } #endif diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp index 422abf6fb2..2663d77380 100644 --- a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp @@ -1,554 +1,512 @@ /*============================================================================ 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 #include #include #include "mitkImageAccessByItk.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { void PlanarFigureMaskGenerator::SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure) { if ( planarFigure.IsNull() ) { throw std::runtime_error( "Error: planar figure empty!" ); } const PlaneGeometry *planarFigurePlaneGeometry = planarFigure->GetPlaneGeometry(); if ( planarFigurePlaneGeometry == nullptr ) { throw std::runtime_error( "Planar-Figure not yet initialized!" ); } const auto *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); if ( planarFigureGeometry == nullptr ) { throw std::runtime_error( "Non-planar planar figures not supported!" ); } if (planarFigure != m_PlanarFigure) { this->Modified(); m_PlanarFigure = planarFigure; } } mitk::Image::ConstPointer PlanarFigureMaskGenerator::GetReferenceImage() { if (IsUpdateRequired()) { this->CalculateMask(); } return m_ReferenceImage; } template < typename TPixel, unsigned int VImageDimension > void PlanarFigureMaskGenerator::InternalCalculateMaskFromPlanarFigure( const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) { typedef itk::Image< unsigned short, 2 > MaskImage2DType; typename MaskImage2DType::Pointer maskImage = MaskImage2DType::New(); maskImage->SetOrigin(image->GetOrigin()); maskImage->SetSpacing(image->GetSpacing()); maskImage->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); maskImage->SetBufferedRegion(image->GetBufferedRegion()); maskImage->SetDirection(image->GetDirection()); maskImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); maskImage->Allocate(); maskImage->FillBuffer(1); // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. // These points are used by the vtkLassoStencilSource to create // a vtkImageStencil. const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); const mitk::BaseGeometry *imageGeometry3D = m_inputImage->GetGeometry( 0 ); // If there is a second poly line in a closed planar figure, treat it as a hole. PlanarFigure::PolyLineType planarFigureHolePolyline; if (m_PlanarFigure->GetPolyLinesSize() == 2) planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); // Determine x- and y-dimensions depending on principal axis // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis int i0, i1; switch ( axis ) { case 0: i0 = 1; i1 = 2; break; case 1: i0 = 0; i1 = 2; break; case 2: default: i0 = 0; i1 = 1; break; } // store the polyline contour as vtkPoints object - bool outOfBounds = false; vtkSmartPointer points = vtkSmartPointer::New(); - typename PlanarFigure::PolyLineType::const_iterator it; - for ( it = planarFigurePolyline.begin(); - it != planarFigurePolyline.end(); - ++it ) + for (const auto& point : planarFigurePolyline) { Point3D point3D; - // Convert 2D point back to the local index coordinates of the selected - // image - // Fabian: From PlaneGeometry documentation: - // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). - // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. - planarFigurePlaneGeometry->Map( *it, point3D ); + // Convert 2D point back to the local index coordinates of the selected image + planarFigurePlaneGeometry->Map(point, point3D); + imageGeometry3D->WorldToIndex(point3D, point3D); - // Polygons (partially) outside of the image bounds can not be processed - // further due to a bug in vtkPolyDataToImageStencil - if ( !imageGeometry3D->IsInside( point3D ) ) - { - outOfBounds = true; - } - - imageGeometry3D->WorldToIndex( point3D, point3D ); - - points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); + points->InsertNextPoint(point3D[i0], point3D[i1], 0); } - vtkSmartPointer holePoints = nullptr; + vtkSmartPointer holePoints; if (!planarFigureHolePolyline.empty()) { holePoints = vtkSmartPointer::New(); - Point3D point3D; - PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); - for (it = planarFigureHolePolyline.begin(); it != end; ++it) + for (const auto& point : planarFigureHolePolyline) { - // Fabian: same as above - planarFigurePlaneGeometry->Map(*it, point3D); + planarFigurePlaneGeometry->Map(point, point3D); imageGeometry3D->WorldToIndex(point3D, point3D); holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); } } // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero - double bounds[6] = {0, 0, 0, 0, 0, 0}; - points->GetBounds( bounds ); + double bounds[6] = {0}; + points->GetBounds(bounds); bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent - if ( m_PlanarFigure->IsClosed() && - ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) + if (m_PlanarFigure->IsClosed() && ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) { mitkThrow() << "Figure has a zero area and cannot be used for masking."; } - if ( outOfBounds ) - { - throw std::runtime_error( "Figure at least partially outside of image bounds!" ); - } - // create a vtkLassoStencilSource and set the points of the Polygon vtkSmartPointer lassoStencil = vtkSmartPointer::New(); lassoStencil->SetShapeToPolygon(); - lassoStencil->SetPoints( points ); + lassoStencil->SetPoints(points); vtkSmartPointer holeLassoStencil = nullptr; if (holePoints.GetPointer() != nullptr) { holeLassoStencil = vtkSmartPointer::New(); holeLassoStencil->SetShapeToPolygon(); holeLassoStencil->SetPoints(holePoints); } // Export from ITK to VTK (to use a VTK filter) typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; typename ImageExportType::Pointer itkExporter = ImageExportType::New(); itkExporter->SetInput( maskImage ); // itkExporter->SetInput( castFilter->GetOutput() ); vtkSmartPointer vtkImporter = vtkSmartPointer::New(); this->ConnectPipelines( itkExporter, vtkImporter ); // Apply the generated image stencil to the input image vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); imageStencilFilter->ReverseStencilOff(); imageStencilFilter->SetBackgroundValue( 0 ); imageStencilFilter->Update(); vtkSmartPointer holeStencilFilter = nullptr; if (holeLassoStencil.GetPointer() != nullptr) { holeStencilFilter = vtkSmartPointer::New(); holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); holeStencilFilter->ReverseStencilOn(); holeStencilFilter->SetBackgroundValue(0); holeStencilFilter->Update(); } // Export from VTK back to ITK vtkSmartPointer vtkExporter = vtkSmartPointer::New(); vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr ? imageStencilFilter->GetOutputPort() : holeStencilFilter->GetOutputPort()); vtkExporter->Update(); typename ImageImportType::Pointer itkImporter = ImageImportType::New(); this->ConnectPipelines( vtkExporter, itkImporter ); itkImporter->Update(); typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage( itkImporter->GetOutput() ); duplicator->Update(); // Store mask m_InternalITKImageMask2D = duplicator->GetOutput(); } template < typename TPixel, unsigned int VImageDimension > void PlanarFigureMaskGenerator::InternalCalculateMaskFromOpenPlanarFigure( const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) { typedef itk::Image< unsigned short, 2 > MaskImage2DType; typedef itk::LineIterator< MaskImage2DType > LineIteratorType; typedef MaskImage2DType::IndexType IndexType2D; typedef std::vector< IndexType2D > IndexVecType; typename MaskImage2DType::Pointer maskImage = MaskImage2DType::New(); maskImage->SetOrigin(image->GetOrigin()); maskImage->SetSpacing(image->GetSpacing()); maskImage->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); maskImage->SetBufferedRegion(image->GetBufferedRegion()); maskImage->SetDirection(image->GetDirection()); maskImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); maskImage->Allocate(); maskImage->FillBuffer(0); // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); const mitk::BaseGeometry *imageGeometry3D = m_inputImage->GetGeometry( 0 ); // Determine x- and y-dimensions depending on principal axis // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis int i0, i1; switch ( axis ) { case 0: i0 = 1; i1 = 2; break; case 1: i0 = 0; i1 = 2; break; case 2: default: i0 = 0; i1 = 1; break; } int numPolyLines = m_PlanarFigure->GetPolyLinesSize(); for ( int lineId = 0; lineId < numPolyLines; ++lineId ) { // store the polyline contour as vtkPoints object - bool outOfBounds = false; IndexVecType pointIndices; - typename PlanarFigure::PolyLineType::const_iterator it; - for ( it = planarFigurePolyline.begin(); - it != planarFigurePolyline.end(); - ++it ) + for(const auto& point : planarFigurePolyline) { Point3D point3D; - planarFigurePlaneGeometry->Map( *it, point3D ); - - if ( !imageGeometry3D->IsInside( point3D ) ) - { - outOfBounds = true; - } - - imageGeometry3D->WorldToIndex( point3D, point3D ); + planarFigurePlaneGeometry->Map(point, point3D); + imageGeometry3D->WorldToIndex(point3D, point3D); IndexType2D index2D; index2D[0] = point3D[i0]; index2D[1] = point3D[i1]; pointIndices.push_back( index2D ); } - if ( outOfBounds ) + size_t numLineSegments = pointIndices.size() - 1; + for (size_t i = 0; i < numLineSegments; ++i) { - throw std::runtime_error( "Figure at least partially outside of image bounds!" ); - } - - for ( IndexVecType::const_iterator it = pointIndices.begin(); it != pointIndices.end()-1; ++it ) - { - IndexType2D ind1 = *it; - IndexType2D ind2 = *(it+1); - - LineIteratorType lineIt( maskImage, ind1, ind2 ); - while ( !lineIt.IsAtEnd() ) + LineIteratorType lineIt(maskImage, pointIndices[i], pointIndices[i+1]); + while (!lineIt.IsAtEnd()) { - lineIt.Set( 1 ); + lineIt.Set(1); ++lineIt; } } } // Store mask m_InternalITKImageMask2D = maskImage; } bool PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(const PlaneGeometry* planarGeometry, const BaseGeometry *geometry) { if (!planarGeometry) return false; if (!geometry) return false; unsigned int axis; return GetPrincipalAxis(geometry,planarGeometry->GetNormal(), axis); } bool PlanarFigureMaskGenerator::GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, unsigned int &axis ) { vector.Normalize(); for ( unsigned int i = 0; i < 3; ++i ) { Vector3D axisVector = geometry->GetAxisVector( i ); axisVector.Normalize(); //normal mitk::eps is to pedantic for this check. See e.g. T27122 //therefore choose a larger epsilon. The value was set a) as small as //possible but b) still allowing to datasets like in (T27122) to pass //when floating rounding errors sum up. const double epsilon = 5e-5; if ( fabs( fabs( axisVector * vector ) - 1.0) < epsilon) { axis = i; return true; } } return false; } void PlanarFigureMaskGenerator::CalculateMask() { if (m_inputImage.IsNull()) { MITK_ERROR << "Image is not set."; } if (m_PlanarFigure.IsNull()) { MITK_ERROR << "PlanarFigure is not set."; } if (m_TimeStep != 0) { MITK_WARN << "Multiple TimeSteps are not supported in PlanarFigureMaskGenerator (yet)."; } const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); if ( imageGeometry == nullptr ) { throw std::runtime_error( "Image geometry invalid!" ); } if (m_inputImage->GetTimeSteps() > 0) { mitk::ImageTimeSelector::Pointer imgTimeSel = mitk::ImageTimeSelector::New(); imgTimeSel->SetInput(m_inputImage); imgTimeSel->SetTimeNr(m_TimeStep); imgTimeSel->UpdateLargestPossibleRegion(); m_InternalTimeSliceImage = imgTimeSel->GetOutput(); } else { m_InternalTimeSliceImage = m_inputImage; } m_InternalITKImageMask2D = nullptr; const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const auto *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); //const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); // Find principal direction of PlanarFigure in input image unsigned int axis; if ( !this->GetPrincipalAxis( imageGeometry, planarFigureGeometry->GetNormal(), axis ) ) { throw std::runtime_error( "Non-aligned planar figures not supported!" ); } m_PlanarFigureAxis = axis; // Find slice number corresponding to PlanarFigure in input image itk::Image< unsigned short, 3 >::IndexType index; imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); unsigned int slice = index[axis]; m_PlanarFigureSlice = slice; // extract image slice which corresponds to the planarFigure and store it in m_InternalImageSlice mitk::Image::ConstPointer inputImageSlice = extract2DImageSlice(axis, slice); //mitk::IOUtil::Save(inputImageSlice, "/home/fabian/inputSliceImage.nrrd"); // Compute mask from PlanarFigure // rastering for open planar figure: if ( !m_PlanarFigure->IsClosed() ) { AccessFixedDimensionByItk_1(inputImageSlice, InternalCalculateMaskFromOpenPlanarFigure, 2, axis) } else//for closed planar figure { AccessFixedDimensionByItk_1(inputImageSlice, InternalCalculateMaskFromPlanarFigure, 2, axis) } //convert itk mask to mitk::Image::Pointer and return it mitk::Image::Pointer planarFigureMaskImage; planarFigureMaskImage = mitk::GrabItkImageMemory(m_InternalITKImageMask2D); //mitk::IOUtil::Save(planarFigureMaskImage, "/home/fabian/planarFigureMaskImage.nrrd"); //Convert2Dto3DImageFilter::Pointer sliceTo3DImageConverter = Convert2Dto3DImageFilter::New(); //sliceTo3DImageConverter->SetInput(planarFigureMaskImage); //sliceTo3DImageConverter->Update(); //mitk::IOUtil::Save(sliceTo3DImageConverter->GetOutput(), "/home/fabian/3DsliceImage.nrrd"); m_ReferenceImage = inputImageSlice; //mitk::IOUtil::Save(m_ReferenceImage, "/home/fabian/referenceImage.nrrd"); m_InternalMask = planarFigureMaskImage; } void PlanarFigureMaskGenerator::SetTimeStep(unsigned int timeStep) { if (timeStep != m_TimeStep) { m_TimeStep = timeStep; } } mitk::Image::Pointer PlanarFigureMaskGenerator::GetMask() { if (IsUpdateRequired()) { this->CalculateMask(); this->Modified(); } m_InternalMaskUpdateTime = this->GetMTime(); return m_InternalMask; } mitk::Image::ConstPointer PlanarFigureMaskGenerator::extract2DImageSlice(unsigned int axis, unsigned int slice) { // Extract slice with given position and direction from image unsigned int dimension = m_InternalTimeSliceImage->GetDimension(); if (dimension == 3) { ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); imageExtractor->SetInput( m_InternalTimeSliceImage ); imageExtractor->SetSliceDimension( axis ); imageExtractor->SetSliceIndex( slice ); imageExtractor->Update(); return imageExtractor->GetOutput(); } else if(dimension == 2) { return m_InternalTimeSliceImage; } else { MITK_ERROR << "Unsupported image dimension. Dimension is: " << dimension << ". Only 2D and 3D images are supported."; return nullptr; } } bool PlanarFigureMaskGenerator::IsUpdateRequired() const { unsigned long thisClassTimeStamp = this->GetMTime(); unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); unsigned long planarFigureTimeStamp = m_PlanarFigure->GetMTime(); unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed { return true; } if (m_InternalMaskUpdateTime < planarFigureTimeStamp || m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class { return true; } if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class { return true; } return false; } } diff --git a/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h index e2a2cbd736..750801df0d 100644 --- a/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h +++ b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h @@ -1,32 +1,32 @@ /*============================================================================ 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 mitkStatisticsToImageRelationRule_h #define mitkStatisticsToImageRelationRule_h #include #include "mitkGenericIDRelationRule.h" namespace mitk { class MITKIMAGESTATISTICS_EXPORT StatisticsToImageRelationRule : public mitk::GenericIDRelationRule { public: - mitkClassMacroItkParent(StatisticsToImageRelationRule, mitk::GenericIDRelationRule); + mitkClassMacro(StatisticsToImageRelationRule, mitk::GenericIDRelationRule); itkNewMacro(Self); protected: StatisticsToImageRelationRule(); }; } #endif diff --git a/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h index b1b86d66b2..4516f21043 100644 --- a/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h +++ b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h @@ -1,32 +1,32 @@ /*============================================================================ 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 mitkStatisticsToMaskRelationRule_h #define mitkStatisticsToMaskRelationRule_h #include #include "mitkGenericIDRelationRule.h" namespace mitk { class MITKIMAGESTATISTICS_EXPORT StatisticsToMaskRelationRule : public mitk::GenericIDRelationRule { public: - mitkClassMacroItkParent(StatisticsToMaskRelationRule, mitk::GenericIDRelationRule); + mitkClassMacro(StatisticsToMaskRelationRule, mitk::GenericIDRelationRule); itkNewMacro(Self); protected: StatisticsToMaskRelationRule(); }; } #endif diff --git a/Modules/ImageStatisticsUI/CMakeLists.txt b/Modules/ImageStatisticsUI/CMakeLists.txt index 30b2a1de22..9aefacf838 100644 --- a/Modules/ImageStatisticsUI/CMakeLists.txt +++ b/Modules/ImageStatisticsUI/CMakeLists.txt @@ -1,4 +1,8 @@ MITK_CREATE_MODULE( INCLUDE_DIRS Qmitk DEPENDS MitkCore MitkChart MitkImageStatistics MitkQtWidgets -) \ No newline at end of file +) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp new file mode 100644 index 0000000000..8bc428671a --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp @@ -0,0 +1,51 @@ +/*============================================================================ + +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 "QmitkDataGenerationJobBase.h" + + +std::string QmitkDataGenerationJobBase::GetLastErrorMessage() const +{ + return m_LastErrorMessage; +} + +bool QmitkDataGenerationJobBase::GetComputationSuccessFlag() const +{ + return m_ComputationSuccessful; +} + +void QmitkDataGenerationJobBase::run() +{ + try + { + m_ComputationSuccessful = this->RunComputation(); + if (m_ComputationSuccessful) + { + emit ResultsAvailable(this->GetResults(), this); + } + else + { + emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + } + } + catch (const std::exception& e) + { + m_LastErrorMessage = e.what(); + emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + + } + catch (...) + { + m_LastErrorMessage = "Unknown exception"; + emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + } +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h new file mode 100644 index 0000000000..a9f1f61401 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -0,0 +1,74 @@ +/*============================================================================ + +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 QmitkDataGenerationJobBase_h +#define QmitkDataGenerationJobBase_h + + +//QT +#include +#include +#include + +//MITK +#include + +#include + +/*! +\brief QmitkDataGenerationJobBase +Base class for generation jobs used by QmitkDataGenerationBase and derived classes. +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGenerationJobBase : public QObject, public QRunnable +{ + Q_OBJECT + +public: + /** Result map that indicates all results generated by the job. + The key is a job specific label for the results.*/ + using ResultMapType = std::map; + + virtual ResultMapType GetResults() const = 0; + + /** Calls RunComputation() and takes care of the error handling and result signalling.*/ + void run() final; + + /*! + /brief Returns a flag the indicates if the job computation was successfull.*/ + bool GetComputationSuccessFlag() const; + + std::string GetLastErrorMessage() const; + +signals: + void Error(QString err, const QmitkDataGenerationJobBase* job); + /*! @brief Signal is emitted when results are available. + @param results produced by the job and ready to be used. + @param the job that produced the data + */ + void ResultsAvailable(ResultMapType results, const QmitkDataGenerationJobBase* job); + +protected: + QmitkDataGenerationJobBase() = default; + + virtual ~QmitkDataGenerationJobBase() = default; + + /**Does the real computation. Returns true if there where results produced.*/ + virtual bool RunComputation() = 0; + + std::string m_LastErrorMessage; + +private: + bool m_ComputationSuccessful = false; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp new file mode 100644 index 0000000000..f32be6e324 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -0,0 +1,275 @@ +/*============================================================================ + +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 "QmitkDataGeneratorBase.h" + +#include "QmitkDataGenerationJobBase.h" +#include "mitkDataNode.h" +#include "mitkProperties.h" +#include "mitkImageStatisticsContainerManager.h" + +#include + +QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent) +{ + this->SetDataStorage(storage); +} + +QmitkDataGeneratorBase::QmitkDataGeneratorBase(QObject* parent): QObject(parent) +{} + +QmitkDataGeneratorBase::~QmitkDataGeneratorBase() +{ + auto dataStorage = m_Storage.Lock(); + if (dataStorage.IsNotNull()) + { + // remove "change node listener" from data storage + dataStorage->ChangedNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } +} + +mitk::DataStorage::Pointer QmitkDataGeneratorBase::GetDataStorage() const +{ + return m_Storage.Lock(); +} + +bool QmitkDataGeneratorBase::GetAutoUpdate() const +{ + return m_AutoUpdate; +} + +bool QmitkDataGeneratorBase::IsGenerating() const +{ + return m_WIP; +} + +void QmitkDataGeneratorBase::SetDataStorage(mitk::DataStorage* storage) +{ + if (storage == m_Storage) return; + + std::lock_guard mutexguard(m_DataMutex); + + auto oldStorage = m_Storage.Lock(); + if (oldStorage.IsNotNull()) + { + // remove "change node listener" from old data storage + oldStorage->ChangedNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } + + m_Storage = storage; + + auto newStorage = m_Storage.Lock(); + + if (newStorage.IsNotNull()) + { + // add change node listener for new data storage + newStorage->ChangedNodeEvent.AddListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } +} + +void QmitkDataGeneratorBase::SetAutoUpdate(bool autoUpdate) +{ + m_AutoUpdate = autoUpdate; +} + +void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const +{ + emit JobError(error, failedJob); +} + +void QmitkDataGeneratorBase::OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const +{ + auto resultnodes = mitk::DataStorage::SetOfObjects::New(); + + for (auto pos : results) + { + resultnodes->push_back(this->PrepareResultForStorage(pos.first, pos.second, job)); + } + + { + std::lock_guard mutexguard(m_DataMutex); + auto storage = m_Storage.Lock(); + if (storage.IsNotNull()) + { + m_AddingToStorage = true; + for (auto pos = resultnodes->Begin(); pos != resultnodes->End(); ++pos) + { + storage->Add(pos->Value()); + } + m_AddingToStorage = false; + } + } + + emit NewDataAvailable(resultnodes.GetPointer()); + + if (!resultnodes->empty()) + { + this->EnsureRecheckingAndGeneration(); + } +} + +void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) +{ + if (!m_AddingToStorage) + { + if (this->ChangedNodeIsRelevant(node)) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +void QmitkDataGeneratorBase::EnsureRecheckingAndGeneration() const +{ + m_RestartGeneration = true; + if (!m_InGenerate) + { + this->Generate(); + } +} + +bool QmitkDataGeneratorBase::Generate() const +{ + bool everythingValid = false; + if (m_InGenerate) + { + m_RestartGeneration = true; + } + else + { + m_InGenerate = true; + m_RestartGeneration = true; + while (m_RestartGeneration) + { + m_RestartGeneration = false; + everythingValid = DoGenerate(); + } + + m_InGenerate = false; + } + return everythingValid; +} + +mitk::DataNode::Pointer QmitkDataGeneratorBase::CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName) +{ + if (!dataDummy) { + mitkThrow() << "data is nullptr"; + } + + auto interimResultNode = mitk::DataNode::New(); + interimResultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + dataDummy->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_PENDING)); + interimResultNode->SetVisibility(false); + interimResultNode->SetData(dataDummy); + if (!nodeName.empty()) + { + interimResultNode->SetName(nodeName); + } + return interimResultNode; +} + + +QmitkDataGeneratorBase::InputPairVectorType QmitkDataGeneratorBase::FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const +{ + std::lock_guard mutexguard(m_DataMutex); + + InputPairVectorType filteredImageROICombinations; + + auto storage = m_Storage.Lock(); + if (storage.IsNotNull()) + { + for (auto inputPair : imageROICombinations) + { + if (storage->Exists(inputPair.first) && (inputPair.second.IsNull() || storage->Exists(inputPair.second))) + { + filteredImageROICombinations.emplace_back(inputPair); + } + else + { + MITK_DEBUG << "Ignore pair because at least one of the nodes is not in storage. Pair: " << GetPairDescription(inputPair); + } + } + } + return filteredImageROICombinations; +} + +std::string QmitkDataGeneratorBase::GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const +{ + if (imageAndSeg.second.IsNotNull()) + { + return imageAndSeg.first->GetName() + " and ROI " + imageAndSeg.second->GetName(); + } + else + { + return imageAndSeg.first->GetName(); + } +} + +bool QmitkDataGeneratorBase::DoGenerate() const +{ + auto filteredImageROICombinations = FilterImageROICombinations(this->GetAllImageROICombinations()); + + QThreadPool* threadPool = QThreadPool::globalInstance(); + + bool everythingValid = true; + + for (const auto& imageAndSeg : filteredImageROICombinations) + { + MITK_DEBUG << "checking node " << GetPairDescription(imageAndSeg); + + if (!this->IsValidResultAvailable(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer())) + { + this->IndicateFutureResults(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); + + if (everythingValid) + { + m_WIP = true; + everythingValid = false; + } + + MITK_DEBUG << "No valid result available. Requesting next necessary job." << imageAndSeg.first->GetName(); + auto nextJob = this->GetNextMissingGenerationJob(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); + + //other jobs are pending, nothing has to be done + if (nextJob.first==nullptr && nextJob.second.IsNotNull()) + { + MITK_DEBUG << "Last generation job still running, pass on till job is finished..."; + } + else if(nextJob.first != nullptr && nextJob.second.IsNotNull()) + { + MITK_DEBUG << "Next generation job started..."; + nextJob.first->setAutoDelete(true); + nextJob.second->GetData()->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS)); + connect(nextJob.first, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError, Qt::BlockingQueuedConnection); + connect(nextJob.first, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable, Qt::BlockingQueuedConnection); + emit DataGenerationStarted(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer(), nextJob.first); + threadPool->start(nextJob.first); + } + } + else + { + this->RemoveObsoleteDataNodes(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); + } + } + + if (everythingValid && m_WIP) + { + m_WIP = false; + emit GenerationFinished(); + } + + return everythingValid; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h new file mode 100644 index 0000000000..f3988f0060 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -0,0 +1,174 @@ +/*============================================================================ + +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 QmitkDataGeneratorBase_h +#define QmitkDataGeneratorBase_h + +#include + +//QT +#include + +//MITK +#include + +#include "QmitkDataGenerationJobBase.h" + +#include + +/*! +\brief QmitkDataGeneratorBase +BaseClass that implements the organisation of (statistic) data generation for pairs of images and ROIs. +The key idea is that this class ensures that for vector of given image ROI pairs (defined by derived classes) +a result instance (e.g ImageStatisticsContainer) will be calculated, if needed (e.g. because it is missing or +not uptodate anymore), and stored in the data storage passed to a generator instance. While derived classes i.a. +specify how to generate the image ROI pairs, how to detect latest results, what the next generation step is and +how to remove obsolete data from the storage, the base class takes care of the observation of the data storage +and orchestrates the whole checking and generation workflow. +In all the generation/orchestration process the data storage, passed to the generator, 1) serves as place where the final +results are stored and searched and 2) it resembles the state of the genertion process with these final results and WIP +place holder nodes that indicate planed or currently processed generation steps. +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGeneratorBase : public QObject +{ + Q_OBJECT +public: + QmitkDataGeneratorBase(const QmitkDataGeneratorBase& other) = delete; + QmitkDataGeneratorBase& operator=(const QmitkDataGeneratorBase& other) = delete; + + virtual ~QmitkDataGeneratorBase(); + + using JobResultMapType = QmitkDataGenerationJobBase::ResultMapType; + + mitk::DataStorage::Pointer GetDataStorage() const; + + /** Indicates if the generator may trigger the update automatically (true). Reasons for an update are: + - Input data has been changed or modified + - Generation relevant settings in derived classes have been changed (must be implemented in derived classes) + */ + bool GetAutoUpdate() const; + + /** Indicates if there is currently work in progress, thus data generation jobs are running or pending. + It is set to true when GenerationStarted is triggered and becomes false as soon as GenerationFinished is triggered. + */ + bool IsGenerating() const; + + /** Checks data validity and triggers generation of data, if needed. + The generation itselfs will be done with a thread pool and is orchestrated by this class. To learn if the threads are finished and + everything is uptodate, listen to the signal GenerationFinished. + @return indicates if everything is already valid (true) or if the generation of new data was triggerd (false).*/ + bool Generate() const; + + /** Indicates if for a given image and ROI a valid final result is available.*/ + virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + +public slots: + /** Sets the data storage the generator should monitor and where WIP placeholder nodes and final result nodes should be stored.*/ + void SetDataStorage(mitk::DataStorage* storage); + + void SetAutoUpdate(bool autoUpdate); + +protected slots: + /** Used by QmitkDataGenerationJobBase to signal the generator that an error occured. */ + void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; + /** Used by QmitkDataGenerationJobBase to signal and communicate the results of there computation. */ + void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; + +signals: + + /*! @brief Signal that is emitted if a data generation job is started to generat outdated/inexistant data. + */ + void DataGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job) const; + + /*! @brief Signal that is emitted if new final data is produced. + */ + void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; + + /*! @brief Signal that is emitted if all jobs are finished and everything is up to date. + */ + void GenerationFinished() const; + + /*! @brief Signal that is emitted in case of job errors. + */ + void JobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; + +protected: + /*! @brief Constructor + @param storage the data storage where all produced data should be stored + */ + QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr); + QmitkDataGeneratorBase(QObject* parent = nullptr); + + using InputPairVectorType = std::vector>; + + /** This method must be implemented by derived to indicate if a changed node is relevant and therefore if an update must be triggered.*/ + virtual bool ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const = 0; + /** This method must be impemented by derived classes to return the pairs of images and ROIs + (ROI may be null if no ROI is needed) for which data are needed.*/ + virtual InputPairVectorType GetAllImageROICombinations() const = 0; + /** This method should indicate all missing and outdated (interim) results in the data storage, with new placeholder nodes and WIP dummy data + added to the storage. The placeholder nodes will be replaced by the real results as soon as they are ready. + The strategy how to detact which placeholder node is need and how the dummy data should look like must be implemented by derived classes.*/ + virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + /*! @brief Is called to generate the next job instance that needs to be done and is associated dummy node + in order to progress the data generation workflow. + @remark The method can assume that the caller takes care of the job instance deletion. + @return std::pair of job pointer and placeholder node associated with the job. Following combinations are possible: + - Both are null: nothing to do; + - Both are set: there is something to do for a pending dumme node -> trigger computation; + - Job null and node set: a job for this node is already work in progress -> pass on till its finished.*/ + virtual std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; + /** Remove all obsolete data nodes for the given image and ROI node from the data storage. + Obsolete nodes are (interim) result nodes that are not the most recent any more.*/ + virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + /** Prepares result to be added to the storage in an appropriate way and returns the data node for that.*/ + virtual mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const = 0; + + /*! Creates a data node for WIP place holder results. It can be used by IndicateFutureResults().*/ + static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); + + /** Filters a passed pair vector. The returned pair vector only contains pair of nodes that exist in the data storage.*/ + InputPairVectorType FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const; + + /** Return a descriptive label of a passed pair. Used e.g. for some debug log messages.*/ + std::string GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const; + + /** Internal part of the generation strategy. Here is where the heavy lifting is done.*/ + bool DoGenerate() const; + + /** Methods either directly calls generation or if its allready onging flags to restart the generation.*/ + void EnsureRecheckingAndGeneration() const; + + mitk::WeakPointer m_Storage; + + bool m_AutoUpdate = false; + + mutable std::mutex m_DataMutex; + +private: + /** Indicates if we are currently in the Generation() verification and generation of pending jobs triggering loop. + Only needed for the internal logic.*/ + mutable bool m_InGenerate = false; + /** Internal flag that is set if a generation was requested, while one generation loop was already ongoing.*/ + mutable bool m_RestartGeneration = false; + /** Indicates if there are still jobs pending or computing (true) or if everything is valid (false).*/ + mutable bool m_WIP = false; + /** Internal flag that indicates that generator is currently in the process of adding results to the storage*/ + mutable bool m_AddingToStorage = false; + + /**Member is called when a node is added to the storage.*/ + void NodeAddedOrModified(const mitk::DataNode* node); + + unsigned long m_DataStorageDeletedTag; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp new file mode 100644 index 0000000000..6b6c95dfd1 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp @@ -0,0 +1,103 @@ +/*============================================================================ + +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 "QmitkImageAndRoiDataGeneratorBase.h" + +QmitkImageAndRoiDataGeneratorBase::NodeVectorType +QmitkImageAndRoiDataGeneratorBase::GetROINodes() const +{ + return m_ROINodes; +} + +QmitkImageAndRoiDataGeneratorBase::NodeVectorType +QmitkImageAndRoiDataGeneratorBase::GetImageNodes() const +{ + return m_ImageNodes; +} + +void +QmitkImageAndRoiDataGeneratorBase::SetImageNodes(const NodeVectorType& imageNodes) +{ + if (m_ImageNodes != imageNodes) + { + { + std::lock_guard mutexguard(m_DataMutex); + m_ImageNodes = imageNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +void +QmitkImageAndRoiDataGeneratorBase::SetROINodes(const NodeVectorType& roiNodes) +{ + if (m_ROINodes != roiNodes) + { + { + std::lock_guard mutexguard(m_DataMutex); + m_ROINodes = roiNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +bool +QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const +{ + if (m_AutoUpdate) + { + auto finding = std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); + if (finding != m_ImageNodes.end()) + { + return true; + } + + finding = std::find(m_ROINodes.begin(), m_ROINodes.end(), changedNode); + if (finding != m_ROINodes.end()) + { + return true; + } + } + + return false; +} + +QmitkImageAndRoiDataGeneratorBase::InputPairVectorType +QmitkImageAndRoiDataGeneratorBase::GetAllImageROICombinations() const +{ + std::lock_guard mutexguard(m_DataMutex); + + InputPairVectorType allCombinations; + for (const auto& imageNode : m_ImageNodes) + { + if (m_ROINodes.empty()) + { + allCombinations.emplace_back(imageNode, nullptr); + } + else + { + for (const auto& roiNode : m_ROINodes) + { + allCombinations.emplace_back(imageNode, roiNode); + } + } + } + return allCombinations; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h new file mode 100644 index 0000000000..164023b752 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h @@ -0,0 +1,58 @@ +/*============================================================================ + +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 QmitkImageAndRoiDataGeneratorBase_h +#define QmitkImageAndRoiDataGeneratorBase_h + +#include "QmitkDataGeneratorBase.h" +#include + +/*! +Base class that can be used for generators that should alow the image nodes and the ROI nodes as vectors (like generated by node selection widgets). +This class ensures that data for every combination of images and ROIs (basicly a folding) will be processed. +@sa QmitkDataGeneratorBase +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkImageAndRoiDataGeneratorBase : public QmitkDataGeneratorBase +{ +public: + using Superclass = QmitkDataGeneratorBase; + + using NodeVectorType = std::vector; + + NodeVectorType GetImageNodes() const; + NodeVectorType GetROINodes() const; + +public slots: + /*! @brief Setter for image nodes + */ + void SetImageNodes(const NodeVectorType& imageNodes); + /*! @brief Setter for roi nodes + */ + void SetROINodes(const NodeVectorType& roiNodes); + +protected: + QmitkImageAndRoiDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkDataGeneratorBase(storage, parent) {}; + QmitkImageAndRoiDataGeneratorBase(QObject* parent = nullptr) : QmitkDataGeneratorBase(parent) {}; + + using InputPairVectorType = Superclass::InputPairVectorType; + + bool ChangedNodeIsRelevant(const mitk::DataNode *changedNode) const override; + InputPairVectorType GetAllImageROICombinations() const override; + + NodeVectorType m_ImageNodes; + NodeVectorType m_ROINodes; + + QmitkImageAndRoiDataGeneratorBase(const QmitkImageAndRoiDataGeneratorBase&) = delete; + QmitkImageAndRoiDataGeneratorBase& operator = (const QmitkImageAndRoiDataGeneratorBase&) = delete; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp index 69af24f548..51850b06ed 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp @@ -1,226 +1,186 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsCalculationRunnable.h" #include "mitkImageStatisticsCalculator.h" #include #include #include +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkImageStatisticsContainerManager.h" +#include "mitkProperties.h" QmitkImageStatisticsCalculationRunnable::QmitkImageStatisticsCalculationRunnable() - : QObject(), QRunnable() + : QmitkDataGenerationJobBase() , m_StatisticsImage(nullptr) , m_BinaryMask(nullptr) , m_PlanarFigureMask(nullptr) , m_IgnoreZeros(false) , m_HistogramNBins(100) - , m_CalculationSuccessful(false) { } QmitkImageStatisticsCalculationRunnable::~QmitkImageStatisticsCalculationRunnable() { } void QmitkImageStatisticsCalculationRunnable::Initialize(const mitk::Image *image, const mitk::Image *binaryImage, const mitk::PlanarFigure *planarFig) { this->m_StatisticsImage = image; this->m_BinaryMask = binaryImage; this->m_PlanarFigureMask = planarFig; } mitk::ImageStatisticsContainer* QmitkImageStatisticsCalculationRunnable::GetStatisticsData() const { return this->m_StatisticsContainer.GetPointer(); } const mitk::Image* QmitkImageStatisticsCalculationRunnable::GetStatisticsImage() const { return this->m_StatisticsImage.GetPointer(); } const mitk::Image* QmitkImageStatisticsCalculationRunnable::GetMaskImage() const { return this->m_BinaryMask.GetPointer(); } const mitk::PlanarFigure* QmitkImageStatisticsCalculationRunnable::GetPlanarFigure() const { return this->m_PlanarFigureMask.GetPointer(); } void QmitkImageStatisticsCalculationRunnable::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationRunnable::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationRunnable::SetHistogramNBins(unsigned int nbins) { this->m_HistogramNBins = nbins; } unsigned int QmitkImageStatisticsCalculationRunnable::GetHistogramNBins() const { return this->m_HistogramNBins; } -std::string QmitkImageStatisticsCalculationRunnable::GetLastErrorMessage() const +QmitkDataGenerationJobBase::ResultMapType QmitkImageStatisticsCalculationRunnable::GetResults() const { - return m_message; + ResultMapType result; + result.emplace("statistics", this->GetStatisticsData()); + return result; } -const QmitkImageStatisticsCalculationRunnable::HistogramType* -QmitkImageStatisticsCalculationRunnable::GetTimeStepHistogram(unsigned int t) const -{ - if (t >= this->m_HistogramVector.size()) - return nullptr; - - return this->m_HistogramVector.at(t).GetPointer(); -} - -bool QmitkImageStatisticsCalculationRunnable::GetStatisticsUpdateSuccessFlag() const -{ - return m_CalculationSuccessful; -} - -void QmitkImageStatisticsCalculationRunnable::run() +bool QmitkImageStatisticsCalculationRunnable::RunComputation() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if (this->m_StatisticsImage.IsNotNull()) { calculator->SetInputImage(m_StatisticsImage); } else { statisticCalculationSuccessful = false; } // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality // of the masked and input image differ, we need to catch them and mark the calculation as failed // the same holds for the ::SetPlanarFigure() try { if (this->m_BinaryMask.IsNotNull()) { mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); imgMask->SetImageMask(m_BinaryMask->Clone()); calculator->SetMask(imgMask.GetPointer()); } if (this->m_PlanarFigureMask.IsNotNull()) { mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); pfMaskGen->SetInputImage(m_StatisticsImage); pfMaskGen->SetPlanarFigure(m_PlanarFigureMask->Clone()); calculator->SetMask(pfMaskGen.GetPointer()); } } - catch (const mitk::Exception& e) - { - MITK_ERROR << "MITK Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch (const itk::ExceptionObject& e) - { - MITK_ERROR << "ITK Exception:" << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch (const std::runtime_error &e) - { - MITK_ERROR << "Runtime Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } catch (const std::exception &e) { - MITK_ERROR << "Standard Exception: " << e.what(); - m_message = e.what(); + MITK_ERROR << "Error while configuring the statistics calculator: " << e.what(); + m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } if (this->m_IgnoreZeros) { mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); ignorePixelValueMaskGen->SetIgnoredPixelValue(0); ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); } else { calculator->SetSecondaryMask(nullptr); } calculator->SetNBinsForHistogramStatistics(m_HistogramNBins); try { calculator->GetStatistics(); } - catch (mitk::Exception& e) - { - m_message = e.GetDescription(); - MITK_ERROR << "MITK Exception: " << e.what(); - statisticCalculationSuccessful = false; - } - catch (const std::runtime_error &e) - { - m_message = "Failure: " + std::string(e.what()); - MITK_ERROR << "Runtime Exception: " << e.what(); - statisticCalculationSuccessful = false; - } catch (const std::exception &e) { - m_message = "Failure: " + std::string(e.what()); - MITK_ERROR << "Standard Exception: " << e.what(); + m_LastErrorMessage = "Failure while calculating the statistics: " + std::string(e.what()); + MITK_ERROR << m_LastErrorMessage; statisticCalculationSuccessful = false; } - this->m_CalculationSuccessful = statisticCalculationSuccessful; - if (statisticCalculationSuccessful) { m_StatisticsContainer = calculator->GetStatistics(); - this->m_HistogramVector.clear(); - for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + imageRule->Connect(m_StatisticsContainer, m_StatisticsImage); + + if (nullptr != m_PlanarFigureMask) { - HistogramType::ConstPointer tempHistogram; - try { - if (calculator->GetStatistics()->TimeStepExists(i)) - { - tempHistogram = calculator->GetStatistics()->GetStatisticsForTimeStep(i).m_Histogram; - this->m_HistogramVector.push_back(tempHistogram); - } - } - catch (mitk::Exception&) { - MITK_WARN << ":-("; - } + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + maskRule->Connect(m_StatisticsContainer, m_PlanarFigureMask); + } + if (nullptr != m_BinaryMask) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + maskRule->Connect(m_StatisticsContainer, m_BinaryMask); } + + m_StatisticsContainer->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); + m_StatisticsContainer->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeros)); } - emit finished(); + return statisticCalculationSuccessful; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h index d46d8e28c9..53c0bc182f 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h @@ -1,101 +1,86 @@ /*============================================================================ 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 QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED -#define QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED - -//QT headers -#include -#include -#include +#ifndef QmitkImageStatisticsCalculationRunnable_h +#define QmitkImageStatisticsCalculationRunnable_h //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsContainer.h" -#include + +#include "QmitkDataGenerationJobBase.h" // itk headers #ifndef __itkHistogram_h #include #endif +#include /** * /brief This class is executed as background thread for image statistics calculation. * * This class is derived from QRunnable and is intended to be used by QmitkImageStatisticsView * to run the image statistics calculation in a background thread keeping the GUI usable. */ -class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsCalculationRunnable : public QObject, public QRunnable +class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsCalculationRunnable : public QmitkDataGenerationJobBase { Q_OBJECT public: typedef itk::Statistics::Histogram HistogramType; /*! /brief standard constructor. */ QmitkImageStatisticsCalculationRunnable(); /*! /brief standard destructor. */ ~QmitkImageStatisticsCalculationRunnable(); /*! /brief Initializes the object with necessary data. */ void Initialize(const mitk::Image* image, const mitk::Image* binaryImage, const mitk::PlanarFigure* planarFig); /*! /brief returns the calculated image statistics. */ mitk::ImageStatisticsContainer* GetStatisticsData() const; const mitk::Image* GetStatisticsImage() const; const mitk::Image* GetMaskImage() const; const mitk::PlanarFigure* GetPlanarFigure() const; /*! /brief Set flag to ignore zero valued voxels */ void SetIgnoreZeroValueVoxel(bool _arg); /*! /brief Get status of zero value voxel ignoring. */ bool GetIgnoreZeroValueVoxel() const; /*! /brief Set bin size for histogram resolution.*/ void SetHistogramNBins(unsigned int nbins); /*! /brief Get bin size for histogram resolution.*/ unsigned int GetHistogramNBins() const; - /*! - /brief Returns the histogram of the currently selected time step. */ - const HistogramType* GetTimeStepHistogram(unsigned int t = 0) const; - /*! - /brief Returns a flag the indicates if the statistics are updated successfully */ - bool GetStatisticsUpdateSuccessFlag() const; - /*! - /brief Method called once the thread is executed. */ - void run() override; + ResultMapType GetResults() const override; - std::string GetLastErrorMessage() const; -signals: - void finished(); +protected: + bool RunComputation() override; private: mitk::Image::ConstPointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. mitk::Image::ConstPointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. mitk::PlanarFigure::ConstPointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. mitk::ImageStatisticsContainer::Pointer m_StatisticsContainer; bool m_IgnoreZeros; ///< member variable holds flag to indicate if zero valued voxel should be suppressed unsigned int m_HistogramNBins; ///< member variable holds the bin size for histogram resolution. - bool m_CalculationSuccessful; ///< flag set if statistics calculation was successful - std::vector m_HistogramVector; ///< member holds the histograms of all time steps. - std::string m_message; }; #endif // QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp new file mode 100644 index 0000000000..70a8da970b --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -0,0 +1,273 @@ +/*============================================================================ + +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 "QmitkImageStatisticsDataGenerator.h" + +#include "mitkImageStatisticsContainer.h" +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkNodePredicateFunction.h" +#include "mitkNodePredicateAnd.h" +#include "mitkNodePredicateNot.h" +#include "mitkNodePredicateDataProperty.h" +#include "mitkProperties.h" +#include "mitkImageStatisticsContainerManager.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + +void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + m_IgnoreZeroValueVoxel = _arg; + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + m_HistogramNBins = nbins; + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const +{ + return this->m_HistogramNBins; +} + +bool QmitkImageStatisticsDataGenerator::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const +{ + auto result = QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(changedNode); + + if (!result) + { + if (changedNode->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()) == nullptr) + { + auto stats = dynamic_cast(changedNode->GetData()); + result = stats != nullptr; + } + } + return result; +} + +bool QmitkImageStatisticsDataGenerator::IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + auto resultNode = this->GetLatestResult(imageNode, roiNode, true, true); + return resultNode.IsNotNull(); +} + +mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate, bool noWIP) const +{ + auto storage = m_Storage.Lock(); + + if (imageNode == nullptr || !imageNode->GetData()) + { + mitkThrow() << "Image is nullptr"; + } + + const auto image = imageNode->GetData(); + const mitk::BaseData* mask = nullptr; + if (roiNode) + { + mask = roiNode->GetData(); + } + + std::lock_guard mutexguard(m_DataMutex); + return mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(storage, image, mask, m_IgnoreZeroValueVoxel, m_HistogramNBins, onlyIfUpToDate, noWIP); +} + +void QmitkImageStatisticsDataGenerator::IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + if (imageNode == nullptr || !imageNode->GetData()) + { + mitkThrow() << "Image node is nullptr"; + } + + auto image = dynamic_cast(imageNode->GetData()); + if (!image) + { + mitkThrow() << "Image node date is nullptr or no image."; + } + + const mitk::BaseData* mask = nullptr; + if (roiNode != nullptr) + { + mask = roiNode->GetData(); + } + + auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); + if (resultDataNode.IsNull()) + { + auto dummyStats = mitk::ImageStatisticsContainer::New(); + + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + imageRule->Connect(dummyStats, image); + + if (nullptr != mask) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + maskRule->Connect(dummyStats, mask); + } + + dummyStats->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); + dummyStats->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); + + auto dummyNode = CreateWIPDataNode(dummyStats, "WIP_"+GenerateStatisticsNodeName(image, mask)); + + auto storage = m_Storage.Lock(); + if (storage != nullptr) + { + std::lock_guard mutexguard(m_DataMutex); + storage->Add(dummyNode); + } + } +} + +std::pair QmitkImageStatisticsDataGenerator::GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); + + std::string status; + if (resultDataNode.IsNull() || (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_PENDING)) + { + if (imageNode == nullptr || !imageNode->GetData()) + { + mitkThrow() << "Image node is nullptr"; + } + + auto image = dynamic_cast(imageNode->GetData()); + if (image == nullptr) + { + mitkThrow() << "Image node date is nullptr or no image."; + } + + const mitk::Image* mask = nullptr; + const mitk::PlanarFigure* planar = nullptr; + if (roiNode != nullptr) + { + mask = dynamic_cast(roiNode->GetData()); + planar = dynamic_cast(roiNode->GetData()); + } + + auto newJob = new QmitkImageStatisticsCalculationRunnable; + + newJob->Initialize(image, mask, planar); + newJob->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); + newJob->SetHistogramNBins(m_HistogramNBins); + + return std::pair(newJob, resultDataNode.GetPointer()); + } + else if (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS) + { + return std::pair(nullptr, resultDataNode.GetPointer()); + } + return std::pair(nullptr, nullptr); +} + +void QmitkImageStatisticsDataGenerator::RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + if (imageNode == nullptr || !imageNode->GetData()) + { + mitkThrow() << "Image is nullptr"; + } + + const auto image = imageNode->GetData(); + const mitk::BaseData* mask = nullptr; + if (roiNode != nullptr) + { + mask = roiNode->GetData(); + } + + auto lastResult = this->GetLatestResult(imageNode, roiNode, false, false); + + auto rulePredicate = mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(image, mask); + auto notLatestPredicate = mitk::NodePredicateFunction::New([lastResult](const mitk::DataNode* node) { return node != lastResult; }); + auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); + auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); + + mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(rulePredicate, notLatestPredicate).GetPointer(); + predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); + + auto storage = m_Storage.Lock(); + if (storage != nullptr) + { + std::lock_guard mutexguard(m_DataMutex); + + auto oldStatisticContainerNodes = storage->GetSubset(predicate); + storage->Remove(oldStatisticContainerNodes); + } +} + +mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::PrepareResultForStorage(const std::string& /*label*/, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const +{ + auto statsJob = dynamic_cast(job); + + if (statsJob != nullptr) + { + auto resultNode = mitk::DataNode::New(); + resultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + resultNode->SetVisibility(false); + resultNode->SetData(result); + + const mitk::BaseData* roi = statsJob->GetMaskImage(); + if (roi == nullptr) + { + roi = statsJob->GetPlanarFigure(); + } + resultNode->SetName(this->GenerateStatisticsNodeName(statsJob->GetStatisticsImage(), roi)); + + return resultNode; + } + + return nullptr; +} + +std::string QmitkImageStatisticsDataGenerator::GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const +{ + std::stringstream statisticsNodeName; + statisticsNodeName << "statistics_bins-" << m_HistogramNBins <<"_"; + + if (m_IgnoreZeroValueVoxel) + { + statisticsNodeName << "noZeros_"; + } + + if (image == nullptr) + { + mitkThrow() << "Image is nullptr"; + } + + statisticsNodeName << image->GetUID(); + + if (roi != nullptr) + { + statisticsNodeName << "_" + roi->GetUID(); + } + + return statisticsNodeName.str(); +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h new file mode 100644 index 0000000000..4d3766e246 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.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 QmitkImageStatisticsDataGenerator_h +#define QmitkImageStatisticsDataGenerator_h + +#include "QmitkImageAndRoiDataGeneratorBase.h" + +#include + +/** +Generates ImageStatisticContainers by using QmitkImageStatisticsCalculationRunnables for each pair if image and ROIs and ensures their +validity. +It also encodes the HistogramNBins and IgnoreZeroValueVoxel as properties to the results as these settings are important criteria for +discreminating statistics results. +For more details of how the generation is done see QmitkDataGenerationBase. +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsDataGenerator : public QmitkImageAndRoiDataGeneratorBase +{ +public: + QmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(storage, parent) {}; + QmitkImageStatisticsDataGenerator(QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(parent) {}; + + bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; + + /** Returns the latest result for a given image and ROI and the current settings of the generator. + @param onlyIfUpToDate Indicates if results should only be returned if the are up to date, thus not older then image and ROI. + @param noWIP If noWIP is true, the function only returns valid final result and not just its placeholder (WIP). + If noWIP equals false it might also return a WIP, thus the valid result is currently processed/ordered but might not be ready yet.*/ + mitk::DataNode::Pointer GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate = false, bool noWIP = true) const; + + std::string GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const; + + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + +protected: + bool ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const; + void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; + std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; + void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; + mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const; + + QmitkImageStatisticsDataGenerator(const QmitkImageStatisticsDataGenerator&) = delete; + QmitkImageStatisticsDataGenerator& operator = (const QmitkImageStatisticsDataGenerator&) = delete; + + bool m_IgnoreZeroValueVoxel = false; + unsigned int m_HistogramNBins = 100; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp index 6c30c1bf76..3ee475c4e8 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp @@ -1,119 +1,132 @@ /*============================================================================ 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 "QmitkImageStatisticsTreeItem.h" QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem( ImageStatisticsObject statisticsData, StatisticNameVector statisticNames, - QVariant label, + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parent) - : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent) + : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent), m_IsWIP(isWIP) { } QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, QVariant label, + bool isWIP, QmitkImageStatisticsTreeItem *parentItem) - : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, parentItem ) + : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, isWIP, parentItem ) { } - QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), nullptr ) {} + QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), false, nullptr ) {} QmitkImageStatisticsTreeItem::~QmitkImageStatisticsTreeItem() { qDeleteAll(m_childItems); } void QmitkImageStatisticsTreeItem::appendChild(QmitkImageStatisticsTreeItem *item) { m_childItems.append(item); } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::child(int row) { return m_childItems.value(row); } int QmitkImageStatisticsTreeItem::childCount() const { return m_childItems.count(); } int QmitkImageStatisticsTreeItem::columnCount() const { return m_statisticNames.size() + 1; } struct StatValueVisitor : boost::static_visitor { QVariant operator()(const mitk::ImageStatisticsContainer::RealType& val) const { return QVariant(val); } QVariant operator()(const mitk::ImageStatisticsContainer::VoxelCountType& val) const { return QVariant::fromValue(val); } QVariant operator()(const mitk::ImageStatisticsContainer::IndexType& val) const { std::stringstream ss; ss << val; return QVariant(QString::fromStdString(ss.str())); } }; QVariant QmitkImageStatisticsTreeItem::data(int column) const { QVariant result; if (column > 0 && !m_statisticNames.empty()) { if (column - 1 < static_cast(m_statisticNames.size())) { - auto statisticKey = m_statisticNames.at(column - 1); - if (m_statistics.HasStatistic(statisticKey)) + if (m_IsWIP) { - return boost::apply_visitor(StatValueVisitor(), m_statistics.GetValueNonConverted(statisticKey)); + result = QVariant(QString("...")); } else { - return QVariant(); + auto statisticKey = m_statisticNames.at(column - 1); + if (m_statistics.HasStatistic(statisticKey)) + { + return boost::apply_visitor(StatValueVisitor(), m_statistics.GetValueNonConverted(statisticKey)); + } + else + { + return QVariant(); + } } } else { return QVariant(); } } else if (column == 0) { result = m_label; } return result; } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::parentItem() { return m_parentItem; } int QmitkImageStatisticsTreeItem::row() const { if (m_parentItem) return m_parentItem->m_childItems.indexOf(const_cast(this)); return 0; } + +bool QmitkImageStatisticsTreeItem::isWIP() const +{ + return m_IsWIP; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h index 055cb54991..76c982b934 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h @@ -1,54 +1,59 @@ /*============================================================================ 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 QmitkImageStatisticsTreeItem_h #define QmitkImageStatisticsTreeItem_h #include #include #include "mitkImageStatisticsContainer.h" /*! \class QmitkImageStatisticsTreeItem An item that represents an entry (usually ImageStatisticsObject) for the QmitkImageStatisticsTreeModel */ class QmitkImageStatisticsTreeItem { public: using ImageStatisticsObject = mitk::ImageStatisticsContainer::ImageStatisticsObject; using StatisticNameVector = mitk::ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector; QmitkImageStatisticsTreeItem(); explicit QmitkImageStatisticsTreeItem(ImageStatisticsObject statisticsData, - StatisticNameVector statisticNames, QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + StatisticNameVector statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); explicit QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, - QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); ~QmitkImageStatisticsTreeItem(); void appendChild(QmitkImageStatisticsTreeItem *child); QmitkImageStatisticsTreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; int row() const; QmitkImageStatisticsTreeItem *parentItem(); + /**indicates that the statistic container owned by this instance is only a dummy + WIP containter and the calculation of the up-to-date statistic is not yet finished.**/ + bool isWIP() const; + private: ImageStatisticsObject m_statistics; StatisticNameVector m_statisticNames; QVariant m_label; QmitkImageStatisticsTreeItem *m_parentItem = nullptr; QList m_childItems; + bool m_IsWIP; }; #endif // QmitkImageStatisticsTreeItem_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp index 033598353c..f0b184289b 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp @@ -1,383 +1,425 @@ /*============================================================================ 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 "QmitkImageStatisticsTreeModel.h" #include "QmitkImageStatisticsTreeItem.h" #include "itkMutexLockHolder.h" #include "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" +#include "QmitkStyleManager.h" + QmitkImageStatisticsTreeModel::QmitkImageStatisticsTreeModel(QObject *parent) : QmitkAbstractDataStorageModel(parent) { m_RootItem = new QmitkImageStatisticsTreeItem(); } QmitkImageStatisticsTreeModel ::~QmitkImageStatisticsTreeModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); delete m_RootItem; }; void QmitkImageStatisticsTreeModel::DataStorageChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodePredicateChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } int QmitkImageStatisticsTreeModel::columnCount(const QModelIndex& /*parent*/) const { int columns = m_StatisticNames.size() + 1; return columns; } int QmitkImageStatisticsTreeModel::rowCount(const QModelIndex &parent) const { QmitkImageStatisticsTreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } QVariant QmitkImageStatisticsTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - if (role != Qt::DisplayRole) - return QVariant(); - - QmitkImageStatisticsTreeItem *item = static_cast(index.internalPointer()); + QmitkImageStatisticsTreeItem* item = static_cast(index.internalPointer()); - return item->data(index.column()); + if (role == Qt::DisplayRole) + { + return item->data(index.column()); + } + else if (role == Qt::DecorationRole && index.column() == 0 && item->isWIP() && item->childCount()==0) + { + return QVariant(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/hourglass-half-solid.svg"))); + } + return QVariant(); } QModelIndex QmitkImageStatisticsTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); QmitkImageStatisticsTreeItem *parentItem; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); QmitkImageStatisticsTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkImageStatisticsTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkImageStatisticsTreeItem *childItem = static_cast(child.internalPointer()); QmitkImageStatisticsTreeItem *parentItem = childItem->parentItem(); if (parentItem == m_RootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } Qt::ItemFlags QmitkImageStatisticsTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return QAbstractItemModel::flags(index); } QVariant QmitkImageStatisticsTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (section == 0) { return m_HeaderFirstColumn; } else { return QVariant(m_StatisticNames.at(section - 1).c_str()); } } return QVariant(); } void QmitkImageStatisticsTreeModel::SetImageNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedImageNodes = std::move(tempNodes); m_ImageNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::SetMaskNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); // special case: apply one mask to each timestep of an 4D image if (timeSteps == 1 && m_TimeStepResolvedImageNodes.size() > 1) { timeSteps = m_TimeStepResolvedImageNodes.size(); } for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedMaskNodes = std::move(tempNodes); m_MaskNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::Clear() { emit beginResetModel(); m_Statistics.clear(); m_ImageNodes.clear(); m_TimeStepResolvedImageNodes.clear(); m_MaskNodes.clear(); m_StatisticNames.clear(); emit endResetModel(); emit modelChanged(); } +void QmitkImageStatisticsTreeModel::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + emit beginResetModel(); + m_IgnoreZeroValueVoxel = _arg; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +bool QmitkImageStatisticsTreeModel::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsTreeModel::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + emit beginResetModel(); + m_HistogramNBins = nbins; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +unsigned int QmitkImageStatisticsTreeModel::GetHistogramNBins() const +{ + return this->m_HistogramNBins; +} + void QmitkImageStatisticsTreeModel::UpdateByDataStorage() { StatisticsContainerVector newStatistics; auto datamanager = m_DataStorage.Lock(); if (datamanager.IsNotNull()) { for (const auto &image : m_ImageNodes) { if (m_MaskNodes.empty()) { - auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData()); + auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), nullptr, m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } else { for (const auto &mask : m_MaskNodes) { auto stats = - mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData()); + mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData(), m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } } } if (!newStatistics.empty()) { emit dataAvailable(); } } { itk::MutexLockHolder locked(m_Mutex); m_Statistics = newStatistics; } m_StatisticNames = mitk::GetAllStatisticNames(m_Statistics); BuildHierarchicalModel(); } void QmitkImageStatisticsTreeModel::BuildHierarchicalModel() { // reset old model delete m_RootItem; m_RootItem = new QmitkImageStatisticsTreeItem(); bool hasMask = false; bool hasMultipleTimesteps = false; std::map dataNodeToTreeItem; for (auto statistic : m_Statistics) { + bool isWIP = statistic->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()).IsNotNull(); // get the connected image data node/mask data node auto imageRule = mitk::StatisticsToImageRelationRule::New(); auto imageOfStatisticsPredicate = imageRule->GetDestinationsDetector(statistic); auto imageFinding = std::find_if(m_ImageNodes.begin(), m_ImageNodes.end(), [&imageOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return imageOfStatisticsPredicate->CheckNode(testNode); }); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); auto maskOfStatisticsPredicate = maskRule->GetDestinationsDetector(statistic); auto maskFinding = std::find_if(m_MaskNodes.begin(), m_MaskNodes.end(), [&maskOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return maskOfStatisticsPredicate->CheckNode(testNode); }); if (imageFinding == m_ImageNodes.end()) { mitkThrow() << "no image found connected to statistic" << statistic << " Aborting."; } auto& image = *imageFinding; // image: 1. hierarchy level QmitkImageStatisticsTreeItem *imageItem = nullptr; auto search = dataNodeToTreeItem.find(image); // the tree item was created previously if (search != dataNodeToTreeItem.end()) { imageItem = search->second; } // create the tree item else { QString imageLabel = QString::fromStdString(image->GetName()); if (statistic->GetTimeSteps() == 1 && maskFinding == m_MaskNodes.end()) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem); } else { - imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, m_RootItem); } m_RootItem->appendChild(imageItem); dataNodeToTreeItem.emplace(image, imageItem); } // mask: 2. hierarchy level (optional, only if mask exists) QmitkImageStatisticsTreeItem *lastParent = nullptr; if (maskFinding != m_MaskNodes.end()) { auto& mask = *maskFinding; QString maskLabel = QString::fromStdString(mask->GetName()); QmitkImageStatisticsTreeItem *maskItem; // add statistical values directly in this hierarchy level if (statistic->GetTimeSteps() == 1) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, isWIP, imageItem); } else { - maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, imageItem); } imageItem->appendChild(maskItem); lastParent = maskItem; hasMask = true; } else { lastParent = imageItem; } // 3. hierarchy level (optional, only if >1 timestep) if (statistic->GetTimeSteps() > 1) { for (unsigned int i = 0; i < statistic->GetTimeSteps(); i++) { QString timeStepLabel = "[" + QString::number(i) + "] " + QString::number(statistic->GetTimeGeometry()->TimeStepToTimePoint(i)) + " ms"; if (statistic->TimeStepExists(i)) { auto statisticsItem = new QmitkImageStatisticsTreeItem( - statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, lastParent); + statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, isWIP, lastParent); lastParent->appendChild(statisticsItem); } } hasMultipleTimesteps = true; } } QString headerString = "Images"; if (hasMask) { headerString += "/Masks"; } if (hasMultipleTimesteps) { headerString += "/Timesteps"; } m_HeaderFirstColumn = headerString; } void QmitkImageStatisticsTreeModel::NodeRemoved(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h index 4797719702..800959cba8 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h @@ -1,111 +1,127 @@ /*============================================================================ 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 QmitkImageStatisticsTreeModel_h #define QmitkImageStatisticsTreeModel_h #include "itkSimpleFastMutexLock.h" #include "QmitkAbstractDataStorageModel.h" //MITK #include #include "mitkImageStatisticsContainer.h" class QmitkImageStatisticsTreeItem; /*! \class QmitkImageStatisticsTreeModel -Model that takes a mitk::ImageStatisticsContainer and represents it as model in context of the QT view-model-concept. +The class is used to represent the information of mitk::ImageStatisticsContainer in the set datastorage in the context of the QT view-model-concept. +The represented ImageStatisticContainer are specified by setting the image and mask nodes that should be regarded. +In addition you may specified the statistic computation property HistorgramNBins and IgnoreZeroValueVoxel to select the correct +statistics. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkImageStatisticsTreeModel(QObject *parent = nullptr); ~QmitkImageStatisticsTreeModel() override; void SetImageNodes(const std::vector& nodes); void SetMaskNodes(const std::vector& nodes); void Clear(); + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; signals: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: /* * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode *node) override; private: void UpdateByDataStorage(); using StatisticsContainerVector = std::vector; /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] --> 3. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); StatisticsContainerVector m_Statistics; /** Relevant images set by the user.*/ std::vector m_ImageNodes; /** Helper that is constructed when m_ImageNodes is set. It has the same order like m_ImageNodes, but each image is represented n times, while n is the number of time steps the respective image has. This structure makes the business logic to select the correct image given a QIndex much simpler and therefore easy to understand/maintain. */ std::vector > m_TimeStepResolvedImageNodes; /** relevant masks set by the user.*/ std::vector m_MaskNodes; /** @sa m_TimeStepResolvedImageNodes */ std::vector> m_TimeStepResolvedMaskNodes; std::vector m_StatisticNames; itk::SimpleFastMutexLock m_Mutex; QmitkImageStatisticsTreeItem *m_RootItem; QVariant m_HeaderFirstColumn; + + bool m_IgnoreZeroValueVoxel = false; + unsigned int m_HistogramNBins = 100; }; #endif // mitkQmitkImageStatisticsTreeModel_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp index e8da1c95cb..cfaea25dce 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp @@ -1,81 +1,102 @@ /*============================================================================ 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 "QmitkImageStatisticsWidget.h" #include "QmitkStatisticsModelToStringConverter.h" #include "QmitkImageStatisticsTreeModel.h" #include #include QmitkImageStatisticsWidget::QmitkImageStatisticsWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_imageStatisticsModel = new QmitkImageStatisticsTreeModel(parent); CreateConnections(); m_ProxyModel = new QSortFilterProxyModel(this); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.treeViewStatistics->setModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_imageStatisticsModel); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::dataAvailable, this, &QmitkImageStatisticsWidget::OnDataAvailable); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::modelChanged, m_Controls.treeViewStatistics, &QTreeView::expandAll); } void QmitkImageStatisticsWidget::SetDataStorage(mitk::DataStorage* newDataStorage) { m_imageStatisticsModel->SetDataStorage(newDataStorage); } void QmitkImageStatisticsWidget::SetImageNodes(const std::vector& nodes) { m_imageStatisticsModel->SetImageNodes(nodes); } void QmitkImageStatisticsWidget::SetMaskNodes(const std::vector& nodes) { m_imageStatisticsModel->SetMaskNodes(nodes); } void QmitkImageStatisticsWidget::Reset() { m_imageStatisticsModel->Clear(); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(false); } + +void QmitkImageStatisticsWidget::SetIgnoreZeroValueVoxel(bool _arg) +{ + m_imageStatisticsModel->SetIgnoreZeroValueVoxel(_arg); +} + +bool QmitkImageStatisticsWidget::GetIgnoreZeroValueVoxel() const +{ + return this->m_imageStatisticsModel->GetIgnoreZeroValueVoxel(); +} + +void QmitkImageStatisticsWidget::SetHistogramNBins(unsigned int nbins) +{ + m_imageStatisticsModel->SetHistogramNBins(nbins); +} + +unsigned int QmitkImageStatisticsWidget::GetHistogramNBins() const +{ + return this->m_imageStatisticsModel->GetHistogramNBins(); +} + void QmitkImageStatisticsWidget::CreateConnections() { connect(m_Controls.buttonCopyImageStatisticsToClipboard, &QPushButton::clicked, this, &QmitkImageStatisticsWidget::OnClipboardButtonClicked); } void QmitkImageStatisticsWidget::OnDataAvailable() { m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(true); m_Controls.treeViewStatistics->setEnabled(true); } void QmitkImageStatisticsWidget::OnClipboardButtonClicked() { QmitkStatisticsModelToStringConverter converter; converter.SetColumnDelimiter('\t'); converter.SetModel(m_imageStatisticsModel); converter.SetRootIndex(m_Controls.treeViewStatistics->rootIndex()); converter.SetIncludeHeaderData(true); QString clipboardAsString = converter.GetString(); QApplication::clipboard()->clear(); QApplication::clipboard()->setText(clipboardAsString, QClipboard::Clipboard); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h index 2635b6fd29..13e4fef97d 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h @@ -1,53 +1,63 @@ /*============================================================================ 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 QmitkImageStatisticsWidget_H__INCLUDED #define QmitkImageStatisticsWidget_H__INCLUDED #include #include #include #include class QSortFilterProxyModel; class QmitkImageStatisticsTreeModel; class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsWidget : public QWidget { Q_OBJECT public: QmitkImageStatisticsWidget(QWidget *parent = nullptr); /**Documentation Set the data storage the model should fetch its statistic objects from. @pre data storage must be valid */ void SetDataStorage(mitk::DataStorage *newDataStorage); void SetImageNodes(const std::vector &nodes); void SetMaskNodes(const std::vector &nodes); void Reset(); + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + private: void CreateConnections(); void OnDataAvailable(); /** \brief Saves the image statistics to the clipboard */ void OnClipboardButtonClicked(); private: Ui::QmitkImageStatisticsControls m_Controls; QmitkImageStatisticsTreeModel *m_imageStatisticsModel; QSortFilterProxyModel *m_ProxyModel; }; #endif // QmitkImageStatisticsWidget_H__INCLUDED diff --git a/Modules/ImageStatisticsUI/files.cmake b/Modules/ImageStatisticsUI/files.cmake index d94da90986..4d0f2f5d6a 100644 --- a/Modules/ImageStatisticsUI/files.cmake +++ b/Modules/ImageStatisticsUI/files.cmake @@ -1,33 +1,38 @@ set(CPP_FILES Qmitk/QmitkHistogramVisualizationWidget.cpp Qmitk/QmitkImageStatisticsCalculationRunnable.cpp Qmitk/QmitkIntensityProfileVisualizationWidget.cpp Qmitk/QmitkImageStatisticsTreeModel.cpp Qmitk/QmitkImageStatisticsCalculationJob.cpp Qmitk/QmitkStatisticsModelToStringConverter.cpp Qmitk/QmitkImageStatisticsWidget.cpp Qmitk/QmitkImageStatisticsTreeItem.cpp + Qmitk/QmitkDataGenerationJobBase.cpp + Qmitk/QmitkDataGeneratorBase.cpp + Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp + Qmitk/QmitkImageStatisticsDataGenerator.cpp ) set(H_FILES Qmitk/QmitkStatisticsModelToStringConverter.h Qmitk/QmitkImageStatisticsTreeItem.h -) - -set(TPP_FILES + Qmitk/QmitkImageAndRoiDataGeneratorBase.h + Qmitk/QmitkImageStatisticsDataGenerator.h ) set(UI_FILES Qmitk/QmitkHistogramVisualizationWidget.ui Qmitk/QmitkIntensityProfileVisualizationWidget.ui Qmitk/QmitkImageStatisticsWidget.ui ) set(MOC_H_FILES Qmitk/QmitkHistogramVisualizationWidget.h Qmitk/QmitkImageStatisticsCalculationRunnable.h Qmitk/QmitkIntensityProfileVisualizationWidget.h Qmitk/QmitkImageStatisticsTreeModel.h Qmitk/QmitkImageStatisticsCalculationJob.h Qmitk/QmitkImageStatisticsWidget.h + Qmitk/QmitkDataGenerationJobBase.h + Qmitk/QmitkDataGeneratorBase.h ) diff --git a/Modules/ImageStatisticsUI/test/CMakeLists.txt b/Modules/ImageStatisticsUI/test/CMakeLists.txt new file mode 100644 index 0000000000..153cd81e2e --- /dev/null +++ b/Modules/ImageStatisticsUI/test/CMakeLists.txt @@ -0,0 +1 @@ +MITK_CREATE_MODULE_TESTS() diff --git a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp new file mode 100644 index 0000000000..a64ab03339 --- /dev/null +++ b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp @@ -0,0 +1,519 @@ +/*============================================================================ + +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 "QmitkImageStatisticsDataGenerator.h" +#include +#include +#include "mitkImage.h" +#include "mitkPlanarFigure.h" +#include "mitkIOUtil.h" + +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkImageStatisticsContainerManager.h" +#include "mitkProperties.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + +#include +#include + +class TestQmitkImageStatisticsDataGenerator : public QmitkImageStatisticsDataGenerator +{ +public: + TestQmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageStatisticsDataGenerator(storage, parent) + { + connect(this, &QmitkDataGeneratorBase::NewDataAvailable, this, &TestQmitkImageStatisticsDataGenerator::NewDataAvailableEmited); + connect(this, &QmitkDataGeneratorBase::DataGenerationStarted, this, &TestQmitkImageStatisticsDataGenerator::DataGenerationStartedEmited); + connect(this, &QmitkDataGeneratorBase::GenerationFinished, this, &TestQmitkImageStatisticsDataGenerator::GenerationFinishedEmited); + connect(this, &QmitkDataGeneratorBase::JobError, this, &TestQmitkImageStatisticsDataGenerator::JobErrorEmited); + }; + + mutable std::vector m_NewDataAvailable; + void NewDataAvailableEmited(mitk::DataStorage::SetOfObjects::ConstPointer data) const + { + m_NewDataAvailable.emplace_back(data); + }; + + mutable int m_DataGenerationStartedEmited = 0; + void DataGenerationStartedEmited(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) const + { + m_DataGenerationStartedEmited++; + } + + mutable int m_GenerationFinishedEmited = 0; + void GenerationFinishedEmited() const + { + m_GenerationFinishedEmited++; + QCoreApplication::instance()->quit(); + } + + mutable std::vector m_JobErrorEmited_error; + void JobErrorEmited(QString error, const QmitkDataGenerationJobBase* /*failedJob*/) const + { + m_JobErrorEmited_error.emplace_back(error); + } + +}; + +class QmitkImageStatisticsDataGeneratorTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(QmitkImageStatisticsDataGeneratorTestSuite); + MITK_TEST(GetterSetterTest); + MITK_TEST(NullTest); + MITK_TEST(OneImageTest); + MITK_TEST(MultiImageTest); + MITK_TEST(ImageAndROITest); + MITK_TEST(ImageAndMultiROITest); + MITK_TEST(MultiMultiTest); + MITK_TEST(InputChangedTest); + MITK_TEST(SettingsChangedTest); + MITK_TEST(DataStorageModificationTest); + CPPUNIT_TEST_SUITE_END(); + + mitk::DataStorage::Pointer m_DataStorage; + mitk::DataNode::Pointer m_ImageNode1; + mitk::DataNode::Pointer m_ImageNode2; + mitk::DataNode::Pointer m_MaskImageNode; + mitk::DataNode::Pointer m_PFNode; + mitk::Image::Pointer m_Image1; + mitk::Image::Pointer m_Image2; + mitk::Image::Pointer m_Mask; + mitk::PlanarFigure::Pointer m_PF; + + QCoreApplication* m_TestApp; + +public: + void setUp() override + { + m_DataStorage = mitk::StandaloneDataStorage::New(); + + m_ImageNode1 = mitk::DataNode::New(); + m_ImageNode1->SetName("Image_1"); + auto pic3DCroppedFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_cropped.nrrd"); + m_Image1 = mitk::IOUtil::Load(pic3DCroppedFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image1.IsNotNull()); + m_ImageNode1->SetData(m_Image1); + m_DataStorage->Add(m_ImageNode1); + + m_ImageNode2 = mitk::DataNode::New(); + m_ImageNode2->SetName("Image_2"); + m_Image2 = mitk::IOUtil::Load(pic3DCroppedFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image2.IsNotNull()); + m_ImageNode2->SetData(m_Image2); + m_DataStorage->Add(m_ImageNode2); + + m_MaskImageNode = mitk::DataNode::New(); + m_MaskImageNode->SetName("Mask"); + auto pic3DCroppedBinMaskFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedBinMask.nrrd"); + m_Mask = mitk::IOUtil::Load(pic3DCroppedBinMaskFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D binary mask", m_Mask.IsNotNull()); + m_MaskImageNode->SetData(m_Mask); + m_DataStorage->Add(m_MaskImageNode); + + m_PFNode = mitk::DataNode::New(); + m_PFNode->SetName("PF"); + auto pic3DCroppedPlanarFigureFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedPF.pf"); + m_PF = mitk::IOUtil::Load(pic3DCroppedPlanarFigureFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D planar figure", m_PF.IsNotNull()); + m_PFNode->SetData(m_PF); + m_DataStorage->Add(m_PFNode); + + int argc = 0; + char** argv = nullptr; + m_TestApp = new QCoreApplication(argc, argv); + } + + void tearDown() override + { + delete m_TestApp; + } + + + bool CheckResultNode(const std::vector resultNodes, const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, unsigned int histBin = 100, bool noZero = false) + { + for (auto& resultNode : resultNodes) + { + bool result = false; + + if (resultNode && resultNode->GetData() && imageNode && imageNode->GetData()) + { + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + result = !imageRule->GetRelationUIDs(resultNode, imageNode).empty(); + + if (roiNode) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + result = result && !maskRule->GetRelationUIDs(resultNode, roiNode).empty(); + } + + auto prop = resultNode->GetData()->GetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str()); + auto binProp = dynamic_cast(prop.GetPointer()); + result = result && binProp->GetValue() == histBin; + + prop = resultNode->GetData()->GetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str()); + auto zeroProp = dynamic_cast(prop.GetPointer()); + result = result && zeroProp->GetValue() == noZero; + } + + if (result) + { //node was in the result set + return true; + } + + } + + return false; + } + + void NullTest() + { + TestQmitkImageStatisticsDataGenerator generator(nullptr); + + generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); + + generator.SetDataStorage(m_DataStorage); + generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); + } + + void GetterSetterTest() + { + TestQmitkImageStatisticsDataGenerator generator(nullptr); + CPPUNIT_ASSERT(nullptr == generator.GetDataStorage()); + generator.SetDataStorage(m_DataStorage); + CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); + + TestQmitkImageStatisticsDataGenerator generator2(m_DataStorage); + CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); + + CPPUNIT_ASSERT_EQUAL(100u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetHistogramNBins(3); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetIgnoreZeroValueVoxel(true); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetAutoUpdate(true); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetAutoUpdate()); + } + + void OneImageTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable.front()->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void MultiImageTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2u == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void ImageAndROITest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, m_MaskImageNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, m_PFNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void ImageAndMultiROITest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front()}, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void MultiMultiTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(6, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(6 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void InputChangedTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode2.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(0 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 2 == generator.m_NewDataAvailable.size()); + } + + void SettingsChangedTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, nullptr)); + + generator.SetHistogramNBins(50); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr, 50)); + + generator.SetIgnoreZeroValueVoxel(true); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr, 50, true)); + + //now check auto update feature + generator.SetAutoUpdate(true); + generator.SetHistogramNBins(5); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 4 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[3]->front() }, m_ImageNode1, nullptr, 5, true)); + + generator.SetHistogramNBins(5); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4 == generator.m_NewDataAvailable.size()); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 5 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[4]->front() }, m_ImageNode1, nullptr, 5, false)); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5 == generator.m_NewDataAvailable.size()); + } + + void DataStorageModificationTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + + //now check auto update feature + generator.SetAutoUpdate(true); + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 3 == generator.m_NewDataAvailable.size()); + + + m_DataStorage->Add(mitk::DataNode::New()); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); + + m_Image2->Modified(); + m_ImageNode2->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(QmitkImageStatisticsDataGenerator) diff --git a/Modules/ImageStatisticsUI/test/files.cmake b/Modules/ImageStatisticsUI/test/files.cmake new file mode 100644 index 0000000000..36d9d98569 --- /dev/null +++ b/Modules/ImageStatisticsUI/test/files.cmake @@ -0,0 +1,3 @@ +set(MODULE_TESTS + QmitkImageStatisticsDataGeneratorTest.cpp +) diff --git a/Modules/ModelFit/autoload/IO/mitkModelFitIOActivator.cpp b/Modules/ModelFit/autoload/IO/mitkModelFitIOActivator.cpp index 42cc07157d..1c7e72b7eb 100644 --- a/Modules/ModelFit/autoload/IO/mitkModelFitIOActivator.cpp +++ b/Modules/ModelFit/autoload/IO/mitkModelFitIOActivator.cpp @@ -1,96 +1,100 @@ /*============================================================================ 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 #include #include #include #include #include #include namespace mitk { /* * This is the module activator for the IO aspects of the "ModelFit" module. */ class ModelFitIOActivator : public us::ModuleActivator { public: void registerProperty(const std::string& name, const std::string& key, const std::string& description) { - mitk::CoreServices::GetPropertyDescriptions()->AddDescription(name, description); + mitk::CoreServicePointer propDescService(mitk::CoreServices::GetPropertyDescriptions()); + propDescService->AddDescription(name, description); mitk::PropertyPersistenceInfo::Pointer ppi = mitk::PropertyPersistenceInfo::New(); ppi->SetNameAndKey(name, key); - mitk::CoreServices::GetPropertyPersistence()->AddInfo(ppi, true); + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + propPersistenceService->AddInfo(ppi, true); } void registerProperty(const std::string& name, const std::string& key, const std::string& description, const PropertyPersistenceInfo::DeserializationFunctionType &deFnc, const PropertyPersistenceInfo::SerializationFunctionType &serFnc) { - mitk::CoreServices::GetPropertyDescriptions()->AddDescription(name, description); + mitk::CoreServicePointer propDescService(mitk::CoreServices::GetPropertyDescriptions()); + propDescService->AddDescription(name, description); mitk::PropertyPersistenceInfo::Pointer ppi = mitk::PropertyPersistenceInfo::New(); ppi->SetNameAndKey(name, key); ppi->SetDeserializationFunction(deFnc); ppi->SetSerializationFunction(serFnc); - mitk::CoreServices::GetPropertyPersistence()->AddInfo(ppi, true); + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + propPersistenceService->AddInfo(ppi, true); } void Load(us::ModuleContext* /*context*/) override { //register relevant properties registerProperty(mitk::ModelFitConstants::UID_PROPERTY_NAME(), "data_uid", "UID used to identify data in an MITK session."); registerProperty(mitk::ModelFitConstants::INPUT_VARIABLES_PROPERTY_NAME(), "modelfit_input_variables", "Array of input variables used in/for a model fit.", PropertyPersistenceDeserialization::deserializeXMLToScalarListLookupTableProperty, PropertyPersistenceSerialization::serializeScalarListLookupTablePropertyToXML); registerProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME(), "modelfit_parameter_name", "Name of the parameter, that is represented by the data and how it is used in the function string (modelfit.model.function)."); registerProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME(), "modelfit_parameter_unit", "Unit string of the Parameter. Is only used for display. Default value: \"\" (no unit)"); registerProperty(mitk::ModelFitConstants::PARAMETER_SCALE_PROPERTY_NAME(), "modelfit_parameter_scale", "Scaling of the parameter. Default value: 1."); registerProperty(mitk::ModelFitConstants::PARAMETER_TYPE_PROPERTY_NAME(), "modelfit_parameter_type", "Type of the parameters. Default value: parameter. Other options: derived, criterion, evaluation."); registerProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME(), "modelfit_model_type", "Value specifies the type of model (helpfull for classification; e.g. MR perfusion)"); registerProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME(), "modelfit_model_name", "Name of the specific fit. Only used for display."); registerProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME(), "modelfit_model_function", "Functions string, that specifies the model and will be parsed, to plot the curves based on the parameter. Optional parameter that must not be set."); registerProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME(), "modelfit_model_functionClass", "ID of the model class implementation."); registerProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME(), "modelfit_model_x", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME(), "modelfit_xaxis_name", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME(), "modelfit_xaxis_unit", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME(), "modelfit_yaxis_name", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME(), "modelfit_yaxis_unit", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME(), "modelfit_fit_uid", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME(), "modelfit_fit_name", "Human readable name for the fit."); registerProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME(), "modelfit_fit_type", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_INPUT_IMAGEUID_PROPERTY_NAME(), "modelfit_fit_input_imageUID", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_INPUT_ROIUID_PROPERTY_NAME(), "modelfit_fit_input_roiUID", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_INPUT_DATA_PROPERTY_NAME(), "modelfit_fit_input_data", "Value identifies the model type."); registerProperty(mitk::ModelFitConstants::FIT_STATIC_PARAMETERS_PROPERTY_NAME(), "modelfit_fit_staticParameters", "Value identifies the model type.", PropertyPersistenceDeserialization::deserializeXMLToScalarListLookupTableProperty, PropertyPersistenceSerialization::serializeScalarListLookupTablePropertyToXML); } void Unload(us::ModuleContext* ) override { } private: }; } US_EXPORT_MODULE_ACTIVATOR(mitk::ModelFitIOActivator) diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp index ecb58c61f0..41f5f03a67 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp @@ -1,135 +1,234 @@ /*============================================================================ 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 #include #include #include +#include +#include +#include +#include std::string pathToImage; class mitkLabelSetImageIOTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetImageIOTestSuite); MITK_TEST(TestReadWrite3DLabelSetImage); MITK_TEST(TestReadWrite3DplusTLabelSetImage); + MITK_TEST(TestReadWrite3DplusTLabelSetImageWithArbitraryGeometry); + MITK_TEST(TestReadWriteProperties); CPPUNIT_TEST_SUITE_END(); private: mitk::Image::Pointer regularImage; mitk::LabelSetImage::Pointer multilabelImage; public: void setUp() override { regularImage = mitk::Image::New(); } void tearDown() override { regularImage = nullptr; multilabelImage = nullptr; } - + void TestReadWrite3DLabelSetImage() { - unsigned int dimensions[3] = {256, 256, 312}; + unsigned int dimensions[3] = {30, 20, 10}; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); multilabelImage = mitk::LabelSetImage::New(); multilabelImage->Initialize(regularImage); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); newlayer->SetLayer(1); mitk::Label::Pointer label0 = mitk::Label::New(); label0->SetName("Background"); label0->SetValue(0); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label0); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); multilabelImage->AddLayer(newlayer); pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); pathToImage.append("/LabelSetTestImage3D.nrrd"); mitk::IOUtil::Save(multilabelImage, pathToImage); - mitk::LabelSetImage::Pointer loadedImage = + auto loadedImage = mitk::IOUtil::Load(pathToImage); // This information is currently not serialized but also checked within the Equals function loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); itksys::SystemTools::RemoveFile(pathToImage); } void TestReadWrite3DplusTLabelSetImage() { - unsigned int dimensions[4] = {256, 256, 312, 10}; + unsigned int dimensions[4] = {30, 20, 15, 10}; regularImage->Initialize(mitk::MakeScalarPixelType(), 4, dimensions); multilabelImage = mitk::LabelSetImage::New(); multilabelImage->Initialize(regularImage); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); newlayer->SetLayer(1); mitk::Label::Pointer label0 = mitk::Label::New(); label0->SetName("Background"); label0->SetValue(0); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label0); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); multilabelImage->AddLayer(newlayer); pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); pathToImage.append("/LabelSetTestImage3DplusT.nrrd"); mitk::IOUtil::Save(multilabelImage, pathToImage); - mitk::LabelSetImage::Pointer loadedImage = + auto loadedImage = mitk::IOUtil::Load(pathToImage); // This information is currently not serialized but also checked within the Equals function loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); + CPPUNIT_ASSERT_MESSAGE("Error reading time geometry of label set image", mitk::Equal(*(multilabelImage->GetTimeGeometry()), *(loadedImage->GetTimeGeometry()), 0.000000001, true)); itksys::SystemTools::RemoveFile(pathToImage); } + + void TestReadWrite3DplusTLabelSetImageWithArbitraryGeometry() + { + unsigned int dimensions[4] = { 30, 20, 10, 4 }; + regularImage->Initialize(mitk::MakeScalarPixelType(), 4, dimensions); + + multilabelImage = mitk::LabelSetImage::New(); + multilabelImage->Initialize(regularImage); + mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); + newlayer->SetLayer(1); + mitk::Label::Pointer label0 = mitk::Label::New(); + label0->SetName("Background"); + label0->SetValue(0); + + mitk::Label::Pointer label1 = mitk::Label::New(); + label1->SetName("Label1"); + label1->SetValue(1); + + mitk::Label::Pointer label2 = mitk::Label::New(); + label2->SetName("Label2"); + label2->SetValue(200); + + newlayer->AddLabel(label0); + newlayer->AddLabel(label1); + newlayer->AddLabel(label2); + newlayer->SetActiveLabel(200); + + multilabelImage->AddLayer(newlayer); + + auto geometry = multilabelImage->GetGeometry()->Clone(); + + auto refTimeGeometry = mitk::ArbitraryTimeGeometry::New(); + refTimeGeometry->AppendNewTimeStep(geometry, 0., 0.5); + refTimeGeometry->AppendNewTimeStep(geometry, 0.5, 1.); + refTimeGeometry->AppendNewTimeStep(geometry, 1., 2.); + refTimeGeometry->AppendNewTimeStep(geometry, 2., 5.5); + multilabelImage->SetTimeGeometry(refTimeGeometry); + + pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); + pathToImage.append("/LabelSetTestImage3DplusTWithArbitraryTimeGeometry.nrrd"); + + mitk::IOUtil::Save(multilabelImage, pathToImage); + + auto loadedImage = + mitk::IOUtil::Load(pathToImage); + + // This information is currently not serialized but also checked within the Equals function + loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); + + CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); + CPPUNIT_ASSERT_MESSAGE("Error reading time geometry of label set image", mitk::Equal(*refTimeGeometry, *(loadedImage->GetTimeGeometry()), 0.000000001, true)); + itksys::SystemTools::RemoveFile(pathToImage); + } + + void TestReadWriteProperties() + { + unsigned int dimensions[3] = { 30, 20, 10 }; + regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); + + multilabelImage = mitk::LabelSetImage::New(); + multilabelImage->Initialize(regularImage); + mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); + newlayer->SetLayer(1); + mitk::Label::Pointer label0 = mitk::Label::New(); + label0->SetName("Background"); + label0->SetValue(0); + newlayer->AddLabel(label0); + multilabelImage->AddLayer(newlayer); + + auto propPersistenceInfo = mitk::PropertyPersistenceInfo::New(); + propPersistenceInfo->SetNameAndKey("my.cool.test.property", "my_cool_test_property"); + mitk::CoreServicePointer propPersService(mitk::CoreServices::GetPropertyPersistence()); + propPersService->AddInfo(propPersistenceInfo); + + multilabelImage->SetProperty("my.cool.test.property", mitk::StringProperty::New("test_content")); + + pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); + pathToImage.append("/LabelSetPropertiesTestImage.nrrd"); + + mitk::IOUtil::Save(multilabelImage, pathToImage); + + auto loadedImage = + mitk::IOUtil::Load(pathToImage); + + auto loadedProp = loadedImage->GetProperty("my.cool.test.property"); + CPPUNIT_ASSERT_MESSAGE("Error reading properties of label set image", loadedProp.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Error reading properties of label set image", loadedProp->GetValueAsString() == "test_content"); + itksys::SystemTools::RemoveFile(pathToImage); + } + + }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImageIO) diff --git a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp index 5be17b46f6..9990ea7032 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp @@ -1,452 +1,636 @@ /*============================================================================ 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 __mitkLabelSetImageWriter__cpp #define __mitkLabelSetImageWriter__cpp #include "mitkLabelSetImageIO.h" #include "mitkBasePropertySerializer.h" #include "mitkIOMimeTypes.h" #include "mitkImageAccessByItk.h" #include "mitkLabelSetIOHelper.h" #include "mitkLabelSetImageConverter.h" #include +#include +#include +#include +#include // itk #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include "itkNrrdImageIO.h" namespace mitk { + + const char* const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; + const char* const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; + const char* const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; + const char* const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; + LabelSetImageIO::LabelSetImageIO() : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), IOMimeTypes::NRRD_MIMETYPE(), "MITK Multilabel Image") { AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } IFileIO::ConfidenceLevel LabelSetImageIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; const auto *input = static_cast(this->GetInput()); if (input) return Supported; else return Unsupported; } void LabelSetImageIO::Write() { ValidateOutputLocation(); auto input = dynamic_cast(this->GetInput()); mitk::LocaleSwitch localeSwitch("C"); mitk::Image::Pointer inputVector = mitk::ConvertLabelSetImageToImage(input); // image write if (inputVector.IsNull()) { mitkThrow() << "Cannot write non-image data"; } itk::NrrdImageIO::Pointer nrrdImageIo = itk::NrrdImageIO::New(); // Clone the image geometry, because we might have to change it // for writing purposes BaseGeometry::Pointer geometry = inputVector->GetGeometry()->Clone(); // Check if geometry information will be lost if (inputVector->GetDimension() == 2 && !geometry->Is2DConvertable()) { MITK_WARN << "Saving a 2D image with 3D geometry information. Geometry information will be lost! You might " "consider using Convert2Dto3DImageFilter before saving."; // set matrix to identity mitk::AffineTransform3D::Pointer affTrans = mitk::AffineTransform3D::New(); affTrans->SetIdentity(); mitk::Vector3D spacing = geometry->GetSpacing(); mitk::Point3D origin = geometry->GetOrigin(); geometry->SetIndexToWorldTransform(affTrans); geometry->SetSpacing(spacing); geometry->SetOrigin(origin); } LocalFile localFile(this); const std::string path = localFile.GetFileName(); MITK_INFO << "Writing image: " << path << std::endl; try { // Implementation of writer using itkImageIO directly. This skips the use // of templated itkImageFileWriter, which saves the multiplexing on MITK side. const unsigned int dimension = inputVector->GetDimension(); const unsigned int *const dimensions = inputVector->GetDimensions(); const mitk::PixelType pixelType = inputVector->GetPixelType(); const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); const mitk::Point3D mitkOrigin = geometry->GetOrigin(); // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, // though they are not supported in MITK itk::Vector spacing4D; spacing4D[0] = mitkSpacing[0]; spacing4D[1] = mitkSpacing[1]; spacing4D[2] = mitkSpacing[2]; spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here itk::Vector origin4D; origin4D[0] = mitkOrigin[0]; origin4D[1] = mitkOrigin[1]; origin4D[2] = mitkOrigin[2]; origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here // Set the necessary information for imageIO nrrdImageIo->SetNumberOfDimensions(dimension); nrrdImageIo->SetPixelType(pixelType.GetPixelType()); nrrdImageIo->SetComponentType(pixelType.GetComponentType() < PixelComponentUserType ? static_cast(pixelType.GetComponentType()) : itk::ImageIOBase::UNKNOWNCOMPONENTTYPE); nrrdImageIo->SetNumberOfComponents(pixelType.GetNumberOfComponents()); itk::ImageIORegion ioRegion(dimension); for (unsigned int i = 0; i < dimension; i++) { nrrdImageIo->SetDimensions(i, dimensions[i]); nrrdImageIo->SetSpacing(i, spacing4D[i]); nrrdImageIo->SetOrigin(i, origin4D[i]); mitk::Vector3D mitkDirection; mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i)); itk::Vector direction4D; direction4D[0] = mitkDirection[0]; direction4D[1] = mitkDirection[1]; direction4D[2] = mitkDirection[2]; // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. if (i == 3) { direction4D[3] = 1; // homogenous component } else { direction4D[3] = 0; } vnl_vector axisDirection(dimension); for (unsigned int j = 0; j < dimension; j++) { axisDirection[j] = direction4D[j] / spacing4D[i]; } nrrdImageIo->SetDirection(i, axisDirection); ioRegion.SetSize(i, inputVector->GetLargestPossibleRegion().GetSize(i)); ioRegion.SetIndex(i, inputVector->GetLargestPossibleRegion().GetIndex(i)); } // use compression if available nrrdImageIo->UseCompressionOn(); nrrdImageIo->SetIORegion(ioRegion); nrrdImageIo->SetFileName(path); // label set specific meta data char keybuffer[512]; char valbuffer[512]; sprintf(keybuffer, "modality"); sprintf(valbuffer, "org.mitk.image.multilabel"); itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); sprintf(keybuffer, "layers"); sprintf(valbuffer, "%1d", input->GetNumberOfLayers()); itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); for (unsigned int layerIdx = 0; layerIdx < input->GetNumberOfLayers(); layerIdx++) { sprintf(keybuffer, "layer_%03u", layerIdx); // layer idx sprintf(valbuffer, "%1u", input->GetNumberOfLabels(layerIdx)); // number of labels for the layer itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); auto iter = input->GetLabelSet(layerIdx)->IteratorConstBegin(); unsigned int count(0); while (iter != input->GetLabelSet(layerIdx)->IteratorConstEnd()) { std::unique_ptr document; document.reset(new TiXmlDocument()); auto *decl = new TiXmlDeclaration("1.0", "", ""); // TODO what to write here? encoding? etc.... document->LinkEndChild(decl); TiXmlElement *labelElem = mitk::LabelSetIOHelper::GetLabelAsTiXmlElement(iter->second); document->LinkEndChild(labelElem); TiXmlPrinter printer; printer.SetIndent(""); printer.SetLineBreak(""); document->Accept(&printer); sprintf(keybuffer, "org.mitk.label_%03u_%05u", layerIdx, count); itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), printer.Str()); ++iter; ++count; } } // end label set specific meta data + // Handle time geometry + const auto* arbitraryTG = dynamic_cast(input->GetTimeGeometry()); + if (arbitraryTG) + { + itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), + PROPERTY_KEY_TIMEGEOMETRY_TYPE, + ArbitraryTimeGeometry::GetStaticNameOfClass()); + + + auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); + nrrdImageIo->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); + } + + // Handle properties + mitk::PropertyList::Pointer imagePropertyList = input->GetPropertyList(); + for (const auto& property : *imagePropertyList->GetMap()) + { + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, GetMimeType()->GetName(), true); + + if (infoList.empty()) + { + continue; + } + + std::string value = infoList.front()->GetSerializationFunction()(property.second); + + if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) + { + continue; + } + + std::string key = infoList.front()->GetKey(); + + itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), key, value); + } + ImageReadAccessor imageAccess(inputVector); nrrdImageIo->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } // end image write } IFileIO::ConfidenceLevel LabelSetImageIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(fileName); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); std::string value(""); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); if (value.compare("org.mitk.image.multilabel") == 0) { return Supported; } else return Unsupported; } std::vector LabelSetImageIO::Read() { mitk::LocaleSwitch localeSwitch("C"); // begin regular image loading, adapted from mitkItkImageIO itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); Image::Pointer image = Image::New(); const unsigned int MINDIM = 2; const unsigned int MAXDIM = 4; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << " via itk::ImageIOFactory... " << std::endl; // Check to see if we can read the file given the name or prefix if (path.empty()) { mitkThrow() << "Empty filename in mitk::ItkImageIO "; } // Got to allocate space for the image. Determine the characteristics of // the image. nrrdImageIO->SetFileName(path); nrrdImageIO->ReadImageInformation(); unsigned int ndim = nrrdImageIO->GetNumberOfDimensions(); if (ndim < MINDIM || ndim > MAXDIM) { MITK_WARN << "Sorry, only dimensions 2, 3 and 4 are supported. The given file has " << ndim << " dimensions! Reading as 4D."; ndim = MAXDIM; } itk::ImageIORegion ioRegion(ndim); itk::ImageIORegion::SizeType ioSize = ioRegion.GetSize(); itk::ImageIORegion::IndexType ioStart = ioRegion.GetIndex(); unsigned int dimensions[MAXDIM]; dimensions[0] = 0; dimensions[1] = 0; dimensions[2] = 0; dimensions[3] = 0; ScalarType spacing[MAXDIM]; spacing[0] = 1.0f; spacing[1] = 1.0f; spacing[2] = 1.0f; spacing[3] = 1.0f; Point3D origin; origin.Fill(0); unsigned int i; for (i = 0; i < ndim; ++i) { ioStart[i] = 0; ioSize[i] = nrrdImageIO->GetDimensions(i); if (i < MAXDIM) { dimensions[i] = nrrdImageIO->GetDimensions(i); spacing[i] = nrrdImageIO->GetSpacing(i); if (spacing[i] <= 0) spacing[i] = 1.0f; } if (i < 3) { origin[i] = nrrdImageIO->GetOrigin(i); } } ioRegion.SetSize(ioSize); ioRegion.SetIndex(ioStart); MITK_INFO << "ioRegion: " << ioRegion << std::endl; nrrdImageIO->SetIORegion(ioRegion); void *buffer = new unsigned char[nrrdImageIO->GetImageSizeInBytes()]; nrrdImageIO->Read(buffer); image->Initialize(MakePixelType(nrrdImageIO), ndim, dimensions); image->SetImportChannel(buffer, 0, Image::ManageMemory); // access direction of itk::Image and include spacing mitk::Matrix3D matrix; matrix.SetIdentity(); unsigned int j, itkDimMax3 = (ndim >= 3 ? 3 : ndim); for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) matrix[i][j] = nrrdImageIO->GetDirection(j)[i]; // re-initialize PlaneGeometry with origin and direction PlaneGeometry *planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D SlicedGeometry3D *slicedGeometry = image->GetSlicedGeometry(0); slicedGeometry->InitializeEvenlySpaced(planeGeometry, image->GetDimension(2)); slicedGeometry->SetSpacing(spacing); MITK_INFO << slicedGeometry->GetCornerPoint(false, false, false); MITK_INFO << slicedGeometry->GetCornerPoint(true, true, true); // re-initialize TimeGeometry - ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); - timeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); - image->SetTimeGeometry(timeGeometry); - - buffer = nullptr; - MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents() << std::endl; + const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); + TimeGeometry::Pointer timeGeometry; + + if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE) || dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TYPE)) + { // also check for the name because of backwards compatibility. Past code version stored with the name and not with + // the key + itk::MetaDataObject::ConstPointer timeGeometryTypeData; + if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE)) + { + timeGeometryTypeData = + dynamic_cast*>(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TYPE)); + } + else + { + timeGeometryTypeData = + dynamic_cast*>(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TYPE)); + } - const itk::MetaDataDictionary &dictionary = nrrdImageIO->GetMetaDataDictionary(); - for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; - ++iter) - { - std::string key = std::string("meta.") + iter->first; - if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) + if (timeGeometryTypeData->GetMetaDataObjectValue() == ArbitraryTimeGeometry::GetStaticNameOfClass()) { - std::string value = - dynamic_cast *>(iter->second.GetPointer())->GetMetaDataObjectValue(); - image->SetProperty(key.c_str(), mitk::StringProperty::New(value)); + MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass(); + typedef std::vector TimePointVector; + TimePointVector timePoints; + + if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)) + { + timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)); + } + else if (dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)) + { + timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)); + } + + if (timePoints.empty()) + { + MITK_ERROR << "Stored timepoints are empty. Meta information seems to bee invalid. Switch to ProportionalTimeGeometry fallback"; + } + else if (timePoints.size() - 1 != image->GetDimension(3)) + { + MITK_ERROR << "Stored timepoints (" << timePoints.size() - 1 << ") and size of image time dimension (" + << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback"; + } + else + { + ArbitraryTimeGeometry::Pointer arbitraryTimeGeometry = ArbitraryTimeGeometry::New(); + TimePointVector::const_iterator pos = timePoints.begin(); + auto prePos = pos++; + + for (; pos != timePoints.end(); ++prePos, ++pos) + { + arbitraryTimeGeometry->AppendNewTimeStepClone(slicedGeometry, *prePos, *pos); + } + + timeGeometry = arbitraryTimeGeometry; + } } } + if (timeGeometry.IsNull()) + { // Fallback. If no other valid time geometry has been created, create a ProportionalTimeGeometry + MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass(); + ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); + propTimeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); + timeGeometry = propTimeGeometry; + } + + image->SetTimeGeometry(timeGeometry); + + buffer = nullptr; + MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); + // end regular image loading LabelSetImage::Pointer output = ConvertImageToLabelSetImage(image); // get labels and add them as properties to the image char keybuffer[256]; unsigned int numberOfLayers = GetIntByKey(dictionary, "layers"); std::string _xmlStr; mitk::Label::Pointer label; for (unsigned int layerIdx = 0; layerIdx < numberOfLayers; layerIdx++) { sprintf(keybuffer, "layer_%03u", layerIdx); int numberOfLabels = GetIntByKey(dictionary, keybuffer); mitk::LabelSet::Pointer labelSet = mitk::LabelSet::New(); for (int labelIdx = 0; labelIdx < numberOfLabels; labelIdx++) { TiXmlDocument doc; sprintf(keybuffer, "label_%03u_%05d", layerIdx, labelIdx); _xmlStr = GetStringByKey(dictionary, keybuffer); doc.Parse(_xmlStr.c_str()); TiXmlElement *labelElem = doc.FirstChildElement("Label"); if (labelElem == nullptr) mitkThrow() << "Error parsing NRRD header for mitk::LabelSetImage IO"; label = mitk::LabelSetIOHelper::LoadLabelFromTiXmlDocument(labelElem); if (label->GetValue() == 0) // set exterior label is needed to hold exterior information output->SetExteriorLabel(label); labelSet->AddLabel(label); labelSet->SetLayer(layerIdx); } output->AddLabelSetToLayer(layerIdx, labelSet); } - MITK_INFO << "...finished!" << std::endl; + for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; + ++iter) + { + if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) + { + const std::string& key = iter->first; + std::string assumedPropertyName = key; + std::replace(assumedPropertyName.begin(), assumedPropertyName.end(), '_', '.'); + + std::string mimeTypeName = GetMimeType()->GetName(); + + // Check if there is already a info for the key and our mime type. + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); + + auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer& x) { + return x.IsNotNull() && x->GetMimeTypeName() == mimeTypeName; + }; + auto finding = std::find_if(infoList.begin(), infoList.end(), predicate); + + if (finding == infoList.end()) + { + auto predicateWild = [](const PropertyPersistenceInfo::ConstPointer& x) { + return x.IsNotNull() && x->GetMimeTypeName() == PropertyPersistenceInfo::ANY_MIMETYPE_NAME(); + }; + finding = std::find_if(infoList.begin(), infoList.end(), predicateWild); + } + + PropertyPersistenceInfo::ConstPointer info; + + if (finding != infoList.end()) + { + assumedPropertyName = (*finding)->GetName(); + info = *finding; + } + else + { // we have not found anything suitable so we generate our own info + auto newInfo = PropertyPersistenceInfo::New(); + newInfo->SetNameAndKey(assumedPropertyName, key); + newInfo->SetMimeTypeName(PropertyPersistenceInfo::ANY_MIMETYPE_NAME()); + info = newInfo; + } + + std::string value = + dynamic_cast*>(iter->second.GetPointer())->GetMetaDataObjectValue(); + + mitk::BaseProperty::Pointer loadedProp = info->GetDeserializationFunction()(value); + + output->SetProperty(assumedPropertyName.c_str(), loadedProp); + + // Read properties should be persisted unless they are default properties + // which are written anyway + bool isDefaultKey = false; + + for (const auto& defaultKey : m_DefaultMetaDataKeys) + { + if (defaultKey.length() <= assumedPropertyName.length()) + { + // does the start match the default key + if (assumedPropertyName.substr(0, defaultKey.length()).find(defaultKey) != std::string::npos) + { + isDefaultKey = true; + break; + } + } + } + + if (!isDefaultKey) + { + propPersistenceService->AddInfo(info); + } + } + } + + MITK_INFO << "...finished!"; std::vector result; result.push_back(output.GetPointer()); return result; } int LabelSetImageIO::GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return atoi(metaString.c_str()); } } return 0; } std::string LabelSetImageIO::GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return metaString; } } return metaString; } LabelSetImageIO *LabelSetImageIO::IOClone() const { return new LabelSetImageIO(*this); } + + void LabelSetImageIO::InitializeDefaultMetaDataKeys() + { + this->m_DefaultMetaDataKeys.push_back("NRRD.space"); + this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); + this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); + this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); + this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); + this->m_DefaultMetaDataKeys.push_back("label_"); + this->m_DefaultMetaDataKeys.push_back("layer_"); + } + } // namespace #endif //__mitkLabelSetImageWriter__cpp diff --git a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h b/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h index e08088c871..59277a97e5 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h +++ b/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h @@ -1,60 +1,66 @@ /*============================================================================ 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 __mitkLabelSetImageIO_h #define __mitkLabelSetImageIO_h #include #include namespace mitk { /** * Writes a LabelSetImage to a file * @ingroup Process */ // The export macro should be removed. Currently, the unit // tests directly instantiate this class. class LabelSetImageIO : public mitk::AbstractFileIO { public: typedef mitk::LabelSetImage InputType; LabelSetImageIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; /** * @brief Reads a number of mitk::LabelSetImages from the file system * @return a vector of mitk::LabelSetImages * @throws throws an mitk::Exception if an error ocurrs during parsing the nrrd header */ std::vector Read() override; ConfidenceLevel GetReaderConfidenceLevel() const override; // -------------- AbstractFileWriter ------------- void Write() override; ConfidenceLevel GetWriterConfidenceLevel() const override; // -------------- LabelSetImageIO specific functions ------------- int GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str); std::string GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str); + protected: + // Fills the m_DefaultMetaDataKeys vector with default values + virtual void InitializeDefaultMetaDataKeys(); + private: LabelSetImageIO *IOClone() const override; + + std::vector m_DefaultMetaDataKeys; }; } // end of namespace mitk #endif // __mitkLabelSetImageIO_h diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.cpp b/Modules/Multilabel/mitkLabelSetImageConverter.cpp index d87a7b556f..15868c5d6d 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageConverter.cpp @@ -1,142 +1,145 @@ /*============================================================================ 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 #include #include #include #include #include #include #include template static void ConvertLabelSetImageToImage(const itk::Image *, mitk::LabelSetImage::ConstPointer labelSetImage, mitk::Image::Pointer &image) { typedef itk::Image ImageType; typedef itk::ComposeImageFilter ComposeFilterType; typedef itk::ImageDuplicator DuplicatorType; auto numberOfLayers = labelSetImage->GetNumberOfLayers(); if (numberOfLayers > 1) { auto vectorImageComposer = ComposeFilterType::New(); auto activeLayer = labelSetImage->GetActiveLayer(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerImage = mitk::ImageToItkImage( layer != activeLayer ? labelSetImage->GetLayerImage(layer) : labelSetImage); vectorImageComposer->SetInput(layer, layerImage); } vectorImageComposer->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(vectorImageComposer->GetOutput())->Clone(); } else { auto layerImage = mitk::ImageToItkImage(labelSetImage); auto duplicator = DuplicatorType::New(); duplicator->SetInputImage(layerImage); duplicator->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(duplicator->GetOutput())->Clone(); } } mitk::Image::Pointer mitk::ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage) { Image::Pointer image; if (labelSetImage->GetNumberOfLayers() > 0) { if (labelSetImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(labelSetImage, ::ConvertLabelSetImageToImage, 4, (labelSetImage, image)); } else { AccessByItk_2(labelSetImage->GetLayerImage(0), ::ConvertLabelSetImageToImage, labelSetImage, image); } + + image->SetTimeGeometry(labelSetImage->GetTimeGeometry()->Clone()); } return image; } template static void ConvertImageToLabelSetImage(const itk::VectorImage *image, mitk::LabelSetImage::Pointer &labelSetImage) { typedef itk::VectorImage VectorImageType; typedef itk::Image ImageType; typedef itk::VectorIndexSelectionCastImageFilter VectorIndexSelectorType; labelSetImage = mitk::LabelSetImage::New(); auto numberOfLayers = image->GetVectorLength(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerSelector = VectorIndexSelectorType::New(); layerSelector->SetInput(image); layerSelector->SetIndex(layer); layerSelector->Update(); mitk::Image::Pointer layerImage; mitk::CastToMitkImage(layerSelector->GetOutput(), layerImage); if (layer == 0) { labelSetImage->InitializeByLabeledImage(layerImage); } else { labelSetImage->AddLayer(layerImage); } } } mitk::LabelSetImage::Pointer mitk::ConvertImageToLabelSetImage(Image::Pointer image) { LabelSetImage::Pointer labelSetImage; if (image.IsNotNull()) { if (image->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::ImageIOBase::VECTOR) { if (4 == image->GetDimension()) { AccessVectorFixedDimensionByItk_n(image, ::ConvertImageToLabelSetImage, 4, (labelSetImage)); } else { AccessVectorPixelTypeByItk_n(image, ::ConvertImageToLabelSetImage, (labelSetImage)); } } else { labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(image); } + labelSetImage->SetTimeGeometry(image->GetTimeGeometry()->Clone()); } return labelSetImage; } diff --git a/Modules/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index 4f12f499d8..788d6fc8cf 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,26 +1,27 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg + hourglass-half-solid.svg diff --git a/Modules/QtWidgets/resource/hourglass-half-solid.svg b/Modules/QtWidgets/resource/hourglass-half-solid.svg new file mode 100644 index 0000000000..e668867d36 --- /dev/null +++ b/Modules/QtWidgets/resource/hourglass-half-solid.svg @@ -0,0 +1,35 @@ + + diff --git a/Modules/QtWidgets/resource/icon-license.txt b/Modules/QtWidgets/resource/icon-license.txt index 662c801d5f..df5504436b 100644 --- a/Modules/QtWidgets/resource/icon-license.txt +++ b/Modules/QtWidgets/resource/icon-license.txt @@ -1,6 +1,7 @@ See [Font Awsome 4] in Licenses/ICONS.md for: - history-solid.svg based on Font Awsome's history-solid.svg - list-solid.svg based on Font Awsome's list-solid.svg - star-solid.svg based on Font Awsome's star-solid.svg - favorite_add.svg based on Font Awsome's star-solid.svg - favorite_remove.svg based on Font Awsome's star-solid.svg +- hourglass-half-solid.svg based on Font Awsome's hourglass-half-solid.svg diff --git a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp index 45a78fe841..0d86d762d1 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp @@ -1,880 +1,881 @@ /*============================================================================ 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 #include #include #include #include #include #include #include #include #include #include #include "QmitkDataStorageTreeModel.h" #include "QmitkDataStorageTreeModelInternalItem.h" #include "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include QmitkDataStorageTreeModel::QmitkDataStorageTreeModel(mitk::DataStorage *_DataStorage, bool _PlaceNewNodesOnTop, QObject *parent) : QAbstractItemModel(parent), m_DataStorage(nullptr), m_PlaceNewNodesOnTop(_PlaceNewNodesOnTop), m_Root(nullptr), m_BlockDataStorageEvents(false), m_AllowHierarchyChange(false) { this->SetDataStorage(_DataStorage); } QmitkDataStorageTreeModel::~QmitkDataStorageTreeModel() { // set data storage to 0 = remove all listeners this->SetDataStorage(nullptr); m_Root->Delete(); m_Root = nullptr; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::GetNode(const QModelIndex &index) const { return this->TreeItemFromIndex(index)->GetDataNode(); } const mitk::DataStorage::Pointer QmitkDataStorageTreeModel::GetDataStorage() const { return m_DataStorage.Lock(); } QModelIndex QmitkDataStorageTreeModel::index(int row, int column, const QModelIndex &parent) const { TreeItem *parentItem; if (!parent.isValid()) parentItem = m_Root; else parentItem = static_cast(parent.internalPointer()); TreeItem *childItem = parentItem->GetChild(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } int QmitkDataStorageTreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentTreeItem = this->TreeItemFromIndex(parent); return parentTreeItem->GetChildCount(); } Qt::ItemFlags QmitkDataStorageTreeModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } else { return Qt::ItemIsDropEnabled; } } int QmitkDataStorageTreeModel::columnCount(const QModelIndex & /* parent = QModelIndex() */) const { return 1; } QModelIndex QmitkDataStorageTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = this->TreeItemFromIndex(index); TreeItem *parentItem = childItem->GetParent(); if (parentItem == m_Root) return QModelIndex(); return this->createIndex(parentItem->GetIndex(), 0, parentItem); } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItemFromIndex(const QModelIndex &index) const { if (index.isValid()) return static_cast(index.internalPointer()); else return m_Root; } Qt::DropActions QmitkDataStorageTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkDataStorageTreeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } bool QmitkDataStorageTreeModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { // Early exit, returning true, but not actually doing anything (ignoring data). if (action == Qt::IgnoreAction) { return true; } // Note, we are returning true if we handled it, and false otherwise bool returnValue = false; if (data->hasFormat("application/x-qabstractitemmodeldatalist")) { returnValue = true; // First we extract a Qlist of TreeItem* pointers. QList listOfItemsToDrop = ToTreeItemPtrList(data); if (listOfItemsToDrop.empty()) { return false; } // Retrieve the TreeItem* where we are dropping stuff, and its parent. TreeItem *dropItem = this->TreeItemFromIndex(parent); TreeItem *parentItem = dropItem->GetParent(); // If item was dropped onto empty space, we select the root node if (dropItem == m_Root) { parentItem = m_Root; } // Dragging and Dropping is only allowed within the same parent, so use the first item in list to validate. // (otherwise, you could have a derived image such as a segmentation, and assign it to another image). // NOTE: We are assuming the input list is valid... i.e. when it was dragged, all the items had the same parent. // Determine whether or not the drag and drop operation is a valid one. // Examples of invalid operations include: // - dragging nodes with different parents // - dragging nodes from one parent to another parent, if m_AllowHierarchyChange is false // - dragging a node on one of its child nodes (only relevant if m_AllowHierarchyChange is true) bool isValidDragAndDropOperation(true); // different parents { TreeItem *firstParent = listOfItemsToDrop[0]->GetParent(); QList::iterator diIter; for (diIter = listOfItemsToDrop.begin() + 1; diIter != listOfItemsToDrop.end(); diIter++) { if (firstParent != (*diIter)->GetParent()) { isValidDragAndDropOperation = false; break; } } } // dragging from one parent to another if ((!m_AllowHierarchyChange) && isValidDragAndDropOperation) { if (row == -1) // drag onto a node { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == parentItem; } else // drag between nodes { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == dropItem; } } // dragging on a child node of one the dragged nodes { QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *tempItem = dropItem; while (tempItem != m_Root) { tempItem = tempItem->GetParent(); if (tempItem == *diIter) { isValidDragAndDropOperation = false; } } } } if (!isValidDragAndDropOperation) return isValidDragAndDropOperation; if (listOfItemsToDrop[0] != dropItem && isValidDragAndDropOperation) { // Retrieve the index of where we are dropping stuff. QModelIndex parentModelIndex = this->IndexFromTreeItem(parentItem); int dragIndex = 0; // Iterate through the list of TreeItem (which may be at non-consecutive indexes). QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *itemToDrop = *diIter; // if the item is dragged down we have to compensate its final position for the // fact it is deleted lateron, this only applies if it is dragged within the same level if ((itemToDrop->GetIndex() < row) && (itemToDrop->GetParent() == dropItem)) { dragIndex = 1; } // Here we assume that as you remove items, one at a time, that GetIndex() will be valid. this->beginRemoveRows( this->IndexFromTreeItem(itemToDrop->GetParent()), itemToDrop->GetIndex(), itemToDrop->GetIndex()); itemToDrop->GetParent()->RemoveChild(itemToDrop); this->endRemoveRows(); } // row = -1 dropped on an item, row != -1 dropped in between two items // Select the target index position, or put it at the end of the list. int dropIndex = 0; if (row != -1) { if (dragIndex == 0) dropIndex = std::min(row, parentItem->GetChildCount() - 1); else dropIndex = std::min(row - 1, parentItem->GetChildCount() - 1); } else { dropIndex = dropItem->GetIndex(); } QModelIndex dropItemModelIndex = this->IndexFromTreeItem(dropItem); if ((row == -1 && dropItemModelIndex.row() == -1) || dropItemModelIndex.row() > parentItem->GetChildCount()) dropIndex = parentItem->GetChildCount() - 1; // Now insert items again at the drop item position if (m_AllowHierarchyChange) { this->beginInsertRows(dropItemModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } else { this->beginInsertRows(parentModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { // dropped on node, behaviour depends on preference setting if (m_AllowHierarchyChange) { auto dataStorage = m_DataStorage.Lock(); m_BlockDataStorageEvents = true; mitk::DataNode *droppedNode = (*diIter)->GetDataNode(); mitk::DataNode *dropOntoNode = dropItem->GetDataNode(); dataStorage->Remove(droppedNode); dataStorage->Add(droppedNode, dropOntoNode); m_BlockDataStorageEvents = false; dropItem->InsertChild((*diIter), dropIndex); } else { if (row == -1) // drag onto a node { parentItem->InsertChild((*diIter), dropIndex); } else // drag between nodes { dropItem->InsertChild((*diIter), dropIndex); } } dropIndex++; } this->endInsertRows(); // Change Layers to match. this->AdjustLayerProperty(); } } else if (data->hasFormat("application/x-mitk-datanodes")) { returnValue = true; int numberOfNodesDropped = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode *node = nullptr; foreach (node, dataNodeList) { - if (node && !m_DataStorage.IsExpired() && !m_DataStorage.Lock()->Exists(node)) + auto datastorage = m_DataStorage.Lock(); + if (node && datastorage.IsNotNull() && !datastorage->Exists(node)) { m_DataStorage.Lock()->Add(node); mitk::BaseData::Pointer basedata = node->GetData(); if (basedata.IsNotNull()) { mitk::RenderingManager::GetInstance()->InitializeViews( basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); numberOfNodesDropped++; } } } // Only do a rendering update, if we actually dropped anything. if (numberOfNodesDropped > 0) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return returnValue; } QStringList QmitkDataStorageTreeModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << "application/x-qabstractitemmodeldatalist"; types << "application/x-mitk-datanodes"; return types; } QMimeData *QmitkDataStorageTreeModel::mimeData(const QModelIndexList &indexes) const { return mimeDataFromModelIndexList(indexes); } QMimeData *QmitkDataStorageTreeModel::mimeDataFromModelIndexList(const QModelIndexList &indexes) { QMimeData *ret = new QMimeData; QString treeItemAddresses(""); QString dataNodeAddresses(""); QByteArray baTreeItemPtrs; QByteArray baDataNodePtrs; QDataStream dsTreeItemPtrs(&baTreeItemPtrs, QIODevice::WriteOnly); QDataStream dsDataNodePtrs(&baDataNodePtrs, QIODevice::WriteOnly); for (int i = 0; i < indexes.size(); i++) { TreeItem *treeItem = static_cast(indexes.at(i).internalPointer()); dsTreeItemPtrs << reinterpret_cast(treeItem); dsDataNodePtrs << reinterpret_cast(treeItem->GetDataNode().GetPointer()); // --------------- deprecated ----------------- unsigned long long treeItemAddress = reinterpret_cast(treeItem); unsigned long long dataNodeAddress = reinterpret_cast(treeItem->GetDataNode().GetPointer()); QTextStream(&treeItemAddresses) << treeItemAddress; QTextStream(&dataNodeAddresses) << dataNodeAddress; if (i != indexes.size() - 1) { QTextStream(&treeItemAddresses) << ","; QTextStream(&dataNodeAddresses) << ","; } // -------------- end deprecated ------------- } // ------------------ deprecated ----------------- ret->setData("application/x-qabstractitemmodeldatalist", QByteArray(treeItemAddresses.toLatin1())); ret->setData("application/x-mitk-datanodes", QByteArray(dataNodeAddresses.toLatin1())); // --------------- end deprecated ----------------- ret->setData(QmitkMimeTypes::DataStorageTreeItemPtrs, baTreeItemPtrs); ret->setData(QmitkMimeTypes::DataNodePtrs, baDataNodePtrs); return ret; } QVariant QmitkDataStorageTreeModel::data(const QModelIndex &index, int role) const { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); // get name of treeItem (may also be edited) QString nodeName = QString::fromStdString(dataNode->GetName()); if (nodeName.isEmpty()) { nodeName = "unnamed"; } if (role == Qt::DisplayRole) return nodeName; else if (role == Qt::ToolTipRole) return nodeName; else if (role == Qt::DecorationRole) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (role == Qt::CheckStateRole) { return dataNode->IsVisible(nullptr); } else if (role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageTreeModel::DicomPropertiesExists(const mitk::DataNode &node) const { bool propertiesExists = false; mitk::BaseProperty *seriesDescription_deprecated = (node.GetProperty("dicom.series.SeriesDescription")); mitk::BaseProperty *studyDescription_deprecated = (node.GetProperty("dicom.study.StudyDescription")); mitk::BaseProperty *patientsName_deprecated = (node.GetProperty("dicom.patient.PatientsName")); mitk::BaseProperty *seriesDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); mitk::BaseProperty *studyDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); mitk::BaseProperty *patientsName = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); if (patientsName != nullptr && studyDescription != nullptr && seriesDescription != nullptr) { if ((!patientsName->GetValueAsString().empty()) && (!studyDescription->GetValueAsString().empty()) && (!seriesDescription->GetValueAsString().empty())) { propertiesExists = true; } } /** Code coveres the deprecated property naming for backwards compatibility */ if (patientsName_deprecated != nullptr && studyDescription_deprecated != nullptr && seriesDescription_deprecated != nullptr) { if ((!patientsName_deprecated->GetValueAsString().empty()) && (!studyDescription_deprecated->GetValueAsString().empty()) && (!seriesDescription_deprecated->GetValueAsString().empty())) { propertiesExists = true; } } return propertiesExists; } QVariant QmitkDataStorageTreeModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && m_Root) return QString::fromStdString(m_Root->GetDataNode()->GetName()); return QVariant(); } void QmitkDataStorageTreeModel::SetDataStorage(mitk::DataStorage *_DataStorage) { if (m_DataStorage != _DataStorage) // dont take the same again { - if (!m_DataStorage.IsExpired()) + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNotNull()) { - auto dataStorage = m_DataStorage.Lock(); - // remove Listener for the data storage itself dataStorage->RemoveObserver(m_DataStorageDeletedTag); // remove listeners for the nodes dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageTreeModel::AddNode)); dataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); } + this->beginResetModel(); + // take over the new data storage m_DataStorage = _DataStorage; // delete the old root (if necessary, create new) if (m_Root) m_Root->Delete(); mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Manager"); m_Root = new TreeItem(rootDataNode, nullptr); - this->beginResetModel(); - this->endResetModel(); - if (!m_DataStorage.IsExpired()) + dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNotNull()) { - auto dataStorage = m_DataStorage.Lock(); - // add Listener for the data storage itself auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkDataStorageTreeModel::SetDataStorageDeleted); m_DataStorageDeletedTag = dataStorage->AddObserver(itk::DeleteEvent(), command); // add listeners for the nodes dataStorage->AddNodeEvent.AddListener(mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::AddNode)); dataStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); dataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = dataStorage->GetSubset(m_Predicate); - // finally add all nodes to the model this->Update(); } + + this->endResetModel(); } } void QmitkDataStorageTreeModel::SetDataStorageDeleted() { this->SetDataStorage(nullptr); } void QmitkDataStorageTreeModel::AddNodeInternal(const mitk::DataNode *node) { if (node == nullptr || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; // find out if we have a root node TreeItem *parentTreeItem = m_Root; QModelIndex index; mitk::DataNode *parentDataNode = this->GetParentNode(node); if (parentDataNode) // no top level data node { parentTreeItem = m_Root->Find(parentDataNode); // find the corresponding tree item if (!parentTreeItem) { this->AddNode(parentDataNode); parentTreeItem = m_Root->Find(parentDataNode); if (!parentTreeItem) return; } // get the index of this parent with the help of the grand parent index = this->createIndex(parentTreeItem->GetIndex(), 0, parentTreeItem); } // add node if (m_PlaceNewNodesOnTop) { // emit beginInsertRows event beginInsertRows(index, 0, 0); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), 0); } else { int firstRowWithASiblingBelow = 0; int nodeLayer = -1; node->GetIntProperty("layer", nodeLayer); for (TreeItem* siblingTreeItem: parentTreeItem->GetChildren()) { int siblingLayer = -1; if (mitk::DataNode* siblingNode = siblingTreeItem->GetDataNode()) { siblingNode->GetIntProperty("layer", siblingLayer); } if (nodeLayer > siblingLayer) { break; } ++firstRowWithASiblingBelow; } beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), firstRowWithASiblingBelow); } // emit endInsertRows event endInsertRows(); if(m_PlaceNewNodesOnTop) { this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::AddNode(const mitk::DataNode *node) { if (node == nullptr || m_BlockDataStorageEvents || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; this->AddNodeInternal(node); } void QmitkDataStorageTreeModel::SetPlaceNewNodesOnTop(bool _PlaceNewNodesOnTop) { m_PlaceNewNodesOnTop = _PlaceNewNodesOnTop; } void QmitkDataStorageTreeModel::RemoveNodeInternal(const mitk::DataNode *node) { if (!m_Root) return; TreeItem *treeItem = m_Root->Find(node); if (!treeItem) return; // return because there is no treeitem containing this node TreeItem *parentTreeItem = treeItem->GetParent(); QModelIndex parentIndex = this->IndexFromTreeItem(parentTreeItem); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(parentIndex, treeItem->GetIndex(), treeItem->GetIndex()); // remove node - std::vector children = treeItem->GetChildren(); + std::vector children = treeItem->GetChildren(); delete treeItem; // emit endRemoveRows event endRemoveRows(); // move all children of deleted node into its parent - for (std::vector::iterator it = children.begin(); it != children.end(); it++) + for (std::vector::iterator it = children.begin(); it != children.end(); it++) { // emit beginInsertRows event beginInsertRows(parentIndex, parentTreeItem->GetChildCount(), parentTreeItem->GetChildCount()); // add nodes again parentTreeItem->AddChild(*it); // emit endInsertRows event endInsertRows(); } this->AdjustLayerProperty(); } void QmitkDataStorageTreeModel::RemoveNode(const mitk::DataNode *node) { if (node == nullptr || m_BlockDataStorageEvents) return; this->RemoveNodeInternal(node); } void QmitkDataStorageTreeModel::SetNodeModified(const mitk::DataNode *node) { TreeItem *treeItem = m_Root->Find(node); if (treeItem) { TreeItem *parentTreeItem = treeItem->GetParent(); // as the root node should not be removed one should always have a parent item if (!parentTreeItem) return; QModelIndex index = this->createIndex(treeItem->GetIndex(), 0, treeItem); // now emit the dataChanged signal emit dataChanged(index, index); } } mitk::DataNode *QmitkDataStorageTreeModel::GetParentNode(const mitk::DataNode *node) const { mitk::DataNode *dataNode = nullptr; mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage.Lock()->GetSources(node); if (_Sources->Size() > 0) dataNode = _Sources->front(); return dataNode; } bool QmitkDataStorageTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if (!dataNode) return false; if (role == Qt::EditRole && !value.toString().isEmpty()) { dataNode->SetStringProperty("name", value.toString().toStdString().c_str()); mitk::PlanarFigure *planarFigure = dynamic_cast(dataNode->GetData()); if (planarFigure != nullptr) mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (role == Qt::CheckStateRole) { // Please note: value.toInt() returns 2, independentely from the actual checkstate of the index element. // Therefore the checkstate is being estimated again here. QVariant qcheckstate = index.data(Qt::CheckStateRole); int checkstate = qcheckstate.toInt(); bool isVisible = bool(checkstate); dataNode->SetVisibility(!isVisible); emit nodeVisibilityChanged(); } // inform listeners about changes emit dataChanged(index, index); return true; } bool QmitkDataStorageTreeModel::setHeaderData(int /*section*/, Qt::Orientation /*orientation*/, const QVariant & /* value */, int /*role = Qt::EditRole*/) { return false; } void QmitkDataStorageTreeModel::AdjustLayerProperty() { /// transform the tree into an array and set the layer property descending std::vector vec; this->TreeToVector(m_Root, vec); int i = vec.size() - 1; for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) { mitk::DataNode::Pointer dataNode = (*it)->GetDataNode(); bool fixedLayer = false; if (!(dataNode->GetBoolProperty("fixedLayer", fixedLayer) && fixedLayer)) dataNode->SetIntProperty("layer", i); --i; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkDataStorageTreeModel::TreeToVector(TreeItem *parent, std::vector &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); this->TreeToVector(current, vec); vec.push_back(current); } } QModelIndex QmitkDataStorageTreeModel::IndexFromTreeItem(TreeItem *item) const { if (item == m_Root) return QModelIndex(); else return this->createIndex(item->GetIndex(), 0, item); } QList QmitkDataStorageTreeModel::GetNodeSet() const { QList res; if (m_Root) this->TreeToNodeSet(m_Root, res); return res; } void QmitkDataStorageTreeModel::TreeToNodeSet(TreeItem *parent, QList &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); vec.push_back(current->GetDataNode()); this->TreeToNodeSet(current, vec); } } QModelIndex QmitkDataStorageTreeModel::GetIndex(const mitk::DataNode *node) const { if (m_Root) { TreeItem *item = m_Root->Find(node); if (item) return this->IndexFromTreeItem(item); } return QModelIndex(); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QMimeData *mimeData) { if (mimeData == nullptr || !mimeData->hasFormat(QmitkMimeTypes::DataStorageTreeItemPtrs)) { return QList(); } return ToTreeItemPtrList(mimeData->data(QmitkMimeTypes::DataStorageTreeItemPtrs)); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QByteArray &ba) { QList result; QDataStream ds(ba); while (!ds.atEnd()) { quintptr treeItemPtr; ds >> treeItemPtr; result.push_back(reinterpret_cast(treeItemPtr)); } return result; } void QmitkDataStorageTreeModel::Update() { - if (!m_DataStorage.IsExpired()) + auto datastorage = m_DataStorage.Lock(); + if (datastorage.IsNotNull()) { - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage.Lock()->GetAll(); + mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = datastorage->GetAll(); /// Regardless the value of this preference, the new nodes must not be inserted /// at the top now, but at the position according to their layer. bool newNodesWereToBePlacedOnTop = m_PlaceNewNodesOnTop; m_PlaceNewNodesOnTop = false; - for (const auto& node: *_NodeSet) + for (const auto& node : *_NodeSet) { this->AddNodeInternal(node); } m_PlaceNewNodesOnTop = newNodesWereToBePlacedOnTop; /// Adjust the layers to ensure that derived nodes are above their sources. this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::SetAllowHierarchyChange(bool allowHierarchyChange) { m_AllowHierarchyChange = allowHierarchyChange; } diff --git a/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp index 2b0baa06e0..0caa074875 100644 --- a/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp @@ -1,233 +1,233 @@ /*============================================================================ 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. ============================================================================*/ // semantic relations UI module #include "QmitkStatisticsCalculator.h" // semantic relations module #include #include #include #include #include // mitk image statistics module #include #include #include #include QmitkStatisticsCalculator::QmitkStatisticsCalculator() : m_CalculationJob(nullptr) , m_DataStorage(nullptr) , m_MaskVolume(0.0) { m_CalculationJob = new QmitkImageStatisticsCalculationJob(); connect(m_CalculationJob, &QmitkImageStatisticsCalculationJob::finished, this, &QmitkStatisticsCalculator::OnStatisticsCalculationEnds, Qt::QueuedConnection); } QmitkStatisticsCalculator::~QmitkStatisticsCalculator() { if (!m_CalculationJob->isFinished()) { m_CalculationJob->terminate(); m_CalculationJob->wait(); } m_CalculationJob->deleteLater(); } void QmitkStatisticsCalculator::ComputeLesionVolume(mitk::LesionData& lesionData, const mitk::SemanticTypes::CaseID& caseID) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); std::vector lesionVolume; mitk::SemanticTypes::Lesion lesion = lesionData.GetLesion(); double volume = 0.0; mitk::SemanticTypes::ControlPointVector controlPoints = mitk::RelationStorage::GetAllControlPointsOfCase(caseID); // sort the vector of control points for the timeline std::sort(controlPoints.begin(), controlPoints.end()); mitk::SemanticTypes::InformationTypeVector informationTypes = mitk::RelationStorage::GetAllInformationTypesOfCase(caseID); for (const auto& informationType : informationTypes) { for (const auto& controlPoint : controlPoints) { mitk::SemanticRelationsDataStorageAccess semanticRelationsDataStorageAccess(dataStorage); mitk::DataNode::Pointer specificImage; mitk::DataNode::Pointer specificSegmentation; try { specificSegmentation = semanticRelationsDataStorageAccess.GetSpecificSegmentation(caseID, controlPoint, informationType, lesion); if (nullptr == specificSegmentation) { volume = 0.0; } else { // get parent node of the specific segmentation node with the node predicate auto parentNodes = dataStorage->GetSources(specificSegmentation, mitk::NodePredicates::GetImagePredicate(), false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { specificImage = it->Value(); } volume = GetSegmentationMaskVolume(specificImage, specificSegmentation); } } catch (mitk::SemanticRelationException&) { volume = 0.0; } lesionVolume.push_back(volume); } } lesionData.SetLesionVolume(lesionVolume); } double QmitkStatisticsCalculator::GetSegmentationMaskVolume(mitk::DataNode::Pointer imageNode, mitk::DataNode::Pointer segmentationNode) { m_MaskVolume = 0.0; if (m_DataStorage.IsExpired()) { return m_MaskVolume; } auto dataStorage = m_DataStorage.Lock(); if (imageNode.IsNull() || segmentationNode.IsNull()) { return m_MaskVolume; } m_ImageNode = imageNode; m_SegmentationNode = segmentationNode; auto image = dynamic_cast(m_ImageNode->GetData()); auto segmentation = dynamic_cast(m_SegmentationNode->GetData()); if (nullptr == image || nullptr == segmentation) { return m_MaskVolume; } // all nodes and images are valid, retrieve statistics - mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation); + mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation).GetPointer(); bool imageStatisticsOlderThanInputs = false; if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || (imageStatistics->GetMTime() < segmentation->GetMTime()))) { imageStatisticsOlderThanInputs = true; } // statistics need to be (re)computed if (!imageStatistics || imageStatisticsOlderThanInputs) { m_CalculationJob->Initialize(image, segmentation, nullptr); try { m_CalculationJob->start(); return m_MaskVolume; } catch (const std::exception&) { return m_MaskVolume; } } // use a valid statistics object to get the volume of the image-segmentation pair mitk::ImageStatisticsContainer::ImageStatisticsObject statisticsObject; try { statisticsObject = imageStatistics->GetStatisticsForTimeStep(0); } catch (mitk::Exception&) { return m_MaskVolume; } try { if (statisticsObject.HasStatistic(mitk::ImageStatisticsConstants::VOLUME())) { auto valueVariant = statisticsObject.GetValueNonConverted(mitk::ImageStatisticsConstants::VOLUME()); m_MaskVolume = boost::get(valueVariant); } } catch (mitk::Exception&) { return m_MaskVolume; } return m_MaskVolume; } void QmitkStatisticsCalculator::OnStatisticsCalculationEnds() { // taken from 'QmitkImageStatisticsView' (see measurementtoolbox plugin) if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); if (m_CalculationJob->GetStatisticsUpdateSuccessFlag()) { auto statistic = m_CalculationJob->GetStatisticsData(); auto image = m_CalculationJob->GetStatisticsImage(); mitk::BaseData::ConstPointer mask = nullptr; auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statistic, image); if (m_CalculationJob->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); mask = m_CalculationJob->GetMaskImage(); maskRule->Connect(statistic, mask); } auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, mask); // if statistics base data already exist: add to existing node if (nullptr != imageStatistics) { auto allDataNodes = dataStorage->GetAll()->CastToSTLConstContainer(); for (auto node : allDataNodes) { auto nodeData = node->GetData(); if (nullptr != nodeData && nodeData->GetUID() == imageStatistics->GetUID()) { node->SetData(statistic); } } } // statistics base data does not exist: add new node else { auto statisticsNodeName = m_ImageNode->GetName(); if (m_SegmentationNode) { statisticsNodeName += "_" + m_SegmentationNode->GetName(); } statisticsNodeName += "_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statistic, statisticsNodeName); dataStorage->Add(statisticsNode); } } } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp index aa8adaac9f..56e426b8bf 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp @@ -1,278 +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. ============================================================================*/ #include "QmitkNodeSelectionDialog.h" #include #include #include #include #include QmitkNodeSelectionDialog::QmitkNodeSelectionDialog(QWidget* parent, QString title, QString hint) : QDialog(parent), m_NodePredicate(nullptr), m_SelectOnlyVisibleNodes(false), m_SelectedNodes(NodeList()), m_SelectionMode(QAbstractItemView::SingleSelection) { m_Controls.setupUi(this); m_CheckFunction = [](const NodeList &) { return ""; }; auto providers = mitk::DataStorageInspectorGenerator::GetProviders(); auto visibleProviders = mitk::GetVisibleDataStorageInspectors(); auto preferredID = mitk::GetPreferredDataStorageInspector(); if (visibleProviders.empty()) { MITK_DEBUG << "No presets for visible node selection inspectors available. Use fallback (show all available inspectors)"; unsigned int order = 0; for (auto proIter : providers) { visibleProviders.insert(std::make_pair(order, proIter.first)); ++order; } } int preferredIndex = 0; bool preferredFound = false; for (auto proIter : visibleProviders) { auto finding = providers.find(proIter.second); if (finding != providers.end()) { if (finding->second->GetInspectorID() != QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID() && finding->second->GetInspectorID() != QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()) { auto provider = finding->second; this->AddPanel(provider, preferredID, preferredFound, preferredIndex); } } else { MITK_DEBUG << "No provider registered for inspector that is defined as visible in the preferences. Illegal inspector ID: " << proIter.second; } } if (mitk::GetShowFavoritesInspector()) { auto favoritesPorvider = mitk::DataStorageInspectorGenerator::GetProvider(QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID()); if (favoritesPorvider != nullptr) { this->AddPanel(favoritesPorvider, preferredID, preferredFound, preferredIndex); } } if (mitk::GetShowHistoryInspector()) { auto historyPorvider = mitk::DataStorageInspectorGenerator::GetProvider(QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()); if (historyPorvider != nullptr) { this->AddPanel(historyPorvider, preferredID, preferredFound, preferredIndex); } } m_Controls.tabWidget->setCurrentIndex(preferredIndex); this->setWindowTitle(title); this->setToolTip(hint); m_Controls.hint->setText(hint); m_Controls.hint->setVisible(!hint.isEmpty()); if(hint.isEmpty()) { m_Controls.layoutHint->setContentsMargins(0, 0, 0, 0); } else { m_Controls.layoutHint->setContentsMargins(6, 6, 6, 6); } this->SetErrorText(""); m_Controls.btnAddToFav->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/favorite_add.svg"))); connect(m_Controls.btnAddToFav, &QPushButton::clicked, this, &QmitkNodeSelectionDialog::OnFavoriteNodesButtonClicked); connect(m_Controls.buttonBox, &QDialogButtonBox::accepted, this, &QmitkNodeSelectionDialog::OnOK); connect(m_Controls.buttonBox, &QDialogButtonBox::rejected, this, &QmitkNodeSelectionDialog::OnCancel); } void QmitkNodeSelectionDialog::SetDataStorage(mitk::DataStorage* dataStorage) { if (m_DataStorage != dataStorage) { m_DataStorage = dataStorage; if (!m_DataStorage.IsExpired()) { for (auto panel : m_Panels) { panel->SetDataStorage(dataStorage); } } } } void QmitkNodeSelectionDialog::SetNodePredicate(const mitk::NodePredicateBase* nodePredicate) { if (m_NodePredicate != nodePredicate) { m_NodePredicate = nodePredicate; for (auto panel : m_Panels) { panel->SetNodePredicate(m_NodePredicate); } } } const mitk::NodePredicateBase* QmitkNodeSelectionDialog::GetNodePredicate() const { return m_NodePredicate; } QmitkNodeSelectionDialog::NodeList QmitkNodeSelectionDialog::GetSelectedNodes() const { return m_SelectedNodes; } void QmitkNodeSelectionDialog::SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction) { m_CheckFunction = checkFunction; auto checkResponse = m_CheckFunction(m_SelectedNodes); SetErrorText(checkResponse); m_Controls.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(checkResponse.empty()); } void QmitkNodeSelectionDialog::SetErrorText(const std::string& checkResponse) { m_Controls.error->setText(QString::fromStdString(checkResponse)); m_Controls.error->setVisible(!checkResponse.empty()); if (checkResponse.empty()) { m_Controls.layoutError->setContentsMargins(0, 0, 0, 0); } else { m_Controls.layoutError->setContentsMargins(6, 6, 6, 6); } } bool QmitkNodeSelectionDialog::GetSelectOnlyVisibleNodes() const { return m_SelectOnlyVisibleNodes; } void QmitkNodeSelectionDialog::SetSelectionMode(SelectionMode mode) { m_SelectionMode = mode; for (auto panel : m_Panels) { panel->SetSelectionMode(mode); } } QmitkNodeSelectionDialog::SelectionMode QmitkNodeSelectionDialog::GetSelectionMode() const { return m_SelectionMode; } void QmitkNodeSelectionDialog::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) { if (m_SelectOnlyVisibleNodes != selectOnlyVisibleNodes) { m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; for (auto panel : m_Panels) { panel->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); } } } void QmitkNodeSelectionDialog::SetCurrentSelection(NodeList selectedNodes) { m_SelectedNodes = selectedNodes; auto checkResponse = m_CheckFunction(m_SelectedNodes); SetErrorText(checkResponse); m_Controls.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(checkResponse.empty()); for (auto panel : m_Panels) { panel->SetCurrentSelection(selectedNodes); } } void QmitkNodeSelectionDialog::OnSelectionChanged(NodeList selectedNodes) { SetCurrentSelection(selectedNodes); emit CurrentSelectionChanged(selectedNodes); } void QmitkNodeSelectionDialog::OnFavoriteNodesButtonClicked() { for (auto node : m_SelectedNodes) { node->SetBoolProperty("org.mitk.selection.favorite", true); } } void QmitkNodeSelectionDialog::OnOK() { for (auto node : m_SelectedNodes) { QmitkDataStorageSelectionHistoryInspector::AddNodeToHistory(node); } this->accept(); } void QmitkNodeSelectionDialog::OnCancel() { this->reject(); } void QmitkNodeSelectionDialog::AddPanel(const mitk::IDataStorageInspectorProvider * provider, const mitk::IDataStorageInspectorProvider::InspectorIDType& preferredID, bool &preferredFound, int &preferredIndex) { auto inspector = provider->CreateInspector(); QString name = QString::fromStdString(provider->GetInspectorDisplayName()); QString desc = QString::fromStdString(provider->GetInspectorDescription()); inspector->setParent(this); inspector->SetSelectionMode(m_SelectionMode); auto tabPanel = new QWidget(); tabPanel->setObjectName(QString("tab_") + name); tabPanel->setToolTip(desc); auto verticalLayout = new QVBoxLayout(tabPanel); verticalLayout->setSpacing(0); verticalLayout->setContentsMargins(0, 0, 0, 0); verticalLayout->addWidget(inspector); auto panelPos = m_Controls.tabWidget->insertTab(m_Controls.tabWidget->count(), tabPanel, name); auto icon = provider->GetInspectorIcon(); if (!icon.isNull()) { m_Controls.tabWidget->setTabIcon(panelPos, icon); } m_Panels.push_back(inspector); connect(inspector, &QmitkAbstractDataStorageInspector::CurrentSelectionChanged, this, &QmitkNodeSelectionDialog::OnSelectionChanged); + connect(inspector->GetView(), &QAbstractItemView::doubleClicked, this, &QmitkNodeSelectionDialog::OnDoubleClicked); preferredFound = preferredFound || provider->GetInspectorID() == preferredID; if (!preferredFound) { ++preferredIndex; } } + +void QmitkNodeSelectionDialog::OnDoubleClicked(const QModelIndex& /*index*/) +{ + if (!m_SelectedNodes.empty()) + { + this->OnOK(); + } +} diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h index 91bafa3581..25ccda870d 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h @@ -1,142 +1,143 @@ /*============================================================================ 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_NODE_SELECTION_DIALOG_H #define QMITK_NODE_SELECTION_DIALOG_H #include #include #include #include "mitkIDataStorageInspectorProvider.h" #include #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkNodeSelectionDialog.h" #include #include /** * \class QmitkNodeSelectionDialog * \brief Widget that allows to show and edit the content of an mitk::IsoDoseLevel instance. */ class MITK_QT_COMMON QmitkNodeSelectionDialog : public QDialog { Q_OBJECT public: explicit QmitkNodeSelectionDialog(QWidget* parent = nullptr, QString caption = "", QString hint = ""); /** * @brief Sets the data storage that will be used /monitored by widget. * * @param dataStorage A pointer to the data storage to set. */ void SetDataStorage(mitk::DataStorage* dataStorage); /** * @brief Sets the node predicate and updates the widget, according to the node predicate. * * @param nodePredicate A pointer to node predicate. */ virtual void SetNodePredicate(const mitk::NodePredicateBase* nodePredicate); const mitk::NodePredicateBase* GetNodePredicate() const; using NodeList = QList; NodeList GetSelectedNodes() const; /** * @brief Helper function that is used to check the given selection for consistency. * Returning an empty string assumes that everything is alright and the selection is valid. * If the string is not empty, the content of the string will be used as error message. */ using SelectionCheckFunctionType = std::function; /** * @brief A selection check function can be set. If set the dialog uses this function to check the made/set selection. * If the selection is valid, everything is fine. * If the selection is indicated as invalid, the dialog will display the selection check function error message. */ void SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction); bool GetSelectOnlyVisibleNodes() const; using SelectionMode = QAbstractItemView::SelectionMode; void SetSelectionMode(SelectionMode mode); SelectionMode GetSelectionMode() const; Q_SIGNALS: /* * @brief A signal that will be emitted if the selected node has changed. * * @param nodes A list of data nodes that are newly selected. */ void CurrentSelectionChanged(NodeList nodes); public Q_SLOTS: /* * @brief Change the selection modus of the item view's selection model. * * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. * An outgoing selection can then at most contain the filtered nodes. * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, * to include the original selection that could not be modified. * The part of the original selection, that is non-visible are the nodes that are not * * @param selectOnlyVisibleNodes The bool value to define the selection modus. */ void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); /* * @brief Transform a list of data nodes into a model selection and set this as a new selection of the * selection model of the private member item view. * * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). * * @param nodes A list of data nodes that should be newly selected. */ void SetCurrentSelection(NodeList selectedNodes); protected Q_SLOTS: void OnSelectionChanged(NodeList selectedNodes); void OnFavoriteNodesButtonClicked(); void OnOK(); void OnCancel(); + void OnDoubleClicked(const QModelIndex& index); protected: void SetErrorText(const std::string& checkResponse); void AddPanel(const mitk::IDataStorageInspectorProvider* provider, const mitk::IDataStorageInspectorProvider::InspectorIDType &preferredID, bool &preferredFound, int &preferredIndex); mitk::WeakPointer m_DataStorage; mitk::NodePredicateBase::ConstPointer m_NodePredicate; bool m_SelectOnlyVisibleNodes; NodeList m_SelectedNodes; SelectionCheckFunctionType m_CheckFunction; SelectionMode m_SelectionMode; using PanelVectorType = std::vector; PanelVectorType m_Panels; QPushButton* m_FavoriteNodesButton; Ui_QmitkNodeSelectionDialog m_Controls; }; #endif // QMITK_NODE_SELECTION_DIALOG_H diff --git a/Plugins/org.mitk.gui.qt.dicom/CMakeLists.txt b/Plugins/org.mitk.gui.qt.dicom/CMakeLists.txt index e42f755446..9e021c2a81 100644 --- a/Plugins/org.mitk.gui.qt.dicom/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.dicom/CMakeLists.txt @@ -1,28 +1,28 @@ project(org_mitk_gui_qt_dicom) # Note: # If we use an installed version of DCMTK then DCMTK_DIR points to the subdirectory # of the installation directory (share/dcmtk) that contains DCMTKConfig.cmake. # Therefore we look for the the storescp command in the '../../bin' directory, too. find_program(DCMTK_STORESCP NAMES storescp storescp${DCMTK_CMAKE_DEBUG_POSTFIX} storescp${CMAKE_DEBUG_POSTFIX} PATHS "${MITK_EXTERNAL_PROJECT_PREFIX}/bin" PATH_SUFFIXES Release Debug DOC "Dcmtk storage provider which is used to store dicom files which are transfered over network." NO_DEFAULT_PATH ) mark_as_advanced(DCMTK_STORESCP) if(NOT EXISTS ${DCMTK_STORESCP}) message(WARNING "Couldn't find program storescp: Query/retrieve of the DICOM plugin won't work!") else() configure_file( org_mitk_gui_qt_dicom_config.h.in org_mitk_gui_qt_dicom_config.h @ONLY) MITK_INSTALL_HELPER_APP(EXECUTABLES ${DCMTK_STORESCP}) mitk_create_plugin( EXPORT_DIRECTIVE DICOM_EXPORT EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkQtWidgetsExt MitkDicomUI MitkDicomRT MitkDicomRTIO MitkRTUI MitkDICOMReader + MODULE_DEPENDS MitkContourModel MitkQtWidgetsExt MitkDicomUI MitkDicomRT MitkRTUI MitkDICOMReader ) endif() diff --git a/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp b/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp index 28bb270cac..e253097e87 100644 --- a/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp +++ b/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp @@ -1,251 +1,266 @@ /*============================================================================ 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 "mitkPluginActivator.h" #include "DicomEventHandler.h" #include #include #include #include #include #include #include #include #include #include "mitkImage.h" #include +#include +#include #include #include #include #include #include #include #include "mitkBaseDICOMReaderService.h" -#include -#include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +namespace +{ + mitk::IFileReader* GetReader(mitk::FileReaderRegistry& readerRegistry, const mitk::CustomMimeType& mimeType) + { + try + { + return readerRegistry.GetReaders(mitk::MimeType(mimeType, -1, -1)).at(0); + } + catch (const std::out_of_range&) + { + mitkThrow() << "Cannot find " << mimeType.GetCategory() << " " << mimeType.GetComment() << " file reader."; + } + } +} + DicomEventHandler::DicomEventHandler() { } DicomEventHandler::~DicomEventHandler() { } void DicomEventHandler::OnSignalAddSeriesToDataManager(const ctkEvent& ctkEvent) { QStringList listOfFilesForSeries; listOfFilesForSeries = ctkEvent.getProperty("FilesForSeries").toStringList(); if (!listOfFilesForSeries.isEmpty()) { //for rt data, if the modality tag isn't defined or is "CT" the image is handled like before if(ctkEvent.containsProperty("Modality") && (ctkEvent.getProperty("Modality").toString().compare("RTDOSE",Qt::CaseInsensitive) == 0 || ctkEvent.getProperty("Modality").toString().compare("RTSTRUCT",Qt::CaseInsensitive) == 0 || ctkEvent.getProperty("Modality").toString().compare("RTPLAN", Qt::CaseInsensitive) == 0)) { QString modality = ctkEvent.getProperty("Modality").toString(); + mitk::FileReaderRegistry readerRegistry; if(modality.compare("RTDOSE",Qt::CaseInsensitive) == 0) { - auto doseReader = mitk::RTDoseReaderService(); - doseReader.SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); - std::vector > readerOutput = doseReader.Read(); + auto doseReader = GetReader(readerRegistry, mitk::DicomRTMimeTypes::DICOMRT_DOSE_MIMETYPE()); + doseReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); + std::vector > readerOutput = doseReader->Read(); if (!readerOutput.empty()){ mitk::Image::Pointer doseImage = dynamic_cast(readerOutput.at(0).GetPointer()); mitk::DataNode::Pointer doseImageNode = mitk::DataNode::New(); doseImageNode->SetData(doseImage); doseImageNode->SetName("RTDose"); if (doseImage != nullptr) { std::string sopUID; if (mitk::GetBackwardsCompatibleDICOMProperty(0x0008, 0x0016, "dicomseriesreader.SOPClassUID", doseImage->GetPropertyList(), sopUID)) { doseImageNode->SetName(sopUID); }; berry::IPreferencesService* prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::RTUIConstants::ROOT_DOSE_VIS_PREFERENCE_NODE_ID.c_str()); if (prefNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find preset node under given name. Name: " << prefNode->ToString().toStdString(); } //set some specific colorwash and isoline properties bool showColorWashGlobal = prefNode->GetBool(mitk::RTUIConstants::GLOBAL_VISIBILITY_COLORWASH_ID.c_str(), true); //Set reference dose property double referenceDose = prefNode->GetDouble(mitk::RTUIConstants::REFERENCE_DOSE_ID.c_str(), mitk::RTUIConstants::DEFAULT_REFERENCE_DOSE_VALUE); mitk::ConfigureNodeAsDoseNode(doseImageNode, mitk::GenerateIsoLevels_Virtuos(), referenceDose, showColorWashGlobal); ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); dataStorage->Add(doseImageNode); mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); } }//END DOSE } else if(modality.compare("RTSTRUCT",Qt::CaseInsensitive) == 0) { - auto structReader = mitk::RTStructureSetReaderService(); - structReader.SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); - std::vector > readerOutput = structReader.Read(); + auto structReader = GetReader(readerRegistry, mitk::DicomRTMimeTypes::DICOMRT_STRUCT_MIMETYPE()); + structReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); + std::vector > readerOutput = structReader->Read(); if (readerOutput.empty()){ MITK_ERROR << "No structure sets were created" << endl; } else { std::vector modelVector; ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); for (const auto& aStruct : readerOutput){ mitk::ContourModelSet::Pointer countourModelSet = dynamic_cast(aStruct.GetPointer()); mitk::DataNode::Pointer structNode = mitk::DataNode::New(); structNode->SetData(countourModelSet); structNode->SetProperty("name", aStruct->GetProperty("name")); structNode->SetProperty("color", aStruct->GetProperty("contour.color")); structNode->SetProperty("contour.color", aStruct->GetProperty("contour.color")); structNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); structNode->SetVisibility(true, mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); structNode->SetVisibility(false, mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); structNode->SetVisibility(false, mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); structNode->SetVisibility(true, mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); dataStorage->Add(structNode); } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); } } else if (modality.compare("RTPLAN", Qt::CaseInsensitive) == 0) { - auto planReader = mitk::RTPlanReaderService(); - planReader.SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); - std::vector > readerOutput = planReader.Read(); + auto planReader = GetReader(readerRegistry, mitk::DicomRTMimeTypes::DICOMRT_PLAN_MIMETYPE()); + planReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); + std::vector > readerOutput = planReader->Read(); if (!readerOutput.empty()){ //there is no image, only the properties are interesting mitk::Image::Pointer planDummyImage = dynamic_cast(readerOutput.at(0).GetPointer()); mitk::DataNode::Pointer planImageNode = mitk::DataNode::New(); planImageNode->SetData(planDummyImage); planImageNode->SetName("RTPlan"); ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); dataStorage->Add(planImageNode); } } } else { mitk::StringList seriesToLoad; QStringListIterator it(listOfFilesForSeries); while (it.hasNext()) { seriesToLoad.push_back(ImporterUtil::getUTF8String(it.next())); } //Get Reference for default data storage. ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); std::vector baseDatas = mitk::IOUtil::Load(seriesToLoad.front()); for (const auto &data : baseDatas) { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(data); std::string nodeName = mitk::DataNode::NO_NAME_VALUE(); auto nameDataProp = data->GetProperty("name"); if (nameDataProp.IsNotNull()) { //if data has a name property set by reader, use this name nodeName = nameDataProp->GetValueAsString(); } else { //reader didn't specify a name, generate one. nodeName = mitk::GenerateNameFromDICOMProperties(node); } node->SetName(nodeName); dataStorage->Add(node); } } } else { MITK_INFO << "There are no files for the current series"; } } void DicomEventHandler::OnSignalRemoveSeriesFromStorage(const ctkEvent& /*ctkEvent*/) { } void DicomEventHandler::SubscribeSlots() { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); if (ref) { ctkEventAdmin* eventAdmin = mitk::PluginActivator::getContext()->getService(ref); ctkDictionary properties; properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/ADD"; eventAdmin->subscribeSlot(this, SLOT(OnSignalAddSeriesToDataManager(ctkEvent)), properties); properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/DELETED"; eventAdmin->subscribeSlot(this, SLOT(OnSignalRemoveSeriesFromStorage(ctkEvent)), properties); } } diff --git a/Plugins/org.mitk.gui.qt.dicom/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.dicom/src/internal/mitkPluginActivator.cpp index e2ea8a3312..f76afa99af 100644 --- a/Plugins/org.mitk.gui.qt.dicom/src/internal/mitkPluginActivator.cpp +++ b/Plugins/org.mitk.gui.qt.dicom/src/internal/mitkPluginActivator.cpp @@ -1,36 +1,39 @@ /*============================================================================ 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 "mitkPluginActivator.h" #include "QmitkDicomBrowser.h" #include "QmitkDicomPreferencePage.h" +#include + +US_INITIALIZE_MODULE namespace mitk { ctkPluginContext* PluginActivator::pluginContext = nullptr; void PluginActivator::start(ctkPluginContext* context) { BERRY_REGISTER_EXTENSION_CLASS(QmitkDicomBrowser, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkDicomPreferencePage, context) pluginContext = context; } void PluginActivator::stop(ctkPluginContext* context) { Q_UNUSED(context) pluginContext = nullptr; } ctkPluginContext* PluginActivator::getContext() { return pluginContext; } } diff --git a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/QmitkUSNavigationMarkerPlacement.cpp b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/QmitkUSNavigationMarkerPlacement.cpp index ad2f3dcee0..55c077e88e 100644 --- a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/QmitkUSNavigationMarkerPlacement.cpp +++ b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/QmitkUSNavigationMarkerPlacement.cpp @@ -1,802 +1,802 @@ /*============================================================================ 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 "QmitkUSNavigationMarkerPlacement.h" #include "ui_QmitkUSNavigationMarkerPlacement.h" #include "NavigationStepWidgets/QmitkUSNavigationStepCombinedModality.h" #include "NavigationStepWidgets/QmitkUSNavigationStepMarkerIntervention.h" #include "NavigationStepWidgets/QmitkUSNavigationStepPlacementPlanning.h" #include "NavigationStepWidgets/QmitkUSNavigationStepPunctuationIntervention.h" #include "NavigationStepWidgets/QmitkUSNavigationStepTumourSelection.h" #include "NavigationStepWidgets/QmitkUSNavigationStepZoneMarking.h" #include "SettingsWidgets/QmitkUSNavigationCombinedSettingsWidget.h" #include "mitkAbstractUltrasoundTrackerDevice.h" #include "mitkIRenderingManager.h" #include "mitkNodeDisplacementFilter.h" #include "mitkTrackedUltrasound.h" #include #include "IO/mitkUSNavigationStepTimer.h" #include #include #include #include #include #include #include "QmitkRenderWindow.h" #include "QmitkStdMultiWidget.h" #include "QmitkStdMultiWidgetEditor.h" #include "mitkCameraController.h" #include "mitkLayoutAnnotationRenderer.h" #include // scene serialization #include #include #include #include #include const std::string QmitkUSNavigationMarkerPlacement::VIEW_ID = "org.mitk.views.usmarkerplacement"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_TUMOUR = "Tumour"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_TARGETSURFACE = "Target Surface"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_ZONES = "Zones"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_TARGETS = "Targets"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_TARGETS_PATHS = "Target Paths"; const char *QmitkUSNavigationMarkerPlacement::DATANAME_REACHED_TARGETS = "Reached Targets"; QmitkUSNavigationMarkerPlacement::QmitkUSNavigationMarkerPlacement() : m_Parent(nullptr), m_UpdateTimer(new QTimer(this)), m_ImageAndNavigationDataLoggingTimer(new QTimer(this)), m_StdMultiWidget(nullptr), m_CombinedModality(nullptr), m_ReinitAlreadyDone(false), m_IsExperimentRunning(false), m_CurrentApplicationName(), m_NavigationStepTimer(mitk::USNavigationStepTimer::New()), m_IconRunning(QPixmap(":/USNavigation/record.png")), m_IconNotRunning(QPixmap(":/USNavigation/record-gray.png")), m_ResultsDirectory(), m_ExperimentName(), m_ExperimentResultsSubDirectory(), m_NavigationStepNames(), m_LoggingBackend(), m_USImageLoggingFilter(mitk::USImageLoggingFilter::New()), m_NavigationDataRecorder(mitk::NavigationDataRecorder::New()), m_TargetNodeDisplacementFilter(nullptr), m_AblationZonesDisplacementFilter(mitk::NodeDisplacementFilter::New()), m_ToolVisualizationFilter(nullptr), m_AblationZonesVector(), m_NeedleIndex(0), m_MarkerIndex(1), m_SceneNumber(1), m_WarnOverlay(mitk::TextAnnotation2D::New()), m_NavigationDataSource(nullptr), m_CurrentStorage(nullptr), m_ImageStreamNode(nullptr), ui(new Ui::QmitkUSNavigationMarkerPlacement) { connect(m_UpdateTimer, SIGNAL(timeout()), this, SLOT(OnTimeout())); connect( m_ImageAndNavigationDataLoggingTimer, SIGNAL(timeout()), this, SLOT(OnImageAndNavigationDataLoggingTimeout())); // scale running (and not running) icon the specific height m_IconRunning = m_IconRunning.scaledToHeight(20, Qt::SmoothTransformation); m_IconNotRunning = m_IconNotRunning.scaledToHeight(20, Qt::SmoothTransformation); } QmitkUSNavigationMarkerPlacement::~QmitkUSNavigationMarkerPlacement() { this->GetDataStorage()->Remove(m_InstrumentNode); delete ui; } void QmitkUSNavigationMarkerPlacement::OnChangeAblationZone(int id, int newSize) { if ((static_cast(m_AblationZonesVector.size()) < id) || (id < 0)) { return; } MITK_INFO << "Ablation Zone " << id << " changed, new size: " << newSize; // create a vtk sphere with given radius vtkSmartPointer vtkSphere = vtkSmartPointer::New(); vtkSphere->SetRadius(newSize / 2); vtkSphere->SetCenter(0, 0, 0); vtkSphere->SetPhiResolution(20); vtkSphere->SetThetaResolution(20); vtkSphere->Update(); mitk::Surface::Pointer zoneSurface = dynamic_cast(m_AblationZonesVector.at(id)->GetData()); zoneSurface->SetVtkPolyData(vtkSphere->GetOutput()); } void QmitkUSNavigationMarkerPlacement::OnAddAblationZone(int size) { m_AblationZonesDisplacementFilter->SetInitialReferencePose( m_CombinedModality->GetNavigationDataSource()->GetOutput(m_MarkerIndex)); mitk::DataNode::Pointer NewAblationZone = mitk::DataNode::New(); mitk::Point3D origin = m_CombinedModality->GetNavigationDataSource()->GetOutput(m_NeedleIndex)->GetPosition(); MITK_INFO("USNavigationLogging") << "Ablation Zone Added, initial size: " << size << ", origin: " << origin; mitk::Surface::Pointer zone = mitk::Surface::New(); // create a vtk sphere with given radius vtkSmartPointer vtkSphere = vtkSmartPointer::New(); vtkSphere->SetRadius(size / 2); vtkSphere->SetCenter(0, 0, 0); vtkSphere->SetPhiResolution(20); vtkSphere->SetThetaResolution(20); vtkSphere->Update(); zone->SetVtkPolyData(vtkSphere->GetOutput()); // set vtk sphere and origin to data node (origin must be set // again, because of the new sphere set as data) NewAblationZone->SetData(zone); NewAblationZone->GetData()->GetGeometry()->SetOrigin(origin); mitk::Color SphereColor = mitk::Color(); // default color SphereColor[0] = 102; SphereColor[1] = 0; SphereColor[2] = 204; NewAblationZone->SetColor(SphereColor); NewAblationZone->SetOpacity(0.3); // set name of zone std::stringstream name; name << "Ablation Zone" << m_AblationZonesVector.size(); NewAblationZone->SetName(name.str()); // add zone to filter m_AblationZonesDisplacementFilter->AddNode(NewAblationZone); m_AblationZonesVector.push_back(NewAblationZone); this->GetDataStorage()->Add(NewAblationZone); } void QmitkUSNavigationMarkerPlacement::CreateQtPartControl(QWidget *parent) { m_Parent = parent; ui->setupUi(parent); connect(ui->startExperimentButton, SIGNAL(clicked()), this, SLOT(OnStartExperiment())); connect(ui->finishExperimentButton, SIGNAL(clicked()), this, SLOT(OnFinishExperiment())); connect(ui->m_enableNavigationLayout, SIGNAL(clicked()), this, SLOT(OnChangeLayoutClicked())); connect(ui->m_RenderWindowSelection, SIGNAL(valueChanged(int)), this, SLOT(OnRenderWindowSelection())); connect(ui->m_RefreshView, SIGNAL(clicked()), this, SLOT(OnRefreshView())); m_BaseNode = this->GetDataStorage()->GetNamedNode(QmitkUSAbstractNavigationStep::DATANAME_BASENODE); if (m_BaseNode.IsNull()) { m_BaseNode = mitk::DataNode::New(); m_BaseNode->SetName(QmitkUSAbstractNavigationStep::DATANAME_BASENODE); this->GetDataStorage()->Add(m_BaseNode); } connect(ui->m_CtToUsRegistrationWidget, SIGNAL(GetCursorPosition()), this, SLOT(OnGetCursorPosition())); connect(ui->m_CtToUsRegistrationWidget, SIGNAL(ActualizeCtToUsRegistrationWidget()), this, SLOT(OnActualizeCtToUsRegistrationWidget())); connect(ui->m_initializeCtToUsRegistration, SIGNAL(clicked()), this, SLOT(OnInitializeCtToUsRegistration())); connect(ui->m_initializeTargetMarking, SIGNAL(clicked()), this, SLOT(OnInitializeTargetMarking())); connect(ui->m_initializeCritStructureMarking, SIGNAL(clicked()), this, SLOT(OnInitializeCriticalStructureMarking())); connect(ui->m_initializeNavigation, SIGNAL(clicked()), this, SLOT(OnInitializeNavigation())); // indicate that no experiment is running at start ui->runningLabel->setPixmap(m_IconNotRunning); connect(ui->m_settingsWidget, SIGNAL(SettingsChanged(itk::SmartPointer)), this, SLOT(OnSettingsChanged(itk::SmartPointer))); } void QmitkUSNavigationMarkerPlacement::ReInitializeSettingsNodesAndImageStream() { //If update timer is not stopped (signals stopped), setting the m_CombinedModality // will cause a crash of the workbench in some times. m_UpdateTimer->blockSignals(true); m_UpdateTimer->stop(); m_SettingsNode = mitk::DataNode::New(); ui->m_settingsWidget->OnSetSettingsNode(m_SettingsNode, true); InitImageStream(); m_CombinedModality = ui->m_CombinedModalityCreationWidget->GetSelectedCombinedModality(); // Having set the m_CombinedModality reactivate the update timer again m_UpdateTimer->start(50); // every 50 Milliseconds = 20 Frames/Second m_UpdateTimer->blockSignals(false); } void QmitkUSNavigationMarkerPlacement::OnGetCursorPosition() { mitk::Point3D centroid = this->GetRenderWindowPart()->GetSelectedPosition(); ui->m_CtToUsRegistrationWidget->OnCalculateTRE(centroid); } void QmitkUSNavigationMarkerPlacement::OnActualizeCtToUsRegistrationWidget() { m_SettingsNode = mitk::DataNode::New(); ui->m_settingsWidget->OnSetSettingsNode(m_SettingsNode, true); this->InitImageStream(); if (ui->m_CombinedModalityCreationWidget->GetSelectedCombinedModality().IsNull()) { return; } ui->m_CtToUsRegistrationWidget->SetCombinedModality( ui->m_CombinedModalityCreationWidget->GetSelectedCombinedModality()); m_CombinedModality = ui->m_CombinedModalityCreationWidget->GetSelectedCombinedModality(); if (!m_StdMultiWidget) { // try to get the standard multi widget if it couldn't be got before mitk::IRenderWindowPart *renderWindowPart = this->GetRenderWindowPart(); QmitkStdMultiWidgetEditor *multiWidgetEditor = dynamic_cast(renderWindowPart); // if there is a standard multi widget now, disable the level window and // change the layout to 2D up and 3d down if (multiWidgetEditor) { m_StdMultiWidget = dynamic_cast(multiWidgetEditor->GetMultiWidget()); multiWidgetEditor->ShowLevelWindowWidget(false); SetTwoWindowView(); } } else { this->OnRefreshView(); } m_UpdateTimer->start(50); // every 50 Milliseconds = 20 Frames/Second } void QmitkUSNavigationMarkerPlacement::OnInitializeCtToUsRegistration() { ui->m_CtToUsRegistrationWidget->SetDataStorage(this->GetDataStorage()); ui->m_CtToUsRegistrationWidget->OnSettingsChanged(m_SettingsNode); ui->m_CtToUsRegistrationWidget->OnActivateStep(); ui->m_CtToUsRegistrationWidget->OnStartStep(); ui->m_CtToUsRegistrationWidget->Update(); } void QmitkUSNavigationMarkerPlacement::OnInitializeTargetMarking() { ReInitializeSettingsNodesAndImageStream(); ui->m_TargetMarkingWidget->SetCombinedModality(m_CombinedModality); ui->m_TargetMarkingWidget->SetDataStorage(this->GetDataStorage()); ui->m_TargetMarkingWidget->OnSettingsChanged(m_SettingsNode); ui->m_TargetMarkingWidget->OnActivateStep(); ui->m_TargetMarkingWidget->OnStartStep(); ui->m_TargetMarkingWidget->Update(); } void QmitkUSNavigationMarkerPlacement::OnInitializeCriticalStructureMarking() { ReInitializeSettingsNodesAndImageStream(); ui->m_CriticalStructuresWidget->SetCombinedModality(m_CombinedModality); ui->m_CriticalStructuresWidget->SetDataStorage(this->GetDataStorage()); ui->m_CriticalStructuresWidget->OnSettingsChanged(m_SettingsNode); ui->m_CriticalStructuresWidget->OnActivateStep(); ui->m_CriticalStructuresWidget->OnStartStep(); ui->m_CriticalStructuresWidget->Update(); } void QmitkUSNavigationMarkerPlacement::OnInitializeNavigation() { ReInitializeSettingsNodesAndImageStream(); ui->m_NavigationWidget->SetCombinedModality(m_CombinedModality); ui->m_NavigationWidget->SetDataStorage(this->GetDataStorage()); ui->m_NavigationWidget->OnSettingsChanged(m_SettingsNode); ui->m_NavigationWidget->OnActivateStep(); ui->m_NavigationWidget->OnStartStep(); ui->m_NavigationWidget->Update(); // test if it is tracked US, if yes add visualization filter if (m_CombinedModality->GetIsTrackedUltrasoundActive()) { m_InstrumentNode = mitk::DataNode::New(); m_InstrumentNode->SetName("Tracked US Instrument"); m_InstrumentNode->SetData( m_CombinedModality->GetNavigationDataSource()->GetToolMetaData(0)->GetToolSurface()->Clone()); this->GetDataStorage()->Add(m_InstrumentNode); m_ToolVisualizationFilter = mitk::NavigationDataObjectVisualizationFilter::New(); m_ToolVisualizationFilter->ConnectTo(m_CombinedModality->GetNavigationDataSource()); m_ToolVisualizationFilter->SetRepresentationObject(0, m_InstrumentNode->GetData()); //caution: currently hard coded that instrument has id 0 //set dummy objects to avoid spamming of console mitk::Surface::Pointer dummyObject = mitk::Surface::New(); - m_ToolVisualizationFilter->SetRepresentationObject(1, dummyObject); - m_ToolVisualizationFilter->SetRepresentationObject(2, dummyObject); + m_ToolVisualizationFilter->SetRepresentationObject(1, dummyObject.GetPointer()); + m_ToolVisualizationFilter->SetRepresentationObject(2, dummyObject.GetPointer()); } } void QmitkUSNavigationMarkerPlacement::InitImageStream() { if (m_ImageStreamNode.IsNull()) { m_ImageStreamNode = mitk::DataNode::New(); m_ImageStreamNode->SetName("US Navigation Viewing Stream"); this->GetDataStorage()->Add(m_ImageStreamNode); } } void QmitkUSNavigationMarkerPlacement::OnCombinedModalityPropertyChanged(const std::string &key, const std::string &) { if (key == mitk::USDevice::GetPropertyKeys().US_PROPKEY_BMODE_DEPTH) { m_ReinitAlreadyDone = false; this->ReinitOnImage(); if (m_CombinedModality.IsNotNull() && !m_CombinedModality->GetIsCalibratedForCurrentStatus()) { mitk::LayoutAnnotationRenderer::AddAnnotation( m_WarnOverlay.GetPointer(), "stdmulti.widget0", mitk::LayoutAnnotationRenderer::TopLeft); MITK_WARN << "No calibration available for the selected ultrasound image depth."; } } } void QmitkUSNavigationMarkerPlacement::SetFocus() { this->ReinitOnImage(); } void QmitkUSNavigationMarkerPlacement::OnTimeout() { if (m_CombinedModality.IsNull()) return; m_CombinedModality->Modified(); // shouldn't be nessecary ... fix in abstract ultrasound tracker device! m_CombinedModality->Update(); if (m_ToolVisualizationFilter.IsNotNull()) { m_ToolVisualizationFilter->Update(); } ui->m_CtToUsRegistrationWidget->Update(); ui->m_TargetMarkingWidget->Update(); ui->m_CriticalStructuresWidget->Update(); ui->m_NavigationWidget->Update(); mitk::Image::Pointer image = m_CombinedModality->GetOutput(); // make sure that always the current image is set to the data node if (image.IsNotNull() && m_ImageStreamNode->GetData() != image.GetPointer() && image->IsInitialized()) { m_ImageStreamNode->SetData(image); } if (!m_StdMultiWidget) { // try to get the standard multi widget if it couldn't be got before mitk::IRenderWindowPart *renderWindowPart = this->GetRenderWindowPart(); QmitkStdMultiWidgetEditor *multiWidgetEditor = dynamic_cast(renderWindowPart); // if there is a standard multi widget now, disable the level window and // change the layout to 2D up and 3d down if (multiWidgetEditor) { m_StdMultiWidget = dynamic_cast(multiWidgetEditor->GetMultiWidget()); multiWidgetEditor->ShowLevelWindowWidget(false); SetTwoWindowView(); } this->CreateOverlays(); } if (m_CombinedModality.IsNotNull() && !this->m_CombinedModality->GetIsFreezed()) // if the combined modality is freezed: do nothing { m_AblationZonesDisplacementFilter->Update(); // update the 3D window only every fourth time to speed up the rendering (at least in 2D) this->RequestRenderWindowUpdate(mitk::RenderingManager::REQUEST_UPDATE_2DWINDOWS); // make sure that a reinit was performed on the image this->ReinitOnImage(); } } void QmitkUSNavigationMarkerPlacement::OnEnableNavigationLayout() { MITK_INFO << "Navigation Layout"; // try to get the standard multi widget if it couldn't be got before mitk::IRenderWindowPart *renderWindowPart = this->GetRenderWindowPart(); QmitkStdMultiWidgetEditor *multiWidgetEditor = dynamic_cast(renderWindowPart); // if there is a standard multi widget now, disable the level window and // change the layout to 2D up and 3d down if (multiWidgetEditor) { m_StdMultiWidget = dynamic_cast(multiWidgetEditor->GetMultiWidget()); multiWidgetEditor->ShowLevelWindowWidget(false); SetTwoWindowView(); } } void QmitkUSNavigationMarkerPlacement::OnRenderWindowSelection() { SetTwoWindowView(); } void QmitkUSNavigationMarkerPlacement::OnRefreshView() { if (!ui->m_enableNavigationLayout->isChecked()) OnResetStandardLayout(); else { // Reinit the US Image Stream (this might be broken if there was a global reinit somewhere...) try { mitk::RenderingManager::GetInstance()->InitializeViews( // Reinit this ->GetDataStorage() // GetDataStorage ->GetNamedNode("US Viewing Stream - Image 0") ->GetData() ->GetTimeGeometry()); // GetNode } catch (...) { MITK_DEBUG << "No reinit possible"; } SetTwoWindowView(); } } void QmitkUSNavigationMarkerPlacement::SetTwoWindowView() { if (m_StdMultiWidget) { int i, j, k; switch (this->ui->m_RenderWindowSelection->value()) { case 1: mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetCameraController()->SetViewToCaudal(); i = 1; j = 2; // other windows k = 0; break; case 2: mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetCameraController()->SetViewToSinister(); i = 0; j = 2; k = 1; break; case 3: mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetCameraController()->SetViewToAnterior(); i = 1; j = 0; k = 2; break; default: return; } // get the render window which is defined by index "k" and set it as "current render window widget" // chose the layout that will set the current 2D window as top render window and the 3D windows as bottom render window auto renderWindowWidget = m_StdMultiWidget->GetRenderWindowWidget(m_StdMultiWidget->GetNameFromIndex(k)); m_StdMultiWidget->GetMultiWidgetLayoutManager()->SetCurrentRenderWindowWidget(renderWindowWidget.get()); m_StdMultiWidget->GetMultiWidgetLayoutManager()->SetOneTop3DBottomLayout(); ////Crosshair invisible in 3D view this->GetDataStorage()->GetNamedNode("stdmulti.widget" + std::to_string(i) + ".plane") ->SetBoolProperty("visible", false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"))); this->GetDataStorage()->GetNamedNode("stdmulti.widget" + std::to_string(j) + ".plane") ->SetBoolProperty("visible", false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"))); this->GetDataStorage()->GetNamedNode("stdmulti.widget" + std::to_string(k) + ".plane") ->SetBoolProperty("visible", true, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"))); this->GetDataStorage()->GetNamedNode("stdmulti.widget" + std::to_string(i) + ".plane") ->SetIntProperty("Crosshair.Gap Size", 0); this->GetDataStorage()->GetNamedNode("stdmulti.widget" + std::to_string(j) + ".plane") ->SetIntProperty("Crosshair.Gap Size", 0); } } void QmitkUSNavigationMarkerPlacement::OnResetStandardLayout() { if (m_StdMultiWidget) { //reset render windows m_StdMultiWidget->SetCrosshairVisibility(true); m_StdMultiWidget->GetMultiWidgetLayoutManager()->SetDefaultLayout(); } } void QmitkUSNavigationMarkerPlacement::OnChangeLayoutClicked() { if (ui->m_enableNavigationLayout->isChecked()) OnEnableNavigationLayout(); else OnResetStandardLayout(); } void QmitkUSNavigationMarkerPlacement::OnImageAndNavigationDataLoggingTimeout() { // update filter for logging navigation data and ultrasound images if (m_CombinedModality.IsNotNull()) { m_NavigationDataRecorder->Update(); // get last messages for logging filer and store them std::vector messages = m_LoggingBackend.GetNavigationMessages(); std::string composedMessage = ""; for (std::size_t i = 0; i < messages.size(); i++) { composedMessage += messages.at(i); } m_USImageLoggingFilter->AddMessageToCurrentImage(composedMessage); m_LoggingBackend.ClearNavigationMessages(); // update logging filter m_USImageLoggingFilter->Update(); } } void QmitkUSNavigationMarkerPlacement::OnStartExperiment() { // get name for the experiment by a QInputDialog bool ok; if (m_ExperimentName.isEmpty()) { // default: current date m_ExperimentName = QString::number(QDateTime::currentDateTime().date().year()) + "_" + QString::number(QDateTime::currentDateTime().date().month()) + "_" + QString::number(QDateTime::currentDateTime().date().day()) + "_experiment_" + QString::number(QDateTime::currentDateTime().time().hour()) + "." + QString::number(QDateTime::currentDateTime().time().minute()); } m_ExperimentName = QInputDialog::getText( m_Parent, QString("Experiment Name"), QString("Name of the Experiment"), QLineEdit::Normal, m_ExperimentName, &ok); MITK_INFO("USNavigationLogging") << "Experiment started: " << m_ExperimentName.toStdString(); if (ok && !m_ExperimentName.isEmpty()) { // display error message and call the function recursively if a directory // with the given name already exists QDir experimentResultsDir(m_ResultsDirectory + QDir::separator() + m_ExperimentName); if (experimentResultsDir.exists()) { QMessageBox::critical( m_Parent, "Results Directory Exists", "The result directory already exists.\nPlease choose an other name."); this->OnStartExperiment(); } else { QDir(m_ResultsDirectory).mkdir(m_ExperimentName); m_ExperimentResultsSubDirectory = m_ResultsDirectory + QDir::separator() + m_ExperimentName; // experiment is running now ui->runningLabel->setPixmap(m_IconRunning); // (re)start timer for navigation step durations m_NavigationStepTimer->Reset(); m_NavigationStepTimer->SetOutputFileName( QString(m_ExperimentResultsSubDirectory + QDir::separator() + QString("durations.cvs")).toStdString()); m_NavigationStepTimer->SetActiveIndex(0, "Initialization"); ui->finishExperimentButton->setEnabled(true); ui->startExperimentButton->setDisabled(true); // initialize and register logging backend QString loggingFilename = m_ExperimentResultsSubDirectory + QDir::separator() + "logging.txt"; m_LoggingBackend.SetOutputFileName(loggingFilename.toStdString()); mbilog::RegisterBackend(&m_LoggingBackend); // initialize and start navigation data recorder form xml recording m_NavigationDataRecorder->StartRecording(); m_IsExperimentRunning = true; m_ImageAndNavigationDataLoggingTimer->start(1000); } } } void QmitkUSNavigationMarkerPlacement::OnFinishExperiment() { this->WaitCursorOn(); MITK_INFO("USNavigationLogging") << "Experiment finished!"; MITK_INFO("USNavigationLogging") << "Position/Orientation of needle tip: " << (dynamic_cast( m_CombinedModality->GetTrackingDeviceDataSource()->GetOutput(0))) ->GetPosition(); MITK_INFO("USNavigationLogging") << "Position of target: " << m_TargetNodeDisplacementFilter->GetRawDisplacementNavigationData(0)->GetPosition(); MITK_INFO("USNavigationLogging") << "Total duration: " << m_NavigationStepTimer->GetTotalDuration(); m_ImageAndNavigationDataLoggingTimer->stop(); ui->runningLabel->setPixmap(m_IconNotRunning); m_NavigationStepTimer->Stop(); ui->finishExperimentButton->setDisabled(true); ui->startExperimentButton->setEnabled(true); MITK_INFO("USNavigationLogging") << "Writing logging data to " << m_ExperimentResultsSubDirectory.toStdString(); // save ultrasound images to the file system QDir(m_ExperimentResultsSubDirectory).mkdir("ImageStream"); m_USImageLoggingFilter->Update(); m_USImageLoggingFilter->SetImageFilesExtension(".jpg"); m_USImageLoggingFilter->SaveImages( QString(m_ExperimentResultsSubDirectory + QDir::separator() + "ImageStream" + QDir::separator()).toStdString()); m_USImageLoggingFilter = mitk::USImageLoggingFilter::New(); m_NavigationDataRecorder->StopRecording(); // Write data to csv and xml file mitk::IOUtil::Save( m_NavigationDataRecorder->GetNavigationDataSet(), (QString(m_ExperimentResultsSubDirectory + QDir::separator() + "navigation-data.xml").toStdString().c_str())); mitk::IOUtil::Save( m_NavigationDataRecorder->GetNavigationDataSet(), (QString(m_ExperimentResultsSubDirectory + QDir::separator() + "navigation-data.csv").toStdString().c_str())); // write logged navigation data messages to separate file std::stringstream csvNavigationMessagesFilename; csvNavigationMessagesFilename << m_ExperimentResultsSubDirectory.toStdString() << QDir::separator().toLatin1() << "CSVNavigationMessagesLogFile.csv"; MITK_INFO("USNavigationLogging") << "Writing logged navigation messages to separate csv file: " << csvNavigationMessagesFilename.str(); m_LoggingBackend.WriteCSVFileWithNavigationMessages(csvNavigationMessagesFilename.str()); mbilog::UnregisterBackend(&m_LoggingBackend); m_IsExperimentRunning = false; m_ImageAndNavigationDataLoggingTimer->stop(); m_CombinedModality = nullptr; // reset scene number for next experiment m_SceneNumber = 1; this->WaitCursorOff(); MITK_INFO("USNavigationLogging") << "Finished!"; } void QmitkUSNavigationMarkerPlacement::OnSettingsChanged(itk::SmartPointer settings) { // initialize gui according to the experiment mode setting bool experimentMode = false; settings->GetBoolProperty("settings.experiment-mode", experimentMode); ui->startExperimentButton->setVisible(experimentMode); ui->finishExperimentButton->setVisible(experimentMode); ui->runningLabel->setVisible(experimentMode); if (experimentMode && !m_IsExperimentRunning) { ui->runningLabel->setPixmap(m_IconNotRunning); } else if (!experimentMode) { if (m_IsExperimentRunning) { this->OnFinishExperiment(); } } // get the results directory from the settings and use home directory if // there is no results directory configured std::string resultsDirectory; if (settings->GetStringProperty("settings.experiment-results-directory", resultsDirectory)) { m_ResultsDirectory = QString::fromStdString(resultsDirectory); } else { m_ResultsDirectory = QDir::homePath(); } // make sure that the results directory exists QDir resultsDirectoryQDir = QDir(m_ResultsDirectory); if (!resultsDirectoryQDir.exists()) { resultsDirectoryQDir.mkpath(m_ResultsDirectory); } MITK_INFO("USNavigation") << "Results Directory: " << m_ResultsDirectory.toStdString(); } void QmitkUSNavigationMarkerPlacement::ReinitOnImage() { if (!m_ReinitAlreadyDone && m_CombinedModality.IsNotNull()) { // make sure that the output is already calibrated correctly // (if the zoom level was changed recently) m_CombinedModality->Modified(); m_CombinedModality->Update(); mitk::Image::Pointer image = m_CombinedModality->GetOutput(); if (image.IsNotNull() && image->IsInitialized()) { // make a reinit on the ultrasound image mitk::IRenderWindowPart *renderWindowPart = this->GetRenderWindowPart(); if (renderWindowPart != nullptr && image->GetTimeGeometry()->IsValid()) { renderWindowPart->GetRenderingManager()->InitializeViews( image->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); renderWindowPart->GetRenderingManager()->RequestUpdateAll(); } this->RequestRenderWindowUpdate(); m_ReinitAlreadyDone = true; } } } void QmitkUSNavigationMarkerPlacement::Convert2DImagesTo3D(mitk::DataStorage::SetOfObjects::ConstPointer nodes) { for (mitk::DataStorage::SetOfObjects::ConstIterator it = nodes->Begin(); it != nodes->End(); ++it) { if (it->Value()->GetData() && strcmp(it->Value()->GetData()->GetNameOfClass(), "Image") == 0) { // convert image to 3d image if it is 2d at the moment mitk::Image::Pointer image = dynamic_cast(it->Value()->GetData()); if (image.IsNotNull() && image->GetDimension() == 2 && !image->GetGeometry()->Is2DConvertable()) { mitk::Convert2Dto3DImageFilter::Pointer convert2DTo3DImageFilter = mitk::Convert2Dto3DImageFilter::New(); convert2DTo3DImageFilter->SetInput(image); convert2DTo3DImageFilter->Update(); it->Value()->SetData(convert2DTo3DImageFilter->GetOutput()); } } } } void QmitkUSNavigationMarkerPlacement::CreateOverlays() { // initialize warning overlay (and do not display it, yet) m_WarnOverlay->SetText("Warning: No calibration available for current depth."); // set position and font size for the text overlay // (nonesense postition as a layouter is used, but it ignored // the overlay without setting a position here) mitk::Point2D overlayPosition; overlayPosition.SetElement(0, -50.0f); overlayPosition.SetElement(1, -50.0f); m_WarnOverlay->SetPosition2D(overlayPosition); m_WarnOverlay->SetFontSize(22); m_WarnOverlay->SetColor(1, 0, 0); // overlay should be red } void QmitkUSNavigationMarkerPlacement::UpdateToolStorage() { if (m_NavigationDataSource.IsNull()) { m_NavigationDataSource = m_CombinedModality->GetNavigationDataSource(); } if (m_NavigationDataSource.IsNull()) { MITK_WARN << "Found an invalid navigation data source object!"; } us::ModuleContext *context = us::GetModuleContext(); std::string id = m_NavigationDataSource->US_PROPKEY_ID; std::string filter = "(" + mitk::NavigationToolStorage::US_PROPKEY_SOURCE_ID + "=" + id + ")"; // Get Storage std::vector> refs = context->GetServiceReferences(); m_CurrentStorage = context->GetService(refs.front()); if (m_CurrentStorage.IsNull()) { MITK_WARN << "Found an invalid storage object!"; } else if (m_CurrentStorage->GetToolCount() != m_NavigationDataSource->GetNumberOfOutputs()) // there is something wrong with the storage { MITK_WARN << "Found a tool storage, but it has not the same number of tools like the NavigationDataSource. This " "storage won't be used because it isn't the right one."; m_CurrentStorage = nullptr; } } diff --git a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkExample.cpp b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkExample.cpp index 987cc38654..963c4556c3 100644 --- a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkExample.cpp +++ b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkExample.cpp @@ -1,237 +1,237 @@ /*============================================================================ 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 // Qmitk #include "QmitkRenderWindow.h" // Qt #include // mitk #include #include #include #include #include // vtk #include // #include "OpenIGTLinkExample.h" //igtl #include "igtlStringMessage.h" #include "igtlTrackingDataMessage.h" const std::string OpenIGTLinkExample::VIEW_ID = "org.mitk.views.OpenIGTLinkExample"; void OpenIGTLinkExample::SetFocus() { } OpenIGTLinkExample::~OpenIGTLinkExample() { this->DestroyPipeline(); if (m_IGTLDeviceSource.IsNotNull()) { m_IGTLDeviceSource->UnRegisterMicroservice(); } } void OpenIGTLinkExample::CreateQtPartControl( QWidget *parent ) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi( parent ); // connect the widget items with the methods connect( m_Controls.butStart, SIGNAL(clicked()), this, SLOT(Start()) ); connect( &m_Timer, SIGNAL(timeout()), this, SLOT(UpdatePipeline())); //create a new OpenIGTLinkExample Client m_IGTLClient = mitk::IGTLClient::New(false); m_IGTLClient->SetName("OIGTL Example Client Device"); //create a new OpenIGTLinkExample Device source m_IGTLDeviceSource = mitk::IGTLDeviceSource::New(); //set the client as the source for the device source m_IGTLDeviceSource->SetIGTLDevice(m_IGTLClient); m_IGTLDeviceSource->RegisterAsMicroservice(); } void OpenIGTLinkExample::CreatePipeline() { //create a filter that converts OpenIGTLinkExample messages into navigation data m_IGTLMsgToNavDataFilter = mitk::IGTLMessageToNavigationDataFilter::New(); //create a visualization filter m_VisFilter = mitk::NavigationDataObjectVisualizationFilter::New(); //we expect a tracking data message with three tools. Since we cannot change //the outputs at runtime we have to set it manually. m_IGTLMsgToNavDataFilter->SetNumberOfExpectedOutputs(m_Controls.channelSpinBox->value()); //connect the filters with each other //the OpenIGTLinkExample messages will be passed to the first filter that converts //it to navigation data, then it is passed to the visualization filter that //will visualize the transformation m_IGTLMsgToNavDataFilter->ConnectTo(m_IGTLDeviceSource); m_VisFilter->ConnectTo(m_IGTLMsgToNavDataFilter); //create an object that will be moved respectively to the navigation data for (size_t i = 0; i < m_IGTLMsgToNavDataFilter->GetNumberOfIndexedOutputs(); i++) { mitk::DataNode::Pointer newNode = mitk::DataNode::New(); QString name("DemoNode IGTLProviderExmpl T"); name.append(QString::number(i)); newNode->SetName(name.toStdString()); //create small sphere and use it as surface mitk::Surface::Pointer mySphere = mitk::Surface::New(); vtkSmartPointer vtkSphere = vtkSmartPointer::New(); vtkSphere->SetRadius(2.0f); vtkSphere->SetCenter(0.0, 0.0, 0.0); vtkSphere->Update(); mySphere->SetVtkPolyData(vtkSphere->GetOutput()); newNode->SetData(mySphere); - m_VisFilter->SetRepresentationObject(i, mySphere); + m_VisFilter->SetRepresentationObject(i, mySphere.GetPointer()); m_DemoNodes.append(newNode); } this->ResizeBoundingBox(); } void OpenIGTLinkExample::DestroyPipeline() { m_VisFilter = nullptr; foreach(mitk::DataNode::Pointer node, m_DemoNodes) { this->GetDataStorage()->Remove(node); } this->m_DemoNodes.clear(); } void OpenIGTLinkExample::Start() { if (this->m_Controls.butStart->text().contains("Start Pipeline")) { static bool isFirstTime = true; if (isFirstTime) { //Setup the pipeline this->CreatePipeline(); isFirstTime = false; } m_Timer.setInterval(this->m_Controls.visualizationUpdateRateSpinBox->value()); m_Timer.start(); //this->m_Controls.visualizationUpdateRateSpinBox->setEnabled(true); this->m_Controls.butStart->setText("Stop Pipeline"); } else { m_Timer.stop(); igtl::StopTrackingDataMessage::Pointer stopStreaming = igtl::StopTrackingDataMessage::New(); this->m_IGTLClient->SendMessage(mitk::IGTLMessage::New((igtl::MessageBase::Pointer) stopStreaming)); this->m_Controls.butStart->setText("Start Pipeline"); //this->m_Controls.visualizationUpdateRateSpinBox->setEnabled(false); } } void OpenIGTLinkExample::UpdatePipeline() { if (this->m_Controls.visualizeCheckBox->isChecked()) { //update the pipeline m_VisFilter->Update(); ////update the boundings //mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(this->GetDataStorage()); //Update rendering mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { //no visualization so we just update this filter m_IGTLMsgToNavDataFilter->Update(); //record a timestamp if the output is new //static double previousTimestamp; //double curTimestamp = m_IGTLMsgToNavDataFilter->GetOutput()->GetIGTTimeStamp(); //if (previousTimestamp != curTimestamp) static mitk::NavigationData::Pointer previousND = mitk::NavigationData::New(); mitk::NavigationData* curND = m_IGTLMsgToNavDataFilter->GetOutput(); //std::cout << "9: igt timestamp: " << curND->GetIGTTimeStamp() << std::endl; //std::cout << "9: timestamp: " << curND->GetTimeStamp() << std::endl; if ( !mitk::Equal( *(previousND.GetPointer()), *curND ) ) { //previousTimestamp = curTimestamp; previousND->Graft(curND); } } //check if the timer interval changed static int previousValue = 0; int currentValue = this->m_Controls.visualizationUpdateRateSpinBox->value(); if (previousValue != currentValue) { m_Timer.setInterval(currentValue); previousValue = currentValue; } } /** * \brief To initialize the scene to the bounding box of all visible objects */ void OpenIGTLinkExample::ResizeBoundingBox() { // get all nodes mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetDataStorage()->GetAll(); auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(rs); if (bounds.IsNull()) { return; } //expand the bounding box in case the instruments are all at one position mitk::Point3D center = bounds->GetCenterInWorld(); mitk::Geometry3D::BoundsArrayType extended_bounds = bounds->GetGeometryForTimeStep(0)->GetBounds(); for (unsigned int i = 0; i < 3; ++i) { if (bounds->GetExtentInWorld(i) < 500) { // extend the bounding box extended_bounds[i * 2] = center[i] - 500 / 2.0; extended_bounds[i * 2 + 1] = center[i] + 500 / 2.0; } } //set the extended bounds bounds->GetGeometryForTimeStep(0)->SetBounds(extended_bounds); // initialize the views to the bounding geometry mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } diff --git a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkPlugin.cpp b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkPlugin.cpp index 6fedea556c..d32659b08c 100644 --- a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkPlugin.cpp +++ b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkPlugin.cpp @@ -1,197 +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. ============================================================================*/ // Blueberry #include #include // Qmitk #include "OpenIGTLinkPlugin.h" // Qt #include //mitk image #include //VTK #include const std::string OpenIGTLinkPlugin::VIEW_ID = "org.mitk.views.openigtlinkplugin"; void OpenIGTLinkPlugin::SetFocus() { } void OpenIGTLinkPlugin::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.buttonConnect, SIGNAL(clicked()), this, SLOT(ConnectButtonClicked())); connect(m_Controls.buttonReceive, SIGNAL(clicked()), this, SLOT(ReceivingButtonClicked())); connect(&m_Timer, SIGNAL(timeout()), this, SLOT(UpdatePipeline())); m_Image2dNode = mitk::DataNode::New(); m_State = IDLE; StateChanged(m_State); } void OpenIGTLinkPlugin::UpdatePipeline() { m_NavigationDataObjectVisualizationFilter->Update(); mitk::Image::Pointer image2d = m_ImageFilter2D->GetNextImage().at(0); mitk::Image::Pointer image3d = m_ImageFilter3D->GetNextImage().at(0); m_Image2dNode->SetName("US Image Stream"); m_Image2dNode->SetData(image2d); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void OpenIGTLinkPlugin::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList &) { // iterate all selected objects, adjust warning visibility } void OpenIGTLinkPlugin::ConnectButtonClicked() { bool success; switch (m_State) { case IDLE: m_IGTLClient = mitk::IGTLClient::New(true); m_IGTLClient->SetHostname(m_Controls.textEditHostname->text().toStdString()); m_IGTLClient->SetPortNumber(m_Controls.spinBoxPort->value()); success = m_IGTLClient->OpenConnection(); if (!success) { QMessageBox::warning(nullptr, QString("Connection failed"), QString("Client could not connect to given server."), QMessageBox::Ok, QMessageBox::Abort); } else { m_State = CONNECTED; StateChanged(m_State); } break; case CONNECTED: success = m_IGTLClient->CloseConnection(); m_State = IDLE; StateChanged(m_State); break; case RECEIVING: ReceivingButtonClicked(); success = m_IGTLClient->CloseConnection(); m_State = IDLE; StateChanged(m_State); break; } } void OpenIGTLinkPlugin::ReceivingButtonClicked() { switch (m_State) { case IDLE: QMessageBox::warning(nullptr, QString("Not ready.."), QString("The client must be connected to a server first."), QMessageBox::Ok, QMessageBox::Abort); break; case CONNECTED: m_IGTL2DImageDeviceSource = mitk::IGTL2DImageDeviceSource::New(); m_IGTL3DImageDeviceSource = mitk::IGTL3DImageDeviceSource::New(); m_IGTLTransformDeviceSource = mitk::IGTLTrackingDataDeviceSource::New(); m_IGTL2DImageDeviceSource->SetIGTLDevice(m_IGTLClient); m_IGTL3DImageDeviceSource->SetIGTLDevice(m_IGTLClient); m_IGTLTransformDeviceSource->SetIGTLDevice(m_IGTLClient); this->GetDataStorage()->Add(m_Image2dNode); m_IGTLMessageToNavigationDataFilter = mitk::IGTLMessageToNavigationDataFilter::New(); m_NavigationDataObjectVisualizationFilter = mitk::NavigationDataObjectVisualizationFilter::New(); m_ImageFilter2D = mitk::IGTLMessageToUSImageFilter::New(); m_ImageFilter3D = mitk::IGTLMessageToUSImageFilter::New(); m_IGTLMessageToNavigationDataFilter->SetNumberOfExpectedOutputs(3); m_IGTLMessageToNavigationDataFilter->ConnectTo(m_IGTLTransformDeviceSource); m_NavigationDataObjectVisualizationFilter->ConnectTo(m_IGTLMessageToNavigationDataFilter); m_ImageFilter2D->ConnectTo(m_IGTL2DImageDeviceSource); m_ImageFilter3D->ConnectTo(m_IGTL3DImageDeviceSource); //create an object that will be moved respectively to the navigation data for (size_t i = 0; i < m_IGTLMessageToNavigationDataFilter->GetNumberOfIndexedOutputs(); i++) { mitk::DataNode::Pointer newNode = mitk::DataNode::New(); QString name("DemoNode T"); name.append(QString::number(i)); newNode->SetName(name.toStdString()); //create small sphere and use it as surface mitk::Surface::Pointer mySphere = mitk::Surface::New(); vtkSmartPointer vtkSphere = vtkSmartPointer::New(); vtkSphere->SetRadius(2.0f); vtkSphere->SetCenter(0.0, 0.0, 0.0); vtkSphere->Update(); mySphere->SetProperty("color", mitk::ColorProperty::New(1, 0, 0)); mySphere->SetVtkPolyData(vtkSphere->GetOutput()); newNode->SetData(mySphere); this->GetDataStorage()->Add(newNode); - m_NavigationDataObjectVisualizationFilter->SetRepresentationObject(i, mySphere); + m_NavigationDataObjectVisualizationFilter->SetRepresentationObject(i, mySphere.GetPointer()); } m_IGTLClient->StartCommunication(); m_Timer.setInterval(10); m_Timer.start(); m_State = RECEIVING; StateChanged(m_State); break; case RECEIVING: m_IGTLClient->StopCommunication(); this->GetDataStorage()->Remove(this->GetDataStorage()->GetAll()); m_Timer.stop(); m_State = CONNECTED; StateChanged(m_State); break; } } void OpenIGTLinkPlugin::StateChanged(OpenIGTLinkPlugin::State newState) { switch (newState) { case IDLE: m_Controls.buttonConnect->setText(QString("Connect To Server")); m_Controls.buttonReceive->setText(QString("Start Receiving")); m_Controls.buttonReceive->setDisabled(true); break; case CONNECTED: m_Controls.buttonConnect->setText(QString("Disconnect From Server")); m_Controls.buttonReceive->setText(QString("Start Receiving")); m_Controls.buttonReceive->setDisabled(false); break; case RECEIVING: m_Controls.buttonConnect->setText(QString("Disconnect From Server")); m_Controls.buttonReceive->setText(QString("Stop Receiving")); m_Controls.buttonReceive->setDisabled(false); break; } } diff --git a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkProviderExample.cpp b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkProviderExample.cpp index 3fa7ecc854..584e62060b 100644 --- a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkProviderExample.cpp +++ b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/OpenIGTLinkProviderExample.cpp @@ -1,278 +1,278 @@ /*============================================================================ 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 // Qmitk #include "QmitkRenderWindow.h" // Qt #include #include // mitk #include #include //#include #include #include #include // vtk #include // #include "OpenIGTLinkProviderExample.h" //igtl #include "igtlStringMessage.h" const std::string OpenIGTLinkProviderExample::VIEW_ID = "org.mitk.views.OpenIGTLinkProviderExample"; OpenIGTLinkProviderExample::~OpenIGTLinkProviderExample() { this->DestroyPipeline(); if (m_IGTLMessageProvider.IsNotNull()) { m_IGTLMessageProvider->UnRegisterMicroservice(); } if (m_NavDataToIGTLMsgFilter.IsNotNull()) { m_NavDataToIGTLMsgFilter->UnRegisterMicroservice(); } } void OpenIGTLinkProviderExample::SetFocus() { } void OpenIGTLinkProviderExample::CreateQtPartControl( QWidget *parent ) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi( parent ); // connect the widget items with the methods connect( m_Controls.butStart, SIGNAL(clicked()), this, SLOT(Start()) ); connect( m_Controls.butOpenNavData, SIGNAL(clicked()), this, SLOT(OnOpenFile()) ); connect( &m_VisualizerTimer, SIGNAL(timeout()), this, SLOT(UpdateVisualization())); //create a new OpenIGTLink Client m_IGTLServer = mitk::IGTLServer::New(false); m_IGTLServer->SetName("OIGTL Provider Example Device"); //create a new OpenIGTLink Device source m_IGTLMessageProvider = mitk::IGTLMessageProvider::New(); //set the OpenIGTLink server as the source for the device source m_IGTLMessageProvider->SetIGTLDevice(m_IGTLServer); //register the provider so that it can be configured with the IGTL manager //plugin. This could be hardcoded but now I already have the fancy plugin. m_IGTLMessageProvider->RegisterAsMicroservice(); } void OpenIGTLinkProviderExample::CreatePipeline() { //create a navigation data player object that will play nav data from a //recorded file m_NavDataPlayer = mitk::NavigationDataSequentialPlayer::New(); //set the currently read navigation data set m_NavDataPlayer->SetNavigationDataSet(m_NavDataSet); //create a filter that converts navigation data into IGTL messages m_NavDataToIGTLMsgFilter = mitk::NavigationDataToIGTLMessageFilter::New(); //connect the filters with each other //the navigation data player reads a file with recorded navigation data, //passes this data to a filter that converts it into a IGTLMessage. //The provider is not connected because it will search for fitting services. //Once it found the filter it will automatically connect to it. m_NavDataToIGTLMsgFilter->ConnectTo(m_NavDataPlayer); //define the operation mode for this filter, we want to send tracking data //messages m_NavDataToIGTLMsgFilter->SetOperationMode( mitk::NavigationDataToIGTLMessageFilter::ModeSendTDataMsg); // mitk::NavigationDataToIGTLMessageFilter::ModeSendTransMsg); //set the name of this filter to identify it easier m_NavDataToIGTLMsgFilter->SetName("Tracking Data Source From Example"); //register this filter as micro service. The message provider looks for //provided IGTLMessageSources, once it found this microservice and someone //requested this data type then the provider will connect with this filter //automatically. m_NavDataToIGTLMsgFilter->RegisterAsMicroservice(); //also create a visualize filter to visualize the data m_NavDataVisualizer = mitk::NavigationDataObjectVisualizationFilter::New(); //create an object that will be moved respectively to the navigation data for (size_t i = 0; i < m_NavDataPlayer->GetNumberOfIndexedOutputs(); i++) { mitk::DataNode::Pointer newNode = mitk::DataNode::New(); QString name("DemoNode IGTLProviderExmpl T"); name.append(QString::number(i)); newNode->SetName(name.toStdString()); //create small sphere and use it as surface mitk::Surface::Pointer mySphere = mitk::Surface::New(); vtkSmartPointer vtkData = vtkSmartPointer::New(); vtkData->SetRadius(2.0f); vtkData->SetCenter(0.0, 0.0, 0.0); vtkData->Update(); mySphere->SetVtkPolyData(vtkData->GetOutput()); newNode->SetData(mySphere); this->GetDataStorage()->Add(newNode); - m_NavDataVisualizer->SetRepresentationObject(i, mySphere); + m_NavDataVisualizer->SetRepresentationObject(i, mySphere.GetPointer()); m_DemoNodes.append(newNode); } //connect the visualization with the navigation data player m_NavDataVisualizer->ConnectTo(m_NavDataPlayer); //start the player this->Start(); //resize the boundaries this->m_NavDataVisualizer->Update(); this->ResizeBoundingBox(); } void OpenIGTLinkProviderExample::DestroyPipeline() { if (m_NavDataPlayer.IsNotNull()) { //m_NavDataPlayer->StopPlaying(); } foreach(mitk::DataNode::Pointer node, m_DemoNodes) { this->GetDataStorage()->Remove(node); } this->m_DemoNodes.clear(); } void OpenIGTLinkProviderExample::Start() { if ( this->m_Controls.butStart->text().contains("Start") ) { m_NavDataPlayer->SetRepeat(true); //m_NavDataPlayer->StartPlaying(); this->m_Controls.butStart->setText("Stop Playing Recorded Navigation Data "); //start the visualization double fps = m_Controls.m_updateRate->value(); double millisecondsPerFrame = 1 / fps * 1000; this->m_VisualizerTimer.start(millisecondsPerFrame); } else { //m_NavDataPlayer->StopPlaying(); this->m_Controls.butStart->setText("Start Playing Recorded Navigation Data "); //stop the visualization this->m_VisualizerTimer.stop(); } } void OpenIGTLinkProviderExample::OnOpenFile(){ // FIXME Filter for correct files and use correct Reader QString filter = tr("NavigationData File (*.csv *.xml)"); QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open NavigationData Set"), "", filter); if ( fileName.isNull() ) { return; } // user pressed cancel try { m_NavDataSet = dynamic_cast (mitk::IOUtil::Load(fileName.toStdString())[0].GetPointer()); } catch ( const mitk::Exception &e ) { MITK_WARN("NavigationDataPlayerView") << "could not open file " << fileName.toStdString(); QMessageBox::critical(nullptr, "Error Reading File", "The file '" + fileName +"' could not be read.\n" + e.GetDescription() ); return; } this->m_Controls.butStart->setEnabled(true); //Setup the pipeline this->CreatePipeline(); // Update Labels // m_Controls->m_LblFilePath->setText(fileName); // m_Controls->m_LblTools->setText(QString::number(m_NavDataSet->GetNumberOfTools())); } void OpenIGTLinkProviderExample::UpdateVisualization() { this->m_NavDataPlayer->GoToNextSnapshot(); if (this->m_Controls.visualizeCheckBox->isChecked()) { //update the filter this->m_NavDataVisualizer->Update(); ////update the bounds //mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(this->GetDataStorage()); //update rendering mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } /** * \brief To initialize the scene to the bounding box of all visible objects */ void OpenIGTLinkProviderExample::ResizeBoundingBox() { // get all nodes mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetDataStorage()->GetAll(); auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(rs)->Clone(); if (bounds.IsNull()) { return; } //expand the bounding box in case the instruments are all at one position mitk::Point3D center = bounds->GetCenterInWorld(); mitk::Geometry3D::BoundsArrayType extended_bounds = bounds->GetGeometryForTimeStep(0)->GetBounds(); for (unsigned int i = 0; i < 3; ++i) { if (bounds->GetExtentInWorld(i) < 500) { // extend the bounding box extended_bounds[i * 2] = center[i] - 500 / 2.0; extended_bounds[i * 2 + 1] = center[i] + 500 / 2.0; } } //set the extended bounds bounds->GetGeometryForTimeStep(0)->SetBounds(extended_bounds); // initialize the views to the bounding geometry mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } diff --git a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/QmitkIGTTutorialView.cpp b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/QmitkIGTTutorialView.cpp index 12948d7dbb..f5412b58bd 100644 --- a/Plugins/org.mitk.gui.qt.igtexamples/src/internal/QmitkIGTTutorialView.cpp +++ b/Plugins/org.mitk.gui.qt.igtexamples/src/internal/QmitkIGTTutorialView.cpp @@ -1,257 +1,257 @@ /*============================================================================ 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 "QmitkIGTTutorialView.h" #include "mitkNDIPassiveTool.h" #include "mitkNDITrackingDevice.h" #include "mitkVirtualTrackingDevice.h" #include "mitkStandardFileLocations.h" #include "mitkSerialCommunication.h" #include "mitkCone.h" #include #include #include "mitkNDIPolarisTypeInformation.h" const std::string QmitkIGTTutorialView::VIEW_ID = "org.mitk.views.igttutorial"; QmitkIGTTutorialView::QmitkIGTTutorialView() : QmitkAbstractView(), m_Controls(nullptr), m_Source(nullptr), m_Visualizer(nullptr), m_Timer(nullptr) { } QmitkIGTTutorialView::~QmitkIGTTutorialView() { } void QmitkIGTTutorialView::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { // create GUI widget m_Controls = new Ui::QmitkIGTTutorialViewControls; m_Controls->setupUi(parent); this->CreateConnections(); } } void QmitkIGTTutorialView::CreateConnections() { if ( m_Controls ) { connect( (QObject*)(m_Controls->m_StartButton), SIGNAL(clicked()),(QObject*) this, SLOT(OnStartIGT())); connect( (QObject*)(m_Controls->m_StopButton), SIGNAL(clicked()),(QObject*) this, SLOT(OnStopIGT())); } } void QmitkIGTTutorialView::SetFocus() { m_Controls->m_virtualTrackingRadioButton->setFocus(); } //The next line starts a snippet to display this code in the documentation. If you don't revise the documentation, don't remove it! //! [OnStart 1] void QmitkIGTTutorialView::OnStartIGT() { //This method is called when the "Start Image Guided Therapy" button is pressed. Any kind of navigation application will //start with the connection to a tracking system and as we do image guided procedures we want to show //something on the screen. In this tutorial we connect to the NDI Polaris tracking system pr a virtual tracking device and we will //show the movement of a tool as cone in MITK. //! [OnStart 1] //! [OnStart 2] try { if(m_Controls->m_NDITrackingRadioButton->isChecked()) { /**************** Variant 1: Use a NDI Polaris Tracking Device ****************/ //Here we want to use the NDI Polaris tracking device. Therefore we instantiate a object of the class //NDITrackingDevice and make some settings which are necessary for a proper connection to the device. MITK_INFO << "NDI tracking"; QMessageBox::warning ( nullptr, "Warning", "You have to set the parameters for the NDITracking device inside the code (QmitkIGTTutorialView::OnStartIGT()) before you can use it."); mitk::NDITrackingDevice::Pointer tracker = mitk::NDITrackingDevice::New(); //instantiate tracker->SetPortNumber(mitk::SerialCommunication::COM4); //set the comport tracker->SetBaudRate(mitk::SerialCommunication::BaudRate115200); //set the baud rate tracker->SetType(mitk::NDIPolarisTypeInformation::GetTrackingDeviceName()); //set the type there you can choose between Polaris and Aurora //The tools represent the sensors of the tracking device. In this case we have one pointer tool. //The TrackingDevice object it self fills the tool with data. So we have to add the tool to the //TrackingDevice object. // The Polaris system needs a ".rom" file which describes the geometry of the markers related to the tool tip. //NDI provides an own software (NDI architect) to generate those files. tracker->AddTool("MyInstrument", "c:\\myinstrument.rom"); //! [OnStart 2] /**************** End of Variant 1 ****************/ //! [OnStart 3] //The tracking device object is used for the physical connection to the device. To use the //data inside of our tracking pipeline we need a source. This source encapsulate the tracking device //and provides objects of the type mitk::NavigationData as output. The NavigationData objects stores //position, orientation, if the data is valid or not and special error informations in a covariance //matrix. // //Typically the start of a pipeline is a TrackingDeviceSource. To work correct we have to set a //TrackingDevice object. Attention you have to set the tools before you set the whole TrackingDevice //object to the TrackingDeviceSource because the source need to know how many outputs should be //generated. m_Source = mitk::TrackingDeviceSource::New(); //We need the filter objects to stay alive, //therefore they must be members. m_Source->SetTrackingDevice(tracker); //Here we set the tracking device to the source of the pipeline. //! [OnStart 3] } //! [OnStart 4] else { /**************** Variant 2: Emulate a Tracking Device with mitk::VirtualTrackingDevice ****************/ // For tests, it is useful to simulate a tracking device in software. This is what mitk::VirtualTrackingDevice does. // It will produce random position, orientation and error values for each tool that is added. MITK_INFO << "virtual tracking"<SetBounds(bounds); tracker->AddTool("MyInstrument"); // add a tool to tracker //The tracking device object is used for the physical connection to the device. To use the //data inside of our tracking pipeline we need a source. This source encapsulate the tracking device //and provides objects of the type mitk::NavigationData as output. The NavigationData objects stores //position, orientation, if the data is valid or not and special error informations in a covariance //matrix. // //Typically the start of a pipeline is a TrackingDeviceSource. To work correct we have to set a //TrackingDevice object. Attention you have to set the tools before you set the whole TrackingDevice //object to the TrackingDeviceSource because the source need to know how many outputs should be //generated. m_Source = mitk::TrackingDeviceSource::New(); //We need the filter objects to stay alive, //therefore they must be members. m_Source->SetTrackingDevice(tracker); //Here we set the tracking device to the source of the pipeline. /**************** End of Variant 2 ****************/ } //! [OnStart 4] //! [OnStart 5] m_Source->Connect(); //Now we connect to the tracking system. //Note we do not call this on the TrackingDevice object //! [OnStart 5] //! [OnStart 6] //As we wish to visualize our tool we need to have a PolyData which shows us the movement of our tool. //Here we take a cone shaped PolyData. In MITK you have to add the PolyData as a node into the DataStorage //to show it inside of the rendering windows. After that you can change the properties of the cone //to manipulate rendering, e.g. the position and orientation as in our case. mitk::Cone::Pointer cone = mitk::Cone::New(); //instantiate a new cone double scale[] = {10.0, 10.0, 10.0}; cone->GetGeometry()->SetSpacing(scale); //scale it a little that so we can see something mitk::DataNode::Pointer node = mitk::DataNode::New(); //generate a new node to store the cone into //the DataStorage. node->SetData(cone); //The data of that node is our cone. node->SetName("My tracked object"); //The node has additional properties like a name node->SetColor(1.0, 0.0, 0.0); //or the color. Here we make it red. this->GetDataStorage()->Add(node); //After adding the Node with the cone in it to the //DataStorage, MITK will show the cone in the //render windows. //! [OnStart 6] //! [OnStart 7] //For updating the render windows we use another filter of the MITK-IGT pipeline concept. The //NavigationDataObjectVisualizationFilter needs as input a NavigationData and a //PolyData. In our case the input is the source and the PolyData our cone. //First we create a new filter for the visualization update. m_Visualizer = mitk::NavigationDataObjectVisualizationFilter::New(); m_Visualizer->SetInput(0, m_Source->GetOutput()); //Then we connect to the pipeline. - m_Visualizer->SetRepresentationObject(0, cone); //After that we have to assign the cone to the input + m_Visualizer->SetRepresentationObject(0, cone.GetPointer()); // After that we have to assign the cone to the input //Now this simple pipeline is ready, so we can start the tracking. Here again: We do not call the //StartTracking method from the tracker object itself. Instead we call this method from our source. m_Source->StartTracking(); //! [OnStart 7] //! [OnStart 8] //Now every call of m_Visualizer->Update() will show us the cone at the position and orientation //given from the tracking device. //We use a QTimer object to call this Update() method in a fixed interval. if (m_Timer == nullptr) { m_Timer = new QTimer(this); //create a new timer } connect(m_Timer, SIGNAL(timeout()), this, SLOT(OnTimer())); //connect the timer to the method OnTimer() m_Timer->start(100); //Every 100ms the method OnTimer() is called. -> 10fps //! [OnStart 8] //! [OnStart 8a] //disable the tracking device selection this->m_Controls->m_NDITrackingRadioButton->setDisabled(true); this->m_Controls->m_virtualTrackingRadioButton->setDisabled(true); //! [OnStart 8a] } //! [OnStart 9] catch (std::exception& e) { // add cleanup MITK_INFO << "Error in QmitkIGTTutorial::OnStartIGT():" << e.what(); } //! [OnStart 9] } //![OnTimer] void QmitkIGTTutorialView::OnTimer() { //Here we call the Update() method from the Visualization Filter. Internally the filter checks if //new NavigationData is available. If we have a new NavigationData the cone position and orientation //will be adapted. m_Visualizer->Update(); auto geo = this->GetDataStorage()->ComputeBoundingGeometry3D(this->GetDataStorage()->GetAll()); mitk::RenderingManager::GetInstance()->InitializeViews( geo ); this->RequestRenderWindowUpdate(); } //![OnTimer] //![OnStop] void QmitkIGTTutorialView::OnStopIGT() { //This method is called when the Stop button is pressed. Here we disconnect the pipeline. if (m_Timer == nullptr) { MITK_INFO << "No Timer was set yet!"; return; } //To disconnect the pipeline in a save way we first stop the timer than we disconnect the tracking device. //After that we destroy all filters with changing them to nullptr. m_Timer->stop(); disconnect(m_Timer, SIGNAL(timeout()), this, SLOT(OnTimer())); m_Timer = nullptr; m_Source->StopTracking(); m_Source->Disconnect(); m_Source = nullptr; m_Visualizer = nullptr; m_Source = nullptr; this->GetDataStorage()->Remove(this->GetDataStorage()->GetNamedNode("My tracked object")); //enable the tracking device selection this->m_Controls->m_NDITrackingRadioButton->setEnabled(true); this->m_Controls->m_virtualTrackingRadioButton->setEnabled(true); } //![OnStop] diff --git a/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkNavigationDataPlayerView.cpp b/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkNavigationDataPlayerView.cpp index 034b7b6183..84d45f917c 100644 --- a/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkNavigationDataPlayerView.cpp +++ b/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkNavigationDataPlayerView.cpp @@ -1,236 +1,236 @@ /*============================================================================ 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. ============================================================================*/ // Qmitk #include "QmitkNavigationDataPlayerView.h" // QT #include #include //mitk #include #include #include #include #include #include // VTK #include const std::string QmitkNavigationDataPlayerView::VIEW_ID = "org.mitk.views.navigationdataplayer"; QmitkNavigationDataPlayerView::QmitkNavigationDataPlayerView() : m_Controls( nullptr ) { } QmitkNavigationDataPlayerView::~QmitkNavigationDataPlayerView() { } void QmitkNavigationDataPlayerView::CreateQtPartControl( QWidget *parent ) { // build up qt view, unless already done if ( !m_Controls ) { // create GUI widgets from the Qt Designer's .ui file m_Controls = new Ui::QmitkNavigationDataPlayerViewControls; m_Controls->setupUi( parent ); this->CreateConnections(); // make deselected Player invisible m_Controls->m_TimedWidget->setVisible(false); } } void QmitkNavigationDataPlayerView::SetFocus() { if ( m_Controls ) { m_Controls->m_grpbxControls->setFocus(); } } void QmitkNavigationDataPlayerView::CreateConnections() { connect( m_Controls->m_RdbSequential, SIGNAL(released()), this, SLOT(OnSelectPlayer()) ); connect( m_Controls->m_RdbTimeBased, SIGNAL(released()), this, SLOT(OnSelectPlayer()) ); connect( m_Controls->m_BtnOpenFile, SIGNAL(released()), this, SLOT(OnOpenFile()) ); connect( m_Controls->m_ChkDisplay, SIGNAL(released()), this, SLOT(OnSetDisplay()) ); connect( m_Controls->m_chkRepeat, SIGNAL(stateChanged(int)), this, SLOT(OnSetRepeat(int)) ); connect( m_Controls->m_ChkMicroservice, SIGNAL(released()), this, SLOT(OnSetMicroservice()) ); connect( m_Controls->m_SequentialWidget, SIGNAL(SignalUpdate()), this, SLOT(OnUpdate()) ); connect( m_Controls->m_TimedWidget, SIGNAL(SignalUpdate()), this, SLOT(OnUpdate()) ); this->SetInteractionComponentsEnabledState(false); } void QmitkNavigationDataPlayerView::OnOpenFile() { mitk::NavigationDataReaderInterface::Pointer reader = nullptr; QString filter = tr("NavigationData File (*.csv *.xml)"); QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open NavigationData Set"), "", filter); if ( fileName.isNull() ) { return; } // user pressed cancel try { m_Data = dynamic_cast (mitk::IOUtil::Load(fileName.toStdString())[0].GetPointer()); } catch ( const mitk::Exception &e ) { MITK_WARN("NavigationDataPlayerView") << "could not open file " << fileName.toStdString(); QMessageBox::critical(nullptr, "Error Reading File", "The file '" + fileName +"' could not be read.\n" + e.GetDescription() ); return; } if (m_Controls->m_ChkConvertToPointSet->isChecked()) m_Data->ConvertNavigationDataToPointSet(); // Update Labels m_Controls->m_LblFilePath->setText(fileName); m_Controls->m_LblFrames->setText(QString::number(m_Data->Size())); m_Controls->m_LblTools->setText(QString::number(m_Data->GetNumberOfTools())); // Initialize Widgets and create Player this->OnSelectPlayer(); this->SetInteractionComponentsEnabledState(true); } void QmitkNavigationDataPlayerView::OnSelectPlayer() { if (m_Controls->m_RdbSequential->isChecked()) { m_Controls->m_SequentialWidget->setVisible(true); m_Controls->m_TimedWidget->setVisible(false); mitk::NavigationDataSequentialPlayer::Pointer seqPlayer = mitk::NavigationDataSequentialPlayer::New(); seqPlayer->SetNavigationDataSet(m_Data); m_Controls->m_SequentialWidget->SetPlayer(seqPlayer); m_Player = seqPlayer; } else { m_Controls->m_SequentialWidget->setVisible(false); m_Controls->m_TimedWidget->setVisible(true); mitk::NavigationDataPlayer::Pointer timedPlayer = mitk::NavigationDataPlayer::New(); timedPlayer->SetNavigationDataSet(m_Data); m_Controls->m_TimedWidget->SetPlayer(timedPlayer); m_Player = timedPlayer; } this->ConfigurePlayer(); // SetupRenderingPipeline this->OnSetDisplay(); } void QmitkNavigationDataPlayerView::ConfigurePlayer() { // set repeat mode according to the checkbox m_Player->SetRepeat( m_Controls->m_chkRepeat->isChecked() ); } void QmitkNavigationDataPlayerView::OnSetRepeat(int checkState) { m_Player->SetRepeat(checkState != 0); } void QmitkNavigationDataPlayerView::OnSetMicroservice(){ if(m_Controls->m_ChkMicroservice->isChecked()) { m_ToolStorage = mitk::NavigationToolStorage::New(); for (itk::ProcessObject::DataObjectPointerArraySizeType i = 0; i < m_Player->GetNumberOfIndexedOutputs(); i++) { mitk::NavigationTool::Pointer currentDummyTool = mitk::NavigationTool::New(); mitk::VirtualTrackingTool::Pointer dummyTool = mitk::VirtualTrackingTool::New(); std::stringstream name; name << "Virtual Tool " << i; dummyTool->SetToolName(name.str()); currentDummyTool->SetDataNode(m_RenderingNodes.at(i)); currentDummyTool->SetIdentifier(name.str()); m_ToolStorage->AddTool(currentDummyTool); } m_ToolStorage->SetName("NavigationDataPlayer Tool Storage"); m_Player->SetToolMetaDataCollection(m_ToolStorage); m_Player->RegisterAsMicroservice(); m_ToolStorage->SetSourceID(m_Player->GetMicroserviceID()); //DEPRECATED / not needed anymore because NavigationDataSource now holds a member of its tool storage. Only left for backward compatibility. m_ToolStorage->RegisterAsMicroservice(); } else { if (m_ToolStorage.IsNotNull()) m_ToolStorage->UnRegisterMicroservice(); m_ToolStorage = nullptr; m_Player->UnRegisterMicroservice(); } } void QmitkNavigationDataPlayerView::OnUpdate(){ if (m_VisFilter.IsNotNull()) { m_VisFilter->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkNavigationDataPlayerView::OnSetDisplay(){ DestroyPipeline(); if ( (m_Controls->m_ChkDisplay->isChecked()) && ( m_Player.IsNotNull() )) { CreatePipeline(); } } void QmitkNavigationDataPlayerView::CreatePipeline(){ m_VisFilter = mitk::NavigationDataObjectVisualizationFilter::New(); m_VisFilter->ConnectTo(m_Player); for (unsigned int i = 0 ; i < m_Player->GetNumberOfIndexedOutputs(); i++ ) { mitk::DataNode::Pointer node = mitk::DataNode::New(); QString name = "Recorded Tool " + QString::number(i + 1); node->SetName(name.toStdString()); //create small sphere and use it as surface mitk::Surface::Pointer mySphere = mitk::Surface::New(); vtkSmartPointer vtkData = vtkSmartPointer::New(); vtkData->SetRadius(5.0f); vtkData->SetCenter(0.0, 0.0, 0.0); vtkData->Update(); mySphere->SetVtkPolyData(vtkData->GetOutput()); node->SetData(mySphere); - m_VisFilter->SetRepresentationObject(i, mySphere); + m_VisFilter->SetRepresentationObject(i, mySphere.GetPointer()); // Add Node to DataStorageand to local list of Nodes GetDataStorage()->Add(node); m_RenderingNodes.push_back(node); } m_VisFilter->Update(); mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(GetDataStorage()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkNavigationDataPlayerView::DestroyPipeline(){ m_VisFilter = nullptr; for (unsigned int i = 0; i < m_RenderingNodes.size(); i++){ this->GetDataStorage()->Remove(m_RenderingNodes[i]); } m_RenderingNodes.clear(); } void QmitkNavigationDataPlayerView::SetInteractionComponentsEnabledState(bool isActive){ m_Controls->m_grpbxSettings->setEnabled(isActive); m_Controls->m_grpbxControls->setEnabled(isActive); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index fa2c3d0182..e1a1c1261f 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,599 +1,378 @@ /*============================================================================ 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 "QmitkImageStatisticsView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include -#include -#include +#include "mitkPlanarFigureMaskGenerator.h" + +#include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); m_Controls.sliderWidget_histogram->setPrefix("Time: "); m_Controls.sliderWidget_histogram->setDecimals(0); m_Controls.sliderWidget_histogram->setVisible(false); m_Controls.sliderWidget_intensityProfile->setPrefix("Time: "); m_Controls.sliderWidget_intensityProfile->setDecimals(0); m_Controls.sliderWidget_intensityProfile->setVisible(false); ResetGUI(); - m_Controls.widget_statistics->SetDataStorage(GetDataStorage()); + m_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); + m_DataGenerator->SetDataStorage(this->GetDataStorage()); + m_DataGenerator->SetAutoUpdate(true); + m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); CreateConnections(); } void QmitkImageStatisticsView::CreateConnections() { connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); -} - -void QmitkImageStatisticsView::CalculateOrGetMultiStatistics() -{ - m_selectedImageNode = nullptr; - m_selectedMaskNode = nullptr; - if (m_selectedImageNodes.empty()) - { - // no images selected; reset everything - ResetGUI(); - } + connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, + this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); - for (auto imageNode : m_selectedImageNodes) - { - m_selectedImageNode = imageNode; + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::DataGenerationStarted, + this, &QmitkImageStatisticsView::OnGenerationStarted); + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, + this, &QmitkImageStatisticsView::OnGenerationFinished); + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, + this, &QmitkImageStatisticsView::OnJobError); - if (m_selectedMaskNodes.empty()) - { - CalculateOrGetStatistics(); - } - else - { - for (auto maskNode : m_selectedMaskNodes) - { - m_selectedMaskNode = maskNode; - CalculateOrGetStatistics(); - } - } - } } -void QmitkImageStatisticsView::CalculateOrGetStatistics() +void QmitkImageStatisticsView::UpdateIntensityProfile() { - if (nullptr != m_selectedPlanarFigure) - { - m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); - m_selectedPlanarFigure = nullptr; - } - m_Controls.groupBox_intensityProfile->setVisible(false); - m_Controls.widget_statistics->setEnabled(m_selectedImageNode.IsNotNull()); - if (nullptr != m_selectedImageNode) - { - auto image = dynamic_cast(m_selectedImageNode->GetData()); - mitk::Image *mask = nullptr; - mitk::PlanarFigure *maskPlanarFigure = nullptr; - - if (image->GetDimension() == 4) - { - m_Controls.sliderWidget_histogram->setVisible(true); - unsigned int maxTimestep = image->GetTimeSteps(); - m_Controls.sliderWidget_histogram->setMaximum(maxTimestep - 1); - } - else - { - m_Controls.sliderWidget_histogram->setVisible(false); - } + if (m_selectedImageNodes.size()==1) + { //only supported for one image and roi currently + auto image = dynamic_cast(m_selectedImageNodes.front()->GetData()); - if (nullptr != m_selectedMaskNode) + if (m_selectedPlanarFigure.IsNotNull()) { - mask = dynamic_cast(m_selectedMaskNode->GetData()); - if (nullptr == mask) + if (!m_selectedPlanarFigure->IsClosed()) { - maskPlanarFigure = dynamic_cast(m_selectedMaskNode->GetData()); - } - } + mitk::Image::Pointer inputImage; + if (image->GetDimension() == 4) + { + m_Controls.sliderWidget_intensityProfile->setVisible(true); + unsigned int maxTimestep = image->GetTimeSteps(); + m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); + // Intensity profile can only be calculated on 3D, so extract if 4D + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); + timeSelector->SetInput(image); + timeSelector->SetTimeNr(currentTimestep); + timeSelector->Update(); + + inputImage = timeSelector->GetOutput(); + } + else + { + m_Controls.sliderWidget_intensityProfile->setVisible(false); + inputImage = image; + } - mitk::ImageStatisticsContainer::ConstPointer imageStatistics; - if (nullptr != mask) - { - imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - } - else if (nullptr != maskPlanarFigure) - { - m_selectedPlanarFigure = maskPlanarFigure; - ITKCommandType::Pointer changeListener = ITKCommandType::New(); - changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::CalculateOrGetMultiStatistics); - m_PlanarFigureObserverTag = - m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); - if (!maskPlanarFigure->IsClosed()) - { - ComputeAndDisplayIntensityProfile(image, maskPlanarFigure); + auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, m_selectedPlanarFigure); + m_Controls.groupBox_intensityProfile->setVisible(true); + m_Controls.widget_intensityProfile->Reset(); + m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), + "Intensity Profile of " + m_selectedImageNodes.front()->GetName()); } - imageStatistics = - mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, maskPlanarFigure); - } - else - { - imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image); } + } +} - bool imageStatisticsOlderThanInputs = false; - if (nullptr != imageStatistics && - ((nullptr != image && imageStatistics->GetMTime() < image->GetMTime()) || - (nullptr != mask && imageStatistics->GetMTime() < mask->GetMTime()) || - (nullptr != maskPlanarFigure && imageStatistics->GetMTime() < maskPlanarFigure->GetMTime()))) +void QmitkImageStatisticsView::UpdateHistogramWidget() +{ + m_Controls.groupBox_histogram->setVisible(true); + + if (m_selectedImageNodes.size() == 1 && m_selectedMaskNodes.size()<=1) + { //currently only supported for one image and roi due to histogram widget limitations. + auto imageNode = m_selectedImageNodes.front(); + const mitk::DataNode* roiNode = nullptr; + if (!m_selectedMaskNodes.empty()) { - imageStatisticsOlderThanInputs = true; + roiNode = m_selectedMaskNodes.front(); } + auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode, true); - if (nullptr != imageStatistics) + if (statisticsNode.IsNotNull()) { - // triggers re-computation when switched between images and the newest one has not 100 bins (default) - if (imageStatistics->TimeStepExists(0)) + auto statistics = dynamic_cast(statisticsNode->GetData()); + + if (statistics) { - auto calculatedBins = imageStatistics->GetStatisticsForTimeStep(0).m_Histogram.GetPointer()->Size(); - if (calculatedBins != 100) + std::stringstream label; + label << "Histogram " << imageNode->GetName(); + if (imageNode->GetData()->GetTimeSteps() > 1) { - OnRequestHistogramUpdate(m_Controls.widget_histogram->GetBins()); + label << "[0]"; } - } - } - // statistics need to be computed - if (!imageStatistics || imageStatisticsOlderThanInputs || m_ForceRecompute) - { - CalculateStatistics(image, mask, maskPlanarFigure); - } - // statistics already computed - else - { - // Not an open planar figure: show histogram (intensity profile already shown) - if (!(maskPlanarFigure && !maskPlanarFigure->IsClosed())) - { - if (imageStatistics->TimeStepExists(0)) + if (roiNode) { - auto histogram = imageStatistics->GetStatisticsForTimeStep(0).m_Histogram.GetPointer(); - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ histogram }, { histogramLabelName }); + label << " with " << roiNode->GetName(); } + + m_Controls.widget_histogram->SetTheme(GetColorTheme()); + m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(0), label.str()); } + m_Controls.groupBox_histogram->setVisible(statisticsNode.IsNotNull()); } } } -void QmitkImageStatisticsView::CalculateStatistics(const mitk::Image *image, const mitk::Image *mask, - const mitk::PlanarFigure *maskPlanarFigure) -{ - auto runnable = new QmitkImageStatisticsCalculationRunnable(); - runnable->Initialize(image, mask, maskPlanarFigure); - runnable->setAutoDelete(false); - runnable->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); - m_Runnables.push_back(runnable); - connect(runnable, &QmitkImageStatisticsCalculationRunnable::finished, - this, &QmitkImageStatisticsView::OnStatisticsCalculationEnds, Qt::QueuedConnection); - - try - { - // Compute statistics - QThreadPool::globalInstance()->start(runnable); - m_Controls.label_currentlyComputingStatistics->setVisible(true); - } - catch (const mitk::Exception &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.GetDescription()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } - catch (const std::runtime_error &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } - catch (const std::exception &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } -} - -void QmitkImageStatisticsView::ComputeAndDisplayIntensityProfile(mitk::Image *image, - mitk::PlanarFigure *maskPlanarFigure) -{ - mitk::Image::Pointer inputImage; - if (image->GetDimension() == 4) - { - m_Controls.sliderWidget_intensityProfile->setVisible(true); - unsigned int maxTimestep = image->GetTimeSteps(); - m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); - // Intensity profile can only be calculated on 3D, so extract if 4D - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); - timeSelector->SetInput(image); - timeSelector->SetTimeNr(currentTimestep); - timeSelector->Update(); - - inputImage = timeSelector->GetOutput(); - } - else - { - m_Controls.sliderWidget_intensityProfile->setVisible(false); - inputImage = image; - } - - auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, maskPlanarFigure); - // Don't show histogram for intensity profiles - m_Controls.groupBox_histogram->setVisible(false); - m_Controls.groupBox_intensityProfile->setVisible(true); - m_Controls.widget_intensityProfile->Reset(); - m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), - "Intensity Profile of " + m_selectedImageNode->GetName()); -} - -void QmitkImageStatisticsView::FillHistogramWidget(const std::vector &histogram, - const std::vector &dataLabels) -{ - m_Controls.groupBox_histogram->setVisible(true); - m_Controls.widget_histogram->SetTheme(GetColorTheme()); - m_Controls.widget_histogram->SetHistogram(histogram.front(), dataLabels.front()); - connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, - this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); -} - QmitkChartWidget::ColorTheme QmitkImageStatisticsView::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); } -void QmitkImageStatisticsView::ResetGUIDefault() +void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { - m_Controls.widget_histogram->ResetDefault(); - m_Controls.checkBox_ignoreZero->setChecked(false); - m_IgnoreZeroValueVoxel = false; + m_Controls.label_currentlyComputingStatistics->setVisible(true); } -void QmitkImageStatisticsView::OnStatisticsCalculationEnds() +void QmitkImageStatisticsView::OnGenerationFinished() { - auto runnable = qobject_cast(sender()); - if (nullptr == runnable) - { - return; - } + m_Controls.label_currentlyComputingStatistics->setVisible(false); mitk::StatusBar::GetInstance()->Clear(); - auto it = std::find(m_Runnables.begin(), m_Runnables.end(), runnable); - if (it != m_Runnables.end()) - { - m_Runnables.erase(it); - } - - auto image = runnable->GetStatisticsImage(); - - // get mask - const mitk::BaseData* mask = nullptr; - if (runnable->GetMaskImage()) - { - mask = runnable->GetMaskImage(); - } - else if (runnable->GetPlanarFigure()) - { - mask = runnable->GetPlanarFigure(); - } - - // get current statistics - auto currentImageStatistics = - mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - - if (runnable->GetStatisticsUpdateSuccessFlag()) // case: calculation was successful - { - auto statistic = runnable->GetStatisticsData(); - - SetupRelationRules(image, mask, statistic); - - // checks for existing statistic, and add it to data manager - HandleExistingStatistics(image, mask, statistic); - - auto maskPlanarFigure = dynamic_cast(mask); - if (!maskPlanarFigure || maskPlanarFigure->IsClosed()) - { - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ runnable->GetTimeStepHistogram() }, { histogramLabelName }); - } - } - else // case: calculation was not successful - { - // handle histogram - auto emptyHistogram = HistogramType::New(); - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ emptyHistogram }, { histogramLabelName }); - - // handle statistics - mitk::ImageStatisticsContainer::Pointer statistic = mitk::ImageStatisticsContainer::New(); - statistic->SetTimeGeometry(const_cast(image->GetTimeGeometry())); - - // add empty histogram to statistics for all time steps - for (unsigned int i = 0; i < image->GetTimeSteps(); ++i) - { - auto statisticObject = mitk::ImageStatisticsContainer::ImageStatisticsObject(); - statisticObject.m_Histogram = emptyHistogram; - statistic->SetStatisticsForTimeStep(i, statisticObject); - } - - SetupRelationRules(image, mask, statistic); - - HandleExistingStatistics(image, mask, statistic); - - mitk::StatusBar::GetInstance()->DisplayErrorText(runnable->GetLastErrorMessage().c_str()); - m_Controls.widget_histogram->setEnabled(false); - } + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); +} - m_Controls.label_currentlyComputingStatistics->setVisible(false); +void QmitkImageStatisticsView::OnJobError(QString error, const QmitkDataGenerationJobBase* /*failedJob*/) +{ + mitk::StatusBar::GetInstance()->DisplayErrorText(error.toStdString().c_str()); + MITK_WARN << "Error when calculating statistics: " << error; } -void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int) +void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int nbins) { - // NEEDS TO BE IMPLEMENTED - SEE T27032 + m_Controls.widget_statistics->SetHistogramNBins(nbins); + m_DataGenerator->SetHistogramNBins(nbins); + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { - m_ForceRecompute = true; - m_IgnoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; - - CalculateOrGetMultiStatistics(); - - m_ForceRecompute = false; + auto ignoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; + m_Controls.widget_statistics->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); + m_DataGenerator->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnButtonSelectionPressed() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(nullptr, "Select input for the statistic","You may select images and ROIs to compute their statistic. ROIs may be segmentations or planar figures."); dialog->SetDataStorage(GetDataStorage()); dialog->SetSelectionCheckFunction(CheckForSameGeometry()); // set predicates auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); auto isImageOrMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isMaskOrPlanarFigurePredicate, isImagePredicate); dialog->SetNodePredicate(isImageOrMaskOrPlanarFigurePredicate); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetCurrentSelection(m_SelectedNodeList); if (dialog->exec()) { - m_selectedImageNodes.resize(0); - m_selectedMaskNodes.resize(0); + std::vector imageNodes; + std::vector maskNodes; m_SelectedNodeList = dialog->GetSelectedNodes(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); for (const auto& node : m_SelectedNodeList) { if (isImagePredicate->CheckNode(node)) { - m_selectedImageNodes.push_back(node.GetPointer()); + imageNodes.push_back(node.GetPointer()); } else { - m_selectedMaskNodes.push_back(node.GetPointer()); + maskNodes.push_back(node.GetPointer()); } } - m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); + m_selectedImageNodes = imageNodes; + m_selectedMaskNodes = maskNodes; - m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); - - CalculateOrGetMultiStatistics(); - } - - delete dialog; -} - -void QmitkImageStatisticsView::HandleExistingStatistics(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask, - mitk::ImageStatisticsContainer::Pointer statistic) -{ - auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - - // if statistics base data already exist: add to existing node - if (imageStatistics) - { - auto node = GetNodeForStatisticsContainer(imageStatistics); - node->SetData(statistic); - } - // statistics base data does not exist: add new node - else - { - auto statisticsNodeName = GenerateStatisticsNodeName(image, mask); - auto statisticsNode = mitk::CreateImageStatisticsNode(statistic, statisticsNodeName); - GetDataStorage()->Add(statisticsNode); - } -} - -std::string QmitkImageStatisticsView::GenerateStatisticsNodeName(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask) -{ - std::string statisticsNodeName = "no"; + if (nullptr != m_selectedPlanarFigure) + { + m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); + m_selectedPlanarFigure = nullptr; + } - if (image.IsNotNull()) - { - statisticsNodeName = image->GetUID(); - } + mitk::PlanarFigure* maskPlanarFigure = nullptr; - if (mask.IsNotNull()) - { - statisticsNodeName += "_" + mask->GetUID(); - } + if (m_selectedMaskNodes.size() == 1) + { //currently we support this only with one ROI selected + maskPlanarFigure = dynamic_cast(m_selectedMaskNodes.front()->GetData()); + } - statisticsNodeName += "_statistics"; + if (nullptr != maskPlanarFigure) + { + m_selectedPlanarFigure = maskPlanarFigure; + ITKCommandType::Pointer changeListener = ITKCommandType::New(); + changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::UpdateIntensityProfile); + m_PlanarFigureObserverTag = + m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); - return statisticsNodeName; -} + this->UpdateIntensityProfile(); + } -void QmitkImageStatisticsView::SetupRelationRules(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask, - mitk::ImageStatisticsContainer::Pointer statistic) -{ - auto imageRule = mitk::StatisticsToImageRelationRule::New(); - imageRule->Connect(statistic, image); + m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); + m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); - if (nullptr != mask) - { - auto maskRule = mitk::StatisticsToMaskRelationRule::New(); - maskRule->Connect(statistic, mask); - } -} + m_DataGenerator->SetAutoUpdate(false); + m_DataGenerator->SetImageNodes(m_selectedImageNodes); + m_DataGenerator->SetROINodes(m_selectedMaskNodes); + m_DataGenerator->Generate(); + m_DataGenerator->SetAutoUpdate(true); -mitk::DataNode::Pointer QmitkImageStatisticsView::GetNodeForStatisticsContainer(mitk::ImageStatisticsContainer::ConstPointer container) -{ - if (container.IsNull()) - { - mitkThrow() << "Given container is null!"; + m_Controls.widget_statistics->setEnabled(!m_selectedImageNodes.empty()); } - auto allDataNodes = GetDataStorage()->GetAll()->CastToSTLConstContainer(); - for (auto node : allDataNodes) - { - auto nodeData = node->GetData(); - if (nodeData && nodeData->GetUID() == container->GetUID()) - { - return node; - } - } - mitkThrow() << "No DataNode is found which holds the given statistics container!"; + delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto lambda = [](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { imageNodeData = dynamic_cast(node->GetData()); if (imageNodeData) { break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

"; return ss.str(); } auto imageGeoPredicate = mitk::NodePredicateGeometry::New(imageNodeData->GetGeometry()); for (auto& rightNode : nodes) { if (imageNodeData != rightNode->GetData()) { bool sameGeometry = true; if (dynamic_cast(rightNode->GetData())) { sameGeometry = imageGeoPredicate->CheckNode(rightNode); } else { const mitk::PlanarFigure* planar2 = dynamic_cast(rightNode->GetData()); if (planar2) { sameGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), imageNodeData->GetGeometry()); } } if (!sameGeometry) { std::stringstream ss; ss << "

Invalid selection: All selected nodes must have the same geometry.

Differing node i.a.: \""; ss << rightNode->GetName() <<"\"

"; return ss.str(); } } } return std::string(); }; return lambda; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h index 23d456eb19..1d8c754f5c 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,107 +1,89 @@ /*============================================================================ 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 QMITKIMAGESTATISTICSVIEW_H #define QMITKIMAGESTATISTICSVIEW_H #include "ui_QmitkImageStatisticsViewControls.h" #include -#include #include #include #include +class QmitkImageStatisticsDataGenerator; +class QmitkDataGenerationJobBase; + /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a separate thread to keep the gui accessible during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default destructor */ ~QmitkImageStatisticsView() override; /*! \brief Creates the widget containing the application controls, like sliders, buttons etc.*/ void CreateQtPartControl(QWidget *parent) override; protected: using HistogramType = mitk::ImageStatisticsContainer::HistogramType; void SetFocus() override { }; virtual void CreateConnections(); - void CalculateOrGetMultiStatistics(); - void CalculateOrGetStatistics(); - void CalculateStatistics(const mitk::Image* image, const mitk::Image* mask = nullptr, - const mitk::PlanarFigure* maskPlanarFigure = nullptr); + void UpdateIntensityProfile(); + void UpdateHistogramWidget(); - void ComputeAndDisplayIntensityProfile(mitk::Image * image, mitk::PlanarFigure* maskPlanarFigure); - void FillHistogramWidget(const std::vector &histogram, - const std::vector &dataLabels); QmitkChartWidget::ColorTheme GetColorTheme() const; void ResetGUI(); - void ResetGUIDefault(); - void OnStatisticsCalculationEnds(); + void OnGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job); + void OnGenerationFinished(); + void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob); void OnRequestHistogramUpdate(unsigned int); void OnCheckBoxIgnoreZeroStateChanged(int state); void OnButtonSelectionPressed(); // member variable Ui::QmitkImageStatisticsViewControls m_Controls; private: - - void HandleExistingStatistics(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer, - mitk::ImageStatisticsContainer::Pointer); - - std::string GenerateStatisticsNodeName(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer); - - void SetupRelationRules(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer, - mitk::ImageStatisticsContainer::Pointer); - - mitk::DataNode::Pointer GetNodeForStatisticsContainer(mitk::ImageStatisticsContainer::ConstPointer container); - QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry() const; typedef itk::SimpleMemberCommand ITKCommandType; - mitk::DataNode::ConstPointer m_selectedImageNode = nullptr; - mitk::DataNode::ConstPointer m_selectedMaskNode = nullptr; mitk::PlanarFigure::Pointer m_selectedPlanarFigure = nullptr; long m_PlanarFigureObserverTag; - bool m_ForceRecompute = false; - bool m_IgnoreZeroValueVoxel = false; std::vector m_selectedMaskNodes; std::vector m_selectedImageNodes; QmitkNodeSelectionDialog::NodeList m_SelectedNodeList; std::vector m_StatisticsForSelection; - std::vector m_Runnables; - + QmitkImageStatisticsDataGenerator* m_DataGenerator = nullptr; }; #endif // QMITKIMAGESTATISTICSVIEW_H 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 728b8cf75c..f471a8a318 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,875 +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 "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(); } 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()); - auto propertyFilters = mitk::CoreServices::GetPropertyFilters(); + mitk::PropertyFilter filter; + filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); - if (propertyFilters != nullptr) - { - mitk::PropertyFilter filter; - filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); - - propertyFilters->AddFilter(filter, "PlanarPolygon"); - } + 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); }