diff --git a/CMake/PackageDepends/MITK_Boost_Config.cmake b/CMake/PackageDepends/MITK_Boost_Config.cmake index b152bfa4f5..04243ce630 100644 --- a/CMake/PackageDepends/MITK_Boost_Config.cmake +++ b/CMake/PackageDepends/MITK_Boost_Config.cmake @@ -1,13 +1,15 @@ +set(Boost_ADDITIONAL_VERSIONS "1.78.0" "1.78") + find_package(Boost REQUIRED COMPONENTS ${Boost_REQUIRED_COMPONENTS_BY_MODULE}) if(Boost_REQUIRED_COMPONENTS_BY_MODULE) foreach(boost_component ${Boost_REQUIRED_COMPONENTS_BY_MODULE}) list(APPEND ALL_LIBRARIES "Boost::${boost_component}") endforeach() endif() list(APPEND ALL_LIBRARIES "Boost::boost") if(MSVC) list(APPEND ALL_LIBRARIES "Boost::dynamic_linking" "bcrypt") endif() diff --git a/CMake/mitkFunctionGetMSVCVersion.cmake b/CMake/mitkFunctionGetMSVCVersion.cmake index e12baec1a3..440d316b7d 100644 --- a/CMake/mitkFunctionGetMSVCVersion.cmake +++ b/CMake/mitkFunctionGetMSVCVersion.cmake @@ -1,25 +1,30 @@ function(mitkFunctionGetMSVCVersion ) if(MSVC) if(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) set(VISUAL_STUDIO_PRODUCT_NAME "Visual Studio 2017" PARENT_SCOPE) set(VISUAL_STUDIO_VERSION_MAJOR 14 PARENT_SCOPE) string(SUBSTRING ${MSVC_VERSION} 2 -1 version_minor) set(VISUAL_STUDIO_VERSION_MINOR ${version_minor} PARENT_SCOPE) elseif(MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1930) set(VISUAL_STUDIO_PRODUCT_NAME "Visual Studio 2019" PARENT_SCOPE) set(VISUAL_STUDIO_VERSION_MAJOR 14 PARENT_SCOPE) string(SUBSTRING ${MSVC_VERSION} 2 -1 version_minor) set(VISUAL_STUDIO_VERSION_MINOR ${version_minor} PARENT_SCOPE) + elseif(MSVC_VERSION GREATER_EQUAL 1930 AND MSVC_VERSION LESS 1940) + set(VISUAL_STUDIO_PRODUCT_NAME "Visual Studio 2022" PARENT_SCOPE) + set(VISUAL_STUDIO_VERSION_MAJOR 14 PARENT_SCOPE) + string(SUBSTRING ${MSVC_VERSION} 2 -1 version_minor) + set(VISUAL_STUDIO_VERSION_MINOR ${version_minor} PARENT_SCOPE) else() message(WARNING "Unknown Visual Studio version ${MSVC_VERSION} (CMake/mitkFunctionGetMSVCVersion.cmake)") endif() if(CMAKE_VS_PLATFORM_NAME STREQUAL x64) set(CMAKE_LIBRARY_ARCHITECTURE x64 PARENT_SCOPE) else() set(CMAKE_LIBRARY_ARCHITECTURE x86 PARENT_SCOPE) endif() endif() endfunction() diff --git a/CMakeExternals/Boost.cmake b/CMakeExternals/Boost.cmake index 208c32fcc9..2a92b4030c 100644 --- a/CMakeExternals/Boost.cmake +++ b/CMakeExternals/Boost.cmake @@ -1,337 +1,342 @@ #----------------------------------------------------------------------------- # Boost #----------------------------------------------------------------------------- include(mitkFunctionGetMSVCVersion) #[[ Sanity checks ]] if(DEFINED BOOST_ROOT AND NOT EXISTS ${BOOST_ROOT}) message(FATAL_ERROR "BOOST_ROOT variable is defined but corresponds to non-existing directory") endif() string(REPLACE "^^" ";" MITK_USE_Boost_LIBRARIES "${MITK_USE_Boost_LIBRARIES}") set(proj Boost) set(proj_DEPENDENCIES ) set(Boost_DEPENDS ${proj}) if(NOT DEFINED BOOST_ROOT AND NOT MITK_USE_SYSTEM_Boost) #[[ Reset variables. ]] set(patch_cmd "") set(configure_cmd "") set(install_cmd "") set(BOOST_ROOT ${ep_prefix}) if(WIN32) set(BOOST_LIBRARYDIR "${BOOST_ROOT}/lib") endif() #[[ If you update Boost, make sure that the FindBoost module of the minimum required version of CMake supports the new version of Boost. In case you are using a higher version of CMake, download at least the source code of the minimum required version of CMake to look into the right version of the FindBoost module: /share/cmake-/Modules/FindBoost.cmake Search for a list called _Boost_KNOWN_VERSIONS. If the new version is not included in this list, you have three options: * Update the minimum required version of CMake. This may require adaptions of other parts of our CMake scripts and has the most impact on other MITK developers. Yet this is the safest and cleanest option. * Set Boost_ADDITIONAL_VERSIONS (see the documentation of the FindBoost module). As Boost libraries and dependencies between them are hard-coded in the FindBoost module only for known versions, this may cause trouble for other MITK developers relying on new components of Boost or components with changed dependencies. * Copy a newer version of the FindBoost module into our CMake directory. Our CMake directory has a higher precedence than the default CMake module directory. Doublecheck if the minimum required version of CMake is able to process the newer version of the FindBoost module. Also, DO NOT FORGET to mention this option right above the call of cmake_minimum_required() in the top-level CMakeLists.txt file AND in this file right above the set(url) command below so if we update the minimum required version of CMake or use another option in the future, we do not forget to remove our copy of the FindBoost module again. ]] - set(url "${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/boost_1_74_0.tar.gz") - set(md5 3c8fb92ce08b9ad5a5f0b35731ac2c8e) + set(url "${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/boost_1_78_0_b1.tar.gz") + set(md5 bbaa634603e3789d7dd0c21d0bdf4f09) if(MITK_USE_Boost_LIBRARIES) #[[ Boost has a two-step build process. In the first step, a bootstrap script is called to build b2, an executable that is used to actually build Boost in the second step. The bootstrap script expects a toolset (compiler) argument that is used to build the b2 executable. The scripts and their expected argument format differ between Windows and Unix. ]] if(WIN32) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_VERSION_MINOR EQUAL 0) #[[ Use just the major version in the toolset name. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}) elseif(VISUAL_STUDIO_VERSION_MAJOR EQUAL 14 AND VISUAL_STUDIO_VERSION_MINOR LESS 20) #[[ Assume Visual Studio 2017. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}1) elseif(VISUAL_STUDIO_VERSION_MAJOR EQUAL 14 AND VISUAL_STUDIO_VERSION_MINOR LESS 30) #[[ Assume Visual Studio 2019. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}2) + elseif(VISUAL_STUDIO_VERSION_MAJOR EQUAL 14 AND VISUAL_STUDIO_VERSION_MINOR LESS 40) + + #[[ Assume Visual Studio 2022. ]] + set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}3) + else() #[[ Fallback to the generic case. Be prepared to add another elseif branch above for future versions of Visual Studio. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}) endif() else() #[[ We support GCC and Clang on Unix. On macOS, the toolset must be set to clang. The actual compiler for all of these toolkits is set further below, after the bootstrap script but before b2. ]] if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) set(toolset gcc) elseif(CMAKE_CXX_COMPILER_ID STREQUAL Clang OR APPLE) set(toolset clang) endif() if(toolset) set(bootstrap_args --with-toolset=${toolset}) endif() #[[ At least give it a shot if the toolset is something else and let the bootstrap script decide on the toolset by not passing any argument. ]] endif() #[[ The call of b2 is more complex. b2 arguments are grouped into options and properties. Options follow the standard format for arguments while properties are plain key-value pairs. ]] set(b2_options --build-dir= --stagedir= --ignore-site-config #[[ Build independent of any site.config file ]] -q #[[ Stop at first error ]] ) if(APPLE AND CMAKE_OSX_SYSROOT) #[[ Specify the macOS platform SDK to be used. ]] list(APPEND b2_options --sysroot=${CMAKE_OSX_SYSROOT}) endif() foreach(lib ${MITK_USE_Boost_LIBRARIES}) list(APPEND b2_options --with-${lib}) endforeach() set(b2_properties threading=multi runtime-link=shared "cxxflags=${MITK_CXX14_FLAG} ${CMAKE_CXX_FLAGS}" ) if(CMAKE_SIZEOF_VOID_P EQUAL 8) list(APPEND b2_properties address-model=64) else() list(APPEND b2_properties address-model=32) endif() if(BUILD_SHARED_LIBS) list(APPEND b2_properties link=shared) else() list(APPEND b2_properties link=static) endif() list(APPEND b2_properties "\ $<$:variant=debug>\ $<$:variant=release>\ $<$:variant=release>\ $<$:variant=release>") if(WIN32) set(bootstrap_cmd if not exist b2.exe \( call bootstrap.bat ${bootstrap_args} \)) set(b2_cmd b2 ${b2_options} ${b2_properties} stage) else() set(bootstrap_cmd #[[ test -e ./b2 || ]] ./bootstrap.sh ${bootstrap_args}) set(b2_cmd ./b2 ${b2_options} ${b2_properties} stage) #[[ We already told Boost if we want to use GCC or Clang but so far we were not able to specify the exact same compiler we set in CMake when configuring the MITK superbuild for the first time. For example, this can be different from the system default when multiple versions of the same compiler are installed at the same time. The bootstrap script creates a configuration file for b2 that should be modified if necessary before b2 is called. We look for a line like using gcc ; and replace it with something more specific like using gcc : : /usr/bin/gcc-7.3 ; We use the stream editor sed for the replacement but since macOS is based on BSD Unix, we use the limited but portable BSD syntax instead of the more powerful GNU syntax. We also use | instead of the more commonly used / separator for sed because the replacement contains slashes. 2021/06/15: The custom project-config.jam does not work well with SDK paths on macOS anymore, so we use a custom project-config.jam only on Linux for now. ]] if(toolset AND NOT APPLE) set(configure_cmd sed -i.backup "\ s|\ using[[:space:]][[:space:]]*${toolset}[[:space:]]*$|\ using ${toolset} : : ${CMAKE_CXX_COMPILER} $|\ g" /project-config.jam ) endif() endif() endif() if(WIN32) set(dummy_cmd cd .) else() set(dummy_cmd true) #[[ "cd ." does not work reliably ]] endif() if(NOT patch_cmd) set(patch_cmd ${dummy_cmd}) #[[ Do nothing ]] endif() if(NOT configure_cmd) set(configure_cmd ${dummy_cmd}) #[[ Do nothing ]] endif() if(WIN32) set(install_cmd if not exist $ \( ${CMAKE_COMMAND} -E copy_directory /boost /include/boost \) ) else() set(install_cmd # test -e /include/boost/config.hpp || ${CMAKE_COMMAND} -E copy_directory /boost /include/boost ) endif() ExternalProject_Add(${proj} URL ${url} URL_MD5 ${md5} PATCH_COMMAND ${patch_cmd} CONFIGURE_COMMAND ${configure_cmd} BUILD_COMMAND "" INSTALL_COMMAND ${install_cmd} ) ExternalProject_Add_Step(${proj} bootstrap COMMAND ${bootstrap_cmd} DEPENDEES patch DEPENDERS configure WORKING_DIRECTORY ) ExternalProject_Add_Step(${proj} b2 COMMAND ${b2_cmd} DEPENDEES bootstrap DEPENDERS build WORKING_DIRECTORY ) if(WIN32) #[[ Reuse already extracted files. ]] set(stamp_dir ${ep_prefix}/src/Boost-stamp) configure_file( ${CMAKE_CURRENT_LIST_DIR}/extract-Boost.replacement.cmake ${stamp_dir}/extract-Boost.replacement.cmake COPYONLY) ExternalProject_Add_Step(${proj} pre_download COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-pre_download.cmake DEPENDEES mkdir DEPENDERS download WORKING_DIRECTORY ${stamp_dir} ) endif() set(install_manifest_dependees install) if(MITK_USE_Boost_LIBRARIES) if(WIN32) #[[ Move DLLs from lib to bin directory. ]] ExternalProject_Add_Step(${proj} post_install COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-post_install-WIN32.cmake DEPENDEES install WORKING_DIRECTORY /lib ) set(install_manifest_dependees post_install) elseif(APPLE) #[[ Boost does not follow the common practice of either using rpath or absolute paths for referencing dependencies. We have to use the install_name_tool to fix this. ]] ExternalProject_Add_Step(${proj} post_install COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-post_install-APPLE.cmake DEPENDEES install WORKING_DIRECTORY /lib ) set(install_manifest_dependees post_install) endif() endif() ExternalProject_Add_Step(${proj} install_manifest COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-install_manifest.cmake DEPENDEES ${install_manifest_dependees} WORKING_DIRECTORY ${ep_prefix} ) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index a9bbe37243..d77d4c55b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1437 +1,1443 @@ +#[[ When increasing the minimum required version, check if Boost_ADDITIONAL_VERSIONS + in CMake/PackageDepends/MITK_Boost_Config.cmake can be removed. See the first + long comment in CMakeExternals/Boost.cmake for details. ]] + set(MITK_CMAKE_MINIMUM_REQUIRED_VERSION 3.18) cmake_minimum_required(VERSION ${MITK_CMAKE_MINIMUM_REQUIRED_VERSION}) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND CMAKE_VERSION VERSION_LESS 3.19.2) message(FATAL_ERROR "\ CMake v${CMAKE_VERSION} is defective [1]. \ Please either downgrade to v3.18 or upgrade to at least v3.19.2.\n\ [1] https://gitlab.kitware.com/cmake/cmake/-/issues/21529") endif() #----------------------------------------------------------------------------- # Policies #----------------------------------------------------------------------------- #[[ T28060 https://cmake.org/cmake/help/v3.18/policy/CMP0091.html https://cmake.org/cmake/help/v3.18/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html We pass CMP0091 to all external projects as command-line argument: -DCMAKE_POLICY_DEFAULT_CMP0091:STRING=OLD ]] cmake_policy(SET CMP0091 OLD) #----------------------------------------------------------------------------- # 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 2021.02.99) + project(MITK VERSION 2021.10.99) include_directories(SYSTEM ${MITK_SUPERBUILD_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # MITK Extension Feature #----------------------------------------------------------------------------- set(MITK_EXTENSION_DIRS "" CACHE STRING "") unset(MITK_ABSOLUTE_EXTENSION_DIRS) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) get_filename_component(MITK_ABSOLUTE_EXTENSION_DIR "${MITK_EXTENSION_DIR}" ABSOLUTE) list(APPEND MITK_ABSOLUTE_EXTENSION_DIRS "${MITK_ABSOLUTE_EXTENSION_DIR}") endforeach() set(MITK_DIR_PLUS_EXTENSION_DIRS "${MITK_SOURCE_DIR}" ${MITK_ABSOLUTE_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_ABSOLUTE_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMake") 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(CTest) include(CMakeParseArguments) include(FindPackageHandleStandardArgs) # MITK macros include(mitkFunctionGetGccVersion) include(mitkFunctionCheckCompilerFlags) include(mitkFunctionSuppressWarnings) # includes several functions include(mitkMacroEmptyExternalProject) 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.14. If you use a version less than # 10.14, 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.14") message(WARNING "Detected macOS version \"${macos_version}\" is not supported anymore. Minimum required macOS version is at least 10.14.") endif() if (CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14) message(WARNING "Detected macOS deployment target \"${CMAKE_OSX_DEPLOYMENT_TARGET}\" is not supported anymore. Minimum required macOS version is at least 10.14.") 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) mark_as_advanced( MITK_XVFB_TESTING MITK_FAST_TESTING MITK_BUILD_ALL_APPS ) #----------------------------------------------------------------------------- # Set UI testing flags #----------------------------------------------------------------------------- if(MITK_XVFB_TESTING) set(MITK_XVFB_TESTING_COMMAND "xvfb-run" "--auto-servernum" CACHE STRING "Command and options to test through Xvfb") mark_as_advanced(MITK_XVFB_TESTING_COMMAND) endif(MITK_XVFB_TESTING) # ----------------------------------------- # 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_ABSOLUTE_EXTENSION_DIRS}) set(MITK_CMAKE_EXTERNALS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMakeExternals") 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_ABSOLUTE_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) if(WIN32) set(MITK_QT5_MINIMUM_VERSION 5.12.9) else() set(MITK_QT5_MINIMUM_VERSION 5.12) endif() 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 + if (CMAKE_MATCH_0 EQUAL 2022) + list(APPEND _compilers "msvc2019" "msvc2017") # Binary compatible + elseif (CMAKE_MATCH_0 EQUAL 2019) + list(APPEND _compilers "msvc2017") # Binary compatible 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}) endif() # ----------------------------------------- # Custom dependency logic if(WIN32 AND Qt5_DIR) set(_dir_candidate "${Qt5_DIR}/../../../../../Tools/OpenSSL/Win_x64") get_filename_component(_dir_candidate ${_dir_candidate} ABSOLUTE) if(EXISTS "${_dir_candidate}") set(OPENSSL_ROOT_DIR "${_dir_candidate}") endif() endif() find_package(OpenSSL) 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) 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 either install Win32 OpenSSL:\n" " https://slproweb.com/products/Win32OpenSSL.html\n" "Or use the Qt Maintenance tool to install:\n" " Developer and Designer Tools > OpenSSL Toolkit > OpenSSL 64-bit binaries\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() find_package(Git REQUIRED) #----------------------------------------------------------------------------- # 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(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) #----------------------------------------------------------------------------- if(OpenSSL_FOUND AND WIN32) set(MITK_OPENSSL_SSL_DLL "" CACHE FILEPATH "") set(MITK_OPENSSL_CRYPTO_DLL "" CACHE FILEPATH "") if(MITK_OPENSSL_SSL_DLL AND EXISTS "${MITK_OPENSSL_SSL_DLL}" AND MITK_OPENSSL_CRYPTO_DLL AND EXISTS "${MITK_OPENSSL_CRYPTO_DLL}") foreach(config_type ${CMAKE_CONFIGURATION_TYPES}) execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${MITK_BINARY_DIR}/bin/${config_type}") configure_file("${MITK_OPENSSL_SSL_DLL}" "${MITK_BINARY_DIR}/bin/${config_type}/" COPYONLY) configure_file("${MITK_OPENSSL_CRYPTO_DLL}" "${MITK_BINARY_DIR}/bin/${config_type}/" COPYONLY) endforeach() MITK_INSTALL(FILES "${MITK_OPENSSL_SSL_DLL}" "${MITK_OPENSSL_CRYPTO_DLL}" ) endif() endif() # 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) if(NOT MITK_FAST_TESTING) if(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") 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_ABSOLUTE_EXTENSION_DIRS}) set(MITK_PACKAGE_DEPENDS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMake/PackageDepends") 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.74 1.74.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) 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() 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() 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) #[[ See T27701 # 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& e) { fprintf(stderr, \"%s\\n\", e.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 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_ABSOLUTE_EXTENSION_DIRS}) get_filename_component(MITK_ROOT_FOLDER "${MITK_EXTENSION_DIR}" NAME) set(MITK_MODULES_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Modules") 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) 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_ABSOLUTE_EXTENSION_DIRS}) set(MITK_PLUGINS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Plugins") 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") 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") 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_ABSOLUTE_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") 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/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox index 59195c0a9a..dc44f28335 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox @@ -1,236 +1,236 @@ /** \page BuildInstructionsPage Build Instructions \tableofcontents \section BuildInstructions_Introduction Introduction The CMake-based build system of MITK supports a "superbuild" process, meaning that it will download, configure, and build all required third-party libraries (except Qt) automatically. These instructions will show you how to use the MITK superbuild. \note This page explains explicitly how to build MITK itself. If you want to create your own project based on MITK, the process described below is completely automated. Please see \ref HowToNewProject. For more advanced users, the last sections explains how to inject custom build libraries into the superbuild process. \section BuildInstructions_Prerequisites Prerequisites You need: -# Git (there are also numerous third-party graphical clients available). We recommend using Git, but see below for a way how to get the current source code without using it. -# CMake (version \minimumCMakeVersion or higher) -# Qt \minimumQt5Version if you plan to develop Qt-based applications -# If you are using macOS you need an XCode installation and the Command Line Tools as it provides the neccessary compilers and SDKs To build MITK on Linux, install the following packages, e. g. with APT: \code{.unparsed} sudo apt install build-essential doxygen git graphviz libfreetype6-dev libglu1-mesa-dev libssl-dev libtiff5-dev libwrap0-dev libxcomposite1 libxcursor1 libxdamage-dev libxi-dev libxkbcommon-x11-0 libxt-dev mesa-common-dev \endcode For the optional and experimental (!) Python integration, install NumPy and SimpleITK v1.x, e. g.: \code{.unparsed} sudo apt install python3-numpy python3-pip -pip3 install SimpleITK==1.* +pip3 install SimpleITK \endcode \section BuildInstructions_Qt A note about Qt As we do not provide Qt in the MITK superbuild you need to install Qt manually. The Qt Company provides online installers for all supported platforms. We highly recommend to install Qt to the default location of the installer as it will allow MITK to automatically find Qt without any further action needed. Make sure to also select the following required components: - QtWebEngine - QtScript On Windows, the Qt installer offers a welcome and straight forward way to install OpenSSL. You find it under the Tools node. \section BuildInstructions_Get_Source Get a source tree Since MITK is under active development we recommend to use Git to check out the latest stable release from the homepage. If you decide to use the most current nightly release, make sure to get a stable tree: Check the MITK dashboard before checking out. If the build tree is not clean, you can specify an older revision for the checkout or get a stable tar ball from www.mitk.org. To clone MITK's current Git repository do: \code git clone https://phabricator.mitk.org/source/mitk.git MITK \endcode \section BuildInstructions_Build_With_CMake Build MITK with CMake Create a new directory for the superbuild binary tree, change to it and call CMake: In the shell (assuming your current directory is the same as the one where you issued the git clone command): \code mkdir MITK-superbuild cd MITK-superbuild ccmake ../MITK \endcode If you use Windows or prefer to use the CMake GUI, start the CMake GUI and enter the location of the source tree and binary tree, choose a suitable generator and configure the project. CMake will present you a couple of options, these are the most important ones: - CMAKE_PREFIX_PATH The path to your Qt installation, e.g., C:/Qt/5.12.9/msvc2017_64 or /home/user/Qt/5.12.9/gcc_64 - MITK_USE_BLUEBERRY Build the BlueBerry application framework - MITK_USE_Boost_LIBRARIES If you need binary Boost libraries, specify them here. - MITK_USE_OpenCV Build MITK code which depends on OpenCV (this will download and build OpenCV 2.4) - MITK_USE_Python3 Enables Python wrapping in MITK. This will also configure ITK, VTK, and OpenCV (if enabled) to build Python wrappers. - MITK_USE_Qt5 Build MITK code which depends on Qt 5 If you are satisfied with the configuration of your MITK superbuild, generate the project files with CMake by pressing "Generate". Linux and macOS users usually just enter "make" (optionally supplying the number threads to be used for a parallel build): \code make -j6 \endcode Windows users using Visual Studio can open the generated MITK-superbuild.sln solution file in the MITK-superbuild directory and start the build by building the BUILD_ALL project. \section BuildInstructions_Customize Customize your MITK superbuild The MITK superbuild configures MITK as well as all external libraries. The build directories of these libraries, and of MITK itself are located inside the MITK-superbuild directory. For example, the directory layout may look like: \code MITK-superbuild |- ep "external projects" |-bin |-lib |-include |-src |- MITK-build \endcode To change the configuration of the MITK build itself, choose the MITK-build directory as the binary directory in the CMake GUI (not the MITK-superbuild directory). After generating the project files, build the MITK project by either issuing "make" in the MITK-build directory (Linux, macOS), or by opening MITK-build/MITK.sln (Windows). You may also change the configuration of any project configured via the superbuild process. Make sure to also build the changed project and also the projects which depend on it. \section BuildInstructions_Running Running Applications On Linux, just execute the application you want to run. MITK executables are located in MITK-superbuild/MITK-build/bin On Windows, the PATH environment variable must contain the directories containing the third-party libraries. This is automatically done from Visual Studio. For running the applications directly use the generated batch files in the MITK-superbuild/MITK-build/bin. \section BuildInstructions_Documentation Documentation If you have the Doxygen documentation tool installed, you get a new project (Visual Studio) or "make" target named "doc". You can build this to generate the HTML documentation of MITK in the Documentation/Doxygen directory of your MITK-build binary tree or in the MITK_DOXYGEN_OUTPUT_DIR CMake variable (if specified). \section BuildInstructions_Extending Extend MITK on your own (using the application framework BlueBerry) Please see \ref NewPluginPage \section BuildInstructions_As_Toolkit Use MITK in your own project (as a toolkit) To use MITK in your external project, add the CMake command find_package(MITK REQUIRED) to your CMakeLists.txt and make use of the CMake macros mitk_create_module() and mitk_create_executable() provided by MITK. Here is a very basic example CMakeLists.txt including MITK as a project: \code cmake_minimum_required(VERSION 3.10 FATAL_ERROR) project(MyProject) -find_package(MITK 2021.02 REQUIRED) +find_package(MITK 2021.10 REQUIRED) add_executable(MyApp main.cpp) target_link_libraries(MyApp MitkCore) \endcode with the main.ccp being \code #include #include int main() { MITK_INFO << "Hello world!"; return 0; } \endcode \section BuildInstructions_Advanced_Customization Superbuild customization You can inject pre-build third-party libraries into the MITK superbuild by setting certain CMake variables before the first configure step. MITK will then use these third-party libraries instead of downloading and building them by itself. Note that you must take care of configuring those libraries with all options MITK requires. The variables listed below are provided for injecting third-party libraries. Their occurrence in the CMake GUI or in ccmake may depend on specific MITK_USE_* options set to ON. You may also use the variable names below without the EXTERNAL_ prefix, for example when providing their values on a command line call to CMake. - EXTERNAL_BOOST_ROOT Set this variable to your custom Boost installation - EXTERNAL_CTK_DIR Set this variable to your CTK binary tree (the directory containing the CTKConfig.cmake file) - EXTERNAL_CableSwig_DIR Set this variable to your CableSwig binary tree for Python wrapping (the directory containing the CableSwigConfig.cmake file) - EXTERNAL_DCMTK_DIR Set this variable to your DCMTK binary tree (the directory containing the DCMTKConfig.cmake file) - EXTERNAL_GDCM_DIR Set this variable to your GDCM binary tree (the directory containing the GDCMConfig.cmake file) - EXTERNAL_ITK_DIR Set this variable to your ITK binary tree (the directory containing the ITKConfig.cmake file) - EXTERNAL_OpenCV_DIR Set this variable to your OpenCV binary tree (the directory containing the OpenCVConfig.cmake file) - EXTERNAL_VTK_DIR Set this variable to your VTK binary tree (the directory containing the VTKConfig.cmake file) To set CMake options before the first configure step is invoked, supply them on the command line, i.e. \code ccmake -DITK_DIR:PATH=/opt/ITK-release ../MITK \endcode */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md index 0124dd3aa1..41c0a30b1b 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md @@ -1,52 +1,55 @@ Supported Platforms {#SupportedPlatformsPage} =================== MITK is a cross-platform framework that is available for the following platforms: - Windows - Linux - macOS Supported Platforms Details --------------------------- The MITK team provides support for the most frequently used platforms and continuously runs testing procedures to ensure compatibility. Due to the large amount of possible combinations of operating systems and compiler versions, we divide platform support into two test categories: Tier 1 and Tier 2. Although MITK may be built on a broader range of platform-compiler combinations, only a subset of these are actively supported by the MITK development team. +In general, only 64-bit builds are supported. + Tier 1 Platforms ---------------- All Tier 1 platforms are continuously tested by our unit test suite and other internal testing procedures. Errors or bugs discovered in these platforms are prioritized and corrected as soon as possible. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- -| Microsoft Windows 10 (x64) | Visual Studio 2019 (latest update) -| Linux Ubuntu 20.04 (x64) | Default GCC version -| Linux Ubuntu 18.04 (x64) | Default GCC version +| Microsoft Windows 10 | Visual Studio 2019 (latest update) +| Linux Ubuntu 20.04 | Default GCC version +| Linux Ubuntu 18.04 | Default GCC version Tier 2 Platforms ---------------- Tier 2 platforms may or may not be tested on a regular basis. Some Tier 2 platforms are used by individual members of the MITK development team on a daily basis and some only receive occasional testing. While we strive to support these platforms, MITK users should note that errors may be present in released versions as well as in the current master branch. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- +| Microsoft Windows 10 | Visual Studio 2022 (latest update) | Apple macOS 11 "Big Sur" | Default Apple Clang version | Apple macOS 10.15 "Catalina" | Default Apple Clang version All platforms not listed above are not officially supported by the MITK team. However, we will happily accept contributions to improve support for other platforms as long as we have the hardware and capacity for maintenance. CI Build Clients ---------------- To get an overview of currently tested platforms, see the build reports on our CDash site. diff --git a/Documentation/Doxygen/4-API/Pages.dox b/Documentation/Doxygen/4-API/Pages.dox index 3cb9ec9d13..2d69ee13f0 100644 --- a/Documentation/Doxygen/4-API/Pages.dox +++ b/Documentation/Doxygen/4-API/Pages.dox @@ -1,39 +1,42 @@ /** \defgroup MITKDeprecatedAPI Deprecated \page deprecatedSince2012_09 Deprecated as of 2012.09 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_03 Deprecated as of 2013.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_06 Deprecated as of 2013.06 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_09 Deprecated as of 2013.09 \ingroup MITKDeprecatedAPI \page deprecatedSince2014_03 Deprecated as of 2014.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2014_10 Deprecated as of 2014.10 \ingroup MITKDeprecatedAPI \page deprecatedSince2015_05 Deprecated as of 2015.05 \ingroup MITKDeprecatedAPI \page deprecatedSince2016_03 Deprecated as of 2016.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2016_11 Deprecated as of 2016.11 \ingroup MITKDeprecatedAPI \page deprecatedSince2018_04 Deprecated as of 2018.04 \ingroup MITKDeprecatedAPI \page deprecatedSince2021_02 Deprecated as of 2021.02 \ingroup MITKDeprecatedAPI +\page deprecatedSince2021_10 Deprecated as of 2021.10 +\ingroup MITKDeprecatedAPI + */ diff --git a/Modules/AppUtil/CMakeLists.txt b/Modules/AppUtil/CMakeLists.txt index 4685d36486..e306674c8d 100644 --- a/Modules/AppUtil/CMakeLists.txt +++ b/Modules/AppUtil/CMakeLists.txt @@ -1,14 +1,14 @@ -set(qt5_depends Qt5|Widgets+WebEngine) +set(qt5_depends Qt5|Widgets+WebEngineCore) if(UNIX AND NOT APPLE) set(qt5_depends "${qt5_depends}+X11Extras") endif() mitk_create_module( PACKAGE_DEPENDS PUBLIC CTK|CTKPluginFramework ${qt5_depends} Poco|Util PRIVATE VTK|GUISupportQt DEPENDS PUBLIC qtsingleapplication PRIVATE MitkCore ) diff --git a/Modules/CEST/files.cmake b/Modules/CEST/files.cmake index 8d29b23be8..0bf817db33 100644 --- a/Modules/CEST/files.cmake +++ b/Modules/CEST/files.cmake @@ -1,15 +1,23 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCESTImageNormalizationFilter.cpp mitkCustomTagParser.cpp mitkCESTImageDetectionHelper.cpp mitkExtractCESTOffset.cpp mitkCESTPropertyHelper.cpp ) set(RESOURCE_FILES 1416.json 1485.json 1494.json + 1500.json + 1503.json + 1504.json + 1519.json + 1520.json + 1521.json + 1522.json + 1583.json ) diff --git a/Modules/CEST/resource/1500.json b/Modules/CEST/resource/1500.json new file mode 100644 index 0000000000..bfd1c3d0a9 --- /dev/null +++ b/Modules/CEST/resource/1500.json @@ -0,0 +1,28 @@ +{ + "1500" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreSatMode", + "sWiPMemBlock.alFree[6]" : "PreparationType", + "sWiPMemBlock.alFree[7]" : "PulseType", + "sWiPMemBlock.alFree[8]" : "SamplingType", + "sWiPMemBlock.alFree[9]" : "SpoilingType", + "sWiPMemBlock.alFree[10]" : "measurements", + "sWiPMemBlock.alFree[11]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[12]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[13]" : "PulseDuration", + "sWiPMemBlock.alFree[14]" : "DutyCycle", + "sWiPMemBlock.alFree[15]" : "RecoveryTime", + "sWiPMemBlock.alFree[16]" : "RecoveryTimeM0", + "sWiPMemBlock.alFree[17]" : "ReadoutDelay", + "sWiPMemBlock.alFree[18]" : "NumberPreSatPulses", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp", + "sWiPMemBlock.adFree[7]" : "OffsetPreSatPulse" +} diff --git a/Modules/CEST/resource/1503.json b/Modules/CEST/resource/1503.json new file mode 100644 index 0000000000..4f5a1904ae --- /dev/null +++ b/Modules/CEST/resource/1503.json @@ -0,0 +1,24 @@ +{ + "1503" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1504.json b/Modules/CEST/resource/1504.json new file mode 100644 index 0000000000..3150f575fd --- /dev/null +++ b/Modules/CEST/resource/1504.json @@ -0,0 +1,24 @@ +{ + "1504" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1519.json b/Modules/CEST/resource/1519.json new file mode 100644 index 0000000000..5f34cbd684 --- /dev/null +++ b/Modules/CEST/resource/1519.json @@ -0,0 +1,24 @@ +{ + "1519" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1520.json b/Modules/CEST/resource/1520.json new file mode 100644 index 0000000000..20d5894d8b --- /dev/null +++ b/Modules/CEST/resource/1520.json @@ -0,0 +1,24 @@ +{ + "1520" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1521.json b/Modules/CEST/resource/1521.json new file mode 100644 index 0000000000..dd44bdeac9 --- /dev/null +++ b/Modules/CEST/resource/1521.json @@ -0,0 +1,24 @@ +{ + "1521" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1522.json b/Modules/CEST/resource/1522.json new file mode 100644 index 0000000000..1f298205c4 --- /dev/null +++ b/Modules/CEST/resource/1522.json @@ -0,0 +1,24 @@ +{ + "1522" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1583.json b/Modules/CEST/resource/1583.json new file mode 100644 index 0000000000..c6ac35a621 --- /dev/null +++ b/Modules/CEST/resource/1583.json @@ -0,0 +1,24 @@ +{ + "1583" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.cpp b/Modules/ContourModel/DataManagement/mitkContourElement.cpp index bd906acb7b..be73ced97b 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourElement.cpp @@ -1,419 +1,489 @@ /*============================================================================ 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 -bool mitk::ContourElement::ContourModelVertex::operator ==(const ContourModelVertex& other) const +bool mitk::ContourElement::ContourModelVertex::operator==(const ContourModelVertex &other) const { return this->Coordinates == other.Coordinates && this->IsControlPoint == other.IsControlPoint; } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorBegin() const { return this->begin(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorEnd() const { return this->end(); } mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorBegin() { return this->begin(); } mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorEnd() { return this->end(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::begin() const { return this->m_Vertices.begin(); } mitk::ContourElement::ConstVertexIterator mitk::ContourElement::end() const { return this->m_Vertices.end(); } mitk::ContourElement::VertexIterator mitk::ContourElement::begin() { return this->m_Vertices.begin(); } mitk::ContourElement::VertexIterator mitk::ContourElement::end() { return this->m_Vertices.end(); } mitk::ContourElement::ContourElement(const mitk::ContourElement &other) : itk::LightObject(), m_IsClosed(other.m_IsClosed) { - for (const auto& v : other.m_Vertices) + for (const auto &v : other.m_Vertices) { m_Vertices.push_back(new ContourModelVertex(*v)); } } -mitk::ContourElement& mitk::ContourElement::operator = (const ContourElement& other) +mitk::ContourElement &mitk::ContourElement::operator=(const ContourElement &other) { if (this != &other) { this->Clear(); - for (const auto& v : other.m_Vertices) + for (const auto &v : other.m_Vertices) { m_Vertices.push_back(new ContourModelVertex(*v)); } } this->m_IsClosed = other.m_IsClosed; return *this; } mitk::ContourElement::~ContourElement() { this->Clear(); } mitk::ContourElement::VertexSizeType mitk::ContourElement::GetSize() const { return this->m_Vertices.size(); } void mitk::ContourElement::AddVertex(const mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices.push_back(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::AddVertexAtFront(const mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices.push_front(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::InsertVertexAtIndex(const mitk::Point3D &vertex, bool isControlPoint, VertexSizeType index) { if (index >= 0 && this->GetSize() > index) { auto _where = this->m_Vertices.begin(); _where += index; this->m_Vertices.insert(_where, new VertexType(vertex, isControlPoint)); } } void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const Point3D &point) { if (pointId >= 0 && this->GetSize() > pointId) { this->m_Vertices[pointId]->Coordinates = point; } } void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const VertexType *vertex) { if (nullptr == vertex) { mitkThrow() << "Cannot set vertex. Passed vertex instance is invalid. Index to set: " << pointId; } if (pointId >= 0 && this->GetSize() > pointId) { this->m_Vertices[pointId]->Coordinates = vertex->Coordinates; this->m_Vertices[pointId]->IsControlPoint = vertex->IsControlPoint; } } mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(VertexSizeType index) { return this->m_Vertices.at(index); } -const mitk::ContourElement::VertexType* mitk::ContourElement::GetVertexAt(VertexSizeType index) const +const mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(VertexSizeType index) const { return this->m_Vertices.at(index); } bool mitk::ContourElement::IsEmpty() const { return this->m_Vertices.empty(); } +mitk::ContourElement::VertexType *mitk::ContourElement::GetControlVertexAt(const mitk::Point3D &point, float eps) +{ + /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ + + if (eps > 0) + { + // currently no method with better performance is available + return BruteForceGetVertexAt(point, eps, true); + } // if eps < 0 + return nullptr; +} + mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps); } // if eps < 0 return nullptr; } -mitk::ContourElement::VertexType *mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, double eps) +mitk::ContourElement::VertexType *mitk::ContourElement::GetNextControlVertexAt(const mitk::Point3D &point, float eps) +{ + /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ + + if (eps > 0) + { + // currently no method with better performance is available + return BruteForceGetVertexAt(point, eps, true, 1); + } // if eps < 0 + return nullptr; +} + +mitk::ContourElement::VertexType *mitk::ContourElement::GetPreviousControlVertexAt(const mitk::Point3D &point, float eps) { + /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ + if (eps > 0) { - std::deque> nearestlist; + // currently no method with better performance is available + return BruteForceGetVertexAt(point, eps, true, -1); + } // if eps < 0 + return nullptr; +} + +mitk::ContourElement::VertexType *mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, + double eps, + bool isControlPoint, + int offset) +{ + VertexListType verticesList; + + if (isControlPoint) + { + verticesList = this->GetControlVertices(); + } + else + { + verticesList = *this->GetVertexList(); + } - ConstVertexIterator it = this->m_Vertices.begin(); + int vertexIndex = BruteForceGetVertexIndexAt(point, eps, verticesList); - ConstVertexIterator end = this->m_Vertices.end(); + if (vertexIndex!=-1) + { + vertexIndex += offset; - while (it != end) + if (vertexIndex < 0) { - mitk::Point3D currentPoint = (*it)->Coordinates; + // for negative offset + // if the offset exceeds the first vertex, we start from the end of the vertex list backwards + vertexIndex = verticesList.size() + offset; + } + else if (vertexIndex >= (int) verticesList.size()) + { + // if the offset exceeds the last vertex, we start from the beginning of the vertex list + vertexIndex = vertexIndex - verticesList.size(); + } - double distance = currentPoint.EuclideanDistanceTo(point); - if (distance < eps) - { - // if list is emtpy, add point to list - if (nearestlist.size() < 1) - { - nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); - } - // found an approximate point - check if current is closer then first in nearestlist - else if (distance < nearestlist.front().first) - { - // found even closer vertex - nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); - } - } // if distance > eps + return verticesList[vertexIndex]; + } + return nullptr; +} - it++; - } // while - if (nearestlist.size() > 0) +int mitk::ContourElement::BruteForceGetVertexIndexAt(const mitk::Point3D &point, + double eps, + VertexListType verticesList) +{ + if (eps < 0) + { + mitkThrow() << "Distance cannot be negative"; + } + + ConstVertexIterator nearestPointIterator; + bool nearestPointIsInitialized = false; + double nearestPointDistance = std::numeric_limits::max(); + + ConstVertexIterator it = verticesList.begin(); + ConstVertexIterator end = verticesList.end(); + + while (it != end) + { + mitk::Point3D currentPoint = (*it)->Coordinates; + + double distance = currentPoint.EuclideanDistanceTo(point); + if (distance < eps) { - /*++++++++++++++++++++ return the nearest active point if one was found++++++++++++++++++*/ - auto it = nearestlist.begin(); - auto end = nearestlist.end(); - while (it != end) + if (distance < nearestPointDistance) { - if ((*it).second->IsControlPoint) - { - return (*it).second; - } - it++; + nearestPointIterator = it; + nearestPointIsInitialized = true; + nearestPointDistance = distance; } - /*---------------------------------------------------------------------------------------*/ + } // if distance > eps - // return closest point - return nearestlist.front().second; - } + it++; + } // while + + if (nearestPointIsInitialized) + { + return nearestPointIterator - verticesList.begin(); } - return nullptr; + return -1; } const mitk::ContourElement::VertexListType *mitk::ContourElement::GetVertexList() const { return &(this->m_Vertices); } bool mitk::ContourElement::IsClosed() const { return this->m_IsClosed; } bool mitk::ContourElement::IsNearContour(const mitk::Point3D &point, float eps) const { ConstVertexIterator it1 = this->m_Vertices.begin(); ConstVertexIterator it2 = this->m_Vertices.begin(); it2++; // it2 runs one position ahead ConstVertexIterator end = this->m_Vertices.end(); int counter = 0; for (; it1 != end; it1++, it2++, counter++) { if (it2 == end) it2 = this->m_Vertices.begin(); mitk::Point3D v1 = (*it1)->Coordinates; mitk::Point3D v2 = (*it2)->Coordinates; const float l2 = v1.SquaredEuclideanDistanceTo(v2); mitk::Vector3D p_v1 = point - v1; mitk::Vector3D v2_v1 = v2 - v1; double tc = (p_v1 * v2_v1) / l2; // take into account we have line segments and not (infinite) lines if (tc < 0.0) tc = 0.0; if (tc > 1.0) tc = 1.0; mitk::Point3D crossPoint = v1 + v2_v1 * tc; double distance = point.SquaredEuclideanDistanceTo(crossPoint); if (distance < eps) { return true; } } return false; } void mitk::ContourElement::Close() { this->m_IsClosed = true; } void mitk::ContourElement::Open() { this->m_IsClosed = false; } void mitk::ContourElement::SetClosed(bool isClosed) { isClosed ? this->Close() : this->Open(); } mitk::ContourElement::VertexListType mitk::ContourElement::GetControlVertices() const { VertexListType controlVertices; - std::copy_if(this->m_Vertices.begin(), this->m_Vertices.end(), std::back_inserter(controlVertices), [](const VertexType* v) {return v->IsControlPoint; }); + std::copy_if( + this->m_Vertices.begin(), this->m_Vertices.end(), std::back_inserter(controlVertices), [](const VertexType *v) { + return v->IsControlPoint; + }); return controlVertices; } void mitk::ContourElement::Concatenate(const mitk::ContourElement *other, bool check) { if (other->GetSize() > 0) { - for (const auto& sourceVertex : other->m_Vertices) + for (const auto &sourceVertex : other->m_Vertices) { if (check) { - auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [sourceVertex](const VertexType* v) {return sourceVertex->Coordinates == v->Coordinates; }); + auto finding = + std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [sourceVertex](const VertexType *v) { + return sourceVertex->Coordinates == v->Coordinates; + }); if (finding == this->m_Vertices.end()) { this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } } else { this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } } } } -mitk::ContourElement::VertexSizeType mitk::ContourElement::GetIndex(const VertexType* vertex) const +mitk::ContourElement::VertexSizeType mitk::ContourElement::GetIndex(const VertexType *vertex) const { VertexSizeType result = NPOS; auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); if (finding != this->m_Vertices.end()) { result = finding - this->m_Vertices.begin(); } return result; } bool mitk::ContourElement::RemoveVertex(const VertexType *vertex) { auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); return RemoveVertexByIterator(finding); } bool mitk::ContourElement::RemoveVertexAt(VertexSizeType index) { if (index >= 0 && index < this->m_Vertices.size()) { auto delIter = this->m_Vertices.begin() + index; return RemoveVertexByIterator(delIter); } return false; } bool mitk::ContourElement::RemoveVertexAt(const mitk::Point3D &point, double eps) { if (eps > 0) { - auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [point, eps](const VertexType* v) {return v->Coordinates.EuclideanDistanceTo(point) < eps; }); + auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [point, eps](const VertexType *v) { + return v->Coordinates.EuclideanDistanceTo(point) < eps; + }); return RemoveVertexByIterator(finding); } return false; } -bool mitk::ContourElement::RemoveVertexByIterator(VertexListType::iterator& iter) +bool mitk::ContourElement::RemoveVertexByIterator(VertexListType::iterator &iter) { if (iter != this->m_Vertices.end()) { - delete* iter; + delete *iter; this->m_Vertices.erase(iter); return true; } return false; } void mitk::ContourElement::Clear() { for (auto vertex : m_Vertices) { delete vertex; } this->m_Vertices.clear(); } //---------------------------------------------------------------------- void mitk::ContourElement::RedistributeControlVertices(const VertexType *selected, int period) { int counter = 0; auto _where = this->m_Vertices.begin(); if (selected != nullptr) { auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), selected); if (finding != this->m_Vertices.end()) { _where = finding; } } auto _iter = _where; while (_iter != this->m_Vertices.end()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter++; } _iter = _where; counter = 0; while (_iter != this->m_Vertices.begin()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter--; } } diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.h b/Modules/ContourModel/DataManagement/mitkContourElement.h index 42be258cc3..9a9f17186e 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.h +++ b/Modules/ContourModel/DataManagement/mitkContourElement.h @@ -1,260 +1,289 @@ /*============================================================================ 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 _mitkContourElement_H_ #define _mitkContourElement_H_ #include "mitkCommon.h" #include #include #include namespace mitk { /** \brief Represents a contour in 3D space. A ContourElement is consisting of linked vertices implicitely defining the contour. They are stored in a double ended queue making it possible to add vertices at front and end of the contour and to iterate in both directions. To mark a vertex as a special one it can be set as a control point. \note This class assumes that it manages its vertices. So if a vertex instance is added to this class the ownership of the vertex is transfered to the ContourElement instance. The ContourElement instance takes care of deleting vertex instances if needed. It is highly not recommend to use this class directly as it is designed as a internal class of ContourModel. Therefore it is adviced to use ContourModel if contour representations are needed in MITK. */ class MITKCONTOURMODEL_EXPORT ContourElement : public itk::LightObject { public: mitkClassMacroItkParent(ContourElement, itk::LightObject); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Represents a single vertex of a contour. */ struct MITKCONTOURMODEL_EXPORT ContourModelVertex { ContourModelVertex(const mitk::Point3D& point, bool active = false) : IsControlPoint(active), Coordinates(point) {}; ContourModelVertex(const ContourModelVertex& other) : IsControlPoint(other.IsControlPoint), Coordinates(other.Coordinates) { }; /** \brief Treat point special. */ bool IsControlPoint; /** \brief Coordinates in 3D space. */ mitk::Point3D Coordinates; bool operator ==(const ContourModelVertex& other) const; }; using VertexType = ContourModelVertex; using VertexListType = std::deque; using VertexIterator = VertexListType::iterator; using ConstVertexIterator = VertexListType::const_iterator; using VertexSizeType = VertexListType::size_type; /**Indicates an invalid index. * It is always the maximum of the unsigned int type.*/ static const VertexSizeType NPOS = -1; /** \brief Return a const iterator a the front. */ ConstVertexIterator ConstIteratorBegin() const; /** \brief Return a const iterator a the end. */ ConstVertexIterator ConstIteratorEnd() const; /** \brief Return an iterator a the front. */ VertexIterator IteratorBegin(); /** \brief Return an iterator a the end. */ VertexIterator IteratorEnd(); /** \brief Return a const iterator a the front. * For easier support of stl functionality. */ ConstVertexIterator begin() const; /** \brief Return a const iterator a the end. * For easier support of stl functionality. */ ConstVertexIterator end() const; /** \brief Return an iterator a the front. * For easier support of stl functionality. */ VertexIterator begin(); /** \brief Return an iterator a the end. * For easier support of stl functionality. */ VertexIterator end(); /** \brief Returns the number of contained vertices. */ VertexSizeType GetSize() const; /** \brief Add a vertex at the end of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. */ void AddVertex(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at the front of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a control point. */ void AddVertexAtFront(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at a given index of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. \param index - the index to be inserted at. */ void InsertVertexAtIndex(const mitk::Point3D &point, bool isControlPoint, VertexSizeType index); /** \brief Set coordinates a given index. \param pointId Index of vertex. \param point Coordinates. */ void SetVertexAt(VertexSizeType pointId, const mitk::Point3D &point); /** \brief Set vertex a given index (by copying the values). \param pointId Index of vertex. \param vertex Vertex. \pre Passed vertex is a valid instance */ void SetVertexAt(VertexSizeType pointId, const VertexType* vertex); /** \brief Returns the vertex a given index \param index \pre index must be valid. */ VertexType* GetVertexAt(VertexSizeType index); const VertexType* GetVertexAt(VertexSizeType index) const; - /** \brief Returns the approximate nearest vertex a given posoition in 3D space + /** \brief Returns the approximate nearest vertex a given position in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ VertexType *GetVertexAt(const mitk::Point3D &point, float eps); + /** \brief Returns the next vertex to the approximate nearest vertex of a given position in 3D space + \param point - query position in 3D space. + \param eps - the error bound for search algorithm. + */ + VertexType *GetNextControlVertexAt(const mitk::Point3D &point, float eps); + + /** \brief Returns the previous vertex to the approximate nearest vertex of a given position in 3D space + \param point - query position in 3D space. + \param eps - the error bound for search algorithm. + */ + VertexType *GetPreviousControlVertexAt(const mitk::Point3D &point, float eps); + + /** \brief Returns the approximate nearest control vertex a given posoition in 3D space, if the clicked position is within a specific range. + \param point - query position in 3D space. + \param eps - the error bound for search algorithm. + */ + VertexType *GetControlVertexAt(const mitk::Point3D &point, float eps); + /** \brief Returns the index of the given vertex within the contour. \param vertex - the vertex to be searched. \return index of vertex. Returns ContourElement::NPOS if not found. */ VertexSizeType GetIndex(const VertexType *vertex) const; /** \brief Returns the container of the vertices. - */ + */ const VertexListType *GetVertexList() const; /** \brief Returns whether the contour element is empty. */ bool IsEmpty() const; /** \brief Returns if the conour is closed or not. */ bool IsClosed() const; /** \brief Returns whether a given point is near a contour, according to eps. \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ bool IsNearContour(const mitk::Point3D &point, float eps) const; /** \brief Close the contour. Connect first with last element. */ void Close(); /** \brief Open the contour. Disconnect first and last element. */ void Open(); /** \brief Set the contours IsClosed property. \param isClosed - true = closed; false = open; */ void SetClosed(bool isClosed); /** \brief Concatenate the contuor with a another contour. All vertices of the other contour will be cloned and added after last vertex. \param other - the other contour \param check - set it true to avoid adding of vertices that are already in the source contour */ void Concatenate(const mitk::ContourElement *other, bool check); /** \brief Remove the given vertex from the container if exists. \param vertex - the vertex to be removed. */ bool RemoveVertex(const VertexType *vertex); /** \brief Remove a vertex at given index within the container if exists. \param index - the index where the vertex should be removed. */ bool RemoveVertexAt(VertexSizeType index); /** \brief Remove the approximate nearest vertex at given position in 3D space if one exists. \param point - query point in 3D space. \param eps - error bound for search algorithm. */ bool RemoveVertexAt(const mitk::Point3D &point, double eps); /** \brief Clear the storage container. */ void Clear(); - /** \brief Returns the approximate nearest vertex a given posoition in 3D space + /** \brief Returns the approximate nearest vertex a given position in 3D space. With the parameter 'isControlPoint', + one can decide if any vertex should be returned, or just control vertices. \param point - query position in 3D space. - \param eps - the error bound for search algorithm. + \param eps - the error bound for search algorithm. It is an open boundary. + \param offset - a offset to the vertex, e.g. 1 if the next vertex should be returned or -1 for the previous vertex + */ + VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, double eps, bool isControlPoint = false, int offset = 0); + + /** \brief Returns the index of the approximate nearest vertex of a given position in 3D space. + \param point - query position in 3D space. + \param eps - the error bound for search algorithm. It is an open boundary. + \param verticesList - the vertex list to search the index in, either only control vertices or all vertices */ - VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, double eps); + int BruteForceGetVertexIndexAt(const mitk::Point3D &point, + double eps, + VertexListType verticesList); /** Returns a list pointing to all vertices that are indicated to be control points. \remark It is important to note, that the vertex pointers in the returned list directly point to the vertices stored interanlly. So they are still owned by the ContourElement instance that returns the list. If one wants to take over ownership, one has to clone the vertex instances. */ VertexListType GetControlVertices() const; /** \brief Uniformly redistribute control points with a given period (in number of vertices) \param vertex - the vertex around which the redistribution is done. \param period - number of vertices between control points. */ void RedistributeControlVertices(const VertexType *vertex, int period); protected: mitkCloneMacro(Self); ContourElement() = default; ContourElement(const mitk::ContourElement &other); ~ContourElement(); ContourElement& operator = (const ContourElement & other); /** Internal helper function to correctly remove the element indicated by the iterator from the list. After the call the iterator is invalid. Caller of the function must ensure that the iterator is valid!. \result Indicates if the element indicated by the iterator was removed. If iterator points to end it returns false.*/ bool RemoveVertexByIterator(VertexListType::iterator& iter); VertexListType m_Vertices; // double ended queue with vertices bool m_IsClosed = false; }; } // namespace mitk #endif // _mitkContourElement_H_ diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.cpp b/Modules/ContourModel/DataManagement/mitkContourModel.cpp index d6e04cb150..02350613d6 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourModel.cpp @@ -1,628 +1,679 @@ /*============================================================================ 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 mitk::ContourModel::ContourModel() : m_UpdateBoundingBox(true) { // set to initial state this->InitializeEmpty(); } mitk::ContourModel::ContourModel(const ContourModel &other) : BaseData(other), m_ContourSeries(other.m_ContourSeries), m_lineInterpolation(other.m_lineInterpolation) { m_SelectedVertex = nullptr; } mitk::ContourModel::~ContourModel() { m_SelectedVertex = nullptr; this->m_ContourSeries.clear(); // TODO check destruction } void mitk::ContourModel::AddVertex(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertex(vertex, false, timestep); } } void mitk::ContourModel::AddVertex(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertex(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::AddVertex(const VertexType &vertex, TimeStepType timestep) { this->AddVertex(vertex.Coordinates, vertex.IsControlPoint, timestep); } void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertexAtFront(vertex, false, timestep); } } void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertexAtFront(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::AddVertexAtFront(const VertexType &vertex, TimeStepType timestep) { this->AddVertexAtFront(vertex.Coordinates, vertex.IsControlPoint, timestep); } bool mitk::ContourModel::SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, point); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } bool mitk::ContourModel::SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep) { if (vertex == nullptr) return false; if (!this->IsEmptyTimeStep(timestep)) { if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, vertex); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } void mitk::ContourModel::InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (index >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(index)) { this->m_ContourSeries[timestep]->InsertVertexAtIndex(vertex, isControlPoint, index); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } void mitk::ContourModel::UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) { mitkThrow() << "Cannot update contour. Passed source model is invalid."; } if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) { mitkThrow() << "Cannot update contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; } if (!this->GetTimeGeometry()->IsValidTimeStep(destinationTimeStep)) { MITK_WARN << "Cannot update contour. Contour time geometry does not support passed time step. Invalid time step: " << destinationTimeStep; return; } this->Clear(destinationTimeStep); std::for_each(sourceModel->Begin(sourceTimeStep), sourceModel->End(sourceTimeStep), [this, destinationTimeStep](ContourElement::VertexType* vertex) { this->m_ContourSeries[destinationTimeStep]->AddVertex(vertex->Coordinates, vertex->IsControlPoint); }); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } bool mitk::ContourModel::IsEmpty(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsEmpty(); } return true; } bool mitk::ContourModel::IsEmpty() const { return this->IsEmpty(0); } int mitk::ContourModel::GetNumberOfVertices(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetSize(); } return -1; } const mitk::ContourModel::VertexType *mitk::ContourModel::GetVertexAt(int index, TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep) && this->m_ContourSeries[timestep]->GetSize() > mitk::ContourElement::VertexSizeType(index)) { return this->m_ContourSeries[timestep]->GetVertexAt(index); } return nullptr; } +const mitk::ContourModel::VertexType *mitk::ContourModel::GetNextControlVertexAt(mitk::Point3D &point, + float eps, + TimeStepType timestep) const +{ + if (!this->IsEmptyTimeStep(timestep)) + { + return this->m_ContourSeries[timestep]->GetNextControlVertexAt(point, eps); + } + return nullptr; +} + +const mitk::ContourModel::VertexType *mitk::ContourModel::GetPreviousControlVertexAt(mitk::Point3D &point, + float eps, + TimeStepType timestep) const +{ + if (!this->IsEmptyTimeStep(timestep)) + { + return this->m_ContourSeries[timestep]->GetPreviousControlVertexAt(point, eps); + } + return nullptr; +} + int mitk::ContourModel::GetIndex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetIndex(vertex); } return -1; } void mitk::ContourModel::Close(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Close(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::Open(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Open(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::SetClosed(bool isClosed, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->SetClosed(isClosed); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } bool mitk::ContourModel::IsEmptyTimeStep(unsigned int t) const { return (this->m_ContourSeries.size() <= t); } bool mitk::ContourModel::IsNearContour(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsNearContour(point, eps); } return false; } void mitk::ContourModel::Concatenate(ContourModel *other, TimeStepType timestep, bool check) { if (!this->IsEmptyTimeStep(timestep)) { if (!this->m_ContourSeries[timestep]->IsClosed()) { this->m_ContourSeries[timestep]->Concatenate(other->m_ContourSeries[timestep], check); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } mitk::ContourModel::VertexIterator mitk::ContourModel::Begin(TimeStepType timestep) const { return this->IteratorBegin(timestep); } mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorBegin(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorBegin(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } mitk::ContourModel::VertexIterator mitk::ContourModel::End(TimeStepType timestep) const { return this->IteratorEnd(timestep); } mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorEnd(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorEnd(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } bool mitk::ContourModel::IsClosed(int timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsClosed(); } return false; } +bool mitk::ContourModel::SelectControlVertexAt(Point3D &point, float eps, TimeStepType timestep) +{ + if (!this->IsEmptyTimeStep(timestep)) + { + this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetControlVertexAt(point, eps); + } + return this->m_SelectedVertex != nullptr; +} + bool mitk::ContourModel::SelectVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); } return this->m_SelectedVertex != nullptr; } bool mitk::ContourModel::SelectVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { return (this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(index)); } return false; } bool mitk::ContourModel::SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } bool mitk::ContourModel::SetControlVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(index); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } bool mitk::ContourModel::RemoveVertex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertex(vertex)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } bool mitk::ContourModel::RemoveVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(index)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } bool mitk::ContourModel::RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(point, eps)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } void mitk::ContourModel::ShiftSelectedVertex(Vector3D &translate) { if (this->m_SelectedVertex) { this->ShiftVertex(this->m_SelectedVertex, translate); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::ShiftContour(Vector3D &translate, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { // shift all vertices for (auto vertex : *(this->m_ContourSeries[timestep])) { this->ShiftVertex(vertex, translate); } this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelShiftEvent()); } } void mitk::ContourModel::ShiftVertex(VertexType *vertex, Vector3D &vector) { vertex->Coordinates[0] += vector[0]; vertex->Coordinates[1] += vector[1]; vertex->Coordinates[2] += vector[2]; } void mitk::ContourModel::Clear(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { // clear data at timestep this->m_ContourSeries[timestep]->Clear(); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::Expand(unsigned int timeSteps) { std::size_t oldSize = this->m_ContourSeries.size(); if (static_cast(timeSteps) > oldSize) { Superclass::Expand(timeSteps); // insert contours for each new timestep for (std::size_t i = oldSize; i < static_cast(timeSteps); i++) { m_ContourSeries.push_back(ContourElement::New()); } this->InvokeEvent(ContourModelExpandTimeBoundsEvent()); } } void mitk::ContourModel::SetRequestedRegionToLargestPossibleRegion() { // no support for regions } bool mitk::ContourModel::RequestedRegionIsOutsideOfTheBufferedRegion() { // no support for regions return false; } bool mitk::ContourModel::VerifyRequestedRegion() { // no support for regions return true; } void mitk::ContourModel::SetRequestedRegion(const itk::DataObject * /*data*/) { // no support for regions } void mitk::ContourModel::Clear() { // clear data and set to initial state again this->ClearData(); this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::RedistributeControlVertices(int period, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->RedistributeControlVertices(this->GetSelectedVertex(), period); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } +mitk::ContourModel::VertexListType mitk::ContourModel::GetControlVertices(TimeStepType timestep) +{ + VertexListType controlVertices; + if (!this->IsEmptyTimeStep(timestep)) + { + controlVertices = this->m_ContourSeries[timestep]->GetControlVertices(); + } + return controlVertices; +} + +mitk::ContourModel::VertexListType mitk::ContourModel::GetVertexList(TimeStepType timestep) +{ + VertexListType controlVertices; + if (!this->IsEmptyTimeStep(timestep)) + { + controlVertices = *this->m_ContourSeries[timestep]->GetVertexList(); + } + return controlVertices; +} + void mitk::ContourModel::ClearData() { // call the superclass, this releases the data of BaseData Superclass::ClearData(); // clear out the time resolved contours this->m_ContourSeries.clear(); } void mitk::ContourModel::Initialize() { this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::Initialize(const ContourModel &other) { TimeStepType numberOfTimesteps = other.GetTimeGeometry()->CountTimeSteps(); this->InitializeTimeGeometry(numberOfTimesteps); for (TimeStepType currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) { this->m_ContourSeries.push_back(ContourElement::New()); this->SetClosed(other.IsClosed(currentTimestep), currentTimestep); } m_SelectedVertex = nullptr; this->m_lineInterpolation = other.m_lineInterpolation; this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::InitializeEmpty() { // clear data at timesteps this->m_ContourSeries.resize(0); this->m_ContourSeries.push_back(ContourElement::New()); // set number of timesteps to one this->InitializeTimeGeometry(1); m_SelectedVertex = nullptr; this->m_lineInterpolation = ContourModel::LINEAR; } void mitk::ContourModel::UpdateOutputInformation() { if (this->GetSource()) { this->GetSource()->UpdateOutputInformation(); } if (this->m_UpdateBoundingBox) { // update the bounds of the geometry according to the stored vertices ScalarType mitkBounds[6]; // calculate the boundingbox at each timestep typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::PointsContainer PointsContainer; int timesteps = this->GetTimeSteps(); // iterate over the timesteps for (int currenTimeStep = 0; currenTimeStep < timesteps; currenTimeStep++) { if (dynamic_cast(this->GetGeometry(currenTimeStep))) { // do not update bounds for 2D geometries, as they are unfortunately defined with min bounds 0! return; } else { // we have a 3D geometry -> let's update bounds // only update bounds if the contour was modified if (this->GetMTime() > this->GetGeometry(currenTimeStep)->GetBoundingBox()->GetMTime()) { mitkBounds[0] = 0.0; mitkBounds[1] = 0.0; mitkBounds[2] = 0.0; mitkBounds[3] = 0.0; mitkBounds[4] = 0.0; mitkBounds[5] = 0.0; BoundingBoxType::Pointer boundingBox = BoundingBoxType::New(); PointsContainer::Pointer points = PointsContainer::New(); auto it = this->IteratorBegin(currenTimeStep); auto end = this->IteratorEnd(currenTimeStep); // fill the boundingbox with the points while (it != end) { Point3D currentP = (*it)->Coordinates; BoundingBoxType::PointType p; p.CastFrom(currentP); points->InsertElement(points->Size(), p); it++; } // construct the new boundingBox boundingBox->SetPoints(points); boundingBox->ComputeBoundingBox(); BoundingBoxType::BoundsArrayType tmp = boundingBox->GetBounds(); mitkBounds[0] = tmp[0]; mitkBounds[1] = tmp[1]; mitkBounds[2] = tmp[2]; mitkBounds[3] = tmp[3]; mitkBounds[4] = tmp[4]; mitkBounds[5] = tmp[5]; // set boundingBox at current timestep BaseGeometry *geometry3d = this->GetGeometry(currenTimeStep); geometry3d->SetBounds(mitkBounds); } } } this->m_UpdateBoundingBox = false; } GetTimeGeometry()->Update(); } void mitk::ContourModel::ExecuteOperation(Operation * /*operation*/) { // not supported yet } diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.h b/Modules/ContourModel/DataManagement/mitkContourModel.h index d234d9cefb..f42452d056 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.h +++ b/Modules/ContourModel/DataManagement/mitkContourModel.h @@ -1,437 +1,467 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _MITK_CONTOURMODEL_H_ #define _MITK_CONTOURMODEL_H_ #include "mitkBaseData.h" #include "mitkCommon.h" #include #include namespace mitk { /** \brief ContourModel is a structure of linked vertices defining a contour in 3D space. The vertices are stored in a mitk::ContourElement is stored for each timestep. The contour line segments are implicitly defined by the given linked vertices. By default two control points are are linked by a straight line.It is possible to add vertices at front and end of the contour and to iterate in both directions. Points are specified containing coordinates and additional (data) information, see mitk::ContourElement. For accessing a specific vertex either an index or a position in 3D Space can be used. The vertices are best accessed by using a VertexIterator. Interaction with the contour is thus available without any mitk interactor class using the api of ContourModel. It is possible to shift single vertices also as shifting the whole contour. A contour can be either open like a single curved line segment or closed. A closed contour can for example represent a jordan curve. \section mitkContourModelDisplayOptions Display Options The default mappers for this data structure are mitk::ContourModelGLMapper2D and mitk::ContourModelMapper3D. See these classes for display options which can can be set via properties. */ class MITKCONTOURMODEL_EXPORT ContourModel : public BaseData { public: mitkClassMacro(ContourModel, BaseData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /*+++++++++++++++ typedefs +++++++++++++++++++++++++++++++*/ typedef ContourElement::VertexType VertexType; typedef ContourElement::VertexListType VertexListType; typedef ContourElement::VertexIterator VertexIterator; typedef ContourElement::ConstVertexIterator ConstVertexIterator; typedef std::vector ContourModelSeries; /*+++++++++++++++ END typedefs ++++++++++++++++++++++++++++*/ /** \brief Possible interpolation of the line segments between control points */ enum LineSegmentInterpolation { LINEAR, B_SPLINE }; /*++++++++++++++++ inline methods +++++++++++++++++++++++*/ /** \brief Get the current selected vertex. */ VertexType *GetSelectedVertex() { return this->m_SelectedVertex; } /** \brief Deselect vertex. */ void Deselect() { this->m_SelectedVertex = nullptr; } /** \brief Set selected vertex as control point */ void SetSelectedVertexAsControlPoint(bool isControlPoint = true) { if (this->m_SelectedVertex) { m_SelectedVertex->IsControlPoint = isControlPoint; this->Modified(); } } /** \brief Set the interpolation of the line segments between control points. */ void SetLineSegmentInterpolation(LineSegmentInterpolation interpolation) { this->m_lineInterpolation = interpolation; this->Modified(); } /** \brief Get the interpolation of the line segments between control points. */ LineSegmentInterpolation GetLineSegmentInterpolation() { return this->m_lineInterpolation; } /*++++++++++++++++ END inline methods +++++++++++++++++++++++*/ /** \brief Add a vertex to the contour at given timestep. The vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep. A copy of the passed vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertex(const Point3D& vertex, bool isControlPoint, TimeStepType timestep = 0); /** Clears the contour of destinationTimeStep and copies the contour of the passed source model at the sourceTimeStep. @pre soureModel must point to a valid instance @pre sourceTimePoint must be valid @note Updateing a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ void AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep = 0); /** \brief Insert a vertex at given index. */ void InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint = false, TimeStepType timestep = 0); /** \brief Set a coordinates for point at given index. */ bool SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep = 0); /** \brief Set a coordinates and control state for point at given index. */ bool SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep = 0); /** \brief Return if the contour is closed or not. */ bool IsClosed(int timestep = 0) const; /** \brief Concatenate two contours. The starting control point of the other will be added at the end of the contour. \param other \param timestep - the timestep at which the vertex will be add ( default 0) \param check - check for intersections ( default false) */ void Concatenate(ContourModel *other, TimeStepType timestep = 0, bool check = false); /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator Begin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator IteratorBegin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator End(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ VertexIterator IteratorEnd(TimeStepType timestep = 0) const; /** \brief Close the contour. The last control point will be linked with the first point. */ virtual void Close(TimeStepType timestep = 0); /** \brief Set isClosed to false contour. The link between the last control point the first point will be removed. */ virtual void Open(TimeStepType timestep = 0); /** \brief Set closed property to given boolean. false - The link between the last control point the first point will be removed. true - The last control point will be linked with the first point. */ virtual void SetClosed(bool isClosed, TimeStepType timestep = 0); /** \brief Returns the number of vertices at a given timestep. \param timestep - default = 0 */ int GetNumberOfVertices(TimeStepType timestep = 0) const; /** \brief Returns whether the contour model is empty at a given timestep. \param timestep - default = 0 */ virtual bool IsEmpty(TimeStepType timestep) const; /** \brief Returns whether the contour model is empty. */ bool IsEmpty() const override; /** \brief Returns the vertex at the index position within the container. * If the index or timestep is invalid a nullptr will be returned. */ virtual const VertexType *GetVertexAt(int index, TimeStepType timestep = 0) const; + /** Returns the next control vertex to the approximate nearest vertex of a given position in 3D space + * If the timestep is invalid a nullptr will be returned. + */ + virtual const VertexType *GetNextControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const; + + /** Returns the previous control vertex to the approximate nearest vertex of a given position in 3D space + * If the timestep is invalid a nullptr will be returned. + */ + virtual const VertexType *GetPreviousControlVertexAt(mitk::Point3D &point, float eps, TimeStepType timestep) const; + /** \brief Remove a vertex at given timestep within the container. \return index of vertex. -1 if not found. */ int GetIndex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Check if there isn't something at this timestep. */ bool IsEmptyTimeStep(unsigned int t) const override; /** \brief Check if mouse cursor is near the contour. */ virtual bool IsNearContour(Point3D &point, float eps, TimeStepType timestep); + /** \brief Mark a vertex at an index in the container as selected. - */ + */ bool SelectVertexAt(int index, TimeStepType timestep = 0); /** \brief Mark a vertex at an index in the container as control point. */ bool SetControlVertexAt(int index, TimeStepType timestep = 0); + /** \brief Mark a control vertex at a given position in 3D space. + + \param point - query point in 3D space + \param eps - radius for nearest neighbour search (error bound). + \param timestep - search at this timestep + + @return true = vertex found; false = no vertex found + */ + bool SelectControlVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); + /** \brief Mark a vertex at a given position in 3D space. \param point - query point in 3D space \param eps - radius for nearest neighbour search (error bound). \param timestep - search at this timestep @return true = vertex found; false = no vertex found */ bool SelectVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); + /* \pararm point - query point in 3D space \pararm eps - radius for nearest neighbour search (error bound). \pararm timestep - search at this timestep @return true = vertex found; false = no vertex found */ bool SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Remove a vertex at given index within the container. @return true = the vertex was successfuly removed; false = wrong index. */ bool RemoveVertexAt(int index, TimeStepType timestep = 0); /** \brief Remove a vertex at given timestep within the container. @return true = the vertex was successfuly removed. */ bool RemoveVertex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Remove a vertex at a query position in 3D space. The vertex to be removed will be search by nearest neighbour search. Note that possibly no vertex at this position and eps is stored inside the contour. @return true = the vertex was successfuly removed; false = no vertex found. */ bool RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Shift the currently selected vertex by a translation vector. \param translate - the translation vector. */ void ShiftSelectedVertex(Vector3D &translate); /** \brief Shift the whole contour by a translation vector at given timestep. \param translate - the translation vector. \param timestep - at this timestep the contour will be shifted. */ void ShiftContour(Vector3D &translate, TimeStepType timestep = 0); /** \brief Clear the storage container at given timestep. All control points are removed at timestep. */ virtual void Clear(TimeStepType timestep); /** \brief Initialize all data objects */ void Initialize() override; /** \brief Initialize object with specs of other contour. Note: No data will be copied. */ void Initialize(const ContourModel &other); + /** \brief Returns a list pointing to all vertices that are indicated to be control points. + */ + VertexListType GetControlVertices(TimeStepType timestep); + + /** \brief Returns the container of the vertices. + */ + VertexListType GetVertexList(TimeStepType timestep); + /*++++++++++++++++++ method inherit from base data +++++++++++++++++++++++++++*/ /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegionToLargestPossibleRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool RequestedRegionIsOutsideOfTheBufferedRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool VerifyRequestedRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegion(const itk::DataObject *data) override; /** \brief Expand the contour model and its TimeGeometry to given number of timesteps. */ void Expand(unsigned int timeSteps) override; /** \brief Update the OutputInformation of a ContourModel object The BoundingBox of the contour will be updated, if necessary. */ void UpdateOutputInformation() override; /** \brief Clear the storage container. The object is set to initial state. All control points are removed and the number of timesteps are set to 1. */ void Clear() override; /** \brief overwrite if the Data can be called by an Interactor (StateMachine). */ void ExecuteOperation(Operation *operation) override; /** \brief Redistributes ontrol vertices with a given period (as number of vertices) \param period - the number of vertices between control points. \param timestep - at this timestep all lines will be rebuilt. */ virtual void RedistributeControlVertices(int period, TimeStepType timestep); protected: mitkCloneMacro(Self); ContourModel(); ContourModel(const ContourModel &other); ~ContourModel() override; // inherit from BaseData. called by Clear() void ClearData() override; // inherit from BaseData. Initial state of a contour with no vertices and a single timestep. void InitializeEmpty() override; // Shift a vertex static void ShiftVertex(VertexType *vertex, Vector3D &vector); // Storage with time resolved support. ContourModelSeries m_ContourSeries; // The currently selected vertex. VertexType *m_SelectedVertex; // The interpolation of the line segment between control points. LineSegmentInterpolation m_lineInterpolation; // only update the bounding geometry if necessary bool m_UpdateBoundingBox; }; itkEventMacro(ContourModelEvent, itk::AnyEvent); itkEventMacro(ContourModelShiftEvent, ContourModelEvent); itkEventMacro(ContourModelSizeChangeEvent, ContourModelEvent); itkEventMacro(ContourModelAddEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelRemoveEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelExpandTimeBoundsEvent, ContourModelEvent); itkEventMacro(ContourModelClosedEvent, ContourModelEvent); } #endif diff --git a/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp b/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp index cd3d5ebcce..e908256b1f 100644 --- a/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp +++ b/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp @@ -1,435 +1,436 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include #include #include #include #include // VTK #include #include // us #include #include mitk::InteractionTestHelper::InteractionTestHelper(const std::string &interactionXmlFilePath) : m_InteractionFilePath(interactionXmlFilePath) { this->Initialize(interactionXmlFilePath); } void mitk::InteractionTestHelper::Initialize(const std::string &interactionXmlFilePath) { tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS == document.LoadFile(interactionXmlFilePath.c_str())) { // get RenderingManager instance auto rm = mitk::RenderingManager::GetInstance(); // create data storage m_DataStorage = mitk::StandaloneDataStorage::New(); // for each renderer found create a render window and configure for (auto *element = document.FirstChildElement(mitk::InteractionEventConst::xmlTagInteractions().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagConfigRoot().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagRenderer().c_str()); element != nullptr; element = element->NextSiblingElement(mitk::InteractionEventConst::xmlTagRenderer().c_str())) { // get name of renderer const char *rendererName = element->Attribute(mitk::InteractionEventConst::xmlEventPropertyRendererName().c_str()); // get view direction mitk::SliceNavigationController::ViewDirection viewDirection = mitk::SliceNavigationController::Axial; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection().c_str()) != nullptr) { int viewDirectionNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection().c_str())); viewDirection = static_cast(viewDirectionNum); } // get mapper slot id mitk::BaseRenderer::MapperSlotId mapperID = mitk::BaseRenderer::Standard2D; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str()) != nullptr) { int mapperIDNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str())); mapperID = static_cast(mapperIDNum); } // Get Size of Render Windows int size[3]; size[0] = size[1] = size[2] = 0; if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX().c_str()) != nullptr) { size[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX().c_str())); } if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY().c_str()) != nullptr) { size[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY().c_str())); } if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ().c_str()) != nullptr) { size[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ().c_str())); } // create renderWindow, renderer and dispatcher auto rw = RenderWindow::New(nullptr, rendererName); // VtkRenderWindow is created within constructor if nullptr if (size[0] != 0 && size[1] != 0) { rw->SetSize(size[0], size[1]); rw->GetRenderer()->Resize(size[0], size[1]); } // set storage of renderer rw->GetRenderer()->SetDataStorage(m_DataStorage); // set view direction to axial rw->GetSliceNavigationController()->SetDefaultViewDirection(viewDirection); // set renderer to render 2D rw->GetRenderer()->SetMapperID(mapperID); rw->GetRenderer()->PrepareRender(); // Some more magic for the 3D render window case: // Camera view direction, position and focal point if (mapperID == mitk::BaseRenderer::Standard3D) { if (element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str()) != nullptr) { double cameraFocalPoint[3]; cameraFocalPoint[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str())); cameraFocalPoint[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointY().c_str())); cameraFocalPoint[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetFocalPoint(cameraFocalPoint); } if (element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str()) != nullptr) { double cameraPosition[3]; cameraPosition[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str())); cameraPosition[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionY().c_str())); cameraPosition[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetPosition(cameraPosition); } if (element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str()) != nullptr) { double viewUp[3]; viewUp[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str())); viewUp[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpY().c_str())); viewUp[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetViewUp(viewUp); } } rw->GetVtkRenderWindow()->Render(); rw->GetVtkRenderWindow()->WaitForCompletion(); // connect SliceNavigationControllers to timestep changed event of TimeNavigationController rw->GetSliceNavigationController()->ConnectGeometryTimeEvent(rm->GetTimeNavigationController(), false); rm->GetTimeNavigationController()->ConnectGeometryTimeEvent(rw->GetSliceNavigationController(), false); // add to list of known render windows m_RenderWindowList.push_back(rw); } // TODO: check the following lines taken from QmitkStdMultiWidget and adapt them to be executed in our code here. // mitkWidget1->GetSliceNavigationController() // ->ConnectGeometrySendEvent(mitk::BaseRenderer::GetInstance(mitkWidget4->GetRenderWindow())); // register interaction event obserer to handle scroll events InitializeDisplayActionEventHandling(); } else { mitkThrow() << "Can not load interaction xml file <" << m_InteractionFilePath << ">"; } // WARNING assumes a 3D window exists !!!! this->AddDisplayPlaneSubTree(); } void mitk::InteractionTestHelper::InitializeDisplayActionEventHandling() { m_DisplayActionEventBroadcast = mitk::DisplayActionEventBroadcast::New(); m_DisplayActionEventBroadcast->LoadStateMachine("DisplayInteraction.xml"); - m_DisplayActionEventBroadcast->SetEventConfig("DisplayConfigMITK.xml"); + m_DisplayActionEventBroadcast->SetEventConfig("DisplayConfigMITKBase.xml"); + m_DisplayActionEventBroadcast->AddEventConfig("DisplayConfigCrosshair.xml"); } mitk::InteractionTestHelper::~InteractionTestHelper() { mitk::RenderingManager *rm = mitk::RenderingManager::GetInstance(); // unregister renderers auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { rm->GetTimeNavigationController()->Disconnect((*it)->GetSliceNavigationController()); (*it)->GetSliceNavigationController()->Disconnect(rm->GetTimeNavigationController()); mitk::BaseRenderer::RemoveInstance((*it)->GetVtkRenderWindow()); } rm->RemoveAllObservers(); } mitk::DataStorage::Pointer mitk::InteractionTestHelper::GetDataStorage() { return m_DataStorage; } void mitk::InteractionTestHelper::AddNodeToStorage(mitk::DataNode::Pointer node) { this->m_DataStorage->Add(node); this->Set3dCameraSettings(); } void mitk::InteractionTestHelper::PlaybackInteraction() { mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); // load events if not loaded yet if (m_Events.empty()) this->LoadInteraction(); auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { (*it)->GetRenderer()->PrepareRender(); (*it)->GetVtkRenderWindow()->Render(); (*it)->GetVtkRenderWindow()->WaitForCompletion(); } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); it = m_RenderWindowList.begin(); for (; it != end; ++it) { (*it)->GetVtkRenderWindow()->Render(); (*it)->GetVtkRenderWindow()->WaitForCompletion(); } // mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); // playback all events in queue for (unsigned long i = 0; i < m_Events.size(); ++i) { // let dispatcher of sending renderer process the event m_Events.at(i)->GetSender()->GetDispatcher()->ProcessEvent(m_Events.at(i)); } if (false) { it--; (*it)->GetVtkRenderWindow()->GetInteractor()->Start(); } } void mitk::InteractionTestHelper::LoadInteraction() { // load interaction pattern from xml file std::ifstream xmlStream(m_InteractionFilePath.c_str()); mitk::XML2EventParser parser(xmlStream); m_Events = parser.GetInteractions(); xmlStream.close(); // Avoid VTK warning: Trying to delete object with non-zero reference count. parser.SetReferenceCount(0); } void mitk::InteractionTestHelper::SetTimeStep(int newTimeStep) { mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); bool timeStepIsvalid = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetCreatedWorldGeometry()->IsValidTimeStep( newTimeStep); if (timeStepIsvalid) { mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(newTimeStep); } } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindowByName(const std::string &name) { auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { if (name.compare((*it)->GetRenderer()->GetName()) == 0) return (*it).GetPointer(); } return nullptr; } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindowByDefaultViewDirection( mitk::SliceNavigationController::ViewDirection viewDirection) { auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { if (viewDirection == (*it)->GetSliceNavigationController()->GetDefaultViewDirection()) return (*it).GetPointer(); } return nullptr; } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindow(unsigned int index) { if (index < m_RenderWindowList.size()) { return m_RenderWindowList.at(index).GetPointer(); } else { return nullptr; } } void mitk::InteractionTestHelper::AddDisplayPlaneSubTree() { // add the displayed planes of the multiwidget to a node to which the subtree // @a planesSubTree points ... mitk::PlaneGeometryDataMapper2D::Pointer mapper; mitk::IntProperty::Pointer layer = mitk::IntProperty::New(1000); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetProperty("name", mitk::StringProperty::New("Widgets")); node->SetProperty("helper object", mitk::BoolProperty::New(true)); m_DataStorage->Add(node); for (auto it : m_RenderWindowList) { if (it->GetRenderer()->GetMapperID() == BaseRenderer::Standard3D) continue; // ... of widget 1 mitk::DataNode::Pointer planeNode1 = (mitk::BaseRenderer::GetInstance(it->GetVtkRenderWindow()))->GetCurrentWorldPlaneGeometryNode(); planeNode1->SetProperty("visible", mitk::BoolProperty::New(true)); planeNode1->SetProperty("name", mitk::StringProperty::New("widget1Plane")); planeNode1->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); planeNode1->SetProperty("helper object", mitk::BoolProperty::New(true)); planeNode1->SetProperty("layer", layer); planeNode1->SetColor(1.0, 0.0, 0.0); mapper = mitk::PlaneGeometryDataMapper2D::New(); planeNode1->SetMapper(mitk::BaseRenderer::Standard2D, mapper); m_DataStorage->Add(planeNode1, node); } } void mitk::InteractionTestHelper::Set3dCameraSettings() { tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS == document.LoadFile(m_InteractionFilePath.c_str())) { // for each renderer found create a render window and configure for (auto *element = document.FirstChildElement(mitk::InteractionEventConst::xmlTagInteractions().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagConfigRoot().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagRenderer().c_str()); element != nullptr; element = element->NextSiblingElement(mitk::InteractionEventConst::xmlTagRenderer().c_str())) { // get name of renderer const char *rendererName = element->Attribute(mitk::InteractionEventConst::xmlEventPropertyRendererName().c_str()); // get mapper slot id mitk::BaseRenderer::MapperSlotId mapperID = mitk::BaseRenderer::Standard2D; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str()) != nullptr) { int mapperIDNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str())); mapperID = static_cast(mapperIDNum); } if (mapperID == mitk::BaseRenderer::Standard3D) { RenderWindow *namedRenderer = nullptr; for (const auto &it : m_RenderWindowList) { if (strcmp(it->GetRenderer()->GetName(), rendererName) == 0) { namedRenderer = it.GetPointer(); break; } } if (namedRenderer == nullptr) { MITK_ERROR << "No match for render window was found."; return; } namedRenderer->GetRenderer()->PrepareRender(); if (element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str()) != nullptr) { double cameraFocalPoint[3]; cameraFocalPoint[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str())); cameraFocalPoint[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointY().c_str())); cameraFocalPoint[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetFocalPoint(cameraFocalPoint); } if (element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str()) != nullptr) { double cameraPosition[3]; cameraPosition[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str())); cameraPosition[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionY().c_str())); cameraPosition[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetPosition(cameraPosition); } if (element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str()) != nullptr) { double viewUp[3]; viewUp[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str())); viewUp[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpY().c_str())); viewUp[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetViewUp(viewUp); } namedRenderer->GetVtkRenderWindow()->Render(); } } } } diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 8faabae848..fd48d9ca2c 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,327 +1,326 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCoreActivator.cpp mitkCoreObjectFactoryBase.cpp mitkCoreObjectFactory.cpp mitkCoreServices.cpp mitkException.cpp Algorithms/mitkBaseDataSource.cpp Algorithms/mitkClippedSurfaceBoundsCalculator.cpp Algorithms/mitkCompareImageDataFilter.cpp Algorithms/mitkCompositePixelValueToString.cpp Algorithms/mitkConvert2Dto3DImageFilter.cpp Algorithms/mitkDataNodeSource.cpp Algorithms/mitkExtractSliceFilter.cpp Algorithms/mitkExtractSliceFilter2.cpp Algorithms/mitkHistogramGenerator.cpp Algorithms/mitkImageChannelSelector.cpp Algorithms/mitkImageSliceSelector.cpp Algorithms/mitkImageSource.cpp Algorithms/mitkImageTimeSelector.cpp Algorithms/mitkImageToImageFilter.cpp Algorithms/mitkImageToSurfaceFilter.cpp Algorithms/mitkMultiComponentImageDataComparisonFilter.cpp Algorithms/mitkPlaneGeometryDataToSurfaceFilter.cpp Algorithms/mitkPointSetSource.cpp Algorithms/mitkPointSetToPointSetFilter.cpp Algorithms/mitkRGBToRGBACastImageFilter.cpp Algorithms/mitkSubImageSelector.cpp Algorithms/mitkSurfaceSource.cpp Algorithms/mitkSurfaceToImageFilter.cpp Algorithms/mitkSurfaceToSurfaceFilter.cpp Algorithms/mitkUIDGenerator.cpp Algorithms/mitkVolumeCalculator.cpp Algorithms/mitkTemporalJoinImagesFilter.cpp Controllers/mitkBaseController.cpp Controllers/mitkCallbackFromGUIThread.cpp Controllers/mitkCameraController.cpp Controllers/mitkCameraRotationController.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSlicesCoordinator.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkAnatomicalStructureColorPresets.cpp DataManagement/mitkArbitraryTimeGeometry.cpp DataManagement/mitkAbstractTransformGeometry.cpp DataManagement/mitkAnnotationProperty.cpp DataManagement/mitkApplicationCursor.cpp DataManagement/mitkApplyTransformMatrixOperation.cpp DataManagement/mitkBaseData.cpp DataManagement/mitkBaseGeometry.cpp DataManagement/mitkBaseProperty.cpp DataManagement/mitkChannelDescriptor.cpp DataManagement/mitkClippingProperty.cpp DataManagement/mitkColorProperty.cpp DataManagement/mitkDataNode.cpp DataManagement/mitkDataStorage.cpp DataManagement/mitkEnumerationProperty.cpp DataManagement/mitkFloatPropertyExtension.cpp DataManagement/mitkGeometry3D.cpp DataManagement/mitkGeometryData.cpp DataManagement/mitkGeometryTransformHolder.cpp DataManagement/mitkGroupTagProperty.cpp DataManagement/mitkGenericIDRelationRule.cpp DataManagement/mitkIdentifiable.cpp DataManagement/mitkImageAccessorBase.cpp DataManagement/mitkImageCaster.cpp DataManagement/mitkImageCastPart1.cpp DataManagement/mitkImageCastPart2.cpp DataManagement/mitkImageCastPart3.cpp DataManagement/mitkImageCastPart4.cpp DataManagement/mitkImage.cpp DataManagement/mitkImageDataItem.cpp DataManagement/mitkImageDescriptor.cpp DataManagement/mitkImageReadAccessor.cpp DataManagement/mitkImageStatisticsHolder.cpp DataManagement/mitkImageVtkAccessor.cpp DataManagement/mitkImageVtkReadAccessor.cpp DataManagement/mitkImageVtkWriteAccessor.cpp DataManagement/mitkImageWriteAccessor.cpp DataManagement/mitkIntPropertyExtension.cpp DataManagement/mitkIPersistenceService.cpp DataManagement/mitkIPropertyAliases.cpp DataManagement/mitkIPropertyDescriptions.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable DataManagement/mitkMaterial.cpp DataManagement/mitkMemoryUtilities.cpp DataManagement/mitkModalityProperty.cpp DataManagement/mitkModifiedLock.cpp DataManagement/mitkNodePredicateAnd.cpp DataManagement/mitkNodePredicateBase.cpp DataManagement/mitkNodePredicateCompositeBase.cpp DataManagement/mitkNodePredicateData.cpp DataManagement/mitkNodePredicateDataType.cpp DataManagement/mitkNodePredicateDataUID.cpp DataManagement/mitkNodePredicateDimension.cpp DataManagement/mitkNodePredicateFirstLevel.cpp DataManagement/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.cpp DataManagement/mitkNodePredicateSource.cpp DataManagement/mitkNodePredicateSubGeometry.cpp DataManagement/mitkNumericConstants.cpp DataManagement/mitkPlaneGeometry.cpp DataManagement/mitkPlaneGeometryData.cpp DataManagement/mitkPlaneOperation.cpp DataManagement/mitkPlaneOrientationProperty.cpp DataManagement/mitkPointOperation.cpp DataManagement/mitkPointSet.cpp DataManagement/mitkPointSetShapeProperty.cpp DataManagement/mitkProperties.cpp DataManagement/mitkPropertyAliases.cpp DataManagement/mitkPropertyDescriptions.cpp DataManagement/mitkPropertyExtension.cpp DataManagement/mitkPropertyExtensions.cpp DataManagement/mitkPropertyFilter.cpp DataManagement/mitkPropertyFilters.cpp DataManagement/mitkPropertyKeyPath.cpp DataManagement/mitkPropertyList.cpp DataManagement/mitkPropertyListReplacedObserver.cpp DataManagement/mitkPropertyNameHelper.cpp DataManagement/mitkPropertyObserver.cpp DataManagement/mitkPropertyPersistence.cpp DataManagement/mitkPropertyPersistenceInfo.cpp DataManagement/mitkPropertyRelationRuleBase.cpp DataManagement/mitkProportionalTimeGeometry.cpp DataManagement/mitkRenderingModeProperty.cpp DataManagement/mitkResliceMethodProperty.cpp DataManagement/mitkRestorePlanePositionOperation.cpp DataManagement/mitkRotationOperation.cpp DataManagement/mitkScaleOperation.cpp DataManagement/mitkSlicedData.cpp DataManagement/mitkSlicedGeometry3D.cpp DataManagement/mitkSmartPointerProperty.cpp DataManagement/mitkStandaloneDataStorage.cpp DataManagement/mitkStringProperty.cpp DataManagement/mitkSurface.cpp DataManagement/mitkSurfaceOperation.cpp DataManagement/mitkSourceImageRelationRule.cpp DataManagement/mitkThinPlateSplineCurvedGeometry.cpp DataManagement/mitkTimeGeometry.cpp DataManagement/mitkTransferFunction.cpp DataManagement/mitkTransferFunctionInitializer.cpp DataManagement/mitkTransferFunctionProperty.cpp DataManagement/mitkTemporoSpatialStringProperty.cpp DataManagement/mitkUIDManipulator.cpp DataManagement/mitkVector.cpp DataManagement/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkVtkVolumeRenderingProperty.cpp DataManagement/mitkWeakPointerProperty.cpp DataManagement/mitkIPropertyRelations.cpp DataManagement/mitkPropertyRelations.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.cpp Interactions/mitkCrosshairPositionEvent.cpp Interactions/mitkDataInteractor.cpp Interactions/mitkDispatcher.cpp Interactions/mitkDisplayActionEventBroadcast.cpp Interactions/mitkDisplayActionEventFunctions.cpp Interactions/mitkDisplayActionEventHandler.cpp Interactions/mitkDisplayActionEventHandlerDesynchronized.cpp Interactions/mitkDisplayActionEventHandlerStd.cpp Interactions/mitkDisplayActionEventHandlerSynchronized.cpp Interactions/mitkDisplayCoordinateOperation.cpp Interactions/mitkDisplayInteractor.cpp Interactions/mitkEventConfig.cpp Interactions/mitkEventFactory.cpp Interactions/mitkEventRecorder.cpp Interactions/mitkEventStateMachine.cpp Interactions/mitkInteractionEventConst.cpp Interactions/mitkInteractionEvent.cpp Interactions/mitkInteractionEventHandler.cpp Interactions/mitkInteractionEventObserver.cpp Interactions/mitkInteractionKeyEvent.cpp Interactions/mitkInteractionPositionEvent.cpp Interactions/mitkInteractionSchemeSwitcher.cpp Interactions/mitkInternalEvent.cpp Interactions/mitkMouseDoubleClickEvent.cpp Interactions/mitkMouseMoveEvent.cpp Interactions/mitkMousePressEvent.cpp Interactions/mitkMouseReleaseEvent.cpp Interactions/mitkMouseWheelEvent.cpp Interactions/mitkPointSetDataInteractor.cpp Interactions/mitkSinglePointDataInteractor.cpp Interactions/mitkStateMachineAction.cpp Interactions/mitkStateMachineCondition.cpp Interactions/mitkStateMachineContainer.cpp Interactions/mitkStateMachineState.cpp Interactions/mitkStateMachineTransition.cpp Interactions/mitkVtkEventAdapter.cpp Interactions/mitkVtkInteractorStyle.cxx Interactions/mitkXML2EventParser.cpp IO/mitkAbstractFileIO.cpp IO/mitkAbstractFileReader.cpp IO/mitkAbstractFileWriter.cpp IO/mitkCustomMimeType.cpp IO/mitkFileReader.cpp IO/mitkFileReaderRegistry.cpp IO/mitkFileReaderSelector.cpp IO/mitkFileReaderWriterBase.cpp IO/mitkFileWriter.cpp IO/mitkFileWriterRegistry.cpp IO/mitkFileWriterSelector.cpp IO/mitkGeometry3DToXML.cpp IO/mitkIFileIO.cpp IO/mitkIFileReader.cpp IO/mitkIFileWriter.cpp IO/mitkGeometryDataReaderService.cpp IO/mitkGeometryDataWriterService.cpp IO/mitkImageGenerator.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLog.cpp IO/mitkMimeType.cpp IO/mitkMimeTypeProvider.cpp IO/mitkOperation.cpp IO/mitkPixelType.cpp IO/mitkPointSetReaderService.cpp IO/mitkPointSetWriterService.cpp IO/mitkProportionalTimeGeometryToXML.cpp IO/mitkRawImageFileReader.cpp IO/mitkStandardFileLocations.cpp IO/mitkSurfaceStlIO.cpp IO/mitkSurfaceVtkIO.cpp IO/mitkSurfaceVtkLegacyIO.cpp IO/mitkSurfaceVtkXmlIO.cpp IO/mitkVtkLoggingAdapter.cpp IO/mitkPreferenceListReaderOptionsFunctor.cpp IO/mitkIOMetaInformationPropertyConstants.cpp Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp #Rendering/mitkGLMapper.cpp Moved to deprecated LegacyGL Module Rendering/mitkGradientBackground.cpp Rendering/mitkImageVtkMapper2D.cpp Rendering/mitkMapper.cpp Rendering/mitkAnnotation.cpp Rendering/mitkPlaneGeometryDataMapper2D.cpp Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp Rendering/mitkPointSetVtkMapper2D.cpp Rendering/mitkPointSetVtkMapper3D.cpp Rendering/mitkRenderWindowBase.cpp Rendering/mitkRenderWindow.cpp Rendering/mitkRenderWindowFrame.cpp #Rendering/mitkSurfaceGLMapper2D.cpp Moved to deprecated LegacyGL Module Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVtkEventProvider.cpp Rendering/mitkVtkMapper.cpp Rendering/mitkVtkPropRenderer.cpp Rendering/mitkVtkWidgetRendering.cpp Rendering/vtkMitkLevelWindowFilter.cpp Rendering/vtkMitkRectangleProp.cpp Rendering/vtkMitkRenderProp.cpp Rendering/vtkMitkThickSlicesFilter.cpp Rendering/vtkNeverTranslucentTexture.cpp ) set(RESOURCE_FILES Interactions/globalConfig.xml Interactions/DisplayInteraction.xml Interactions/DisplayConfig.xml -Interactions/DisplayConfigPACS.xml -Interactions/DisplayConfigPACSCrosshair.xml +Interactions/DisplayConfigMITKBase.xml +Interactions/DisplayConfigPACSBase.xml +Interactions/DisplayConfigCrosshair.xml +Interactions/DisplayConfigRotation.xml +Interactions/DisplayConfigActivateCoupling.xml +Interactions/DisplayConfigSwivel.xml Interactions/DisplayConfigPACSPan.xml Interactions/DisplayConfigPACSScroll.xml Interactions/DisplayConfigPACSZoom.xml Interactions/DisplayConfigPACSLevelWindow.xml -Interactions/DisplayConfigMITK.xml -Interactions/DisplayConfigMITKNoCrosshair.xml -Interactions/DisplayConfigMITKRotation.xml -Interactions/DisplayConfigMITKRotationUnCoupled.xml -Interactions/DisplayConfigMITKSwivel.xml Interactions/DisplayConfigMITKLimited.xml Interactions/DisplayConfigBlockLMB.xml Interactions/PointSet.xml Interactions/Legacy/StateMachine.xml Interactions/Legacy/DisplayConfigMITKTools.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml mitkAnatomicalStructureColorPresets.xml ) diff --git a/Modules/Core/resource/Interactions/DisplayConfigActivateCoupling.xml b/Modules/Core/resource/Interactions/DisplayConfigActivateCoupling.xml new file mode 100644 index 0000000000..469efc9168 --- /dev/null +++ b/Modules/Core/resource/Interactions/DisplayConfigActivateCoupling.xml @@ -0,0 +1,3 @@ + + + diff --git a/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml b/Modules/Core/resource/Interactions/DisplayConfigCrosshair.xml similarity index 100% copy from Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml copy to Modules/Core/resource/Interactions/DisplayConfigCrosshair.xml diff --git a/Modules/Core/resource/Interactions/DisplayConfigMITK.xml b/Modules/Core/resource/Interactions/DisplayConfigMITK.xml deleted file mode 100644 index 269d079e79..0000000000 --- a/Modules/Core/resource/Interactions/DisplayConfigMITK.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Modules/Core/resource/Interactions/DisplayConfigMITKNoCrosshair.xml b/Modules/Core/resource/Interactions/DisplayConfigMITKBase.xml similarity index 90% rename from Modules/Core/resource/Interactions/DisplayConfigMITKNoCrosshair.xml rename to Modules/Core/resource/Interactions/DisplayConfigMITKBase.xml index 8e9e78ebe1..beee9551aa 100644 --- a/Modules/Core/resource/Interactions/DisplayConfigMITKNoCrosshair.xml +++ b/Modules/Core/resource/Interactions/DisplayConfigMITKBase.xml @@ -1,49 +1,49 @@ - + - - - + + + - + diff --git a/Modules/Core/resource/Interactions/DisplayConfigMITKRotation.xml b/Modules/Core/resource/Interactions/DisplayConfigMITKRotation.xml deleted file mode 100644 index a2dfcd187b..0000000000 --- a/Modules/Core/resource/Interactions/DisplayConfigMITKRotation.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Modules/Core/resource/Interactions/DisplayConfigMITKRotationUnCoupled.xml b/Modules/Core/resource/Interactions/DisplayConfigMITKRotationUnCoupled.xml deleted file mode 100644 index 405c554908..0000000000 --- a/Modules/Core/resource/Interactions/DisplayConfigMITKRotationUnCoupled.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Modules/Core/resource/Interactions/DisplayConfigMITKSwivel.xml b/Modules/Core/resource/Interactions/DisplayConfigMITKSwivel.xml deleted file mode 100644 index 9cd3ea50c1..0000000000 --- a/Modules/Core/resource/Interactions/DisplayConfigMITKSwivel.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Modules/Core/resource/Interactions/DisplayConfigPACS.xml b/Modules/Core/resource/Interactions/DisplayConfigPACSBase.xml similarity index 97% rename from Modules/Core/resource/Interactions/DisplayConfigPACS.xml rename to Modules/Core/resource/Interactions/DisplayConfigPACSBase.xml index b5317a0453..5259c473e8 100644 --- a/Modules/Core/resource/Interactions/DisplayConfigPACS.xml +++ b/Modules/Core/resource/Interactions/DisplayConfigPACSBase.xml @@ -1,75 +1,75 @@ - + diff --git a/Modules/Core/resource/Interactions/DisplayConfigRotation.xml b/Modules/Core/resource/Interactions/DisplayConfigRotation.xml new file mode 100644 index 0000000000..0b351c4d52 --- /dev/null +++ b/Modules/Core/resource/Interactions/DisplayConfigRotation.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml b/Modules/Core/resource/Interactions/DisplayConfigSwivel.xml similarity index 54% rename from Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml rename to Modules/Core/resource/Interactions/DisplayConfigSwivel.xml index 88430a286d..1cb4a69094 100644 --- a/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml +++ b/Modules/Core/resource/Interactions/DisplayConfigSwivel.xml @@ -1,11 +1,12 @@ - + + - + - + diff --git a/Modules/Core/resource/Interactions/DisplayInteraction.xml b/Modules/Core/resource/Interactions/DisplayInteraction.xml index acf53f7cd8..81e8d9e23f 100644 --- a/Modules/Core/resource/Interactions/DisplayInteraction.xml +++ b/Modules/Core/resource/Interactions/DisplayInteraction.xml @@ -1,171 +1,175 @@ - + + + + + - - - + + + diff --git a/Modules/Core/src/Algorithms/mitkImageToSurfaceFilter.cpp b/Modules/Core/src/Algorithms/mitkImageToSurfaceFilter.cpp index 46da45fdaf..be0fc11b2c 100644 --- a/Modules/Core/src/Algorithms/mitkImageToSurfaceFilter.cpp +++ b/Modules/Core/src/Algorithms/mitkImageToSurfaceFilter.cpp @@ -1,230 +1,230 @@ /*============================================================================ 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 "mitkException.h" #include #include #include #include #include #include #include #include #include #include #include #include "mitkProgressBar.h" mitk::ImageToSurfaceFilter::ImageToSurfaceFilter() : m_Smooth(false), m_Decimate(NoDecimation), m_Threshold(1.0), m_TargetReduction(0.95f), m_SmoothIteration(50), m_SmoothRelaxation(0.1) { } mitk::ImageToSurfaceFilter::~ImageToSurfaceFilter() { } void mitk::ImageToSurfaceFilter::CreateSurface(int time, vtkImageData *vtkimage, mitk::Surface *surface, const ScalarType threshold) { vtkImageChangeInformation *indexCoordinatesImageFilter = vtkImageChangeInformation::New(); indexCoordinatesImageFilter->SetInputData(vtkimage); indexCoordinatesImageFilter->SetOutputOrigin(0.0, 0.0, 0.0); // MarchingCube -->create Surface vtkSmartPointer skinExtractor = vtkSmartPointer::New(); skinExtractor->ComputeScalarsOff(); skinExtractor->SetInputConnection(indexCoordinatesImageFilter->GetOutputPort()); // RC++ indexCoordinatesImageFilter->Delete(); skinExtractor->SetValue(0, threshold); vtkPolyData *polydata; skinExtractor->Update(); polydata = skinExtractor->GetOutput(); polydata->Register(nullptr); // RC++ - if (m_Smooth) + if (m_Smooth && polydata->GetNumberOfPoints() > 0 && polydata->GetNumberOfCells() > 0) { vtkSmoothPolyDataFilter *smoother = vtkSmoothPolyDataFilter::New(); // read poly1 (poly1 can be the original polygon, or the decimated polygon) smoother->SetInputConnection(skinExtractor->GetOutputPort()); // RC++ smoother->SetNumberOfIterations(m_SmoothIteration); smoother->SetRelaxationFactor(m_SmoothRelaxation); smoother->SetFeatureAngle(60); smoother->FeatureEdgeSmoothingOff(); smoother->BoundarySmoothingOff(); smoother->SetConvergence(0); smoother->Update(); polydata->Delete(); // RC-- polydata = smoother->GetOutput(); polydata->Register(nullptr); // RC++ smoother->Delete(); } ProgressBar::GetInstance()->Progress(); // decimate = to reduce number of polygons if (m_Decimate == DecimatePro) { vtkDecimatePro *decimate = vtkDecimatePro::New(); decimate->SplittingOff(); decimate->SetErrorIsAbsolute(5); decimate->SetFeatureAngle(30); decimate->PreserveTopologyOn(); decimate->BoundaryVertexDeletionOff(); decimate->SetDegree(10); // std-value is 25! decimate->SetInputData(polydata); // RC++ decimate->SetTargetReduction(m_TargetReduction); decimate->SetMaximumError(0.002); decimate->Update(); polydata->Delete(); // RC-- polydata = decimate->GetOutput(); polydata->Register(nullptr); // RC++ decimate->Delete(); } else if (m_Decimate == QuadricDecimation) { vtkQuadricDecimation *decimate = vtkQuadricDecimation::New(); decimate->SetTargetReduction(m_TargetReduction); decimate->SetInputData(polydata); decimate->Update(); polydata->Delete(); polydata = decimate->GetOutput(); polydata->Register(nullptr); decimate->Delete(); } ProgressBar::GetInstance()->Progress(); if (polydata->GetNumberOfPoints() > 0) { mitk::Vector3D spacing = GetInput()->GetGeometry(time)->GetSpacing(); vtkPoints *points = polydata->GetPoints(); vtkMatrix4x4 *vtkmatrix = vtkMatrix4x4::New(); GetInput()->GetGeometry(time)->GetVtkTransform()->GetMatrix(vtkmatrix); double(*matrix)[4] = vtkmatrix->Element; unsigned int i, j; for (i = 0; i < 3; ++i) for (j = 0; j < 3; ++j) matrix[i][j] /= spacing[j]; unsigned int n = points->GetNumberOfPoints(); double point[3]; for (i = 0; i < n; i++) { points->GetPoint(i, point); mitkVtkLinearTransformPoint(matrix, point, point); points->SetPoint(i, point); } vtkmatrix->Delete(); } ProgressBar::GetInstance()->Progress(); // determine point_data normals for the poly data points. vtkSmartPointer normalsGenerator = vtkSmartPointer::New(); normalsGenerator->SetInputData(polydata); normalsGenerator->FlipNormalsOn(); vtkSmartPointer cleanPolyDataFilter = vtkSmartPointer::New(); cleanPolyDataFilter->SetInputConnection(normalsGenerator->GetOutputPort()); cleanPolyDataFilter->PieceInvariantOff(); cleanPolyDataFilter->ConvertLinesToPointsOff(); cleanPolyDataFilter->ConvertPolysToLinesOff(); cleanPolyDataFilter->ConvertStripsToPolysOff(); cleanPolyDataFilter->PointMergingOn(); cleanPolyDataFilter->Update(); surface->SetVtkPolyData(cleanPolyDataFilter->GetOutput(), time); polydata->UnRegister(nullptr); } void mitk::ImageToSurfaceFilter::GenerateData() { mitk::Surface *surface = this->GetOutput(); auto *image = (mitk::Image *)GetInput(); if (image == nullptr || !image->IsInitialized()) mitkThrow() << "No input image set, please set an valid input image!"; mitk::Image::RegionType outputRegion = image->GetRequestedRegion(); int tstart = outputRegion.GetIndex(3); int tmax = tstart + outputRegion.GetSize(3); // GetSize()==1 - will aber 0 haben, wenn nicht zeitaufgeloest if ((tmax - tstart) > 0) { ProgressBar::GetInstance()->AddStepsToDo(4 * (tmax - tstart)); } int t; for (t = tstart; t < tmax; ++t) { vtkImageData *vtkimagedata = image->GetVtkImageData(t); CreateSurface(t, vtkimagedata, surface, m_Threshold); ProgressBar::GetInstance()->Progress(); } } void mitk::ImageToSurfaceFilter::SetSmoothIteration(int smoothIteration) { m_SmoothIteration = smoothIteration; } void mitk::ImageToSurfaceFilter::SetSmoothRelaxation(float smoothRelaxation) { m_SmoothRelaxation = smoothRelaxation; } void mitk::ImageToSurfaceFilter::SetInput(const mitk::Image *image) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(0, const_cast(image)); } const mitk::Image *mitk::ImageToSurfaceFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) { return nullptr; } return static_cast(this->ProcessObject::GetInput(0)); } void mitk::ImageToSurfaceFilter::GenerateOutputInformation() { mitk::Image::ConstPointer inputImage = (mitk::Image *)this->GetInput(); // mitk::Image *inputImage = (mitk::Image*)this->GetImage(); mitk::Surface::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if (inputImage.IsNull()) return; // Set Data } diff --git a/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp b/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp index 0b104903dc..dc9f1fa6ab 100644 --- a/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp +++ b/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp @@ -1,102 +1,108 @@ /*============================================================================ 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 "mitkInteractionSchemeSwitcher.h" // mitk core #include #include mitk::InteractionSchemeSwitcher::InteractionSchemeSwitcher() { // nothing here } mitk::InteractionSchemeSwitcher::~InteractionSchemeSwitcher() { // nothing here } void mitk::InteractionSchemeSwitcher::SetInteractionScheme(InteractionEventHandler* interactionEventHandler, InteractionScheme interactionScheme) { if (nullptr == interactionEventHandler) { mitkThrow() << "Not a valid interaction event handler to set the interaction scheme."; } switch (interactionScheme) { // MITK MODE case MITKStandard: { - interactionEventHandler->SetEventConfig("DisplayConfigMITK.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigMITKBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigCrosshair.xml"); break; } case MITKRotationUncoupled: { - interactionEventHandler->SetEventConfig("DisplayConfigMITKRotationUnCoupled.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigMITKBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigRotation.xml"); break; } case MITKRotationCoupled: { - interactionEventHandler->SetEventConfig("DisplayConfigMITKRotation.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigMITKBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigRotation.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigActivateCoupling.xml"); break; } case MITKSwivel: { - interactionEventHandler->SetEventConfig("DisplayConfigMITKSwivel.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigMITKBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigSwivel.xml"); break; } // PACS MODE case PACSBase: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); break; } case PACSStandard: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); - interactionEventHandler->AddEventConfig("DisplayConfigPACSCrosshair.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigCrosshair.xml"); break; } case PACSLevelWindow: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSLevelWindow.xml"); break; } case PACSPan: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSPan.xml"); break; } case PACSScroll: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSScroll.xml"); break; } case PACSZoom: { - interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigPACSBase.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSZoom.xml"); break; } default: { - interactionEventHandler->SetEventConfig("DisplayConfigMITK.xml"); + interactionEventHandler->SetEventConfig("DisplayConfigMITKBase.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigCrosshair.xml"); } } InvokeEvent(InteractionSchemeChangedEvent()); } diff --git a/Modules/CoreCmdApps/FileConverter.cpp b/Modules/CoreCmdApps/FileConverter.cpp index 009c67c1e1..197fa9fa67 100644 --- a/Modules/CoreCmdApps/FileConverter.cpp +++ b/Modules/CoreCmdApps/FileConverter.cpp @@ -1,112 +1,116 @@ /*============================================================================ 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 "mitkProperties.h" #include "mitkCommandLineParser.h" #include "mitkIOUtil.h" #include #include "mitkPreferenceListReaderOptionsFunctor.h" int main(int argc, char* argv[]) { mitkCommandLineParser parser; parser.setTitle("File Converter"); parser.setCategory("Basic Image Processing"); parser.setDescription(""); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--","-"); // Add command line argument names parser.addArgument("help", "h",mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file:", "Input File",us::Any(),false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file:", "Output file", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.addArgument("reader", "r", mitkCommandLineParser::String, "Reader Name", "Reader Name", us::Any()); parser.addArgument("list-readers", "lr", mitkCommandLineParser::Bool, "Reader Name", "Reader Name", us::Any()); std::map parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.size()==0) return EXIT_FAILURE; // Show a help message if ( parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } std::string inputFilename = us::any_cast(parsedArgs["input"]); std::string outputFilename = us::any_cast(parsedArgs["output"]); mitk::PreferenceListReaderOptionsFunctor::ListType preference = {}; if (parsedArgs.count("reader")) { preference.push_back(us::any_cast(parsedArgs["reader"])); } if (parsedArgs.count("list-readers")) { mitk::IOUtil::LoadInfo loadInfo(inputFilename); auto readers = loadInfo.m_ReaderSelector.Get(); std::string errMsg; if (readers.empty()) { if (!itksys::SystemTools::FileExists(loadInfo.m_Path.c_str())) { errMsg += "File '" + loadInfo.m_Path + "' does not exist\n"; } else { errMsg += "No reader available for '" + loadInfo.m_Path + "'\n"; } MITK_ERROR << errMsg; return 0; } std::cout << "Available Readers: "< 0) { - writeName = path + "/" + filename + "_" + std::to_string(count) + extension; + writeName = path + filename + "_" + std::to_string(count) + extension; } mitk::IOUtil::Save(node, writeName); ++count; } return EXIT_SUCCESS; } diff --git a/Modules/DICOM/src/mitkGantryTiltInformation.cpp b/Modules/DICOM/src/mitkGantryTiltInformation.cpp index b6efd46967..f268ddf8c8 100644 --- a/Modules/DICOM/src/mitkGantryTiltInformation.cpp +++ b/Modules/DICOM/src/mitkGantryTiltInformation.cpp @@ -1,254 +1,255 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG #include "mitkGantryTiltInformation.h" #include "mitkDICOMTag.h" #include "mitkLogMacros.h" mitk::GantryTiltInformation::GantryTiltInformation() : m_ShiftUp(0.0) , m_ShiftRight(0.0) , m_ShiftNormal(0.0) , m_ITKAssumedSliceSpacing(0.0) , m_NumberOfSlicesApart(0) { } #define doublepoint(x) \ Point3Dd x; \ x[0] = x ## f[0]; \ x[1] = x ## f[1]; \ x[2] = x ## f[2]; #define doublevector(x) \ Vector3Dd x; \ x[0] = x ## f[0]; \ x[1] = x ## f[1]; \ x[2] = x ## f[2]; mitk::GantryTiltInformation::GantryTiltInformation( const Point3D& origin1f, const Point3D& origin2f, const Vector3D& rightf, const Vector3D& upf, unsigned int numberOfSlicesApart) : m_ShiftUp(0.0) , m_ShiftRight(0.0) , m_ShiftNormal(0.0) +, m_ITKAssumedSliceSpacing(0.0) , m_NumberOfSlicesApart(numberOfSlicesApart) { assert(numberOfSlicesApart); doublepoint(origin1); doublepoint(origin2); doublevector(right); doublevector(up); // determine if slice 1 (imagePosition1 and imageOrientation1) and slice 2 can be in one orthogonal slice stack: // calculate a line from origin 1, directed along the normal of slice (calculated as the cross product of orientation 1) // check if this line passes through origin 2 /* Determine if line (imagePosition2 + l * normal) contains imagePosition1. Done by calculating the distance of imagePosition1 from line (imagePosition2 + l *normal) E.g. http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html squared distance = | (pointAlongNormal - origin2) x (origin2 - origin1) | ^ 2 / |pointAlongNormal - origin2| ^ 2 ( x meaning the cross product ) */ Vector3Dd normal = itk::CrossProduct(right, up); Point3Dd pointAlongNormal = origin2 + normal; double numerator = itk::CrossProduct( pointAlongNormal - origin2 , origin2 - origin1 ).GetSquaredNorm(); double denominator = (pointAlongNormal - origin2).GetSquaredNorm(); double distance = sqrt(numerator / denominator); if ( distance > 0.001 ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { MITK_DEBUG << " Series seems to contain a tilted (or sheared) geometry"; MITK_DEBUG << " Distance of expected slice origin from actual slice origin: " << distance; MITK_DEBUG << " ==> storing this shift for later analysis:"; MITK_DEBUG << " v origin1: " << origin1; MITK_DEBUG << " v origin2: " << origin2; MITK_DEBUG << " v right: " << right; MITK_DEBUG << " v up: " << up; MITK_DEBUG << " v normal: " << normal; Point3Dd projectionRight = projectPointOnLine( origin1, origin2, right ); Point3Dd projectionNormal = projectPointOnLine( origin1, origin2, normal ); m_ShiftRight = (projectionRight - origin2).GetNorm(); m_ShiftNormal = (projectionNormal - origin2).GetNorm(); /* now also check to which side the image is shifted. Calculation e.g. from http://mathworld.wolfram.com/Point-PlaneDistance.html */ Point3Dd testPoint = origin1; Vector3Dd planeNormal = up; double signedDistance = ( planeNormal[0] * testPoint[0] + planeNormal[1] * testPoint[1] + planeNormal[2] * testPoint[2] - ( planeNormal[0] * origin2[0] + planeNormal[1] * origin2[1] + planeNormal[2] * origin2[2] ) ) / sqrt( planeNormal[0] * planeNormal[0] + planeNormal[1] * planeNormal[1] + planeNormal[2] * planeNormal[2] ); m_ShiftUp = signedDistance; m_ITKAssumedSliceSpacing = (origin2 - origin1).GetNorm(); // How do we now this is assumed? See header documentation for ITK code references //double itkAssumedSliceSpacing = sqrt( m_ShiftUp * m_ShiftUp + m_ShiftNormal * m_ShiftNormal ); MITK_DEBUG << " calculated from slices " << m_NumberOfSlicesApart << " slices apart"; MITK_DEBUG << " shift normal: " << m_ShiftNormal; MITK_DEBUG << " shift normal assumed by ITK: " << m_ITKAssumedSliceSpacing; MITK_DEBUG << " shift up: " << m_ShiftUp; MITK_DEBUG << " shift right: " << m_ShiftRight; MITK_DEBUG << " tilt angle (deg): " << atan( m_ShiftUp / m_ShiftNormal ) * 180.0 / 3.1415926535; } } mitk::GantryTiltInformation mitk::GantryTiltInformation ::MakeFromTagValues( const std::string& origin1String, const std::string& origin2String, const std::string& orientationString, unsigned int numberOfSlicesApart) { Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point bool orientationConversion(false); DICOMStringToOrientationVectors( orientationString, right, up, orientationConversion ); if (orientationConversion && !origin1String.empty() && !origin2String.empty() ) { bool firstOriginConversion(false); bool lastOriginConversion(false); Point3D firstOrigin = DICOMStringToPoint3D( origin1String, firstOriginConversion ); Point3D lastOrigin = DICOMStringToPoint3D( origin2String, lastOriginConversion ); if (firstOriginConversion && lastOriginConversion) { return GantryTiltInformation( firstOrigin, lastOrigin, right, up, numberOfSlicesApart ); } } std::stringstream ss; ss << "Invalid tag values when constructing tilt information from origin1 '" << origin1String << "', origin2 '" << origin2String << "', and orientation '" << orientationString << "'"; throw std::invalid_argument(ss.str()); } void mitk::GantryTiltInformation ::Print(std::ostream& os) const { os << " calculated from slices " << m_NumberOfSlicesApart << " slices apart" << std::endl; os << " shift normal: " << m_ShiftNormal << std::endl; os << " shift normal assumed by ITK: " << m_ITKAssumedSliceSpacing << std::endl; os << " shift up: " << m_ShiftUp << std::endl; os << " shift right: " << m_ShiftRight << std::endl; os << " tilt angle (deg): " << atan( m_ShiftUp / m_ShiftNormal ) * 180.0 / 3.1415926535 << std::endl; } mitk::Point3D mitk::GantryTiltInformation::projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection ) { /** See illustration at http://mo.mathematik.uni-stuttgart.de/inhalt/aussage/aussage472/ vector(lineOrigin,p) = normal * ( innerproduct((p - lineOrigin),normal) / squared-length(normal) ) */ Vector3Dd lineOriginToP = p - lineOrigin; double innerProduct = lineOriginToP * lineDirection; double factor = innerProduct / lineDirection.GetSquaredNorm(); Point3Dd projection = lineOrigin + factor * lineDirection; return projection; } double mitk::GantryTiltInformation::GetTiltCorrectedAdditionalSize(unsigned int imageSizeZ) const { return fabs(m_ShiftUp / static_cast(m_NumberOfSlicesApart) * static_cast(imageSizeZ-1)); } double mitk::GantryTiltInformation::GetTiltAngleInDegrees() const { return atan( fabs(m_ShiftUp) / m_ShiftNormal ) * 180.0 / 3.1415926535; } double mitk::GantryTiltInformation::GetMatrixCoefficientForCorrectionInWorldCoordinates() const { // so many mm need to be shifted per slice! return m_ShiftUp / static_cast(m_NumberOfSlicesApart); } double mitk::GantryTiltInformation::GetRealZSpacing() const { return m_ShiftNormal / static_cast(m_NumberOfSlicesApart); } bool mitk::GantryTiltInformation::IsSheared() const { return m_NumberOfSlicesApart && ( fabs(m_ShiftRight) > 0.001 || fabs(m_ShiftUp) > 0.001); } bool mitk::GantryTiltInformation::IsRegularGantryTilt() const { return m_NumberOfSlicesApart && ( fabs(m_ShiftRight) < 0.001 && fabs(m_ShiftUp) > 0.001); } diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h index f392465b02..5d0aa957bc 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h @@ -1,231 +1,235 @@ /*============================================================================ 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 __itkShortestPathImageFilter_h #define __itkShortestPathImageFilter_h #include "itkImageToImageFilter.h" #include "itkShortestPathCostFunction.h" #include "itkShortestPathNode.h" #include #include // ------- INFORMATION ---------- /// SET FUNCTIONS // void SetInput( ItkImage ) // Compulsory // void SetStartIndex (const IndexType & StartIndex); // Compulsory // void SetEndIndex(const IndexType & EndIndex); // Compulsory // void SetFullNeighborsMode(bool) // Optional (default=false), if false N4, if true N26 // void SetActivateTimeOut(bool) // Optional (default=false), for debug issues: after 30s algorithms terminates. You can // have a look at the VectorOrderImage to see how far it came // void SetMakeOutputImage(bool) // Optional (default=true), Generate an outputimage of the path. You can also get the // path directoy with GetVectorPath() // void SetCalcAllDistances(bool) // Optional (default=false), Calculate Distances over the whole image. CAREFUL, // algorithm time extends a lot. Necessary for GetDistanceImage // void SetStoreVectorOrder(bool) // Optional (default=false), Stores in which order the pixels were checked. Necessary // for GetVectorOrderImage // void AddEndIndex(const IndexType & EndIndex) //Optional. By calling this function you can add several endpoints! The // algorithm will look for several shortest Pathes. From Start to all Endpoints. // /// GET FUNCTIONS // std::vector< itk::Index<3> > GetVectorPath(); // returns the shortest path as vector // std::vector< std::vector< itk::Index<3> > GetMultipleVectorPathe(); // returns a vector of shortest Pathes (which are // vectors of points) // GetDistanceImage // Returns the distance image // GetVectorOrderIMage // Returns the Vector Order image // // EXAMPLE USE // pleae see qmitkmitralvalvesegmentation4dtee bundle namespace itk { template class ShortestPathImageFilter : public ImageToImageFilter { public: // Standard Typedefs typedef ShortestPathImageFilter Self; typedef ImageToImageFilter Superclass; typedef SmartPointer Pointer; typedef SmartPointer ConstPointer; // Typdefs for metric typedef ShortestPathCostFunction CostFunctionType; typedef typename CostFunctionType::Pointer CostFunctionTypePointer; // More typdefs for convenience typedef TInputImageType InputImageType; typedef typename TInputImageType::Pointer InputImagePointer; typedef typename TInputImageType::PixelType InputImagePixelType; typedef typename TInputImageType::SizeType InputImageSizeType; typedef typename TInputImageType::IndexType IndexType; typedef typename itk::ImageRegionIteratorWithIndex InputImageIteratorType; typedef TOutputImageType OutputImageType; typedef typename TOutputImageType::Pointer OutputImagePointer; typedef typename TOutputImageType::PixelType OutputImagePixelType; typedef typename TOutputImageType::IndexType OutputImageIndexType; typedef ImageRegionIteratorWithIndex OutputImageIteratorType; typedef itk::ShapedNeighborhoodIterator itkShapedNeighborhoodIteratorType; // New Macro for smartpointer instantiation itkFactorylessNewMacro(Self); itkCloneMacro(Self); - // Run-time type information - itkTypeMacro(ShortestPathImageFilter, ImageToImageFilter); + // Run-time type information + itkTypeMacro(ShortestPathImageFilter, ImageToImageFilter); - // Display - void PrintSelf(std::ostream &os, Indent indent) const override; + // Display + void PrintSelf(std::ostream &os, Indent indent) const override; // Compare function for A_STAR struct CompareNodeStar { bool operator()(ShortestPathNode *a, ShortestPathNode *b) { return (a->distAndEst > b->distAndEst); } }; // \brief Set Starpoint for ShortestPath Calculation void SetStartIndex(const IndexType &StartIndex); // \brief Adds Endpoint for multiple ShortestPath Calculation void AddEndIndex(const IndexType &index); // \brief Set Endpoint for ShortestPath Calculation void SetEndIndex(const IndexType &EndIndex); // \brief Set FullNeighborsMode. false = no diagonal neighbors, in 2D this means N4 Neigborhood. true = would be N8 // in 2D itkSetMacro(FullNeighborsMode, bool); itkGetMacro(FullNeighborsMode, bool); // \brief Set Graph_fullNeighbors. false = no diagonal neighbors, in 2D this means N4 Neigborhood. true = would be // N8 in 2D itkSetMacro(Graph_fullNeighbors, bool); - // \brief (default=true), Produce output image, which shows the shortest path. But you can also get the shortest - // Path directly as vector with the function GetVectorPath - itkSetMacro(MakeOutputImage, bool); + // \brief (default=true), Produce output image, which shows the shortest path. But you can also get the shortest + // Path directly as vector with the function GetVectorPath + itkSetMacro(MakeOutputImage, bool); itkGetMacro(MakeOutputImage, bool); // \brief (default=false), Store an Vector of Order, so you can call getVectorOrderImage after update itkSetMacro(StoreVectorOrder, bool); itkGetMacro(StoreVectorOrder, bool); // \brief (default=false), // Calculate all Distances to all pixels, so you can call getDistanceImage after update // (warning algo will take a long time) itkSetMacro(CalcAllDistances, bool); itkGetMacro(CalcAllDistances, bool); // \brief (default=false), for debug issues: after 30s algorithms terminates. You can have a look at the // VectorOrderImage to see how far it came itkSetMacro(ActivateTimeOut, bool); itkGetMacro(ActivateTimeOut, bool); // \brief returns shortest Path as vector std::vector GetVectorPath(); // \brief returns Multiple shortest Paths. You can call this function, when u performed a multiple shortest path // search (one start, several ends) std::vector> GetMultipleVectorPaths(); // \brief returns the vector order image. It shows in which order the pixels were checked. good for debugging. Be // sure to have m_StoreVectorOrder=true OutputImagePointer GetVectorOrderImage(); // \brief returns the distance image. It shows the distances from the startpoint to all other pixels. Be sure to // have m_CalcAllDistances=true OutputImagePointer GetDistanceImage(); // \brief Fill m_VectorPath void MakeShortestPathVector(); // \brief cleans up the filter void CleanUp(); itkSetObjectMacro(CostFunction, CostFunctionType); // itkSetObjectMacro = set function that uses pointer as parameter itkGetObjectMacro(CostFunction, CostFunctionType); + void SetUseCostFunction(bool doUseCostFunction) { m_useCostFunction = doUseCostFunction; }; + bool GetUseCostFunction() { return m_useCostFunction; }; + protected: std::vector m_endPoints; // if you fill this vector, the algo will not rest until all endPoints have been reached std::vector m_endPointsClosed; ShortestPathNode *m_Nodes; // main list that contains all nodes NodeNumType m_Graph_NumberOfNodes; NodeNumType m_Graph_StartNode; NodeNumType m_Graph_EndNode; bool m_Graph_fullNeighbors; + bool m_useCostFunction; std::vector m_Graph_DiscoveredNodeList; ShortestPathImageFilter(Self &); // intentionally not implemented void operator=(const Self &); // intentionally not implemented const static int BACKGROUND = 0; const static int FOREGROUND = 255; bool m_FullNeighborsMode; bool m_MakeOutputImage; bool m_StoreVectorOrder; // Store an Vector of Order, so you can call getVectorOrderImage after update bool m_CalcAllDistances; // Calculate all Distances, so you can call getDistanceImage after update (warning algo // will take a long time) bool multipleEndPoints; bool m_ActivateTimeOut; // if true, then i search max. 30 secs. then abort bool m_Initialized; CostFunctionTypePointer m_CostFunction; IndexType m_StartIndex, m_EndIndex; std::vector m_VectorPath; std::vector> m_MultipleVectorPaths; std::vector m_VectorOrder; ShortestPathImageFilter(); ~ShortestPathImageFilter() override; // \brief Create all the outputs void MakeOutputs(); // \brief Generate Data void GenerateData() override; // \brief gets the estimate costs from pixel a to target. double getEstimatedCostsToTarget(const IndexType &a); typename InputImageType::Pointer m_magnitudeImage; // \brief Convert a indexnumber of a node in m_Nodes to image coordinates typename TInputImageType::IndexType NodeToCoord(NodeNumType); // \brief Convert image coordinate to a indexnumber of a node in m_Nodes unsigned int CoordToNode(IndexType); // \brief Returns the neighbors of a node std::vector GetNeighbors(NodeNumType nodeNum, bool FullNeighbors); // \brief Check if coords are in bounds of image bool CoordIsInBounds(IndexType); // \brief Initializes the graph void InitGraph(); // \brief Start ShortestPathSearch void StartShortestPathSearch(); }; } // end of namespace itk #include "itkShortestPathImageFilter.txx" #endif diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx index 73fee98147..1e4a4c2756 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx @@ -1,890 +1,897 @@ /*============================================================================ 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 __itkShortestPathImageFilter_txx #define __itkShortestPathImageFilter_txx #include "itkShortestPathImageFilter.h" #include "mitkMemoryUtilities.h" #include #include #include #include namespace itk { // Constructor (initialize standard values) template ShortestPathImageFilter::ShortestPathImageFilter() : m_Nodes(nullptr), m_Graph_NumberOfNodes(0), m_Graph_fullNeighbors(false), + m_useCostFunction(true), m_FullNeighborsMode(false), m_MakeOutputImage(true), m_StoreVectorOrder(false), m_CalcAllDistances(false), multipleEndPoints(false), m_ActivateTimeOut(false), m_Initialized(false) { m_endPoints.clear(); m_endPointsClosed.clear(); if (m_MakeOutputImage) { this->SetNumberOfRequiredOutputs(1); this->SetNthOutput(0, OutputImageType::New()); } } template ShortestPathImageFilter::~ShortestPathImageFilter() { delete[] m_Nodes; } template inline typename ShortestPathImageFilter::IndexType ShortestPathImageFilter::NodeToCoord(NodeNumType node) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; IndexType coord; if (dim == 2) { coord[1] = node / size[0]; coord[0] = node % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1])) { coord[0] = 0; coord[1] = 0; } } if (dim == 3) { coord[2] = node / (size[0] * size[1]); coord[1] = (node % (size[0] * size[1])) / size[0]; coord[0] = (node % (size[0] * size[1])) % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1]) || ((unsigned long)coord[2] >= size[2])) { coord[0] = 0; coord[1] = 0; coord[2] = 0; } } return coord; } template inline typename itk::NodeNumType ShortestPathImageFilter::CoordToNode( IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; NodeNumType node = 0; if (dim == 2) { node = (coord[1] * size[0]) + coord[0]; } if (dim == 3) { node = (coord[2] * size[0] * size[1]) + (coord[1] * size[0]) + coord[0]; } if ((m_Graph_NumberOfNodes > 0) && (node >= m_Graph_NumberOfNodes)) { /*MITK_INFO << "WARNING! Coordinates outside image!"; MITK_INFO << "Coords = " << coord ; MITK_INFO << "ImageDim = " << dim ; MITK_INFO << "RequestedRegionSize = " << size ;*/ node = 0; } return node; } template inline bool ShortestPathImageFilter::CoordIsInBounds(IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; if (dim == 2) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1])) { return true; } } if (dim == 3) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1]) && (coord[2] >= 0) && ((unsigned long)coord[2] < size[2])) { return true; } } return false; } template inline std::vector ShortestPathImageFilter::GetNeighbors( unsigned int nodeNum, bool FullNeighbors) { // returns a vector of nodepointers.. these nodes are the neighbors int dim = InputImageType::ImageDimension; IndexType Coord = NodeToCoord(nodeNum); IndexType NeighborCoord; std::vector nodeList; int neighborDistance = 1; // if i increase that, i might not hit the endnote // maybe use itkNeighborhoodIterator here, might be faster if (dim == 2) { // N4 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N8 NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } if (dim == 3) { // N6 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N26 // Middle Slice NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice(Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } return nodeList; } template void ShortestPathImageFilter::SetStartIndex( const typename TInputImageType::IndexType &StartIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_StartIndex[i] = StartIndex[i]; } m_Graph_StartNode = CoordToNode(m_StartIndex); // MITK_INFO << "StartIndex = " << StartIndex; // MITK_INFO << "StartNode = " << m_Graph_StartNode; m_Initialized = false; } template void ShortestPathImageFilter::SetEndIndex( const typename TInputImageType::IndexType &EndIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_EndIndex[i] = EndIndex[i]; } m_Graph_EndNode = CoordToNode(m_EndIndex); // MITK_INFO << "EndNode = " << m_Graph_EndNode; } template void ShortestPathImageFilter::AddEndIndex( const typename TInputImageType::IndexType &index) { // ONLY FOR MULTIPLE END POINTS SEARCH IndexType newEndIndex; for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { newEndIndex[i] = index[i]; } m_endPoints.push_back(newEndIndex); SetEndIndex(m_endPoints[0]); multipleEndPoints = true; } template inline double ShortestPathImageFilter::getEstimatedCostsToTarget( const typename TInputImageType::IndexType &a) { // Returns the minimal possible costs for a path from "a" to targetnode. itk::Vector v; v[0] = m_EndIndex[0] - a[0]; v[1] = m_EndIndex[1] - a[1]; v[2] = m_EndIndex[2] - a[2]; return m_CostFunction->GetMinCost() * v.GetNorm(); } template void ShortestPathImageFilter::InitGraph() { if (!m_Initialized) { // Clean up previous stuff CleanUp(); // Calc Number of nodes auto imageDimensions = TInputImageType::ImageDimension; const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); m_Graph_NumberOfNodes = 1; for (NodeNumType i = 0; i < imageDimensions; ++i) m_Graph_NumberOfNodes = m_Graph_NumberOfNodes * size[i]; // Initialize mainNodeList with that number m_Nodes = new ShortestPathNode[m_Graph_NumberOfNodes]; // Initialize each node in nodelist for (NodeNumType i = 0; i < m_Graph_NumberOfNodes; i++) { m_Nodes[i].distAndEst = -1; m_Nodes[i].distance = -1; m_Nodes[i].prevNode = -1; m_Nodes[i].mainListIndex = i; m_Nodes[i].closed = false; } m_Initialized = true; } // In the beginning, the Startnode needs a distance of 0 m_Nodes[m_Graph_StartNode].distance = 0; m_Nodes[m_Graph_StartNode].distAndEst = 0; // initalize cost function m_CostFunction->Initialize(); } template void ShortestPathImageFilter::StartShortestPathSearch() { // Setup Timer clock_t startAll = clock(); clock_t stopAll = clock(); // init variables double durationAll = 0; bool timeout = false; NodeNumType mainNodeListIndex = 0; DistanceType curNodeDistance = 0; NodeNumType numberOfNodesChecked = 0; // Create Multimap (tree structure for fast searching) std::multimap myMap; std::pair::iterator, std::multimap::iterator> ret; std::multimap::iterator it; // At first, only startNote is discovered. myMap.insert( std::pair(m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode])); // While there are discovered Nodes, pick the one with lowest distance, // update its neighbors and eventually delete it from the discovered Nodes list. while (!myMap.empty()) { numberOfNodesChecked++; /*if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) { MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() << "\n"; }*/ // Get element with lowest score mainNodeListIndex = myMap.begin()->second->mainListIndex; curNodeDistance = myMap.begin()->second->distance; myMap.begin()->second->closed = true; // close it // Debug: // MITK_INFO << "INFO: size " << myMap.size(); /* for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ // Kicks out element with lowest score myMap.erase(myMap.begin()); // if wanted, store vector order if (m_StoreVectorOrder) { m_VectorOrder.push_back(mainNodeListIndex); } // Check neighbors std::vector neighborNodes = GetNeighbors(mainNodeListIndex, m_Graph_fullNeighbors); for (NodeNumType i = 0; i < neighborNodes.size(); i++) { if (neighborNodes[i]->closed) continue; // this nodes is already closed, go to next neighbor IndexType coordCurNode = NodeToCoord(mainNodeListIndex); IndexType coordNeighborNode = NodeToCoord(neighborNodes[i]->mainListIndex); // calculate the new Distance to the current neighbor double newDistance = curNodeDistance + (m_CostFunction->GetCost(coordCurNode, coordNeighborNode)); // if it is shorter than any yet known path to this neighbor, than the current path is better. Save that! if ((newDistance < neighborNodes[i]->distance) || (neighborNodes[i]->distance == -1)) { // if that neighbornode is not in discoverednodeList yet, Push it there and update if (neighborNodes[i]->distance == -1) { neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); /* MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ } // or if is already in discoverednodelist, update else { /* it = myMap.find(neighborNodes[i]->distAndEst); if (it == myMap.end() ) { MITK_INFO << "Nothing!"; // look further for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; } } } */ // 1st : find and delete old element bool found = false; ret = myMap.equal_range(neighborNodes[i]->distAndEst); if ((ret.first == ret.second)) { /*+++++++++++++ no exact match +++++++++++++*/ // MITK_INFO << "No exact match!"; // if this happens, you are screwed /* MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; if (ret.first != myMap.end() ) { it = ret.first; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; ++it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; --it; --it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; } // look if that ID is found in the map for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; } } */ } else { for (it = ret.first; it != ret.second; ++it) { if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) { found = true; myMap.erase(it); /* MITK_INFO << "INFO: size " << myMap.size(); MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ break; } } } if (!found) { // MITK_INFO << "Could not find it! :("; continue; } // 2nd: update and insert new element neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; // myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); // MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << // m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; // MITK_INFO << "INFO: size " << myMap.size(); /*for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; }*/ } } } // finished with checking all neighbors. // Check Timeout, if activated if (m_ActivateTimeOut) { stopAll = clock(); durationAll = (double)(stopAll - startAll) / CLOCKS_PER_SEC; if (durationAll >= 30) { // MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; timeout = true; } } // Check end criteria: // For multiple points if (multipleEndPoints) { // super slow, make it faster for (unsigned int i = 0; i < m_endPoints.size(); i++) { if (CoordToNode(m_endPoints[i]) == mainNodeListIndex) { m_endPointsClosed.push_back(NodeToCoord(mainNodeListIndex)); m_endPoints.erase(m_endPoints.begin() + i); if (m_endPoints.empty()) { // Finished! break return; } if (m_Graph_EndNode == mainNodeListIndex) { // set new end SetEndIndex(m_endPoints[0]); } } } } // if single end point, then end, if this one is reached or timeout happened. else if ((mainNodeListIndex == m_Graph_EndNode || timeout) && !m_CalcAllDistances) { /*if (m_StoreVectorOrder) MITK_INFO << "Number of Nodes checked: " << m_VectorOrder.size() ;*/ return; } } } template void ShortestPathImageFilter::MakeOutputs() { // MITK_INFO << "Make Output"; if (m_MakeOutputImage) { OutputImagePointer output0 = this->GetOutput(0); output0->SetRegions(this->GetInput()->GetLargestPossibleRegion()); output0->Allocate(); OutputImageIteratorType shortestPathImageIt(output0, output0->GetRequestedRegion()); // Create ShortestPathImage (Output 0) for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) { // First intialize with background color shortestPathImageIt.Set(BACKGROUND); } if (!multipleEndPoints) // Only one path was calculated { for (unsigned int i = 0; i < m_VectorPath.size(); i++) { shortestPathImageIt.SetIndex(m_VectorPath[i]); shortestPathImageIt.Set(FOREGROUND); } } else // multiple pathes has been calculated, draw all { for (unsigned int i = 0; i < m_MultipleVectorPaths.size(); i++) { for (unsigned int j = 0; j < m_MultipleVectorPaths[i].size(); j++) { shortestPathImageIt.SetIndex(m_MultipleVectorPaths[i][j]); shortestPathImageIt.Set(FOREGROUND); } } } } } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetVectorOrderImage() { // Create Vector Order Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); OutputImageIteratorType vectorOrderImageIt(image, image->GetRequestedRegion()); // MITK_INFO << "GetVectorOrderImage"; for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) { // First intialize with background color vectorOrderImageIt.Value() = BACKGROUND; } for (int i = 0; i < m_VectorOrder.size(); i++) { vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i])); vectorOrderImageIt.Set(BACKGROUND + i); } return image; } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetDistanceImage() { // Create Distance Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); ; OutputImageIteratorType distanceImageIt(image, image->GetRequestedRegion()); // Create Distance Image (Output 1) NodeNumType myNodeNum; for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) { IndexType index = distanceImageIt.GetIndex(); myNodeNum = CoordToNode(index); double newVal = m_Nodes[myNodeNum].distance; distanceImageIt.Set(newVal); } } template std::vector::IndexType> ShortestPathImageFilter::GetVectorPath() { return m_VectorPath; } template std::vector::IndexType>> ShortestPathImageFilter::GetMultipleVectorPaths() { return m_MultipleVectorPaths; } template void ShortestPathImageFilter::MakeShortestPathVector() { // MITK_INFO << "Make ShortestPath Vec"; + if (m_useCostFunction == false) + { + m_VectorPath.push_back(NodeToCoord(m_Graph_StartNode)); + m_VectorPath.push_back(NodeToCoord(m_Graph_EndNode)); + return; + } // single end point if (!multipleEndPoints) { // fill m_VectorPath with the Shortest Path m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = m_Graph_EndNode; while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); } // Multiple end end points and pathes else { for (unsigned int i = 0; i < m_endPointsClosed.size(); i++) { m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = CoordToNode(m_endPointsClosed[i]); while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); // push_back m_MultipleVectorPaths.push_back(m_VectorPath); } } } template void ShortestPathImageFilter::CleanUp() { m_VectorOrder.clear(); m_VectorPath.clear(); // TODO: if multiple Path, clear all multiple Paths if (m_Nodes) delete[] m_Nodes; } template void ShortestPathImageFilter::GenerateData() { // Build Graph InitGraph(); // Calc Shortest Parth StartShortestPathSearch(); // Fill Shortest Path MakeShortestPathVector(); // Make Outputs MakeOutputs(); } template void ShortestPathImageFilter::PrintSelf(std::ostream &os, Indent indent) const { Superclass::PrintSelf(os, indent); } } /* end namespace itk */ #endif // __itkShortestPathImageFilter_txx diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp index e7f7f64ccb..bfa5041c2f 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp @@ -1,552 +1,552 @@ /*============================================================================ 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 "mitkImageStatisticsCalculator.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { void ImageStatisticsCalculator::SetInputImage(const mitk::Image *image) { if (image != m_Image) { m_Image = image; this->Modified(); } } void ImageStatisticsCalculator::SetMask(mitk::MaskGenerator *mask) { if (mask != m_MaskGenerator) { m_MaskGenerator = mask; this->Modified(); } } void ImageStatisticsCalculator::SetSecondaryMask(mitk::MaskGenerator *mask) { if (mask != m_SecondaryMaskGenerator) { m_SecondaryMaskGenerator = mask; this->Modified(); } } void ImageStatisticsCalculator::SetNBinsForHistogramStatistics(unsigned int nBins) { if (nBins != m_nBinsForHistogramStatistics) { m_nBinsForHistogramStatistics = nBins; this->Modified(); this->m_UseBinSizeOverNBins = false; } if (m_UseBinSizeOverNBins) { this->Modified(); this->m_UseBinSizeOverNBins = false; } } unsigned int ImageStatisticsCalculator::GetNBinsForHistogramStatistics() const { return m_nBinsForHistogramStatistics; } void ImageStatisticsCalculator::SetBinSizeForHistogramStatistics(double binSize) { if (binSize != m_binSizeForHistogramStatistics) { m_binSizeForHistogramStatistics = binSize; this->Modified(); this->m_UseBinSizeOverNBins = true; } if (!m_UseBinSizeOverNBins) { this->Modified(); this->m_UseBinSizeOverNBins = true; } } double ImageStatisticsCalculator::GetBinSizeForHistogramStatistics() const { return m_binSizeForHistogramStatistics; } mitk::ImageStatisticsContainer* ImageStatisticsCalculator::GetStatistics(LabelIndex label) { if (m_Image.IsNull()) { mitkThrow() << "no image"; } if (!m_Image->IsInitialized()) { mitkThrow() << "Image not initialized!"; } if (IsUpdateRequired(label)) { auto timeGeometry = m_Image->GetTimeGeometry(); // always compute statistics on all timesteps for (unsigned int timeStep = 0; timeStep < m_Image->GetTimeSteps(); timeStep++) { if (m_MaskGenerator.IsNotNull()) { m_MaskGenerator->SetTimeStep(timeStep); //See T25625: otherwise, the mask is not computed again after setting a different time step m_MaskGenerator->Modified(); m_InternalMask = m_MaskGenerator->GetMask(); if (m_MaskGenerator->GetReferenceImage().IsNotNull()) { m_InternalImageForStatistics = m_MaskGenerator->GetReferenceImage(); } else { m_InternalImageForStatistics = m_Image; } } else { m_InternalImageForStatistics = m_Image; } if (m_SecondaryMaskGenerator.IsNotNull()) { m_SecondaryMaskGenerator->SetTimeStep(timeStep); m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); } ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); imgTimeSel->SetInput(m_InternalImageForStatistics); imgTimeSel->SetTimeNr(timeStep); imgTimeSel->UpdateLargestPossibleRegion(); imgTimeSel->Update(); m_ImageTimeSlice = imgTimeSel->GetOutput(); // Calculate statistics with/without mask if (m_MaskGenerator.IsNull() && m_SecondaryMaskGenerator.IsNull()) { // 1) calculate statistics unmasked: AccessByItk_2(m_ImageTimeSlice, InternalCalculateStatisticsUnmasked, timeGeometry, timeStep) } else { // 2) calculate statistics masked AccessByItk_2(m_ImageTimeSlice, InternalCalculateStatisticsMasked, timeGeometry, timeStep) } } } auto it = m_StatisticContainers.find(label); if (it != m_StatisticContainers.end()) { return (it->second).GetPointer(); } else { mitkThrow() << "unknown label"; return nullptr; } } template void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( typename itk::Image *image, const TimeGeometry *timeGeometry, TimeStepType timeStep) { typedef typename itk::Image ImageType; typedef typename itk::ExtendedStatisticsImageFilter ImageStatisticsFilterType; typedef typename itk::MinMaxImageFilterWithIndex MinMaxFilterType; // reset statistics container if exists ImageStatisticsContainer::Pointer statisticContainerForImage; LabelIndex labelNoMask = 1; auto it = m_StatisticContainers.find(labelNoMask); if (it != m_StatisticContainers.end()) { statisticContainerForImage = it->second; } else { statisticContainerForImage = ImageStatisticsContainer::New(); statisticContainerForImage->SetTimeGeometry(const_cast(timeGeometry)); m_StatisticContainers.emplace(labelNoMask, statisticContainerForImage); } auto statObj = ImageStatisticsContainer::ImageStatisticsObject(); typename ImageStatisticsFilterType::Pointer statisticsFilter = ImageStatisticsFilterType::New(); statisticsFilter->SetInput(image); statisticsFilter->SetCoordinateTolerance(0.001); statisticsFilter->SetDirectionTolerance(0.001); // TODO: this is single threaded. Implement our own image filter that does this multi threaded // typename itk::MinimumMaximumImageCalculator::Pointer imgMinMaxFilter = // itk::MinimumMaximumImageCalculator::New(); imgMinMaxFilter->SetImage(image); // imgMinMaxFilter->Compute(); vnl_vector minIndex, maxIndex; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetInput(image); minMaxFilter->UpdateLargestPossibleRegion(); typename ImageType::PixelType minval = minMaxFilter->GetMin(); typename ImageType::PixelType maxval = minMaxFilter->GetMax(); typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(); typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(); // typename ImageType::IndexType tmpMinIndex = imgMinMaxFilter->GetIndexOfMinimum(); // typename ImageType::IndexType tmpMaxIndex = imgMinMaxFilter->GetIndexOfMaximum(); minIndex.set_size(tmpMaxIndex.GetIndexDimension()); maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); for (unsigned int i = 0; i < tmpMaxIndex.GetIndexDimension(); i++) { minIndex[i] = tmpMinIndex[i]; maxIndex[i] = tmpMaxIndex[i]; } statObj.AddStatistic(mitk::ImageStatisticsConstants::MINIMUMPOSITION(), minIndex); statObj.AddStatistic(mitk::ImageStatisticsConstants::MAXIMUMPOSITION(), maxIndex); // convert m_binSize in m_nBins if necessary unsigned int nBinsForHistogram; if (m_UseBinSizeOverNBins) { nBinsForHistogram = std::max(static_cast(std::ceil(maxval - minval)) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins } else { nBinsForHistogram = m_nBinsForHistogramStatistics; } statisticsFilter->SetHistogramParameters(nBinsForHistogram, minval, maxval); try { statisticsFilter->Update(); } catch (const itk::ExceptionObject &e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } auto voxelVolume = GetVoxelVolume(image); auto numberOfPixels = image->GetLargestPossibleRegion().GetNumberOfPixels(); auto volume = static_cast(numberOfPixels) * voxelVolume; auto variance = statisticsFilter->GetSigma() * statisticsFilter->GetSigma(); auto rms = std::sqrt(std::pow(statisticsFilter->GetMean(), 2.) + statisticsFilter->GetVariance()); // variance = sigma^2 statObj.AddStatistic(mitk::ImageStatisticsConstants::NUMBEROFVOXELS(), static_cast(numberOfPixels)); statObj.AddStatistic(mitk::ImageStatisticsConstants::VOLUME(), volume); statObj.AddStatistic(mitk::ImageStatisticsConstants::MEAN(), statisticsFilter->GetMean()); statObj.AddStatistic(mitk::ImageStatisticsConstants::MINIMUM(), static_cast(statisticsFilter->GetMinimum())); statObj.AddStatistic(mitk::ImageStatisticsConstants::MAXIMUM(), static_cast(statisticsFilter->GetMaximum())); statObj.AddStatistic(mitk::ImageStatisticsConstants::STANDARDDEVIATION(), statisticsFilter->GetSigma()); statObj.AddStatistic(mitk::ImageStatisticsConstants::VARIANCE(), variance); statObj.AddStatistic(mitk::ImageStatisticsConstants::SKEWNESS(), statisticsFilter->GetSkewness()); statObj.AddStatistic(mitk::ImageStatisticsConstants::KURTOSIS(), statisticsFilter->GetKurtosis()); statObj.AddStatistic(mitk::ImageStatisticsConstants::RMS(), rms); statObj.AddStatistic(mitk::ImageStatisticsConstants::MPP(), statisticsFilter->GetMPP()); statObj.AddStatistic(mitk::ImageStatisticsConstants::ENTROPY(), statisticsFilter->GetEntropy()); statObj.AddStatistic(mitk::ImageStatisticsConstants::MEDIAN(), statisticsFilter->GetMedian()); statObj.AddStatistic(mitk::ImageStatisticsConstants::UNIFORMITY(), statisticsFilter->GetUniformity()); statObj.AddStatistic(mitk::ImageStatisticsConstants::UPP(), statisticsFilter->GetUPP()); statObj.m_Histogram = statisticsFilter->GetHistogram().GetPointer(); statisticContainerForImage->SetStatisticsForTimeStep(timeStep, statObj); } template double ImageStatisticsCalculator::GetVoxelVolume(typename itk::Image *image) const { auto spacing = image->GetSpacing(); double voxelVolume = 1.; for (unsigned int i = 0; i < image->GetImageDimension(); i++) { voxelVolume *= spacing[i]; } return voxelVolume; } template void ImageStatisticsCalculator::InternalCalculateStatisticsMasked(typename itk::Image *image, const TimeGeometry *timeGeometry, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image MaskType; typedef typename MaskType::PixelType LabelPixelType; typedef itk::ExtendedLabelStatisticsImageFilter ImageStatisticsFilterType; typedef MaskUtilities MaskUtilType; typedef typename itk::MinMaxLabelImageFilterWithIndex MinMaxLabelFilterType; typedef typename ImageType::PixelType InputImgPixelType; // workaround: if m_SecondaryMaskGenerator ist not null but m_MaskGenerator is! (this is the case if we request a // 'ignore zuero valued pixels' mask in the gui but do not define a primary mask) bool swapMasks = false; if (m_SecondaryMask.IsNotNull() && m_InternalMask.IsNull()) { m_InternalMask = m_SecondaryMask; m_SecondaryMask = nullptr; swapMasks = true; } // maskImage has to have the same dimension as image typename MaskType::Pointer maskImage = MaskType::New(); try { // try to access the pixel values directly (no copying or casting). Only works if mask pixels are of pixelType // unsigned short maskImage = ImageToItkImage(m_InternalMask); } catch (const itk::ExceptionObject &) { // if the pixel type of the mask is not short, then we have to make a copy of m_InternalMask (and cast the values) CastToItkImage(m_InternalMask, maskImage); } // if we have a secondary mask (say a ignoreZeroPixelMask) we need to combine the masks (corresponds to AND) if (m_SecondaryMask.IsNotNull()) { // dirty workaround for a bug when pf mask + any other mask is used in conjunction. We need a proper fix for this // (Fabian Isensee is responsible and probably working on it!) if (m_InternalMask->GetDimension() == 2 && (m_SecondaryMask->GetDimension() == 3 || m_SecondaryMask->GetDimension() == 4)) { mitk::Image::ConstPointer old_img = m_SecondaryMaskGenerator->GetReferenceImage(); m_SecondaryMaskGenerator->SetInputImage(m_MaskGenerator->GetReferenceImage()); m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); m_SecondaryMaskGenerator->SetInputImage(old_img); } typename MaskType::Pointer secondaryMaskImage = MaskType::New(); secondaryMaskImage = ImageToItkImage(m_SecondaryMask); // secondary mask should be a ignore zero value pixel mask derived from image. it has to be cropped to the mask // region (which may be planar or simply smaller) typename MaskUtilities::Pointer secondaryMaskMaskUtil = MaskUtilities::New(); secondaryMaskMaskUtil->SetImage(secondaryMaskImage.GetPointer()); secondaryMaskMaskUtil->SetMask(maskImage.GetPointer()); typename MaskType::Pointer adaptedSecondaryMaskImage = secondaryMaskMaskUtil->ExtractMaskImageRegion(); typename itk::MaskImageFilter2::Pointer maskFilter = itk::MaskImageFilter2::New(); maskFilter->SetInput1(maskImage); maskFilter->SetInput2(adaptedSecondaryMaskImage); maskFilter->SetMaskingValue( 1); // all pixels of maskImage where secondaryMaskImage==1 will be kept, all the others are set to 0 maskFilter->UpdateLargestPossibleRegion(); maskImage = maskFilter->GetOutput(); } typename MaskUtilType::Pointer maskUtil = MaskUtilType::New(); maskUtil->SetImage(image); maskUtil->SetMask(maskImage.GetPointer()); // if mask is smaller than image, extract the image region where the mask is typename ImageType::Pointer adaptedImage = ImageType::New(); adaptedImage = maskUtil->ExtractMaskImageRegion(); // this also checks mask sanity // find min, max, minindex and maxindex typename MinMaxLabelFilterType::Pointer minMaxFilter = MinMaxLabelFilterType::New(); minMaxFilter->SetInput(adaptedImage); minMaxFilter->SetLabelInput(maskImage); minMaxFilter->UpdateLargestPossibleRegion(); // set histogram parameters for each label individually (min/max may be different for each label) typedef typename std::map MapType; typedef typename std::pair PairType; std::vector relevantLabels = minMaxFilter->GetRelevantLabels(); MapType minVals; MapType maxVals; std::map nBins; for (LabelPixelType label : relevantLabels) { minVals.insert(PairType(label, minMaxFilter->GetMin(label))); maxVals.insert(PairType(label, minMaxFilter->GetMax(label))); unsigned int nBinsForHistogram; if (m_UseBinSizeOverNBins) { nBinsForHistogram = std::max(static_cast(std::ceil(minMaxFilter->GetMax(label) - minMaxFilter->GetMin(label))) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins } else { nBinsForHistogram = m_nBinsForHistogramStatistics; } nBins.insert(typename std::pair(label, nBinsForHistogram)); } typename ImageStatisticsFilterType::Pointer imageStatisticsFilter = ImageStatisticsFilterType::New(); imageStatisticsFilter->SetDirectionTolerance(0.001); imageStatisticsFilter->SetCoordinateTolerance(0.001); imageStatisticsFilter->SetInput(adaptedImage); imageStatisticsFilter->SetLabelInput(maskImage); imageStatisticsFilter->SetHistogramParametersForLabels(nBins, minVals, maxVals); imageStatisticsFilter->Update(); std::list labels = imageStatisticsFilter->GetRelevantLabels(); auto it = labels.begin(); while (it != labels.end()) { ImageStatisticsContainer::Pointer statisticContainerForLabelImage; auto labelIt = m_StatisticContainers.find(*it); // reset if statisticContainer already exist if (labelIt != m_StatisticContainers.end()) { statisticContainerForLabelImage = labelIt->second; } // create new statisticContainer else { statisticContainerForLabelImage = ImageStatisticsContainer::New(); statisticContainerForLabelImage->SetTimeGeometry(const_cast(timeGeometry)); // link label (*it) to statisticContainer m_StatisticContainers.emplace(*it, statisticContainerForLabelImage); } ImageStatisticsContainer::ImageStatisticsObject statObj; // find min, max, minindex and maxindex // make sure to only look in the masked region, use a masker for this vnl_vector minIndex, maxIndex; mitk::Point3D worldCoordinateMin; mitk::Point3D worldCoordinateMax; mitk::Point3D indexCoordinateMin; mitk::Point3D indexCoordinateMax; m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMinIndex(*it), worldCoordinateMin); m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMaxIndex(*it), worldCoordinateMax); m_Image->GetGeometry()->WorldToIndex(worldCoordinateMin, indexCoordinateMin); m_Image->GetGeometry()->WorldToIndex(worldCoordinateMax, indexCoordinateMax); minIndex.set_size(3); maxIndex.set_size(3); // for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) for (unsigned int i = 0; i < 3; i++) { minIndex[i] = indexCoordinateMin[i]; maxIndex[i] = indexCoordinateMax[i]; } statObj.AddStatistic(mitk::ImageStatisticsConstants::MINIMUMPOSITION(), minIndex); statObj.AddStatistic(mitk::ImageStatisticsConstants::MAXIMUMPOSITION(), maxIndex); assert(std::abs(minMaxFilter->GetMax(*it) - imageStatisticsFilter->GetMaximum(*it)) < mitk::eps); assert(std::abs(minMaxFilter->GetMin(*it) - imageStatisticsFilter->GetMinimum(*it)) < mitk::eps); auto voxelVolume = GetVoxelVolume(image); auto numberOfVoxels = - static_cast(imageStatisticsFilter->GetSum(*it) / (double)imageStatisticsFilter->GetMean(*it)); + static_cast(imageStatisticsFilter->GetCount(*it)); auto volume = static_cast(numberOfVoxels) * voxelVolume; auto rms = std::sqrt(std::pow(imageStatisticsFilter->GetMean(*it), 2.) + imageStatisticsFilter->GetVariance(*it)); // variance = sigma^2 auto variance = imageStatisticsFilter->GetSigma(*it) * imageStatisticsFilter->GetSigma(*it); statObj.AddStatistic(mitk::ImageStatisticsConstants::NUMBEROFVOXELS(), numberOfVoxels); statObj.AddStatistic(mitk::ImageStatisticsConstants::VOLUME(), volume); statObj.AddStatistic(mitk::ImageStatisticsConstants::MEAN(), imageStatisticsFilter->GetMean(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::MINIMUM(), imageStatisticsFilter->GetMinimum(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::MAXIMUM(), imageStatisticsFilter->GetMaximum(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::STANDARDDEVIATION(), imageStatisticsFilter->GetSigma(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::VARIANCE(), variance); statObj.AddStatistic(mitk::ImageStatisticsConstants::SKEWNESS(), imageStatisticsFilter->GetSkewness(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::KURTOSIS(), imageStatisticsFilter->GetKurtosis(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::RMS(), rms); statObj.AddStatistic(mitk::ImageStatisticsConstants::MPP(), imageStatisticsFilter->GetMPP(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::ENTROPY(), imageStatisticsFilter->GetEntropy(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::MEDIAN(), imageStatisticsFilter->GetMedian(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::UNIFORMITY(), imageStatisticsFilter->GetUniformity(*it)); statObj.AddStatistic(mitk::ImageStatisticsConstants::UPP(), imageStatisticsFilter->GetUPP(*it)); statObj.m_Histogram = imageStatisticsFilter->GetHistogram(*it).GetPointer(); statisticContainerForLabelImage->SetStatisticsForTimeStep(timeStep, statObj); ++it; } // swap maskGenerators back if (swapMasks) { m_SecondaryMask = m_InternalMask; m_InternalMask = nullptr; } } bool ImageStatisticsCalculator::IsUpdateRequired(LabelIndex label) const { unsigned long thisClassTimeStamp = this->GetMTime(); unsigned long inputImageTimeStamp = m_Image->GetMTime(); auto it = m_StatisticContainers.find(label); if (it == m_StatisticContainers.end()) { return true; } unsigned long statisticsTimeStamp = it->second->GetMTime(); if (thisClassTimeStamp > statisticsTimeStamp) // inputs have changed { return true; } if (inputImageTimeStamp > statisticsTimeStamp) // image has changed { return true; } if (m_MaskGenerator.IsNotNull()) { unsigned long maskGeneratorTimeStamp = m_MaskGenerator->GetMTime(); if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a mask generator and it has changed { return true; } } if (m_SecondaryMaskGenerator.IsNotNull()) { unsigned long maskGeneratorTimeStamp = m_SecondaryMaskGenerator->GetMTime(); if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a secondary mask generator and it has changed { return true; } } return false; } } // namespace mitk diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp index 6b8851bdd4..0f63304ab9 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp @@ -1,240 +1,248 @@ /*============================================================================ 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) + if (this->TimeStepExists(i)) { - os << std::endl << indent.GetNextIndent() << aKey << ": " << statisticsValues.GetValueNonConverted(aKey); + auto statisticsValues = GetStatisticsForTimeStep(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); + } + } + else + { + os << std::endl << indent << "N/A"; } } } 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 (const 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/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp index cfaea25dce..91d3eb0141 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp @@ -1,102 +1,106 @@ /*============================================================================ 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); + connect(m_Controls.checkBoxIgnoreZeroValuedVoxel, &QCheckBox::stateChanged, + this, &QmitkImageStatisticsWidget::IgnoreZeroValuedVoxelStateChanged); } 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); + m_Controls.checkBoxIgnoreZeroValuedVoxel->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); + m_Controls.checkBoxIgnoreZeroValuedVoxel->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 13e4fef97d..b755b5fc46 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h @@ -1,63 +1,65 @@ /*============================================================================ 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; +signals: + void IgnoreZeroValuedVoxelStateChanged(int status); + 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/Qmitk/QmitkImageStatisticsWidget.ui b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.ui index db87e06c53..e9f05754a3 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.ui +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.ui @@ -1,92 +1,99 @@ QmitkImageStatisticsControls 0 0 395 366 0 0 Form 2 2 2 2 - + 0 0 0 0 0 false 0 0 Copy to Clipboard - + Qt::Horizontal 40 20 + + + + Ignore zero-valued voxels + + + diff --git a/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp b/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp index ee9a32ef57..03cd67e759 100644 --- a/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp +++ b/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp @@ -1,237 +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. ============================================================================*/ #include "mitkVolumeMapperVtkSmart3D.h" #include "mitkTransferFunctionProperty.h" #include "mitkTransferFunctionInitializer.h" #include "mitkLevelWindowProperty.h" #include #include #include #include void mitk::VolumeMapperVtkSmart3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { bool value; this->GetDataNode()->GetBoolProperty("volumerendering", value, renderer); if (!value) { m_Volume->VisibilityOff(); return; } else { + createMapper(GetInputImage()); m_Volume->VisibilityOn(); } UpdateTransferFunctions(renderer); UpdateRenderMode(renderer); this->Modified(); } vtkProp* mitk::VolumeMapperVtkSmart3D::GetVtkProp(mitk::BaseRenderer *) { if (!m_Volume->GetMapper()) { createMapper(GetInputImage()); createVolume(); createVolumeProperty(); } return m_Volume; } void mitk::VolumeMapperVtkSmart3D::ApplyProperties(vtkActor *, mitk::BaseRenderer *) { } void mitk::VolumeMapperVtkSmart3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // GPU_INFO << "SetDefaultProperties"; node->AddProperty("volumerendering", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.usemip", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.cpu.ambient", mitk::FloatProperty::New(0.10f), renderer, overwrite); node->AddProperty("volumerendering.cpu.diffuse", mitk::FloatProperty::New(0.50f), renderer, overwrite); node->AddProperty("volumerendering.cpu.specular", mitk::FloatProperty::New(0.40f), renderer, overwrite); node->AddProperty("volumerendering.cpu.specular.power", mitk::FloatProperty::New(16.0f), renderer, overwrite); node->AddProperty("volumerendering.usegpu", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.useray", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.gpu.ambient", mitk::FloatProperty::New(0.25f), renderer, overwrite); node->AddProperty("volumerendering.gpu.diffuse", mitk::FloatProperty::New(0.50f), renderer, overwrite); node->AddProperty("volumerendering.gpu.specular", mitk::FloatProperty::New(0.40f), renderer, overwrite); node->AddProperty("volumerendering.gpu.specular.power", mitk::FloatProperty::New(16.0f), renderer, overwrite); node->AddProperty("binary", mitk::BoolProperty::New(false), renderer, overwrite); mitk::Image::Pointer image = dynamic_cast(node->GetData()); if (image.IsNotNull() && image->IsInitialized()) { if ((overwrite) || (node->GetProperty("TransferFunction", renderer) == nullptr)) { // add a default transfer function mitk::TransferFunction::Pointer tf = mitk::TransferFunction::New(); mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(tf); tfInit->SetTransferFunctionMode(0); node->SetProperty("TransferFunction", mitk::TransferFunctionProperty::New(tf.GetPointer())); } } Superclass::SetDefaultProperties(node, renderer, overwrite); } vtkImageData* mitk::VolumeMapperVtkSmart3D::GetInputImage() { auto input = dynamic_cast(this->GetDataNode()->GetData()); return input->GetVtkImageData(this->GetTimestep()); } void mitk::VolumeMapperVtkSmart3D::createMapper(vtkImageData* imageData) { Vector3D spacing; FillVector3D(spacing, 1.0, 1.0, 1.0); m_ImageChangeInformation->SetInputData(imageData); m_ImageChangeInformation->SetOutputSpacing(spacing.GetDataPointer()); m_SmartVolumeMapper->SetBlendModeToComposite(); m_SmartVolumeMapper->SetInputConnection(m_ImageChangeInformation->GetOutputPort()); } void mitk::VolumeMapperVtkSmart3D::createVolume() { - m_Volume->VisibilityOff(); m_Volume->SetMapper(m_SmartVolumeMapper); m_Volume->SetProperty(m_VolumeProperty); } void mitk::VolumeMapperVtkSmart3D::createVolumeProperty() { m_VolumeProperty->ShadeOn(); m_VolumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); } void mitk::VolumeMapperVtkSmart3D::UpdateTransferFunctions(mitk::BaseRenderer *renderer) { vtkSmartPointer opacityTransferFunction; vtkSmartPointer gradientTransferFunction; vtkSmartPointer colorTransferFunction; bool isBinary = false; this->GetDataNode()->GetBoolProperty("binary", isBinary, renderer); if (isBinary) { colorTransferFunction = vtkSmartPointer::New(); float rgb[3]; if (!GetDataNode()->GetColor(rgb, renderer)) rgb[0] = rgb[1] = rgb[2] = 1; colorTransferFunction->AddRGBPoint(0, rgb[0], rgb[1], rgb[2]); colorTransferFunction->Modified(); opacityTransferFunction = vtkSmartPointer::New(); gradientTransferFunction = vtkSmartPointer::New(); } else { auto *transferFunctionProp = dynamic_cast(this->GetDataNode()->GetProperty("TransferFunction", renderer)); if (transferFunctionProp) { opacityTransferFunction = transferFunctionProp->GetValue()->GetScalarOpacityFunction(); gradientTransferFunction = transferFunctionProp->GetValue()->GetGradientOpacityFunction(); colorTransferFunction = transferFunctionProp->GetValue()->GetColorTransferFunction(); } else { opacityTransferFunction = vtkSmartPointer::New(); gradientTransferFunction = vtkSmartPointer::New(); colorTransferFunction = vtkSmartPointer::New(); } } - m_VolumeProperty->SetColor(colorTransferFunction); m_VolumeProperty->SetScalarOpacity(opacityTransferFunction); m_VolumeProperty->SetGradientOpacity(gradientTransferFunction); } void mitk::VolumeMapperVtkSmart3D::UpdateRenderMode(mitk::BaseRenderer *renderer) { bool usegpu = false; bool useray = false; bool usemip = false; this->GetDataNode()->GetBoolProperty("volumerendering.usegpu", usegpu); this->GetDataNode()->GetBoolProperty("volumerendering.useray", useray); this->GetDataNode()->GetBoolProperty("volumerendering.usemip", usemip); if (usegpu) m_SmartVolumeMapper->SetRequestedRenderModeToGPU(); else if (useray) m_SmartVolumeMapper->SetRequestedRenderModeToRayCast(); else m_SmartVolumeMapper->SetRequestedRenderModeToDefault(); int blendMode; if (this->GetDataNode()->GetIntProperty("volumerendering.blendmode", blendMode)) m_SmartVolumeMapper->SetBlendMode(blendMode); else if (usemip) m_SmartVolumeMapper->SetBlendMode(vtkSmartVolumeMapper::MAXIMUM_INTENSITY_BLEND); // shading parameter if (m_SmartVolumeMapper->GetRequestedRenderMode() == vtkSmartVolumeMapper::GPURenderMode) { float value = 0; if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.ambient", value, renderer)) m_VolumeProperty->SetAmbient(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.diffuse", value, renderer)) m_VolumeProperty->SetDiffuse(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.specular", value, renderer)) m_VolumeProperty->SetSpecular(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.specular.power", value, renderer)) m_VolumeProperty->SetSpecularPower(value); } else { float value = 0; if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.ambient", value, renderer)) m_VolumeProperty->SetAmbient(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.diffuse", value, renderer)) m_VolumeProperty->SetDiffuse(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.specular", value, renderer)) m_VolumeProperty->SetSpecular(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.specular.power", value, renderer)) m_VolumeProperty->SetSpecularPower(value); } } mitk::VolumeMapperVtkSmart3D::VolumeMapperVtkSmart3D() { m_SmartVolumeMapper = vtkSmartPointer::New(); m_SmartVolumeMapper->SetBlendModeToComposite(); m_ImageChangeInformation = vtkSmartPointer::New(); m_VolumeProperty = vtkSmartPointer::New(); m_Volume = vtkSmartPointer::New(); } mitk::VolumeMapperVtkSmart3D::~VolumeMapperVtkSmart3D() { } diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index 3452a9a90b..47f5472fdf 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,363 +1,360 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSet.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.GetActiveLabel()->GetValue()), m_Layer(other.GetLayer()) { // clone Labels auto otherIt = other.IteratorConstBegin(); for (; otherIt != other.IteratorConstEnd(); ++otherIt) { m_LabelContainer[otherIt->first] = otherIt->second->Clone(); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); } } void mitk::LabelSet::OnLabelModified() { ModifyLabelEvent.Send(); Superclass::Modified(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const { return m_LabelContainer.begin(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() { return m_LabelContainer.begin(); } unsigned int mitk::LabelSet::GetNumberOfLabels() const { return m_LabelContainer.size(); } void mitk::LabelSet::SetLayer(unsigned int layer) { m_Layer = layer; Modified(); } void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) { m_ActiveLabelValue = pixelValue; ActiveLabelEvent.Send(pixelValue); Modified(); } bool mitk::LabelSet::ExistLabel(PixelType pixelValue) { return m_LabelContainer.count(pixelValue) > 0 ? true : false; } // TODO Parameter as Smartpointer void mitk::LabelSet::AddLabel(mitk::Label *label) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LabelContainer.size() >= max_size) return; mitk::Label::Pointer newLabel(label->Clone()); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); PixelType pixelValue; if (m_LabelContainer.empty()) { pixelValue = newLabel->GetValue(); } else { pixelValue = m_LabelContainer.rbegin()->first; if (pixelValue >= newLabel->GetValue() && m_LabelContainer.find(newLabel->GetValue()) != m_LabelContainer.end()) { ++pixelValue; newLabel->SetValue(pixelValue); } else { pixelValue = newLabel->GetValue(); } } // new map entry m_LabelContainer[pixelValue] = newLabel; UpdateLookupTable(pixelValue); // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); newLabel->AddObserver(itk::ModifiedEvent(), command); // newLabel->AddObserver(itk::ModifiedEvent(),command); SetActiveLabel(newLabel->GetValue()); AddLabelEvent.Send(); Modified(); } void mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { - if (m_LabelContainer.size() > 255) - return; - mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); AddLabel(newLabel); } void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) { mitk::Label *label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) { m_LookupTable = lut; Modified(); } void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const { } void mitk::LabelSet::RemoveLabel(PixelType pixelValue) { auto it = m_LabelContainer.rbegin(); PixelType nextActivePixelValue = it->first; for (; it != m_LabelContainer.rend(); ++it) { if (it->first == pixelValue) { it->second->RemoveAllObservers(); m_LabelContainer.erase(pixelValue); break; } nextActivePixelValue = it->first; } if (m_ActiveLabelValue == pixelValue) { if (ExistLabel(nextActivePixelValue)) SetActiveLabel(nextActivePixelValue); else SetActiveLabel(m_LabelContainer.rbegin()->first); } RemoveLabelEvent.Send(); Modified(); } void mitk::LabelSet::RemoveAllLabels() { auto _it = IteratorBegin(); for (; _it != IteratorConstEnd();) { RemoveLabelEvent.Send(); m_LabelContainer.erase(_it++); } AllLabelsModifiedEvent.Send(); } void mitk::LabelSet::SetNextActiveLabel() { auto it = m_LabelContainer.begin(); for (; it != m_LabelContainer.end(); ++it) { if (it->first == m_ActiveLabelValue) { // go to next label ++it; if (it == m_LabelContainer.end()) { // end of container; next label is first label it = m_LabelContainer.begin(); } break; // found the active label; finish loop } } SetActiveLabel(it->first); } void mitk::LabelSet::SetAllLabelsLocked(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) _it->second->SetLocked(value); AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::SetAllLabelsVisible(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) { _it->second->SetVisible(value); UpdateLookupTable(_it->first); } AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) { const mitk::Color &color = GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) { if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) return nullptr; return m_LabelContainer[pixelValue]; } const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const { auto it = m_LabelContainer.find(pixelValue); if (it == m_LabelContainer.end()) return nullptr; return it->second.GetPointer(); } bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // LabelSetmembers MITK_INFO(verbose) << "--- LabelSet Equal ---"; // m_LookupTable; const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // m_ActiveLabel; returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Active label not equal."; return returnValue; ; } // m_Layer; returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); if (!returnValue) { MITK_INFO(verbose) << "Layer index not equal."; return returnValue; ; } // container size; returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // Label container (map) // m_LabelContainer; auto lhsit = leftHandSide.IteratorConstBegin(); auto rhsit = rightHandSide.IteratorConstBegin(); for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) { returnValue = rhsit->first == lhsit->first; if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 8ebc7838fb..9b4253fc63 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1012 +1,1026 @@ /*============================================================================ 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 "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include //#include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack AddLayer(); } mitk::LabelSetImage::~LabelSetImage() { m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer lset) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } unsigned int newLabelSetId = this->AddLayer(newImage, lset); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer lset) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (lset.IsNotNull()) { ls = lset; } else { ls = mitk::LabelSet::New(); ls->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::Concatenate(mitk::LabelSetImage *other) { const unsigned int *otherDims = other->GetDimensions(); const unsigned int *thisDims = this->GetDimensions(); if ((otherDims[0] != thisDims[0]) || (otherDims[1] != thisDims[1]) || (otherDims[2] != thisDims[2])) mitkThrow() << "Dimensions do not match."; try { int numberOfLayers = other->GetNumberOfLayers(); for (int layer = 0; layer < numberOfLayers; ++layer) { this->SetActiveLayer(layer); AccessByItk_1(this, ConcatenateProcessing, other); mitk::LabelSet *ls = other->GetLabelSet(layer); auto it = ls->IteratorConstBegin(); auto end = ls->IteratorConstEnd(); it++; // skip exterior while (it != end) { GetLabelSet()->AddLabel((it->second)); // AddLabelEvent.Send(); it++; } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { - AccessByItk(this, ClearBufferProcessing); + if (this->GetDimension() == 4) + { //remark: this extra branch was added, because LabelSetImage instances can be + //dynamic (4D), but AccessByItk by support only supports 2D and 3D. + //The option to change the CMake default dimensions for AccessByItk was + //dropped (for details see discussion in T28756) + AccessFixedDimensionByItk(this, ClearBufferProcessing,4); + } + else + { + AccessByItk(this, ClearBufferProcessing); + } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { GetLabelSet(layer)->RemoveLabel(VectorOfLabelPixelValues[idx]); EraseLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int i = 0; i < VectorOfLabelPixelValues.size(); i++) { this->EraseLabel(VectorOfLabelPixelValues[i], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue, unsigned int layer) { try { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, EraseLabelProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, EraseLabelProcessing, pixelValue, layer); } } catch (const itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } Modified(); } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { - mask->Initialize(this); + // mask->Initialize(this) does not work here if this label set image has a single slice, + // since the mask would be automatically flattened to a 2-d image, whereas we expect the + // original dimension of this label set image. Hence, initialize the mask more explicitly: + mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); + mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { // for now, we just retrieve the voxel in the middle typedef itk::ImageRegionConstIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); std::vector indexVector; while (!iter.IsAtEnd()) { // TODO fix comparison warning more effective if (iter.Get() == pixelValue) { indexVector.push_back(iter.GetIndex()); } ++iter; } mitk::Point3D pos; pos.Fill(0.0); if (!indexVector.empty()) { typename itk::ImageRegionConstIteratorWithIndex::IndexType centerIndex; centerIndex = indexVector.at(indexVector.size() / 2); if (centerIndex.GetIndexDimension() == 3) { pos[0] = centerIndex[0]; pos[1] = centerIndex[1]; pos[2] = centerIndex[2]; } else return; } GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } // todo: concatenate all layers and not just the active one template void mitk::LabelSetImage::ConcatenateProcessing(ImageType *itkTarget, mitk::LabelSetImage *other) { typename ImageType::Pointer itkSource = ImageType::New(); mitk::CastToItkImage(other, itkSource); typedef itk::ImageRegionConstIterator ConstIteratorType; typedef itk::ImageRegionIterator IteratorType; ConstIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); IteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); int numberOfTargetLabels = this->GetNumberOfLabels(GetActiveLayer()) - 1; // skip exterior sourceIter.GoToBegin(); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && !this->GetLabel(targetValue)->GetLocked()) // skip exterior and locked labels { targetIter.Set(sourceValue + numberOfTargetLabels); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int /*layer*/) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } diff --git a/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp b/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp index 4c53a5c238..dedc6d3917 100644 --- a/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp +++ b/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp @@ -1,177 +1,182 @@ /*============================================================================ 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 "mitkExtendedToftsModel.h" #include "mitkConvolutionHelper.h" #include #include const std::string mitk::ExtendedToftsModel::MODEL_DISPLAY_NAME = "Extended Tofts Model"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_Ktrans = "KTrans"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_ve = "ve"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_vp = "vp"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_Ktrans = "ml/min/100ml"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_ve = "ml/ml"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_vp = "ml/ml"; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_Ktrans = 0; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_ve = 1; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_vp = 2; const unsigned int mitk::ExtendedToftsModel::NUMBER_OF_PARAMETERS = 3; std::string mitk::ExtendedToftsModel::GetModelDisplayName() const { return MODEL_DISPLAY_NAME; }; std::string mitk::ExtendedToftsModel::GetModelType() const { return "Perfusion.MR"; }; mitk::ExtendedToftsModel::ExtendedToftsModel() { } mitk::ExtendedToftsModel::~ExtendedToftsModel() { } mitk::ExtendedToftsModel::ParameterNamesType mitk::ExtendedToftsModel::GetParameterNames() const { ParameterNamesType result; result.push_back(NAME_PARAMETER_Ktrans); result.push_back(NAME_PARAMETER_ve); result.push_back(NAME_PARAMETER_vp); return result; } mitk::ExtendedToftsModel::ParametersSizeType mitk::ExtendedToftsModel::GetNumberOfParameters() const { return NUMBER_OF_PARAMETERS; } mitk::ExtendedToftsModel::ParamterUnitMapType mitk::ExtendedToftsModel::GetParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair(NAME_PARAMETER_Ktrans, UNIT_PARAMETER_Ktrans)); result.insert(std::make_pair(NAME_PARAMETER_vp, UNIT_PARAMETER_vp)); result.insert(std::make_pair(NAME_PARAMETER_ve, UNIT_PARAMETER_ve)); return result; }; mitk::ExtendedToftsModel::ParameterNamesType mitk::ExtendedToftsModel::GetDerivedParameterNames() const { ParameterNamesType result; result.push_back("kep"); return result; }; mitk::ExtendedToftsModel::ParametersSizeType mitk::ExtendedToftsModel::GetNumberOfDerivedParameters() const { return 1; }; mitk::ExtendedToftsModel::ParamterUnitMapType mitk::ExtendedToftsModel::GetDerivedParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair("kep", "1/min")); return result; }; mitk::ExtendedToftsModel::ModelResultType mitk::ExtendedToftsModel::ComputeModelfunction( const ParametersType& parameters) const { if (this->m_TimeGrid.GetSize() == 0) { itkExceptionMacro("No Time Grid Set! Cannot Calculate Signal"); } AterialInputFunctionType aterialInputFunction; aterialInputFunction = GetAterialInputFunction(this->m_TimeGrid); unsigned int timeSteps = this->m_TimeGrid.GetSize(); //Model Parameters double ktrans = parameters[POSITION_PARAMETER_Ktrans] / 6000.0; double ve = parameters[POSITION_PARAMETER_ve]; double vp = parameters[POSITION_PARAMETER_vp]; + if (ve == 0.0) + { + itkExceptionMacro("ve is 0! Cannot calculate signal"); + } + double lambda = ktrans / ve; mitk::ModelBase::ModelResultType convolution = mitk::convoluteAIFWithExponential(this->m_TimeGrid, aterialInputFunction, lambda); //Signal that will be returned by ComputeModelFunction mitk::ModelBase::ModelResultType signal(timeSteps); signal.fill(0.0); mitk::ModelBase::ModelResultType::iterator signalPos = signal.begin(); mitk::ModelBase::ModelResultType::const_iterator res = convolution.begin(); for (AterialInputFunctionType::iterator Cp = aterialInputFunction.begin(); Cp != aterialInputFunction.end(); ++res, ++signalPos, ++Cp) { *signalPos = (*Cp) * vp + ktrans * (*res); } return signal; } mitk::ModelBase::DerivedParameterMapType mitk::ExtendedToftsModel::ComputeDerivedParameters( const mitk::ModelBase::ParametersType& parameters) const { DerivedParameterMapType result; double kep = parameters[POSITION_PARAMETER_Ktrans] / parameters[POSITION_PARAMETER_ve]; result.insert(std::make_pair("kep", kep)); return result; }; itk::LightObject::Pointer mitk::ExtendedToftsModel::InternalClone() const { ExtendedToftsModel::Pointer newClone = ExtendedToftsModel::New(); newClone->SetTimeGrid(this->m_TimeGrid); return newClone.GetPointer(); }; void mitk::ExtendedToftsModel::PrintSelf(std::ostream& os, ::itk::Indent indent) const { Superclass::PrintSelf(os, indent); }; diff --git a/Modules/Pharmacokinetics/test/files.cmake b/Modules/Pharmacokinetics/test/files.cmake index 713e14913f..d6ce07a366 100644 --- a/Modules/Pharmacokinetics/test/files.cmake +++ b/Modules/Pharmacokinetics/test/files.cmake @@ -1,4 +1,7 @@ SET(MODULE_TESTS mitkDescriptivePharmacokineticBrixModelTest.cpp + mitkStandardToftsModelTest.cpp #ConvertToConcentrationTest.cpp + mitkTwoCompartmentExchangeModelTest.cpp + mitkExtendedToftsModelTest.cpp ) diff --git a/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp b/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp new file mode 100644 index 0000000000..5bd2a350e1 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp @@ -0,0 +1,164 @@ +/*============================================================================ + +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" +#include "math.h" + +//MITK includes +#include "mitkVector.h" +#include "mitkExtendedToftsModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkExtendedToftsModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkExtendedToftsModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(GetDerivedParameterNamesTest); + MITK_TEST(GetNumberOfDerivedParametersTest); + MITK_TEST(GetDerivedParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + MITK_TEST(ComputeDerivedParametersTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::ExtendedToftsModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + mitk::ModelBase::DerivedParameterMapType m_derivedParameters; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(60); + mitk::ModelBase::ParametersType m_testparameters(3); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (60); + + m_testparameters[mitk::ExtendedToftsModel::POSITION_PARAMETER_Ktrans] = 35.0; + m_testparameters(mitk::ExtendedToftsModel::POSITION_PARAMETER_ve) = 0.5; + m_testparameters[mitk::ExtendedToftsModel::POSITION_PARAMETER_vp] = 0.05; + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + for (int i = 0; i < 22; ++i) + { + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::ExtendedToftsModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + m_derivedParameters = m_testmodel->GetDerivedParameters(m_testparameters); + } + + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + m_derivedParameters.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Extended Tofts Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::ExtendedToftsModel::ParameterNamesType parameterNames; + parameterNames.push_back("KTrans"); + parameterNames.push_back("ve"); + parameterNames.push_back("vp"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 3); + } + + + void GetParameterUnitsTest() + { + mitk::ExtendedToftsModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("KTrans", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("vp", "ml/ml")); + parameterUnits.insert(std::make_pair("ve","ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void GetDerivedParameterNamesTest() + { + mitk::ExtendedToftsModel::ParameterNamesType derivedParameterNames; + + derivedParameterNames.push_back("kep"); + + CPPUNIT_ASSERT_MESSAGE("Checking derived parameter names.", m_testmodel->GetDerivedParameterNames() == derivedParameterNames); + } + + void GetNumberOfDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfDerivedParameters() == 1); + } + + void GetDerivedParameterUnitsTest() + { + mitk::ExtendedToftsModel::ParamterUnitMapType derivedParameterUnits; + derivedParameterUnits.insert(std::make_pair("kep", "1/min")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetDerivedParameterUnits() == derivedParameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.438500, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(1.094436, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10", mitk::Equal(0.890956, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 15", mitk::Equal(0.580996, m_output[15], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20", mitk::Equal(0.342851, m_output[20], 1e-6, true) == true); + } + + void ComputeDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking kep.", mitk::Equal(70.00, m_derivedParameters["kep"], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkExtendedToftsModel) diff --git a/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp b/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp new file mode 100644 index 0000000000..5b0c907166 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp @@ -0,0 +1,162 @@ +/*============================================================================ + +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 "mitkVector.h" +#include "mitkStandardToftsModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkStandardToftsModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkStandardToftsModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(GetDerivedParameterNamesTest); + MITK_TEST(GetNumberOfDerivedParametersTest); + MITK_TEST(GetDerivedParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + MITK_TEST(ComputeDerivedParametersTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::StandardToftsModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + mitk::ModelBase::DerivedParameterMapType m_derivedParameters; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(22); + mitk::ModelBase::ParametersType m_testparameters(2); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (22); + + m_testparameters[mitk::StandardToftsModel::POSITION_PARAMETER_Ktrans] = 35.0; + m_testparameters(mitk::StandardToftsModel::POSITION_PARAMETER_ve) = 0.5; + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + for (int i = 0; i < 22; ++i) + { + if (i < 5) + m_arterialInputFunction[i] = 0; + else + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::StandardToftsModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + m_derivedParameters = m_testmodel->GetDerivedParameters(m_testparameters); + } + + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + m_derivedParameters.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Standard Tofts Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::StandardToftsModel::ParameterNamesType parameterNames; + parameterNames.push_back("KTrans"); + parameterNames.push_back("ve"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 2); + } + + + void GetParameterUnitsTest() + { + mitk::StandardToftsModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("KTrans", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("ve", "ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void GetDerivedParameterNamesTest() + { + mitk::StandardToftsModel::ParameterNamesType derivedParameterNames; + + derivedParameterNames.push_back("kep"); + + CPPUNIT_ASSERT_MESSAGE("Checking derived parameter names.", m_testmodel->GetDerivedParameterNames() == derivedParameterNames); + } + + void GetNumberOfDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfDerivedParameters() == 1); + } + + void GetDerivedParameterUnitsTest() + { + mitk::StandardToftsModel::ParamterUnitMapType derivedParameterUnits; + derivedParameterUnits.insert(std::make_pair("kep", "1/min")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetDerivedParameterUnits() == derivedParameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.0, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(0.085056, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10.", mitk::Equal(0.442948, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20.", mitk::Equal(0.254551, m_output[20], 1e-6, true) == true); + } + + void ComputeDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking kep.", mitk::Equal(70.0, m_derivedParameters["kep"], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkStandardToftsModel) diff --git a/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp b/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp new file mode 100644 index 0000000000..652198ec20 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp @@ -0,0 +1,137 @@ +/*============================================================================ + +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 "mitkVector.h" +#include "mitkTwoCompartmentExchangeModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkTwoCompartmentExchangeModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkTwoCompartmentExchangeModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::TwoCompartmentExchangeModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(22); + mitk::ModelBase::ParametersType m_testparameters(4); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (22); + + m_testparameters[mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_F] = 35.0; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_PS) = 5.0; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_ve) = 0.5; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_vp) = 0.05; + + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + + for (int i = 0; i < 22; ++i) + { + if (i < 5) + m_arterialInputFunction[i] = 0; + else + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::TwoCompartmentExchangeModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + } + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Two Compartment Exchange Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::TwoCompartmentExchangeModel::ParameterNamesType parameterNames; + parameterNames.push_back("F"); + parameterNames.push_back("PS"); + parameterNames.push_back("ve"); + parameterNames.push_back("vp"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 4); + } + + + void GetParameterUnitsTest() + { + mitk::TwoCompartmentExchangeModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("F", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("PS", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("ve", "ml/ml")); + parameterUnits.insert(std::make_pair("vp", "ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.0, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(0.057246, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10.", mitk::Equal(0.127806, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 15.", mitk::Equal(0.131465, m_output[15], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20.", mitk::Equal(0.126101, m_output[20], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkTwoCompartmentExchangeModel) diff --git a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h index 6a22193017..63a3f33642 100644 --- a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h +++ b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h @@ -1,197 +1,198 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKPLANARFIGUREINTERACTOR_H #define MITKPLANARFIGUREINTERACTOR_H #include #include "mitkCommon.h" #include "mitkDataInteractor.h" #include "mitkNumericTypes.h" #pragma GCC visibility push(default) #include #pragma GCC visibility pop namespace mitk { class DataNode; class PlaneGeometry; class PlanarFigure; class PositionEvent; class BaseRenderer; class InteractionPositionEvent; class StateMachineAction; #pragma GCC visibility push(default) // Define events for PlanarFigure interaction notifications itkEventMacro(PlanarFigureEvent, itk::AnyEvent); itkEventMacro(StartPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(SelectPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(ContextMenuPlanarFigureEvent, PlanarFigureEvent); + itkEventMacro(PointMovedPlanarFigureEvent, PlanarFigureEvent); #pragma GCC visibility pop /** * \brief Interaction with mitk::PlanarFigure objects via control-points * * @ingroup MitkPlanarFigureModule */ class MITKPLANARFIGURE_EXPORT PlanarFigureInteractor : public DataInteractor { public: mitkClassMacro(PlanarFigureInteractor, DataInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Sets the amount of precision */ void SetPrecision(ScalarType precision); /** \brief Sets the minimal distance between two control points. */ void SetMinimumPointDistance(ScalarType minimumDistance); protected: PlanarFigureInteractor(); ~PlanarFigureInteractor() override; void ConnectActionsAndFunctions() override; //////// Conditions //////// bool CheckFigurePlaced(const InteractionEvent *interactionEvent); bool CheckFigureHovering(const InteractionEvent *interactionEvent); bool CheckControlPointHovering(const InteractionEvent *interactionEvent); bool CheckSelection(const InteractionEvent *interactionEvent); bool CheckPointValidity(const InteractionEvent *interactionEvent); bool CheckFigureFinished(const InteractionEvent *interactionEvent); bool CheckResetOnPointSelect(const InteractionEvent *interactionEvent); bool CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent); bool CheckMinimalFigureFinished(const InteractionEvent *interactionEvent); bool CheckFigureIsExtendable(const InteractionEvent *interactionEvent); bool CheckFigureIsDeletable(const InteractionEvent *interactionEvent); bool CheckFigureIsEditable(const InteractionEvent *interactionEvent); //////// Actions //////// void FinalizeFigure(StateMachineAction *, InteractionEvent *interactionEvent); void MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent); void DeselectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent); void StartHovering(StateMachineAction *, InteractionEvent *interactionEvent); void EndHovering(StateMachineAction *, InteractionEvent *interactionEvent); void DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent); void PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *interactionEvent); void SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent); void HidePreviewPoint(StateMachineAction *, InteractionEvent *interactionEvent); void HideControlPoints(StateMachineAction *, InteractionEvent *interactionEvent); void RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent); void RequestContextMenu(StateMachineAction *, InteractionEvent *interactionEvent); void SelectFigure(StateMachineAction *, InteractionEvent *interactionEvent); void SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void EndInteraction(StateMachineAction *, InteractionEvent *interactionEvent); bool FilterEvents(InteractionEvent *interactionEvent, DataNode *) override; /** \brief Used when clicking to determine if a point is too close to the previous point. */ bool IsMousePositionAcceptableAsNewControlPoint(const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *); bool TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D); bool TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const; /** \brief Returns true if the first specified point is in proximity of the line defined * the other two point; false otherwise. * * Proximity is defined as the rectangle around the line with pre-defined distance * from the line. */ bool IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const; /** \brief Returns true if the point contained in the passed event (in display coordinates) * is over the planar figure (with a pre-defined tolerance range); false otherwise. */ int IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const; /** \brief Returns the index of the marker (control point) over which the point contained * in the passed event (in display coordinates) currently is; -1 if the point is not over * a marker. */ int IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const; void LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure); void ConfigurationChanged() override; private: /** \brief to store the value of precision to pick a point */ ScalarType m_Precision; /** \brief Store the minimal distance between two control points. */ ScalarType m_MinimumPointDistance; /** \brief True if the mouse is currently hovering over the image. */ bool m_IsHovering; }; } #endif // MITKPLANARFIGUREINTERACTOR_H diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 363c3ef628..706ac0b24b 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,1113 +1,1115 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" mitk::PlanarFigureInteractor::PlanarFigureInteractor() : DataInteractor() , m_Precision(6.5) , m_MinimumPointDistance(25.0) , m_IsHovering(false) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) CONNECT_FUNCTION("set_preview_point_position", SetPreviewPointPosition) CONNECT_FUNCTION("move_current_point", MoveCurrentPoint); CONNECT_FUNCTION("deselect_point", DeselectPoint); CONNECT_FUNCTION("add_new_point", AddPoint); CONNECT_FUNCTION("add_initial_point", AddInitialPoint); CONNECT_FUNCTION("remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION("request_context_menu", RequestContextMenu); CONNECT_FUNCTION("select_figure", SelectFigure); CONNECT_FUNCTION("select_point", SelectPoint); CONNECT_FUNCTION("end_interaction", EndInteraction); CONNECT_FUNCTION("start_hovering", StartHovering) CONNECT_FUNCTION("end_hovering", EndHovering); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty("planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction); // hide the control points if necessary // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction); // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); + + planarFigure->InvokeEvent(PointMovedPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); planarFigure->InvokeEvent(EndPlacementPlanarFigureEvent()); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent(EndHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", false); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable(const InteractionEvent * /*interactionEvent*/) { bool isExtendable(false); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const bool wasSelected = planarFigure->DeselectControlPoint(); if (wasSelected) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are necessary because of a macOS specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ const DataNode::Pointer node = this->GetDataNode(); const BaseData::Pointer data = node->GetData(); bool isFigureFinished = false; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } auto planarFigure = dynamic_cast(data.GetPointer()); if (nullptr == planarFigure) { return; } // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } mitk::BaseRenderer *renderer = interactionEvent->GetSender(); auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planeGeometry, point2D)) { return; } // Place PlanarFigure at this point planarFigure->PlaceFigure(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } if (!m_IsHovering) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent(StartHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", true); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); if (selected && isExtendable && isEditable) { renderer->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); return isHovering; } bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); return pointIndex >= 0; } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } bool mitk::PlanarFigureInteractor::CheckPointValidity(const InteractionEvent *interactionEvent) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if (!selected) { planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } planarFigure->InvokeEvent(ContextMenuPlanarFigureEvent()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const double planeThickness = planarFigureGeometry->GetExtentInMM(2); return planarFigureGeometry->Distance(worldPoint3D) <= planeThickness; } void mitk::PlanarFigureInteractor::SetPrecision(mitk::ScalarType precision) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance(ScalarType minimumDistance) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D) { if (nullptr == positionEvent || nullptr == planarFigureGeometry) { return false; } const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if (planarFigureGeometry->Distance(worldPoint3D) > 0.1) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map(worldPoint3D, point2D); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { if (nullptr == objectGeometry || nullptr == rendererGeometry || nullptr == renderer) { return false; } mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, displayPoint); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const { if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry || nullptr == rendererGeometry) { return -1; } mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for (unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { break; // Poly line invalid (not on current 2D plane) --> skip it } if (firstPoint) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if (this->IsPointNearLine(displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine)) { // Point is close enough to line segment --> Return index of the segment return std::distance(polyLine.begin(), it); } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if (planarFigure->IsClosed() && this->IsPointNearLine(displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine)) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry || nullptr == rendererGeometry || nullptr == renderer) { return -1; } const mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // TODO: variable size of markers if (displayPosition.SquaredEuclideanDistanceTo(displayControlPoint) < 20.0) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure) { if (nullptr == planarFigure) { MITK_INFO << "PlanarFigure invalid."; } MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for (unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i) { MITK_INFO << "* " << planarFigure->GetFeatureName(i) << ": " << planarFigure->GetQuantity(i) << " " << planarFigure->GetFeatureUnit(i); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure) { if (nullptr == positionEvent || nullptr == planarFigure) { return false; } const BaseRenderer *renderer = positionEvent->GetSender(); if (nullptr == renderer) { return false; } // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); auto planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); if (nullptr != abstractTransformGeometry) { return false; } Point2D point2D; // Get the point2D from the positionEvent if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. const Point2D correctedPoint = const_cast(planarFigure)->ApplyControlPointConstraints(0, point2D); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map(correctedPoint, newPoint3D); mitk::Point2D newDisplayPosition; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map(planarFigure->GetControlPoint(i), previousPoint3D); if (renderer->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance); } if (tooClose) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { const mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType)6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType)25.0; } } diff --git a/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp b/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp index 399903ff84..dfd18a0a43 100755 --- a/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp +++ b/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp @@ -1,221 +1,214 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkPythonVariableStackTableModel.h" #include #include #include #include #include #include #include "QmitkMimeTypes.h" const QString QmitkPythonVariableStackTableModel::MITK_IMAGE_VAR_NAME = "mitkImage"; const QString QmitkPythonVariableStackTableModel::MITK_SURFACE_VAR_NAME = "mitkSurface"; QmitkPythonVariableStackTableModel::QmitkPythonVariableStackTableModel(QObject *parent) :QAbstractTableModel(parent) { us::ModuleContext* context = us::GetModuleContext(); m_PythonServiceRef = context->GetServiceReference(); m_PythonService = context->GetService(m_PythonServiceRef); m_PythonService->AddPythonCommandObserver( this ); } QmitkPythonVariableStackTableModel::~QmitkPythonVariableStackTableModel() { us::ModuleContext* context = us::GetModuleContext(); context->UngetService( m_PythonServiceRef ); m_PythonService->RemovePythonCommandObserver( this ); } bool QmitkPythonVariableStackTableModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int, int, const QModelIndex &) { // 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(QmitkMimeTypes::DataNodePtrs)) { - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "dropped MITK DataNode"; returnValue = true; int i = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode* node = nullptr; foreach(node, dataNodeList) { mitk::Image* mitkImage = dynamic_cast(node->GetData()); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "mitkImage is not null " << (mitkImage != nullptr? "true": "false"); QRegExp rx("^\\d"); QString varName(node->GetName().c_str()); // regex replace every character that is not allowed in a python variable varName = varName.replace(QRegExp("[.\\+\\-*\\s\\/\\n\\t\\r]"),QString("_")); if( mitkImage ) { if ( varName.isEmpty() ) varName = MITK_IMAGE_VAR_NAME; if ( rx.indexIn(varName) == 0) varName.prepend("_").prepend(MITK_IMAGE_VAR_NAME); if( i > 0 ) varName = QString("%1%2").arg(varName).arg(i); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "varName" << varName.toStdString(); bool exportAsCvImage = mitkImage->GetDimension() == 2 && m_PythonService->IsOpenCvPythonWrappingAvailable(); if( exportAsCvImage ) { int ret = QMessageBox::question(nullptr, "Export option", "2D image detected. Export as OpenCV image to Python instead of an SimpleITK image?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No); exportAsCvImage = ret == QMessageBox::Yes; if(exportAsCvImage) { m_PythonService->CopyToPythonAsCvImage( mitkImage, varName.toStdString() ); ++i; } } if( !exportAsCvImage ) { if( m_PythonService->IsSimpleItkPythonWrappingAvailable() ) { m_PythonService->CopyToPythonAsSimpleItkImage( mitkImage, varName.toStdString() ); ++i; } else { MITK_ERROR << "SimpleITK Python wrapping not available. Skipping export for image " << node->GetName(); } } } else { mitk::Surface* surface = dynamic_cast(node->GetData()); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "found surface"; if( surface ) { if (varName.isEmpty() ) varName = MITK_SURFACE_VAR_NAME; if ( rx.indexIn(varName) == 0) varName.prepend("_").prepend(MITK_SURFACE_VAR_NAME); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "varName" << varName; - if( m_PythonService->IsVtkPythonWrappingAvailable() ) { m_PythonService->CopyToPythonAsVtkPolyData( surface, varName.toStdString() ); } else { MITK_ERROR << "VTK Python wrapping not available. Skipping export for surface " << node->GetName(); } } } } } return returnValue; } QVariant QmitkPythonVariableStackTableModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant headerData; // show only horizontal header if ( role == Qt::DisplayRole ) { if( orientation == Qt::Horizontal ) { // first column: "Attribute" if(section == 0) headerData = "Attribute"; else if(section == 1) headerData = "Type"; else if(section == 2) headerData = "Value"; } } return headerData; } Qt::ItemFlags QmitkPythonVariableStackTableModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if(index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | flags; else return Qt::ItemIsDropEnabled | flags; } int QmitkPythonVariableStackTableModel::rowCount(const QModelIndex &) const { return m_VariableStack.size(); } int QmitkPythonVariableStackTableModel::columnCount(const QModelIndex &) const { return 3; } QVariant QmitkPythonVariableStackTableModel::data(const QModelIndex &index, int role) const { if (index.isValid() && !m_VariableStack.empty()) { if(role == Qt::DisplayRole) { mitk::PythonVariable item = m_VariableStack.at(index.row()); if(index.column() == 0) return QString::fromStdString(item.m_Name); if(index.column() == 1) return QString::fromStdString(item.m_Type); if(index.column() == 2) return QString::fromStdString(item.m_Value); } } return QVariant(); } QStringList QmitkPythonVariableStackTableModel::mimeTypes() const { return QAbstractTableModel::mimeTypes(); QStringList types; types << "application/x-mitk-datanodes"; types << "application/x-qabstractitemmodeldatalist"; return types; } Qt::DropActions QmitkPythonVariableStackTableModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } -void QmitkPythonVariableStackTableModel::CommandExecuted(const std::string& pythonCommand) +void QmitkPythonVariableStackTableModel::CommandExecuted(const std::string&) { - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "command was executed " << pythonCommand; m_VariableStack = m_PythonService->GetVariableStack(); QAbstractTableModel::beginResetModel(); QAbstractTableModel::endResetModel(); } std::vector QmitkPythonVariableStackTableModel::GetVariableStack() const { return m_VariableStack; } diff --git a/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp b/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp index dd290731b4..4423c16d4d 100644 --- a/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp +++ b/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp @@ -1,304 +1,276 @@ /*============================================================================ 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 // mitk core #include // mitk qt widgets #include "QmitkLevelWindowPresetDefinitionDialog.h" #include "QmitkLevelWindowRangeChangeDialog.h" // qt #include QmitkLevelWindowWidgetContextMenu::QmitkLevelWindowWidgetContextMenu(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { m_LevelWindowPreset = mitk::LevelWindowPreset::New(); m_LevelWindowPreset->LoadPreset(); } QmitkLevelWindowWidgetContextMenu::~QmitkLevelWindowWidgetContextMenu() { m_LevelWindowPreset->Delete(); } void QmitkLevelWindowWidgetContextMenu::OnSetPreset(const QAction *presetAction) { QString item = presetAction->text(); if (!(presetAction == m_PresetAction)) { double dlevel = m_LevelWindowPreset->getLevel(item.toStdString()); double dwindow = m_LevelWindowPreset->getWindow(item.toStdString()); - if ((dlevel + dwindow / 2) > m_LevelWindow.GetRangeMax()) - { - double lowerBound = (dlevel - dwindow / 2); - if (!(lowerBound > m_LevelWindow.GetRangeMax())) - { - dwindow = m_LevelWindow.GetRangeMax() - lowerBound; - dlevel = lowerBound + dwindow / 2; - } - else - { - dlevel = m_LevelWindow.GetRangeMax() - 1; - dwindow = 2; - } - } - else if ((dlevel - dwindow / 2) < m_LevelWindow.GetRangeMin()) - { - double upperBound = (dlevel + dwindow / 2); - if (!(upperBound < m_LevelWindow.GetRangeMin())) - { - dwindow = m_LevelWindow.GetRangeMin() + upperBound; - dlevel = upperBound - dwindow / 2; - } - else - { - dlevel = m_LevelWindow.GetRangeMin() + 1; - dwindow = 2; - } - } m_LevelWindow.SetLevelWindow(dlevel, dwindow); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLevelWindowWidgetContextMenu::SetLevelWindowManager(mitk::LevelWindowManager *levelWindowManager) { m_Manager = levelWindowManager; } void QmitkLevelWindowWidgetContextMenu::OnAddPreset() { QmitkLevelWindowPresetDefinitionDialog addPreset(this); addPreset.setPresets(m_LevelWindowPreset->getLevelPresets(), m_LevelWindowPreset->getWindowPresets(), QString::number((int)m_LevelWindow.GetLevel()), QString::number((int)m_LevelWindow.GetWindow())); if (addPreset.exec()) { m_LevelWindowPreset->newPresets(addPreset.getLevelPresets(), addPreset.getWindowPresets()); } } void QmitkLevelWindowWidgetContextMenu::OnSetFixed() { m_LevelWindow.SetFixed(!m_LevelWindow.GetFixed()); m_Manager->SetLevelWindow(m_LevelWindow); } void QmitkLevelWindowWidgetContextMenu::OnUseAllGreyvaluesFromImage() { m_LevelWindow.SetToImageRange(m_Manager->GetCurrentImage()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnUseOptimizedLevelWindow() { m_LevelWindow.SetAuto(m_Manager->GetCurrentImage(), false, false); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetDefaultLevelWindow() { m_LevelWindow.ResetDefaultLevelWindow(); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetMaximumWindow() { m_LevelWindow.SetToMaxWindowSize(); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetDefaultScaleRange() { m_LevelWindow.ResetDefaultRangeMinMax(); m_LevelWindow.SetLevelWindow(m_LevelWindow.GetLevel(), m_LevelWindow.GetWindow()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnChangeScaleRange() { QmitkLevelWindowRangeChangeDialog changeRange(this); changeRange.setLowerLimit((mitk::ScalarType)m_LevelWindow.GetRangeMin()); changeRange.setUpperLimit((mitk::ScalarType)m_LevelWindow.GetRangeMax()); if (changeRange.exec()) { m_LevelWindow.SetRangeMinMax(changeRange.getLowerLimit(), changeRange.getUpperLimit()); m_LevelWindow.SetLevelWindow(m_LevelWindow.GetLevel(), m_LevelWindow.GetWindow()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLevelWindowWidgetContextMenu::OnSetImage(QAction *imageAction) { if (imageAction == m_AutoTopmostAction) { if (m_Manager->IsAutoTopMost() == false) { m_Manager->SetAutoTopMostImage(true); m_SelectedImagesAction->setChecked(false); } else { m_Manager->SetAutoTopMostImage(false); } } else if(imageAction == m_SelectedImagesAction) { if (m_Manager->IsSelectedImages() == false) { m_Manager->SetSelectedImages(true); m_AutoTopmostAction->setChecked(false); } else { m_Manager->SetSelectedImages(false); } } else { m_AutoTopmostAction->setChecked(false); m_SelectedImagesAction->setChecked(false); m_Manager->SetLevelWindowProperty(m_Images.at(imageAction)); } } void QmitkLevelWindowWidgetContextMenu::GetContextMenu(QMenu *contextMenu) { if (nullptr == contextMenu) { return; } try { m_LevelWindow = m_Manager->GetLevelWindow(); QAction *sliderFixed = contextMenu->addAction(tr("Set slider fixed"), this, &QmitkLevelWindowWidgetContextMenu::OnSetFixed); sliderFixed->setCheckable(true); sliderFixed->setChecked(m_LevelWindow.IsFixed()); contextMenu->addSeparator(); contextMenu->addAction(tr("Use whole image grey values"), this, &QmitkLevelWindowWidgetContextMenu::OnUseAllGreyvaluesFromImage); contextMenu->addAction(tr("Use optimized level-window"), this, &QmitkLevelWindowWidgetContextMenu::OnUseOptimizedLevelWindow); contextMenu->addSeparator(); contextMenu->addAction(tr("Set maximum window"), this, &QmitkLevelWindowWidgetContextMenu::OnSetMaximumWindow); contextMenu->addAction(tr("Default level-window"), this, &QmitkLevelWindowWidgetContextMenu::OnSetDefaultLevelWindow); contextMenu->addSeparator(); contextMenu->addAction(tr("Change scale range"), this, &QmitkLevelWindowWidgetContextMenu::OnChangeScaleRange); contextMenu->addAction(tr("Default scale range"), this, &QmitkLevelWindowWidgetContextMenu::OnSetDefaultScaleRange); contextMenu->addSeparator(); m_PresetSubmenu = new QMenu(this); m_PresetSubmenu->setTitle("Presets"); m_PresetAction = m_PresetSubmenu->addAction(tr("Preset definition"), this, &QmitkLevelWindowWidgetContextMenu::OnAddPreset); m_PresetSubmenu->addSeparator(); std::map preset = m_LevelWindowPreset->getLevelPresets(); for (auto iter = preset.begin(); iter != preset.end(); iter++) { QString item = ((*iter).first.c_str()); m_PresetSubmenu->addAction(item); } connect(m_PresetSubmenu, &QMenu::triggered, this, &QmitkLevelWindowWidgetContextMenu::OnSetPreset); contextMenu->addMenu(m_PresetSubmenu); contextMenu->addSeparator(); m_ImageSubmenu = new QMenu(this); m_ImageSubmenu->setTitle("Images"); // add action for "auto topmost image" action m_AutoTopmostAction = m_ImageSubmenu->addAction(tr("Set topmost image")); m_AutoTopmostAction->setCheckable(true); if (m_Manager->IsAutoTopMost()) { m_AutoTopmostAction->setChecked(true); } // add action for "selected images" action m_SelectedImagesAction = m_ImageSubmenu->addAction(tr("Use selected images")); m_SelectedImagesAction->setCheckable(true); if (m_Manager->IsSelectedImages()) { m_SelectedImagesAction->setChecked(true); } // add action for individual images m_ImageSubmenu->addSeparator(); mitk::DataStorage::SetOfObjects::ConstPointer allObjects = m_Manager->GetRelevantNodes(); for (mitk::DataStorage::SetOfObjects::ConstIterator objectIter = allObjects->Begin(); objectIter != allObjects->End(); ++objectIter) { mitk::DataNode *node = objectIter->Value(); if (nullptr == node) { continue; } bool isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (isHelperObject) { continue; } if (!node->IsVisible(nullptr)) { continue; } mitk::LevelWindowProperty::Pointer levelWindowProperty = dynamic_cast(node->GetProperty("levelwindow")); if (levelWindowProperty.IsNotNull()) { std::string name; node->GetName(name); QString item = name.c_str(); QAction *id = m_ImageSubmenu->addAction(item); id->setCheckable(true); m_Images[id] = levelWindowProperty; if (levelWindowProperty == m_Manager->GetLevelWindowProperty()) { id->setChecked(true); } } } connect(m_ImageSubmenu, &QMenu::triggered, this, &QmitkLevelWindowWidgetContextMenu::OnSetImage); contextMenu->addMenu(m_ImageSubmenu); contextMenu->exec(QCursor::pos()); } catch (...) { } } void QmitkLevelWindowWidgetContextMenu::GetContextMenu() { auto contextMenu = new QMenu(this); GetContextMenu(contextMenu); delete contextMenu; } diff --git a/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h b/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h index 5bf7ea8d64..443f1b7cac 100644 --- a/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h @@ -1,80 +1,80 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKTRANSFERFUNCTIONGENERATORWIDGET_H #define QMITKTRANSFERFUNCTIONGENERATORWIDGET_H #include "MitkQtWidgetsExtExports.h" #include "ui_QmitkTransferFunctionGeneratorWidget.h" #include #include #include #include class MITKQTWIDGETSEXT_EXPORT QmitkTransferFunctionGeneratorWidget : public QWidget, public Ui::QmitkTransferFunctionGeneratorWidget { Q_OBJECT public: QmitkTransferFunctionGeneratorWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); ~QmitkTransferFunctionGeneratorWidget() override; - void SetDataNode(mitk::DataNode *node); + void SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep = 0); int AddPreset(const QString &presetName); void SetPresetsTabEnabled(bool enable); void SetThresholdTabEnabled(bool enable); void SetBellTabEnabled(bool enable); public slots: void OnSavePreset(); void OnLoadPreset(); void OnDeltaLevelWindow(int dx, int dy); void OnDeltaThreshold(int dx, int dy); signals: void SignalTransferFunctionModeChanged(int); void SignalUpdateCanvas(); protected slots: void OnPreset(int mode); protected: mitk::TransferFunctionProperty::Pointer tfpToChange; double histoMinimum; double histoMaximum; double thPos; double thDelta; double deltaScale; double deltaMax; double deltaMin; const mitk::Image::HistogramType *histoGramm; QString presetFileName; double ScaleDelta(int d) const; }; #endif diff --git a/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h b/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h index d1d5a901d4..0ab5534657 100755 --- a/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h @@ -1,79 +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 QMITKTRANSFERFUNCTIONWIDGET_H #define QMITKTRANSFERFUNCTIONWIDGET_H #include "MitkQtWidgetsExtExports.h" #include "ui_QmitkTransferFunctionWidget.h" #include #include #include #include #include #include #include namespace mitk { class BaseRenderer; } class MITKQTWIDGETSEXT_EXPORT QmitkTransferFunctionWidget : public QWidget, public Ui::QmitkTransferFunctionWidget { Q_OBJECT public: QmitkTransferFunctionWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); ~QmitkTransferFunctionWidget() override; - void SetDataNode(mitk::DataNode *node, const mitk::BaseRenderer *renderer = nullptr); + void SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep = 0, const mitk::BaseRenderer *renderer = nullptr); void SetScalarLabel(const QString &scalarLabel); void ShowScalarOpacityFunction(bool show); void ShowColorFunction(bool show); void ShowGradientOpacityFunction(bool show); void SetScalarOpacityFunctionEnabled(bool enable); void SetColorFunctionEnabled(bool enable); void SetGradientOpacityFunctionEnabled(bool enable); public slots: void SetXValueScalar(const QString text); void SetYValueScalar(const QString text); void SetXValueGradient(const QString text); void SetYValueGradient(const QString text); void SetXValueColor(const QString text); void OnUpdateCanvas(); void UpdateRanges(); void OnResetSlider(); void OnSpanChanged(int lower, int upper); protected: mitk::TransferFunctionProperty::Pointer tfpToChange; int m_RangeSliderMin; int m_RangeSliderMax; mitk::SimpleHistogramCache histogramCache; }; #endif diff --git a/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp b/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp index e244bb77a1..861c103b23 100644 --- a/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp @@ -1,353 +1,367 @@ /*============================================================================ 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 "QmitkTransferFunctionGeneratorWidget.h" #include #include #include #include #include #include #include #include #include QmitkTransferFunctionGeneratorWidget::QmitkTransferFunctionGeneratorWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), deltaScale(1.0), deltaMax(1024), deltaMin(1) { histoGramm = nullptr; this->setupUi(this); // LevelWindow Tab { connect(m_CrossLevelWindow, SIGNAL(SignalDeltaMove(int, int)), this, SLOT(OnDeltaLevelWindow(int, int))); } // Threshold Tab { connect(m_CrossThreshold, SIGNAL(SignalDeltaMove(int, int)), this, SLOT(OnDeltaThreshold(int, int))); thDelta = 100; } // Presets Tab { m_TransferFunctionComboBox->setVisible(false); connect(m_TransferFunctionComboBox, SIGNAL(activated(int)), this, SIGNAL(SignalTransferFunctionModeChanged(int))); connect(m_TransferFunctionComboBox, SIGNAL(activated(int)), this, SLOT(OnPreset(int))); connect(m_SavePreset, SIGNAL(clicked()), this, SLOT(OnSavePreset())); connect(m_LoadPreset, SIGNAL(clicked()), this, SLOT(OnLoadPreset())); } presetFileName = "."; } int QmitkTransferFunctionGeneratorWidget::AddPreset(const QString &presetName) { m_TransferFunctionComboBox->setVisible(true); m_TransferFunctionComboBox->addItem(presetName); return m_TransferFunctionComboBox->count() - 1; } void QmitkTransferFunctionGeneratorWidget::SetPresetsTabEnabled(bool enable) { m_PresetTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::SetThresholdTabEnabled(bool enable) { m_ThresholdTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::SetBellTabEnabled(bool enable) { m_BellTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::OnSavePreset() { if (tfpToChange.IsNull()) return; mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); presetFileName = QFileDialog::getSaveFileName( this, "Choose a filename to save the transfer function", presetFileName, "Transferfunction (*.xml)"); if (!presetFileName.endsWith(".xml")) presetFileName.append(".xml"); MITK_INFO << "Saving Transferfunction under path: " << presetFileName.toStdString(); if (mitk::TransferFunctionPropertySerializer::SerializeTransferFunction(presetFileName.toLatin1(), tf)) { QFontMetrics metrics(m_InfoPreset->font()); QString text = metrics.elidedText(presetFileName, Qt::ElideMiddle, m_InfoPreset->width()); m_InfoPreset->setText(QString("saved ") + text); } else { m_InfoPreset->setText(QString("saving failed")); } } void QmitkTransferFunctionGeneratorWidget::OnLoadPreset() { if (tfpToChange.IsNull()) return; presetFileName = QFileDialog::getOpenFileName( this, "Choose a file to open the transfer function from", presetFileName, "Transferfunction (*.xml)"); MITK_INFO << "Loading Transferfunction from path: " << presetFileName.toStdString(); mitk::TransferFunction::Pointer tf = mitk::TransferFunctionPropertySerializer::DeserializeTransferFunction(presetFileName.toLatin1()); if (tf.IsNotNull()) { tfpToChange->SetValue(tf); QFontMetrics metrics(m_InfoPreset->font()); QString text = metrics.elidedText(presetFileName, Qt::ElideMiddle, m_InfoPreset->width()); m_InfoPreset->setText(QString("loaded ") + text); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } } void QmitkTransferFunctionGeneratorWidget::OnPreset(int mode) { // first item is only information if (--mode == -1) return; m_InfoPreset->setText(QString("selected ") + m_TransferFunctionComboBox->currentText()); // revert to first item m_TransferFunctionComboBox->setCurrentIndex(0); } static double transformationGlocke(double x) { double z = 0.1; double a = 2 - 2 * z; double b = 2 * z - 1; x = a * x + b; return x; } static double stepFunctionGlocke(double x) { x = 1 - (2 * x - 1.0); // map [0.5;1] to [0,1] x = x * (3 * x - 2 * x * x); // apply smoothing function x = x * x; return x; } double QmitkTransferFunctionGeneratorWidget::ScaleDelta(int d) const { return deltaScale * (double)d; } void QmitkTransferFunctionGeneratorWidget::OnDeltaLevelWindow(int dx, int dy) // bell { if (tfpToChange.IsNull()) return; thPos += ScaleDelta(dx); thDelta -= ScaleDelta(dy); if (thDelta < deltaMin) thDelta = deltaMin; if (thDelta > deltaMax) thDelta = deltaMax; if (thPos < histoMinimum) thPos = histoMinimum; if (thPos > histoMaximum) thPos = histoMaximum; std::stringstream ss; ss << "Click on the cross and move the mouse" << "\n" << "\n" << "center at " << thPos << "\n" << "width " << thDelta * 2; m_InfoLevelWindow->setText(QString(ss.str().c_str())); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); // grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetScalarOpacityFunction(); f->RemoveAllPoints(); for (int r = 0; r <= 6; r++) { double relPos = (r / 6.0) * 0.5 + 0.5; f->AddPoint(thPos + thDelta * (-transformationGlocke(relPos)), stepFunctionGlocke(relPos)); f->AddPoint(thPos + thDelta * (transformationGlocke(relPos)), stepFunctionGlocke(relPos)); } f->Modified(); } // gradient at grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetGradientOpacityFunction(); f->RemoveAllPoints(); f->AddPoint(0, 1.0); f->Modified(); } tf->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } static double stepFunctionThreshold(double x) { x = 0.5 * x + 0.5; // map [-1;1] to [0,1] x = x * (3 * x - 2 * x * x); // apply smoothing function x = x * x; return x; } void QmitkTransferFunctionGeneratorWidget::OnDeltaThreshold(int dx, int dy) // LEVELWINDOW { if (tfpToChange.IsNull()) return; thPos += ScaleDelta(dx); thDelta += ScaleDelta(dy); if (thDelta < deltaMin) thDelta = deltaMin; if (thDelta > deltaMax) thDelta = deltaMax; if (thPos < histoMinimum) thPos = histoMinimum; if (thPos > histoMaximum) thPos = histoMaximum; std::stringstream ss; ss << "Click on the cross and move the mouse" << "\n" << "\n" << "threshold at " << thPos << "\n" << "width " << thDelta * 2; m_InfoThreshold->setText(QString(ss.str().c_str())); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); // grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetScalarOpacityFunction(); f->RemoveAllPoints(); for (int r = 1; r <= 4; r++) { double relPos = r / 4.0; f->AddPoint(thPos + thDelta * (-relPos), stepFunctionThreshold(-relPos)); f->AddPoint(thPos + thDelta * (relPos), stepFunctionThreshold(relPos)); } f->Modified(); } // gradient at grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetGradientOpacityFunction(); f->RemoveAllPoints(); f->AddPoint(0, 1.0); f->Modified(); } tf->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } QmitkTransferFunctionGeneratorWidget::~QmitkTransferFunctionGeneratorWidget() { } -void QmitkTransferFunctionGeneratorWidget::SetDataNode(mitk::DataNode *node) +void QmitkTransferFunctionGeneratorWidget::SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep) { histoGramm = nullptr; if (node) { tfpToChange = dynamic_cast(node->GetProperty("TransferFunction")); if (!tfpToChange) node->SetProperty("TransferFunction", tfpToChange = mitk::TransferFunctionProperty::New()); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); if (mitk::Image *image = dynamic_cast(node->GetData())) { - mitk::ImageStatisticsHolder *statistics = image->GetStatistics(); + mitk::Image::Pointer inputImage = image; + if (image->GetTimeSteps() > 1) + { + if (!image->GetTimeGeometry()->IsValidTimeStep(timestep)) + { + return; + } + mitk::ImageTimeSelector::Pointer timeselector = mitk::ImageTimeSelector::New(); + timeselector->SetInput(image); + timeselector->SetTimeNr(timestep); + timeselector->UpdateLargestPossibleRegion(); + inputImage = timeselector->GetOutput(); + } + + mitk::ImageStatisticsHolder *statistics = inputImage->GetStatistics(); histoMinimum = statistics->GetScalarValueMin(); histoMaximum = statistics->GetScalarValueMax(); } else if (mitk::UnstructuredGrid *grid = dynamic_cast(node->GetData())) { double *range = grid->GetVtkUnstructuredGrid()->GetScalarRange(); histoMinimum = range[0]; histoMaximum = range[1]; double histoRange = histoMaximum - histoMinimum; deltaMax = histoRange / 4.0; deltaMin = histoRange / 400.0; deltaScale = histoRange / 1024.0; } else { MITK_WARN << "QmitkTransferFunctonGeneratorWidget does not support " << node->GetData()->GetNameOfClass() << " instances"; } thPos = (histoMinimum + histoMaximum) / 2.0; } else { tfpToChange = nullptr; m_InfoPreset->setText(QString("")); } } diff --git a/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp b/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp index bde5c54854..2f5e727ba6 100755 --- a/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp @@ -1,246 +1,263 @@ /*============================================================================ 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 "QmitkTransferFunctionWidget.h" +#include "mitkImageTimeSelector.h" #include QmitkTransferFunctionWidget::QmitkTransferFunctionWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { this->setupUi(this); // signals and slots connections connect(m_XEditScalarOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueScalar(const QString &))); connect(m_YEditScalarOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetYValueScalar(const QString &))); connect(m_XEditGradientOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueGradient(const QString &))); connect(m_YEditGradientOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetYValueGradient(const QString &))); connect(m_XEditColor, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueColor(const QString &))); m_RangeSlider->setMinimum(-2048); m_RangeSlider->setMaximum(2048); connect(m_RangeSlider, SIGNAL(valuesChanged(int, int)), this, SLOT(OnSpanChanged(int, int))); // reset button connect(m_RangeSliderReset, SIGNAL(pressed()), this, SLOT(OnResetSlider())); m_ScalarOpacityFunctionCanvas->SetQLineEdits(m_XEditScalarOpacity, m_YEditScalarOpacity); m_GradientOpacityCanvas->SetQLineEdits(m_XEditGradientOpacity, m_YEditGradientOpacity); m_ColorTransferFunctionCanvas->SetQLineEdits(m_XEditColor, nullptr); m_ScalarOpacityFunctionCanvas->SetTitle("Grayvalue -> Opacity"); m_GradientOpacityCanvas->SetTitle("Grayvalue/Gradient -> Opacity"); m_ColorTransferFunctionCanvas->SetTitle("Grayvalue -> Color"); } QmitkTransferFunctionWidget::~QmitkTransferFunctionWidget() { } void QmitkTransferFunctionWidget::SetScalarLabel(const QString &scalarLabel) { m_textLabelX->setText(scalarLabel); m_textLabelX_2->setText(scalarLabel); m_textLabelX_3->setText(scalarLabel); m_ScalarOpacityFunctionCanvas->SetTitle(scalarLabel + " -> Opacity"); m_GradientOpacityCanvas->SetTitle(scalarLabel + "/Gradient -> Opacity"); m_ColorTransferFunctionCanvas->SetTitle(scalarLabel + " -> Color"); } void QmitkTransferFunctionWidget::ShowScalarOpacityFunction(bool show) { m_ScalarOpacityWidget->setVisible(show); } void QmitkTransferFunctionWidget::ShowColorFunction(bool show) { m_ColorWidget->setVisible(show); } void QmitkTransferFunctionWidget::ShowGradientOpacityFunction(bool show) { m_GradientOpacityWidget->setVisible(show); } void QmitkTransferFunctionWidget::SetScalarOpacityFunctionEnabled(bool enable) { m_ScalarOpacityWidget->setEnabled(enable); } void QmitkTransferFunctionWidget::SetColorFunctionEnabled(bool enable) { m_ColorWidget->setEnabled(enable); } void QmitkTransferFunctionWidget::SetGradientOpacityFunctionEnabled(bool enable) { m_GradientOpacityWidget->setEnabled(enable); } -void QmitkTransferFunctionWidget::SetDataNode(mitk::DataNode *node, const mitk::BaseRenderer *renderer) +void QmitkTransferFunctionWidget::SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep, const mitk::BaseRenderer *renderer) { if (node) { tfpToChange = dynamic_cast(node->GetProperty("TransferFunction", renderer)); - if (!tfpToChange) { if (!dynamic_cast(node->GetData())) { MITK_WARN << "QmitkTransferFunctionWidget::SetDataNode called with non-image node"; goto turnOff; } node->SetProperty("TransferFunction", tfpToChange = mitk::TransferFunctionProperty::New()); } mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); - if (mitk::BaseData *data = node->GetData()) + if (mitk::Image *data = dynamic_cast(node->GetData())) { - mitk::SimpleHistogram *h = histogramCache[data]; + mitk::SimpleHistogram *h = nullptr; + if (data->GetTimeSteps() > 1) + { + if (!data->GetTimeGeometry()->IsValidTimeStep(timestep)) + { + return; + } + mitk::ImageTimeSelector::Pointer timeselector = mitk::ImageTimeSelector::New(); + timeselector->SetInput(data); + timeselector->SetTimeNr(timestep); + timeselector->UpdateLargestPossibleRegion(); + auto inputImage = timeselector->GetOutput(); + h = histogramCache[inputImage]; + } + else + { + h = histogramCache[data]; + } m_RangeSliderMin = h->GetMin(); m_RangeSliderMax = h->GetMax(); m_RangeSlider->blockSignals(true); m_RangeSlider->setMinimum(m_RangeSliderMin); m_RangeSlider->setMaximum(m_RangeSliderMax); m_RangeSlider->setMinimumValue(m_RangeSliderMin); m_RangeSlider->setMaximumValue(m_RangeSliderMax); m_RangeSlider->blockSignals(false); m_ScalarOpacityFunctionCanvas->SetHistogram(h); m_GradientOpacityCanvas->SetHistogram(h); m_ColorTransferFunctionCanvas->SetHistogram(h); } OnUpdateCanvas(); return; } turnOff: m_ScalarOpacityFunctionCanvas->setEnabled(false); m_ScalarOpacityFunctionCanvas->SetHistogram(nullptr); m_GradientOpacityCanvas->setEnabled(false); m_GradientOpacityCanvas->SetHistogram(nullptr); m_ColorTransferFunctionCanvas->setEnabled(false); m_ColorTransferFunctionCanvas->SetHistogram(nullptr); tfpToChange = nullptr; } void QmitkTransferFunctionWidget::OnUpdateCanvas() { if (tfpToChange.IsNull()) return; mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); if (tf.IsNull()) return; m_ScalarOpacityFunctionCanvas->SetPiecewiseFunction(tf->GetScalarOpacityFunction()); m_GradientOpacityCanvas->SetPiecewiseFunction(tf->GetGradientOpacityFunction()); m_ColorTransferFunctionCanvas->SetColorTransferFunction(tf->GetColorTransferFunction()); UpdateRanges(); m_ScalarOpacityFunctionCanvas->update(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); } void QmitkTransferFunctionWidget::SetXValueScalar(const QString text) { if (!text.endsWith(".")) { m_ScalarOpacityFunctionCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetYValueScalar(const QString text) { if (!text.endsWith(".")) { m_ScalarOpacityFunctionCanvas->SetY(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetXValueGradient(const QString text) { if (!text.endsWith(".")) { m_GradientOpacityCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetYValueGradient(const QString text) { if (!text.endsWith(".")) { m_GradientOpacityCanvas->SetY(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetXValueColor(const QString text) { if (!text.endsWith(".")) { m_ColorTransferFunctionCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::UpdateRanges() { int lower = m_RangeSlider->minimumValue(); int upper = m_RangeSlider->maximumValue(); m_ScalarOpacityFunctionCanvas->SetMin(lower); m_ScalarOpacityFunctionCanvas->SetMax(upper); m_GradientOpacityCanvas->SetMin(lower); m_GradientOpacityCanvas->SetMax(upper); m_ColorTransferFunctionCanvas->SetMin(lower); m_ColorTransferFunctionCanvas->SetMax(upper); } void QmitkTransferFunctionWidget::OnSpanChanged(int, int) { UpdateRanges(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); m_ScalarOpacityFunctionCanvas->update(); } void QmitkTransferFunctionWidget::OnResetSlider() { m_RangeSlider->blockSignals(true); m_RangeSlider->setMaximumValue(m_RangeSliderMax); m_RangeSlider->setMinimumValue(m_RangeSliderMin); m_RangeSlider->blockSignals(false); UpdateRanges(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); m_ScalarOpacityFunctionCanvas->update(); } diff --git a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp index c55bf4e681..46060c5661 100644 --- a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp +++ b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp @@ -1,457 +1,462 @@ /*============================================================================ 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 "mitkSceneReaderV1.h" #include "Poco/Path.h" #include "mitkBaseRenderer.h" #include "mitkIOUtil.h" #include "mitkProgressBar.h" #include "mitkPropertyListDeserializer.h" #include "mitkSerializerMacros.h" #include #include #include MITK_REGISTER_SERIALIZER(SceneReaderV1) namespace { typedef std::pair> NodesAndParentsPair; bool NodeSortByLayerIsLessThan(const NodesAndParentsPair &left, const NodesAndParentsPair &right) { if (left.first.IsNotNull() && right.first.IsNotNull()) { int leftLayer; int rightLayer; if (left.first->GetIntProperty("layer", leftLayer) && right.first->GetIntProperty("layer", rightLayer)) { return leftLayer < rightLayer; } else { // fall back to name sort return left.first->GetName() < right.first->GetName(); } } // in all other cases, fall back to stupid pointer comparison // this is not reasonable but at least answers the sorting // question clearly return left.first.GetPointer() < right.first.GetPointer(); } // This is a workaround until we are able to save time-related information in an // actual file format of surfaces. void ApplyProportionalTimeGeometryProperties(mitk::BaseData* data) { if (nullptr == data) return; auto properties = data->GetPropertyList(); if (properties.IsNull()) return; auto* geometry = dynamic_cast(data->GetTimeGeometry()); if (nullptr == geometry) return; float value = 0.0f; if (properties->GetFloatProperty("ProportionalTimeGeometry.FirstTimePoint", value)) + { + if (value == -std::numeric_limits::infinity()) + value = std::numeric_limits::lowest(); + geometry->SetFirstTimePoint(value); + } if (properties->GetFloatProperty("ProportionalTimeGeometry.StepDuration", value)) geometry->SetStepDuration(value); } } bool mitk::SceneReaderV1::LoadScene(tinyxml2::XMLDocument &document, const std::string &workingDirectory, DataStorage *storage) { assert(storage); bool error(false); // TODO prepare to detect errors (such as cycles) from wrongly written or edited xml files // Get number of elements to initialze progress bar // 1. if there is a element, // - construct a name for the appropriate serializer // - try to instantiate this serializer via itk object factory // - if serializer could be created, use it to read the file into a BaseData object // - if successful, call the new node's SetData(..) // create a node for the tag "data" and test if node was created typedef std::vector DataNodeVector; DataNodeVector DataNodes; unsigned int listSize = 0; for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { ++listSize; } ProgressBar::GetInstance()->AddStepsToDo(listSize * 2); for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { DataNodes.push_back(LoadBaseDataFromDataTag(element->FirstChildElement("data"), workingDirectory, error)); ProgressBar::GetInstance()->Progress(); } // iterate all nodes // first level nodes should be elements auto nit = DataNodes.begin(); for (auto *element = document.FirstChildElement("node"); element != nullptr || nit != DataNodes.end(); element = element->NextSiblingElement("node"), ++nit) { mitk::DataNode::Pointer node = *nit; // in case dataXmlElement is valid test whether it containts the "properties" child tag // and process further if and only if yes auto *dataXmlElement = element->FirstChildElement("data"); if (dataXmlElement && dataXmlElement->FirstChildElement("properties")) { auto *baseDataElement = dataXmlElement->FirstChildElement("properties"); if (node->GetData()) { DecorateBaseDataWithProperties(node->GetData(), baseDataElement, workingDirectory); ApplyProportionalTimeGeometryProperties(node->GetData()); } else { MITK_WARN << "BaseData properties stored in scene file, but BaseData could not be read" << std::endl; } } // 2. check child nodes const char *uida = element->Attribute("UID"); std::string uid(""); if (uida) { uid = uida; m_NodeForID[uid] = node.GetPointer(); m_IDForNode[node.GetPointer()] = uid; } else { MITK_ERROR << "No UID found for current node. Node will have no parents."; error = true; } // 3. if there are nodes, // - instantiate the appropriate PropertyListDeSerializer // - use them to construct PropertyList objects // - add these properties to the node (if necessary, use renderwindow name) bool success = DecorateNodeWithProperties(node, element, workingDirectory); if (!success) { MITK_ERROR << "Could not load properties for node."; error = true; } // remember node for later adding to DataStorage m_OrderedNodePairs.push_back(std::make_pair(node, std::list())); // 4. if there are elements, remember parent objects for (auto *source = element->FirstChildElement("source"); source != nullptr; source = source->NextSiblingElement("source")) { const char *sourceUID = source->Attribute("UID"); if (sourceUID) { m_OrderedNodePairs.back().second.push_back(std::string(sourceUID)); } } ProgressBar::GetInstance()->Progress(); } // end for all // sort our nodes by their "layer" property // (to be inserted in that order) m_OrderedNodePairs.sort(&NodeSortByLayerIsLessThan); // remove all unknown parent UIDs for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end();) { if (m_NodeForID.find(*parentsIter) == m_NodeForID.end()) { parentsIter = nodesIter->second.erase(parentsIter); MITK_WARN << "Found a DataNode with unknown parents. Will add it to DataStorage without any parent objects."; error = true; } else { ++parentsIter; } } } // repeat the following loop ... // ... for all created nodes unsigned int lastMapSize(0); while (lastMapSize != m_OrderedNodePairs .size()) // this is to prevent infinite loops; each iteration must at least add one node to DataStorage { lastMapSize = m_OrderedNodePairs.size(); // iterate (layer) ordered nodes backwards // we insert the highest layers first for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { bool addThisNode(true); // if any parent node is not yet in DataStorage, skip node for now and check later for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { if (!storage->Exists(m_NodeForID[*parentsIter])) { addThisNode = false; break; } } if (addThisNode) { DataStorage::SetOfObjects::Pointer parents = DataStorage::SetOfObjects::New(); for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { parents->push_back(m_NodeForID[*parentsIter]); } // if all parents are found in datastorage (or are unknown), add node to DataStorage storage->Add(nodesIter->first, parents); // remove this node from m_OrderedNodePairs m_OrderedNodePairs.erase(nodesIter); // break this for loop because iterators are probably invalid break; } } } // All nodes that are still in m_OrderedNodePairs at this point are not part of a proper directed graph structure. // We'll add such nodes without any parent information. for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { storage->Add(nodesIter->first); MITK_WARN << "Encountered node that is not part of a directed graph structure. Will be added to DataStorage " "without parents."; error = true; } return !error; } mitk::DataNode::Pointer mitk::SceneReaderV1::LoadBaseDataFromDataTag(const tinyxml2::XMLElement *dataElement, const std::string &workingDirectory, bool &error) { DataNode::Pointer node; if (dataElement) { const char *filename = dataElement->Attribute("file"); if (filename && strlen(filename) != 0) { try { std::vector baseData = IOUtil::Load(workingDirectory + Poco::Path::separator() + filename); if (baseData.size() > 1) { MITK_WARN << "Discarding multiple base data results from " << filename << " except the first one."; } node = DataNode::New(); node->SetData(baseData.front()); } catch (std::exception &e) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Exception says: " << e.what(); error = true; } if (node.IsNull()) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Factory returned nullptr object."; error = true; } } const char* dataUID = dataElement->Attribute("UID"); if (!error && dataUID != nullptr) { UIDManipulator manip(node->GetData()); manip.SetUID(dataUID); } } // in case there was no element we create a new empty node (for appending a propertylist later) if (node.IsNull()) { node = DataNode::New(); } return node; } void mitk::SceneReaderV1::ClearNodePropertyListWithExceptions(DataNode &node, PropertyList &propertyList) { // Basically call propertyList.Clear(), but implement exceptions (see bug 19354) BaseData *data = node.GetData(); PropertyList::Pointer propertiesToKeep = PropertyList::New(); if (dynamic_cast(data)) { /* Older scene files (before changes of bug 17547) could contain a RenderingMode property with value "LevelWindow_Color". Since bug 17547 this value has been removed and replaced by the default value LookupTable_LevelWindow_Color. This new default value does only result in "black-to-white" CT images (or others) if there is a corresponding lookup table. Such a lookup table is provided as a default value by the Image mapper. Since that value was never present in older scene files, we do well in not removing the new default value here. Otherwise the mapper would fall back to another default which is all the colors of the rainbow :-( */ BaseProperty::Pointer lutProperty = propertyList.GetProperty("LookupTable"); propertiesToKeep->SetProperty("LookupTable", lutProperty); /* Older scene files (before changes of T14807) may contain multi-component images without the "Image.Displayed Component" property. As the treatment as multi-component image and the corresponding visualization options hinges on that property we should not delete it, if it was added by the mapper. This is a fix for the issue reported in T19919. */ BaseProperty::Pointer compProperty = propertyList.GetProperty("Image.Displayed Component"); if (compProperty.IsNotNull()) { propertiesToKeep->SetProperty("Image.Displayed Component", compProperty); } } propertyList.Clear(); propertyList.ConcatenatePropertyList(propertiesToKeep); } bool mitk::SceneReaderV1::DecorateNodeWithProperties(DataNode *node, const tinyxml2::XMLElement *nodeElement, const std::string &workingDirectory) { assert(node); assert(nodeElement); bool error(false); for (auto *properties = nodeElement->FirstChildElement("properties"); properties != nullptr; properties = properties->NextSiblingElement("properties")) { const char *propertiesfilea(properties->Attribute("file")); std::string propertiesfile(propertiesfilea ? propertiesfilea : ""); const char *renderwindowa(properties->Attribute("renderwindow")); std::string renderwindow(renderwindowa ? renderwindowa : ""); PropertyList::Pointer propertyList = node->GetPropertyList(renderwindow); // DataNode implementation always returns a propertylist ClearNodePropertyListWithExceptions(*node, *propertyList); // use deserializer to construct new properties PropertyListDeserializer::Pointer deserializer = PropertyListDeserializer::New(); deserializer->SetFilename(workingDirectory + Poco::Path::separator() + propertiesfile); bool success = deserializer->Deserialize(); error |= !success; PropertyList::Pointer readProperties = deserializer->GetOutput(); if (readProperties.IsNotNull()) { propertyList->ConcatenatePropertyList(readProperties, true); // true = replace } else { MITK_ERROR << "Property list reader did not return a property list. This is an implementation error. Please tell " "your developer."; error = true; } } return !error; } bool mitk::SceneReaderV1::DecorateBaseDataWithProperties(BaseData::Pointer data, const tinyxml2::XMLElement *baseDataNodeElem, const std::string &workingDir) { // check given variables, initialize error variable assert(baseDataNodeElem); bool error(false); // get the file name stored in the tag const char *baseDataPropertyFile(baseDataNodeElem->Attribute("file")); // check if the filename was found if (baseDataPropertyFile) { // PropertyList::Pointer dataPropList = data->GetPropertyList(); PropertyListDeserializer::Pointer propertyDeserializer = PropertyListDeserializer::New(); // initialize the property reader propertyDeserializer->SetFilename(workingDir + Poco::Path::separator() + baseDataPropertyFile); bool ioSuccess = propertyDeserializer->Deserialize(); error = !ioSuccess; // get the output PropertyList::Pointer inProperties = propertyDeserializer->GetOutput(); // store the read-in properties to the given node or throw error otherwise if (inProperties.IsNotNull()) { data->SetPropertyList(inProperties); } else { MITK_ERROR << "The property deserializer did not return a (valid) property list."; error = true; } } else { MITK_ERROR << "Function DecorateBaseDataWithProperties(...) called with false XML element. \n \t ->Given element " "does not contain a 'file' attribute. \n"; error = true; } return !error; } diff --git a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h index a2710f1d7b..19eca9289d 100644 --- a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h +++ b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h @@ -1,153 +1,151 @@ /*============================================================================ 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 mitkVectorPropertySerializer_h #define mitkVectorPropertySerializer_h #include "mitkBasePropertySerializer.h" #include "mitkVectorProperty.h" #include #include namespace mitk { /** \brief Serializes a VectorProperty Serializes an instance of VectorProperty into a XML structure like \verbatim \endverbatim This class is implemented as a template and makes use of std::stringstream for necessary conversions of specific data types to and from string. For numeric types, the class adds a precision token to stringstream that should usually suffice. */ template class MITKSCENESERIALIZATIONBASE_EXPORT VectorPropertySerializer : public BasePropertySerializer { public: // Expand manually most of mitkClassMacro: // mitkClassMacro(VectorProperty, mitk::BaseProperty); // This manual expansion is done to override explicitely // the GetNameOfClass methods typedef VectorProperty PropertyType; typedef VectorPropertySerializer Self; typedef BasePropertySerializer SuperClass; typedef itk::SmartPointer Pointer; typedef itk::SmartPointer ConstPointer; std::vector GetClassHierarchy() const override { return mitk::GetClassHierarchy(); } // This function must return different // strings in function of the template parameter! // Serialization depends on this feature. static const char *GetStaticNameOfClass() { // concatenate a prefix dependent on the template type and our own classname static std::string nameOfClass = std::string(VectorPropertyDataType::prefix()) + "VectorPropertySerializer"; return nameOfClass.c_str(); } const char *GetNameOfClass() const override { return this->GetStaticNameOfClass(); } itkFactorylessNewMacro(Self); itkCloneMacro(Self); //! Build an XML version of this property tinyxml2::XMLElement* Serialize(tinyxml2::XMLDocument& doc) override { auto *listElement = doc.NewElement("Values"); if (const PropertyType *prop = dynamic_cast(m_Property.GetPointer())) { typename PropertyType::VectorType elements = prop->GetValue(); unsigned int index(0); for (auto listEntry : elements) { std::stringstream indexS; indexS << index++; auto *entryElement = doc.NewElement("Value"); entryElement->SetAttribute("idx", indexS.str().c_str()); entryElement->SetAttribute("value", boost::lexical_cast(listEntry).c_str()); listElement->InsertEndChild(entryElement); } return listElement; } else { return nullptr; } } //! Construct a property from an XML serialization BaseProperty::Pointer Deserialize(const tinyxml2::XMLElement *listElement) override { typename PropertyType::VectorType datalist; if (listElement) { - MITK_DEBUG << "Deserializing " << *listElement; - unsigned int index(0); std::string valueString; DATATYPE value; for (auto *valueElement = listElement->FirstChildElement("Value"); valueElement; valueElement = valueElement->NextSiblingElement("Value")) { valueString = valueElement->Attribute("value"); if (valueString.empty()) { MITK_ERROR << "Missing value attribute in list"; return nullptr; } try { value = boost::lexical_cast(valueString); } catch (boost::bad_lexical_cast &e) { MITK_ERROR << "Could not parse '" << valueString << "' as number: " << e.what(); return nullptr; } datalist.push_back(value); ++index; } typename PropertyType::Pointer property = PropertyType::New(); property->SetValue(datalist); return property.GetPointer(); } else { MITK_ERROR << "Missing tag."; } return nullptr; } }; typedef VectorPropertySerializer DoubleVectorPropertySerializer; typedef VectorPropertySerializer IntVectorPropertySerializer; } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h index b3c2f482d5..e8780e6a56 100644 --- a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h +++ b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h @@ -1,156 +1,158 @@ /*============================================================================ 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 _mitkImageLiveWireContourModelFilter_h__ #define _mitkImageLiveWireContourModelFilter_h__ #include "mitkCommon.h" #include "mitkContourModel.h" #include "mitkContourModelSource.h" #include #include #include #include #include #include namespace mitk { /** \brief Calculates a LiveWire contour between two points in an image. For defining costs between two pixels specific features are extraced from the image and tranformed into a single cost value. \sa ShortestPathCostFunctionLiveWire The filter is able to create dynamic cost tranfer map and thus use on the fly training. \note On the fly training will only be used for next update. The computation uses the last calculated segment to map cost according to features in the area of the segment. Caution: time support currently not available. Filter will always work on the first timestep in its current implementation. \ingroup ContourModelFilters \ingroup Process */ class MITKSEGMENTATION_EXPORT ImageLiveWireContourModelFilter : public ContourModelSource { public: mitkClassMacro(ImageLiveWireContourModelFilter, ContourModelSource); itkFactorylessNewMacro(Self); itkCloneMacro(Self); typedef ContourModel OutputType; typedef OutputType::Pointer OutputTypePointer; typedef mitk::Image InputType; typedef itk::Image InternalImageType; typedef itk::ShortestPathImageFilter ShortestPathImageFilterType; typedef itk::ShortestPathCostFunctionLiveWire CostFunctionType; typedef std::vector> ShortestPathType; /** \brief start point in world coordinates*/ itkSetMacro(StartPoint, mitk::Point3D); itkGetMacro(StartPoint, mitk::Point3D); /** \brief end point in woorld coordinates*/ itkSetMacro(EndPoint, mitk::Point3D); itkGetMacro(EndPoint, mitk::Point3D); /** \brief Create dynamic cost tranfer map - use on the fly training. \note On the fly training will be used for next update only. The computation uses the last calculated segment to map cost according to features in the area of the segment. */ itkSetMacro(UseDynamicCostMap, bool); itkGetMacro(UseDynamicCostMap, bool); /** \brief Clear all repulsive points used in the cost function */ void ClearRepulsivePoints(); /** \brief Set a vector with repulsive points to use in the cost function */ void SetRepulsivePoints(const ShortestPathType &points); /** \brief Add a single repulsive point to the cost function */ void AddRepulsivePoint(const itk::Index<2> &idx); /** \brief Remove a single repulsive point from the cost function */ void RemoveRepulsivePoint(const itk::Index<2> &idx); virtual void SetInput(const InputType *input); using Superclass::SetInput; virtual void SetInput(unsigned int idx, const InputType *input); const InputType *GetInput(void); const InputType *GetInput(unsigned int idx); virtual OutputType *GetOutput(); virtual void DumpMaskImage(); /** \brief Create dynamic cost tranfer map - on the fly training*/ bool CreateDynamicCostMap(mitk::ContourModel *path = nullptr); + void SetUseCostFunction(bool doUseCostFunction) { m_ShortestPathFilter->SetUseCostFunction(doUseCostFunction); }; + protected: ImageLiveWireContourModelFilter(); ~ImageLiveWireContourModelFilter() override; void GenerateOutputInformation() override{}; void GenerateData() override; void UpdateLiveWire(); /** \brief start point in worldcoordinates*/ mitk::Point3D m_StartPoint; /** \brief end point in woorldcoordinates*/ mitk::Point3D m_EndPoint; /** \brief Start point in index*/ mitk::Point3D m_StartPointInIndex; /** \brief End point in index*/ mitk::Point3D m_EndPointInIndex; /** \brief The cost function to compute costs between two pixels*/ CostFunctionType::Pointer m_CostFunction; /** \brief Shortest path filter according to cost function m_CostFunction*/ ShortestPathImageFilterType::Pointer m_ShortestPathFilter; /** \brief Flag to use a dynmic cost map or not*/ bool m_UseDynamicCostMap; unsigned int m_TimeStep; template void ItkPreProcessImage(const itk::Image *inputImage); template void CreateDynamicCostMapByITK(const itk::Image *inputImage, mitk::ContourModel *path = nullptr); InternalImageType::Pointer m_InternalImage; }; } #endif diff --git a/Modules/Segmentation/Algorithms/mitkManualSegmentationToSurfaceFilter.cpp b/Modules/Segmentation/Algorithms/mitkManualSegmentationToSurfaceFilter.cpp index 3f42d39014..6c4ba80f85 100644 --- a/Modules/Segmentation/Algorithms/mitkManualSegmentationToSurfaceFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkManualSegmentationToSurfaceFilter.cpp @@ -1,164 +1,184 @@ /*============================================================================ 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 "mitkProgressBar.h" mitk::ManualSegmentationToSurfaceFilter::ManualSegmentationToSurfaceFilter() { m_MedianFilter3D = false; m_MedianKernelSizeX = 3; m_MedianKernelSizeY = 3; m_MedianKernelSizeZ = 3; m_UseGaussianImageSmooth = false; m_GaussianStandardDeviation = 1.5; m_Interpolation = false; m_InterpolationX = 1.0f; m_InterpolationY = 1.0f; m_InterpolationZ = 1.0f; }; mitk::ManualSegmentationToSurfaceFilter::~ManualSegmentationToSurfaceFilter(){}; void mitk::ManualSegmentationToSurfaceFilter::GenerateData() { mitk::Surface *surface = this->GetOutput(); auto *image = (mitk::Image *)GetInput(); mitk::Image::RegionType outputRegion = image->GetRequestedRegion(); int tstart = outputRegion.GetIndex(3); int tmax = tstart + outputRegion.GetSize(3); // GetSize()==1 - will aber 0 haben, wenn nicht zeitaufgeloest ScalarType thresholdExpanded = this->m_Threshold; if ((tmax - tstart) > 0) { ProgressBar::GetInstance()->AddStepsToDo(4 * (tmax - tstart)); } else { ProgressBar::GetInstance()->AddStepsToDo(4); } for (int t = tstart; t < tmax; ++t) { vtkSmartPointer vtkimage = image->GetVtkImageData(t); + // If the image has a single slice, pad it with an empty slice to explicitly make it + // recognizable as 3-d by VTK. Otherwise, the vtkMarchingCubes filter will + // complain about dimensionality and won't produce any output. + if (2 == vtkimage->GetDataDimension()) + { + std::array extent; + vtkimage->GetExtent(extent.data()); + extent[5] = 1; + + auto padFilter = vtkSmartPointer::New(); + padFilter->SetInputData(vtkimage); + padFilter->SetOutputWholeExtent(extent.data()); + padFilter->UpdateInformation(); + padFilter->Update(); + vtkimage = padFilter->GetOutput(); + } + // Median -->smooth 3D // MITK_INFO << (m_MedianFilter3D ? "Applying median..." : "No median filtering"); if (m_MedianFilter3D) { vtkImageMedian3D *median = vtkImageMedian3D::New(); median->SetInputData(vtkimage); // RC++ (VTK < 5.0) median->SetKernelSize(m_MedianKernelSizeX, m_MedianKernelSizeY, m_MedianKernelSizeZ); // Std: 3x3x3 median->ReleaseDataFlagOn(); median->UpdateInformation(); median->Update(); vtkimage = median->GetOutput(); //->Out median->Delete(); } ProgressBar::GetInstance()->Progress(); // Interpolate image spacing // MITK_INFO << (m_Interpolation ? "Resampling..." : "No resampling"); if (m_Interpolation) { vtkImageResample *imageresample = vtkImageResample::New(); imageresample->SetInputData(vtkimage); // Set Spacing Manual to 1mm in each direction (Original spacing is lost during image processing) imageresample->SetAxisOutputSpacing(0, m_InterpolationX); imageresample->SetAxisOutputSpacing(1, m_InterpolationY); imageresample->SetAxisOutputSpacing(2, m_InterpolationZ); imageresample->UpdateInformation(); imageresample->Update(); vtkimage = imageresample->GetOutput(); //->Output imageresample->Delete(); } ProgressBar::GetInstance()->Progress(); // MITK_INFO << (m_UseGaussianImageSmooth ? "Applying gaussian smoothing..." : "No gaussian smoothing"); if (m_UseGaussianImageSmooth) // gauss { vtkImageShiftScale *scalefilter = vtkImageShiftScale::New(); scalefilter->SetScale(100); scalefilter->SetInputData(vtkimage); scalefilter->Update(); vtkImageGaussianSmooth *gaussian = vtkImageGaussianSmooth::New(); gaussian->SetInputConnection(scalefilter->GetOutputPort()); gaussian->SetDimensionality(3); gaussian->SetRadiusFactor(0.49); gaussian->SetStandardDeviation(m_GaussianStandardDeviation); gaussian->ReleaseDataFlagOn(); gaussian->UpdateInformation(); gaussian->Update(); vtkimage = scalefilter->GetOutput(); double range[2]; vtkimage->GetScalarRange(range); if (range[1] != 0) // too little slices, image smoothing eliminates all segmentation pixels { vtkimage = gaussian->GetOutput(); //->Out } else { MITK_INFO << "Smoothing would remove all pixels of the segmentation. Use unsmoothed result instead."; } gaussian->Delete(); scalefilter->Delete(); } ProgressBar::GetInstance()->Progress(); // Create surface for t-Slice CreateSurface(t, vtkimage, surface, thresholdExpanded); ProgressBar::GetInstance()->Progress(); } // MITK_INFO << "Updating Time Geometry to ensure right timely displaying"; // Fixing wrong time geometry TimeGeometry *surfaceTG = surface->GetTimeGeometry(); auto *surfacePTG = dynamic_cast(surfaceTG); TimeGeometry *imageTG = image->GetTimeGeometry(); auto *imagePTG = dynamic_cast(imageTG); // Requires ProportionalTimeGeometries to work. May not be available for all steps. assert(surfacePTG != nullptr); assert(imagePTG != nullptr); if ((surfacePTG != nullptr) && (imagePTG != nullptr)) { TimePointType firstTime = imagePTG->GetFirstTimePoint(); TimePointType duration = imagePTG->GetStepDuration(); surfacePTG->SetFirstTimePoint(firstTime); surfacePTG->SetStepDuration(duration); // MITK_INFO << "First Time Point: " << firstTime << " Duration: " << duration; } }; void mitk::ManualSegmentationToSurfaceFilter::SetMedianKernelSize(int x, int y, int z) { m_MedianKernelSizeX = x; m_MedianKernelSizeY = y; m_MedianKernelSizeZ = z; } void mitk::ManualSegmentationToSurfaceFilter::SetInterpolation(vtkDouble x, vtkDouble y, vtkDouble z) { m_InterpolationX = x; m_InterpolationY = y; m_InterpolationZ = z; } diff --git a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp index c578a4e198..f64e713f13 100644 --- a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp +++ b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp @@ -1,316 +1,333 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkShowSegmentationAsSurface.h" #include "mitkManualSegmentationToSurfaceFilter.h" #include "mitkVtkRepresentationProperty.h" #include #include #include namespace mitk { ShowSegmentationAsSurface::ShowSegmentationAsSurface() : m_UIDGeneratorSurfaces("Surface_"), m_IsLabelSetImage(false) { } ShowSegmentationAsSurface::~ShowSegmentationAsSurface() { } void ShowSegmentationAsSurface::Initialize(const NonBlockingAlgorithm *other) { Superclass::Initialize(other); bool syncVisibility(false); if (other) { other->GetParameter("Sync visibility", syncVisibility); } SetParameter("Sync visibility", syncVisibility); SetParameter("Median kernel size", 3u); SetParameter("Apply median", true); SetParameter("Smooth", true); SetParameter("Gaussian SD", 1.5); SetParameter("Decimate mesh", true); SetParameter("Decimation rate", 0.8); SetParameter("Wireframe", false); m_SurfaceNodes.clear(); } bool ShowSegmentationAsSurface::ReadyToRun() { try { Image::Pointer image; GetPointerParameter("Input", image); return image.IsNotNull() && GetGroupNode(); } catch (std::invalid_argument &) { return false; } } bool ShowSegmentationAsSurface::ThreadedUpdateFunction() { Image::Pointer image; GetPointerParameter("Input", image); bool smooth(true); GetParameter("Smooth", smooth); bool applyMedian(true); GetParameter("Apply median", applyMedian); bool decimateMesh(true); GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize(3); GetParameter("Median kernel size", medianKernelSize); double gaussianSD(1.5); GetParameter("Gaussian SD", gaussianSD); double reductionRate(0.8); GetParameter("Decimation rate", reductionRate); MITK_INFO << "Creating polygon model with smoothing " << smooth << " gaussianSD " << gaussianSD << " median " << applyMedian << " median kernel " << medianKernelSize << " mesh reduction " << decimateMesh << " reductionRate " << reductionRate; auto labelSetImage = dynamic_cast(image.GetPointer()); if (nullptr != labelSetImage) { auto numberOfLayers = labelSetImage->GetNumberOfLayers(); for (decltype(numberOfLayers) layerIndex = 0; layerIndex < numberOfLayers; ++layerIndex) { auto labelSet = labelSetImage->GetLabelSet(layerIndex); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { if (0 == labelIter->first) continue; // Do not process background label auto labelImage = labelSetImage->CreateLabelMask(labelIter->first, false, layerIndex); if (labelImage.IsNull()) continue; auto labelSurface = this->ConvertBinaryImageToSurface(labelImage); if (labelSurface.IsNull()) continue; + auto* polyData = labelSurface->GetVtkPolyData(); + + if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) + { + MITK_WARN << "Label \"" << labelIter->second->GetName() << "\" didn't produce any smoothed surface data (try again without smoothing)."; + continue; + } + auto node = DataNode::New(); node->SetData(labelSurface); node->SetColor(labelIter->second->GetColor()); node->SetName(labelIter->second->GetName()); m_SurfaceNodes.push_back(node); } } } else { auto surface = this->ConvertBinaryImageToSurface(image); if (surface.IsNotNull()) { - auto node = DataNode::New(); - node->SetData(surface); - m_SurfaceNodes.push_back(node); + auto* polyData = surface->GetVtkPolyData(); + + if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) + { + MITK_WARN << "Could not produce smoothed surface data (try again without smoothing)."; + } + else + { + auto node = DataNode::New(); + node->SetData(surface); + m_SurfaceNodes.push_back(node); + } } } m_IsLabelSetImage = nullptr != labelSetImage; return true; } void ShowSegmentationAsSurface::ThreadedUpdateSuccessful() { for (const auto &node : m_SurfaceNodes) { bool wireframe = false; GetParameter("Wireframe", wireframe); if (wireframe) { auto representation = dynamic_cast(node->GetProperty("material.representation")); if (nullptr != representation) representation->SetRepresentationToWireframe(); } node->SetProperty("opacity", FloatProperty::New(0.3f)); node->SetProperty("line width", FloatProperty::New(1.0f)); node->SetProperty("scalar visibility", BoolProperty::New(false)); auto name = node->GetName(); auto groupNode = this->GetGroupNode(); if (!m_IsLabelSetImage) { if ((name.empty() || DataNode::NO_NAME_VALUE() == name) && nullptr != groupNode) name = groupNode->GetName(); if (name.empty()) name = "Surface"; } bool smooth = true; GetParameter("Smooth", smooth); if (smooth) name.append(" (smoothed)"); node->SetName(name); if (!m_IsLabelSetImage) { auto colorProp = groupNode->GetProperty("color"); if (nullptr != colorProp) { node->ReplaceProperty("color", colorProp->Clone()); } else { node->SetProperty("color", ColorProperty::New(1.0, 1.0, 0.0)); } } bool showResult = true; GetParameter("Show result", showResult); bool syncVisibility = false; GetParameter("Sync visibility", syncVisibility); auto visibleProp = groupNode->GetProperty("visible"); if (nullptr != visibleProp && syncVisibility) { node->ReplaceProperty("visible", visibleProp->Clone()); } else { node->SetProperty("visible", BoolProperty::New(showResult)); } if (!m_IsLabelSetImage) { Image::Pointer image; GetPointerParameter("Input", image); if (image.IsNotNull()) { auto organTypeProp = image->GetProperty("organ type"); if (nullptr != organTypeProp) node->GetData()->SetProperty("organ type", organTypeProp); } } this->InsertBelowGroupNode(node); } Superclass::ThreadedUpdateSuccessful(); } Surface::Pointer ShowSegmentationAsSurface::ConvertBinaryImageToSurface(Image::Pointer binaryImage) { bool smooth = true; GetParameter("Smooth", smooth); bool applyMedian = true; GetParameter("Apply median", applyMedian); bool decimateMesh = true; GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize = 3; GetParameter("Median kernel size", medianKernelSize); double gaussianSD = 1.5; GetParameter("Gaussian SD", gaussianSD); double reductionRate = 0.8; GetParameter("Decimation rate", reductionRate); auto filter = ManualSegmentationToSurfaceFilter::New(); filter->SetInput(binaryImage); filter->SetThreshold(0.5); filter->SetUseGaussianImageSmooth(smooth); filter->SetSmooth(smooth); filter->SetMedianFilter3D(applyMedian); if (smooth) { filter->InterpolationOn(); filter->SetGaussianStandardDeviation(gaussianSD); } if (applyMedian) filter->SetMedianKernelSize(medianKernelSize, medianKernelSize, medianKernelSize); // Fix to avoid VTK warnings (see T5390) if (binaryImage->GetDimension() > 3) decimateMesh = false; if (decimateMesh) { filter->SetDecimate(ImageToSurfaceFilter::QuadricDecimation); filter->SetTargetReduction(reductionRate); } else { filter->SetDecimate(ImageToSurfaceFilter::NoDecimation); } filter->UpdateLargestPossibleRegion(); auto surface = filter->GetOutput(); auto polyData = surface->GetVtkPolyData(); if (nullptr == polyData) throw std::logic_error("Could not create polygon model"); polyData->SetVerts(nullptr); polyData->SetLines(nullptr); if (smooth || applyMedian || decimateMesh) { auto normals = vtkSmartPointer::New(); normals->AutoOrientNormalsOn(); normals->FlipNormalsOff(); normals->SetInputData(polyData); normals->Update(); surface->SetVtkPolyData(normals->GetOutput()); } else { surface->SetVtkPolyData(polyData); } return surface; } } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp index 63c827db0c..881f688fc3 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp @@ -1,205 +1,209 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkAutoMLSegmentationWithPreviewTool.h" #include "mitkImageAccessByItk.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include // ITK #include #include mitk::AutoMLSegmentationWithPreviewTool::AutoMLSegmentationWithPreviewTool() : AutoSegmentationWithPreviewTool(true) { } void mitk::AutoMLSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& regions) { if (m_SelectedLabels != regions) { m_SelectedLabels = regions; //Note: we do not call this->Modified() on puprose. Reason: changing the //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. } } const mitk::LabelSetImage* mitk::AutoMLSegmentationWithPreviewTool::GetMLPreview() const { if (m_MLPreviewNode.IsNotNull()) { const auto mlPreviewImage = dynamic_cast(this->m_MLPreviewNode->GetData()); return mlPreviewImage; } return nullptr; } mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType mitk::AutoMLSegmentationWithPreviewTool::GetSelectedLabels() const { return this->m_SelectedLabels; } void mitk::AutoMLSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_SelectedLabels = {}; m_MLPreviewNode = mitk::DataNode::New(); m_MLPreviewNode->SetProperty("name", StringProperty::New(std::string(this->GetName()) + "ML preview")); m_MLPreviewNode->SetProperty("helper object", BoolProperty::New(true)); m_MLPreviewNode->SetVisibility(true); m_MLPreviewNode->SetOpacity(1.0); this->GetToolManager()->GetDataStorage()->Add(m_MLPreviewNode); } void mitk::AutoMLSegmentationWithPreviewTool::Deactivated() { this->GetToolManager()->GetDataStorage()->Remove(m_MLPreviewNode); m_MLPreviewNode = nullptr; Superclass::Deactivated(); } void mitk::AutoMLSegmentationWithPreviewTool::UpdateCleanUp() { if (m_MLPreviewNode.IsNotNull()) m_MLPreviewNode->SetVisibility(m_SelectedLabels.empty()); if (nullptr != this->GetPreviewSegmentationNode()) this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedLabels.empty()); if (m_SelectedLabels.empty()) { this->ResetPreviewNode(); } } -void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +void mitk::AutoMLSegmentationWithPreviewTool::SetNodeProperties(LabelSetImage::Pointer newMLPreview) { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (nullptr == m_MLPreviewNode->GetData() - || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() - || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic - //segmentations have to compute a preview - //for all time steps on confirmation - || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg - //previews work with dynamic images - //with avoiding unnecessary other computations - ) - { - if (nullptr == inputAtTimeStep) - { - MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; - return; - } - - this->m_LastMLTimeStep = timeStep; - - auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); - - if (newMLPreview.IsNotNull()) + if (newMLPreview.IsNotNull()) { this->m_MLPreviewNode->SetData(newMLPreview); this->m_MLPreviewNode->SetProperty("binary", mitk::BoolProperty::New(false)); mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); this->m_MLPreviewNode->SetProperty("Image Rendering.Mode", renderingMode); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetHueRange(1.0, 0.0); lookupTable->SetSaturationRange(1.0, 1.0); lookupTable->SetValueRange(1.0, 1.0); lookupTable->SetTableRange(-1.0, 1.0); lookupTable->Build(); lut->SetVtkLookupTable(lookupTable); prop->SetLookupTable(lut); this->m_MLPreviewNode->SetProperty("LookupTable", prop); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); mitk::LevelWindow levelwindow; levelwindow.SetRangeMinMax(0, newMLPreview->GetStatistics()->GetScalarValueMax()); levWinProp->SetLevelWindow(levelwindow); this->m_MLPreviewNode->SetProperty("levelwindow", levWinProp); } +} + +void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +{ + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + if (nullptr == m_MLPreviewNode->GetData() + || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() + || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic + //segmentations have to compute a preview + //for all time steps on confirmation + || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg + //previews work with dynamic images + //with avoiding unnecessary other computations + ) + { + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastMLTimeStep = timeStep; + + auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); + this->SetNodeProperties(newMLPreview); } if (!m_SelectedLabels.empty()) { const auto mlPreviewImage = this->GetMLPreview(); if (nullptr != mlPreviewImage) { AccessByItk_n(mlPreviewImage, CalculateMergedSimplePreview, (previewImage, timeStep)); } } } template void mitk::AutoMLSegmentationWithPreviewTool::CalculateMergedSimplePreview(const itk::Image* itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; typename OutputImageType::Pointer itkBinaryResultImage; filter->SetInput(itkImage); filter->SetLowerThreshold(m_SelectedLabels[0]); filter->SetUpperThreshold(m_SelectedLabels[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); itkBinaryResultImage = filter->GetOutput(); itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions for (const auto labelID : m_SelectedLabels) { if (labelID != m_SelectedLabels[0]) { filter->SetLowerThreshold(labelID); filter->SetUpperThreshold(labelID); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); typename OutputImageType::Pointer tempImage = filter->GetOutput(); typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(tempImage); orFilter->SetInput2(itkBinaryResultImage); orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); orFilter->UpdateLargestPossibleRegion(); itkBinaryResultImage = orFilter->GetOutput(); } } //---------------------------------------------------------------------------------------------------- segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h index 878fd5592c..60921765c6 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h @@ -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. ============================================================================*/ #ifndef MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #define MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkLabelSetImage.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation and generates segmentations with multiple labels. This tool class implements the basic logic to handle previews of multi label segmentations and to allow to pick arbitrary labels as selected and merge them to a single segmentation to store this segmentation as confirmed segmentation. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoMLSegmentationWithPreviewTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(AutoMLSegmentationWithPreviewTool, AutoSegmentationWithPreviewTool); void Activated() override; void Deactivated() override; using SelectedLabelVectorType = std::vector; void SetSelectedLabels(const SelectedLabelVectorType& regions); SelectedLabelVectorType GetSelectedLabels() const; const LabelSetImage* GetMLPreview() const; protected: AutoMLSegmentationWithPreviewTool(); ~AutoMLSegmentationWithPreviewTool() = default; void UpdateCleanUp() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - + virtual void SetNodeProperties(LabelSetImage::Pointer); /** Function to generate the new multi lable preview for a given time step input image. * The function must be implemented by derived tools. * This function is called by DoUpdatePreview if needed. * Reasons are: * - ML preview does not exist * - Modify time of tools is newer then of ML preview * - ML preview was not generated for the current selected timestep of input image or for the current selected timepoint.*/ virtual LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) = 0; private: /** Function to generate a simple (single lable) preview by merging all labels of the ML preview that are selected and * copies that single label preview to the passed previewImage. * This function is called by DoUpdatePreview if needed. * @param mlPreviewImage Multi label preview that is the source. * @param previewImage Pointer to the single label preview image that should receive the merged selected labels. * @timeStep Time step of the previewImage that should be filled.*/ template void CalculateMergedSimplePreview(const itk::Image* mlImage, mitk::Image* segmentation, unsigned int timeStep); SelectedLabelVectorType m_SelectedLabels = {}; // holds the multilabel result as a preview image mitk::DataNode::Pointer m_MLPreviewNode; TimeStepType m_LastMLTimeStep = 0; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp index e19770ed6a..10f5b55451 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -1,549 +1,551 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : AutoSegmentationTool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() { } bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } void mitk::AutoSegmentationWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() { if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() { itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { mitk::LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... - newPreviewImage->GetActiveLabel()->SetColor(previewColor); - newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); + auto* activeLayer = newPreviewImage->GetActiveLabelSet(); + auto* activeLabel = activeLayer->GetActiveLabel(); + activeLabel->SetColor(previewColor); + activeLayer->UpdateLookupTable(activeLabel->GetValue()); } else { mitk::Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { mitk::Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = mitk::Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } template static void ITKSetVolume(const itk::Image *originalImage, mitk::Image *segmentation, unsigned int timeStep) { auto constPixelContainer = originalImage->GetPixelContainer(); //have to make a const cast because itk::PixelContainer does not provide a const correct access :( auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); } void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume if (sourceImageAtTimeStep->GetDimension() == 2) { AccessFixedDimensionByItk_2( sourceImageAtTimeStep, ITKSetVolume, 2, destinationImage, timeStep); } else { AccessFixedDimensionByItk_2( sourceImageAtTimeStep, ITKSetVolume, 3, destinationImage, timeStep); } } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } if (m_OverwriteExistingSegmentation) { //if we overwrite the segmentation (and not just store it as a new result //in the data storage) we update also the tool manager state. this->GetToolManager()->SetWorkingData(resultSegmentationNode); this->GetToolManager()->GetWorkingData(0)->Modified(); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() { mitk::DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback image. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback image feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp index e2e0e95b68..27e02d9986 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -1,485 +1,426 @@ /*============================================================================ 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 "mitkContourModelLiveWireInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include "mitkIOUtil.h" mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor() : ContourModelInteractor() { m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); - + m_LiveWireFilter->SetUseCostFunction(true); m_NextActiveVertexDown.Fill(0); m_NextActiveVertexUp.Fill(0); } mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() { } void mitk::ContourModelLiveWireInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); CONNECT_FUNCTION("finish", OnFinishEditing); } bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour"; return false; } contour->Deselect(); // Check distance to any vertex. // Transition YES if click close to a vertex mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timeStep)) - { - contour->SetSelectedVertexAsControlPoint(false); - m_ContourLeft = mitk::ContourModel::New(); - // get coordinates of next active vertex downwards from selected vertex - int downIndex = this->SplitContourFromSelectedVertex(contour, m_ContourLeft, false, timeStep); - - m_NextActiveVertexDownIter = contour->IteratorBegin() + downIndex; - m_NextActiveVertexDown = (*m_NextActiveVertexDownIter)->Coordinates; - - m_ContourRight = mitk::ContourModel::New(); - - // get coordinates of next active vertex upwards from selected vertex - int upIndex = this->SplitContourFromSelectedVertex(contour, m_ContourRight, true, timeStep); - - m_NextActiveVertexUpIter = contour->IteratorBegin() + upIndex; - m_NextActiveVertexUp = (*m_NextActiveVertexUpIter)->Coordinates; + bool isVertexSelected = false; + // Check, if clicked position is close to control vertex and if so, select closest control vertex. + isVertexSelected = contour->SelectControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - // clear previous void positions - this->m_LiveWireFilter->ClearRepulsivePoints(); + // If the position is not close to control vertex. but hovering the contour line, we check, if it is close to non-control vertex. + // The closest vertex will be set as a control vertex. + if (isVertexSelected == false) + isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); - // set the current contour as void positions in the cost map - // start with down side - auto iter = contour->IteratorBegin(timeStep); - for (; iter != m_NextActiveVertexDownIter; iter++) + // If the position is not close to control or non-control vertex. but hovering the contour line, we create a vertex at the position. + if (isVertexSelected == false) + { + bool isHover = false; + if (this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()) == false) { - itk::Index<2> idx; - this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); - this->m_LiveWireFilter->AddRepulsivePoint(idx); + MITK_WARN << "Unknown property contour.hovering"; } - - // continue with upper side - iter = m_NextActiveVertexUpIter + 1; - for (; iter != contour->IteratorEnd(timeStep); iter++) + if (isHover) { - itk::Index<2> idx; - this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); - this->m_LiveWireFilter->AddRepulsivePoint(idx); + contour->AddVertex(click, timeStep); + isVertexSelected = contour->SelectVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); } + } + + if (isVertexSelected) + { + contour->SetSelectedVertexAsControlPoint(true); + auto controlVertices = contour->GetControlVertices(timeStep); + const mitk::ContourModel::VertexType *nextPoint = contour->GetNextControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + const mitk::ContourModel::VertexType *previousPoint = contour->GetPreviousControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); + this->SplitContourFromSelectedVertex(contour, nextPoint, previousPoint, timeStep); + m_NextActiveVertexUp = nextPoint->Coordinates; + m_NextActiveVertexDown = previousPoint->Coordinates; + + // clear previous void positions + this->m_LiveWireFilter->ClearRepulsivePoints(); + // all points in lower and upper part should be marked as repulsive points to not be changed + this->SetRepulsivePoints(previousPoint, m_ContourLeft, timeStep); + this->SetRepulsivePoints(nextPoint, m_ContourRight, timeStep); // clear container with void points between neighboring control points m_ContourBeingModified.clear(); - // let us have the selected point as a control point - contour->SetSelectedVertexAsControlPoint(true); - // finally, return true to pass this condition return true; } else { // do not pass condition return false; } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); return true; } void mitk::ContourModelLiveWireInteractor::SetEditingContourModelNode(mitk::DataNode *_arg) { if (this->m_EditingContourNode != _arg) { this->m_EditingContourNode = _arg; } } void mitk::ContourModelLiveWireInteractor::SetWorkingImage(mitk::Image *_arg) { if (this->m_WorkingSlice != _arg) { this->m_WorkingSlice = _arg; this->m_LiveWireFilter->SetInput(this->m_WorkingSlice); } } void mitk::ContourModelLiveWireInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour!"; return; } if (contour->GetSelectedVertex()) { mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); newContour->Concatenate(m_ContourLeft, timeStep); // recompute contour between neighbored two active control points this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(this->m_NextActiveVertexUp); - // this->m_LiveWireFilter->ClearRepulsivePoints(); this->m_LiveWireFilter->Update(); mitk::ContourModel *liveWireContour = this->m_LiveWireFilter->GetOutput(); assert(liveWireContour); if (liveWireContour->IsEmpty(timeStep)) return; liveWireContour->RemoveVertexAt(0, timeStep); liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timeStep) - 1, timeStep); // insert new live wire computed points newContour->Concatenate(liveWireContour, timeStep); // insert right side of original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); // instead of leaving a single point, delete all points if (newContour->GetNumberOfVertices(timeStep) <= 2) { newContour->Clear(timeStep); } this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } void mitk::ContourModelLiveWireInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "invalid contour"; return; } mitk::ContourModel::Pointer editingContour = mitk::ContourModel::New(); editingContour->Expand(contour->GetTimeSteps()); editingContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // recompute left live wire, i.e. the contour between previous active vertex and selected vertex this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(currentPosition); // remove void positions between previous active vertex and next active vertex. if (!m_ContourBeingModified.empty()) { std::vector>::const_iterator iter = m_ContourBeingModified.begin(); for (; iter != m_ContourBeingModified.end(); iter++) { this->m_LiveWireFilter->RemoveRepulsivePoint((*iter)); } } // update to get the left livewire. Remember that the points in the rest of the contour are already // set as void positions in the filter this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer leftLiveWire = this->m_LiveWireFilter->GetOutput(); assert(leftLiveWire); if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(leftLiveWire, timeStep); // the new index of the selected vertex unsigned int selectedVertexIndex = this->m_ContourLeft->GetNumberOfVertices(timeStep) + leftLiveWire->GetNumberOfVertices(timeStep) - 1; // at this point the container has to be empty m_ContourBeingModified.clear(); // add points from left live wire contour auto iter = leftLiveWire->IteratorBegin(timeStep); for (; iter != leftLiveWire->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); // add indices m_ContourBeingModified.push_back(idx); } // recompute right live wire, i.e. the contour between selected vertex and next active vertex this->m_LiveWireFilter->SetStartPoint(currentPosition); this->m_LiveWireFilter->SetEndPoint(m_NextActiveVertexUp); // update filter with all contour points set as void but the right live wire portion to be calculated now this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer rightLiveWire = this->m_LiveWireFilter->GetOutput(); assert(rightLiveWire); // reject strange paths if (abs(rightLiveWire->GetNumberOfVertices(timeStep) - leftLiveWire->GetNumberOfVertices(timeStep)) > 50) { return; } if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timeStep); if (!rightLiveWire->IsEmpty(timeStep)) rightLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(rightLiveWire, timeStep); m_EditingContourNode->SetData(editingContour); mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // concatenate left original contour newContour->Concatenate(this->m_ContourLeft, timeStep); newContour->Concatenate(editingContour, timeStep, true); // set last inserted vertex as selected newContour->SelectVertexAt(selectedVertexIndex, timeStep); // set as control point newContour->SetSelectedVertexAsControlPoint(true); // concatenate right original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } bool mitk::ContourModelLiveWireInteractor::IsHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, 1.5, timeStep)) + if (contour->IsNearContour(currentPosition, mitk::ContourModelLiveWireInteractor::eps, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } + return true; } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } return false; } -int mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, - mitk::ContourModel *destContour, - bool fromSelectedUpwards, - int timestep) +void mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, + const mitk::ContourModel::VertexType *nextPoint, + const mitk::ContourModel::VertexType *previousPoint, + int timeStep) { - auto end = srcContour->IteratorEnd(); - auto begin = srcContour->IteratorBegin(); + m_ContourLeft = mitk::ContourModel::New(); + m_ContourRight = mitk::ContourModel::New(); - // search next active control point to left and rigth and set as start and end point for filter - auto itSelected = begin; + auto it = srcContour->IteratorBegin(); + // part between nextPoint and end of Countour + bool upperPart = false; + // part between start of countour and previousPoint + bool lowerPart = true; - // move iterator to position - while ((*itSelected) != srcContour->GetSelectedVertex()) + // edge cases when point right before first control vertex is selected or first control vertex is selected + if (nextPoint == (*it) || srcContour->GetSelectedVertex() == (*it)) { - itSelected++; + upperPart = true; + lowerPart = false; + m_ContourLeft->AddVertex(previousPoint->Coordinates, previousPoint->IsControlPoint, timeStep); } - - // CASE search upwards for next control point - if (fromSelectedUpwards) + // if first control vertex is selected, move to next point before adding vertices to m_ContourRight + // otherwise, second line appears when moving the vertex + if (srcContour->GetSelectedVertex() == (*it)) { - auto itUp = itSelected; - - if (itUp != end) + while (*it != nextPoint) { - itUp++; // step once up otherwise the loop breaks immediately + it++; } + } - while (itUp != end && !((*itUp)->IsControlPoint)) + for (; it != srcContour->IteratorEnd(timeStep); it++) + { + // everything in lower part should be added to m_CountoutLeft + if (lowerPart) { - itUp++; + m_ContourLeft->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } - - auto it = itUp; - - if (itSelected != begin) + // start of "restricted area" where no vertex should be added to m_CountoutLeft or m_CountoutRight + if (*it == previousPoint) { - // copy the rest of the original contour - while (it != end) - { - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } + lowerPart = false; + upperPart = false; } - // else do not copy the contour - - // return the offset of iterator at one before next-vertex-upwards - if (itUp != begin) + // start of upperPart + if (*it == nextPoint) { - return std::distance(begin, itUp) - 1; + upperPart = true; } - else + // everything in upper part should be added to m_CountoutRight + if (upperPart) { - return std::distance(begin, itUp); + m_ContourRight->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } } - else // CASE search downwards for next control point - { - auto itDown = itSelected; - auto it = srcContour->IteratorBegin(); +} - if (itSelected != begin) - { - if (itDown != begin) - { - itDown--; // step once down otherwise the the loop breaks immediately - } - - while (itDown != begin && !((*itDown)->IsControlPoint)) - { - itDown--; - } - - if (it != end) // if not empty - { - // always add the first vertex - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - // copy from begin to itDown - while (it <= itDown) - { - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - } - else - { - // if selected vertex is the first element search from end of contour downwards - itDown = end; - itDown--; - while (!((*itDown)->IsControlPoint) && itDown != begin) - { - itDown--; - } - - // move one forward as we don't want the first control point - it++; - // move iterator to second control point - while ((it != end) && !((*it)->IsControlPoint)) - { - it++; - } - // copy from begin to itDown - while (it <= itDown) - { - // copy the contour from second control point to itDown - destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); - it++; - } - } - /* - //add vertex at itDown - it's not considered during while loop - if( it != begin && it != end) - { - //destContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); - } - */ - // return the offset of iterator at one after next-vertex-downwards - if (itDown != end) - { - return std::distance(begin, itDown); // + 1;//index of next vertex - } - else +void mitk::ContourModelLiveWireInteractor::SetRepulsivePoints(const mitk::ContourModel::VertexType *pointToExclude, + mitk::ContourModel *contour, + int timeStep) +{ + auto it = contour->IteratorBegin(); + for (; it != contour->IteratorEnd(timeStep); it++) + { + if (*it != pointToExclude) { - return std::distance(begin, itDown) - 1; + itk::Index<2> idx; + this->m_WorkingSlice->GetGeometry()->WorldToIndex((*it)->Coordinates, idx); + this->m_LiveWireFilter->AddRepulsivePoint(idx); } } } void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *editingContour = dynamic_cast(this->m_EditingContourNode->GetData()); editingContour->Clear(timeStep); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h index 5d437fa213..6352775048 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h @@ -1,84 +1,95 @@ /*============================================================================ 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 mitkContourModelLiveWireInteractor_h_Included #define mitkContourModelLiveWireInteractor_h_Included #include "mitkCommon.h" #include "mitkContourModelInteractor.h" #include #include namespace mitk { /** \brief \sa Interactor \sa ContourModelInteractor \ingroup Interaction \warning Make sure the working image is properly set, otherwise the algorithm for computing livewire contour segments will not work! */ class MITKSEGMENTATION_EXPORT ContourModelLiveWireInteractor : public ContourModelInteractor { public: mitkClassMacro(ContourModelLiveWireInteractor, ContourModelInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); virtual void SetEditingContourModelNode(mitk::DataNode *_arg); virtual void SetWorkingImage(mitk::Image *_arg); void ConnectActionsAndFunctions() override; protected: ContourModelLiveWireInteractor(); ~ContourModelLiveWireInteractor() override; + /// \brief Select/ add and select vertex to modify contour and prepare for modification of contour. bool OnCheckPointClick(const InteractionEvent *interactionEvent) override; + /// \brief Check if mouse is hovering over contour bool IsHovering(const InteractionEvent *interactionEvent) override; + /// \brief Update contour when point is moved. void OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) override; + /// \brief Delete selected vertex and recompute contour. void OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) override; + /// \brief Finish modification of contour. void OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) override; - int SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, - mitk::ContourModel *destContour, - bool fromSelectedUpwards, - int timestep); - + /// \brief Split contour into a part before the selected vertex and after the selected vertex + void SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, + const mitk::ContourModel::VertexType *nextPoint, + const mitk::ContourModel::VertexType *previousPoint, + int timestep); + /// \brief Set repulsive points which should not be changed during editing of the contour. + void SetRepulsivePoints(const mitk::ContourModel::VertexType *nextPoint, + mitk::ContourModel *contour, + int timestep); + + const float eps = 3.0; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; mitk::Image::Pointer m_WorkingSlice; mitk::Point3D m_NextActiveVertexDown; mitk::Point3D m_NextActiveVertexUp; mitk::ContourModel::VertexIterator m_NextActiveVertexDownIter; mitk::ContourModel::VertexIterator m_NextActiveVertexUpIter; std::vector> m_ContourBeingModified; mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModel::Pointer m_ContourLeft; mitk::ContourModel::Pointer m_ContourRight; }; } // namespace mitk #endif // mitkContourModelLiveWireInteractor_h_Included diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index 49c56b305e..e39f44b64e 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,620 +1,740 @@ /*============================================================================ 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 { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() - : SegTool2D("LiveWireTool"), m_CreateAndUseDynamicCosts(false) + : SegTool2D("LiveWireTool"), m_SnapClosureContour(false), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::RemoveHelperObjects() { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_LiveWireContourNode.IsNotNull()) dataStorage->Remove(m_LiveWireContourNode); + if (m_ClosureContourNode.IsNotNull()) + dataStorage->Remove(m_ClosureContourNode); + if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::LiveWireTool2D::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; + m_ClosureContourNode = nullptr; + m_ClosureContour = nullptr; + m_ContourNode = nullptr; m_Contour = nullptr; } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourLiveWireInteraction(false); m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { - CONNECT_CONDITION("CheckContourClosed", OnCheckPoint); - CONNECT_FUNCTION("InitObject", OnInitLiveWire); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourLiveWireInteraction(true); } void mitk::LiveWireTool2D::Deactivated() { this->ConfirmSegmentation(); Superclass::Deactivated(); } void mitk::LiveWireTool2D::UpdateLiveWireContour() { if (m_Contour.IsNotNull()) { auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); - m_LiveWireContour->SetTimeGeometry(timeGeometry); //needed because the results of the filter are always from 0 ms to 1 ms and the filter also resets its outputs. + m_LiveWireContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms + // to 1 ms and the filter also resets its outputs. m_LiveWireContourNode->SetData(this->m_LiveWireContour); + + if (m_SnapClosureContour) + { + m_ClosureContour = this->m_LiveWireFilterClosure->GetOutput(); + } + + m_ClosureContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms + // to 1 ms and the filter also resets its outputs. + m_ClosureContourNode->SetData(this->m_ClosureContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_LiveWireContourNode.IsNull()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); + m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); + + m_LiveWireFilterClosure->Update(); + this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::ConfirmSegmentation() { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); if (nullptr == contour || contour->IsEmpty()) continue; auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); if (finding == sliceInfos.end()) { auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); finding = std::prev(sliceInfos.end()); } //cast const away is OK in this case, because these are all slices created and manipulated //localy in this function call. And we want to keep the high constness of SliceInformation for //public interfaces. auto workingSlice = const_cast(finding->slice.GetPointer()); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); ContourModelUtils::FillContourInSlice( projectedContour, workingSlice, workingImage, activePixelValue); } this->WriteBackSegmentationResults(sliceInfos); } this->ClearSegmentation(); } void mitk::LiveWireTool2D::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } +void mitk::LiveWireTool2D::SetSnapClosureContour(bool snap) +{ + m_SnapClosureContour = snap; + if (!m_LiveWireContour || !m_Contour) + { + return; + } + + if (m_LiveWireContour->GetNumberOfVertices() > 0) + { + UpdateClosureContour(m_LiveWireContour->GetVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1)->Coordinates); + } + else if (m_Contour->GetNumberOfVertices() > 0) + { + UpdateClosureContour(m_Contour->GetVertexAt(m_Contour->GetNumberOfVertices() - 1)->Coordinates); + } + + this->UpdateLiveWireContour(); +} + bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, mitk::BaseData *data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } mitk::ContourModel::Pointer mitk::LiveWireTool2D::CreateNewContour() const { auto workingData = this->GetWorkingData(); if (nullptr == workingData) { this->InteractiveSegmentationBugMessage("Cannot create new contour. No valid working data is set. Application is in invalid state."); mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; } auto contour = ContourModel::New(); //generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); contour->SetTimeGeometry(contourTimeGeometry); return contour; } void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_Contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(m_Contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode = mitk::DataNode::New(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_LiveWireContourNode->SetName("active livewire node"); m_LiveWireContourNode->SetProperty("layer", IntProperty::New(101)); m_LiveWireContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_LiveWireContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_LiveWireContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_LiveWireContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); + m_ClosureContour = this->CreateNewContour(); + m_ClosureContourNode = mitk::DataNode::New(); + m_ClosureContourNode->SetData(m_ClosureContour); + m_ClosureContourNode->SetName("active closure node"); + m_ClosureContourNode->SetProperty("layer", IntProperty::New(101)); + m_ClosureContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); + m_ClosureContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + m_ClosureContourNode->AddProperty("contour.color", ColorProperty::New(0.0f, 1.0f, 0.1f), nullptr, true); + m_ClosureContourNode->AddProperty("contour.width", mitk::FloatProperty::New(2.0f), nullptr, true); + m_EditingContour = this->CreateNewContour(); m_EditingContourNode = mitk::DataNode::New(); m_EditingContourNode->SetData(m_EditingContour); m_EditingContourNode->SetName("editing node"); m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_LiveWireContourNode, workingDataNode); + dataStorage->Add(m_ClosureContourNode, workingDataNode); dataStorage->Add(m_EditingContourNode, workingDataNode); // Set current slice as input for ImageToLiveWireContourFilter m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); + m_LiveWireFilter->SetUseCostFunction(true); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); + m_LiveWireFilterClosure = ImageLiveWireContourModelFilter::New(); + m_LiveWireFilterClosure->SetUseCostFunction(true); + m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); + // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); click[0] = indexWithHighestGradient[0]; click[1] = indexWithHighestGradient[1]; click[2] = indexWithHighestGradient[2]; m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); // Set initial start point m_Contour->AddVertex(click, true); m_LiveWireFilter->SetStartPoint(click); + //m_LiveWireFilterClosure->SetStartPoint(click); + m_LiveWireFilterClosure->SetEndPoint(click); + if (!m_SnapClosureContour) + { + m_ClosureContour->AddVertex(click); + } // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // Complete LiveWire interaction for the last segment. Add current LiveWire contour to // the finished contour and reset to start a new segment and computation. auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } // Add repulsive points to avoid getting the same path again std::for_each(m_LiveWireContour->IteratorBegin(), m_LiveWireContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); + this->m_LiveWireFilterClosure->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour m_LiveWireContour->RemoveVertexAt(0); // Set last point as control point m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); // Merge contours m_Contour->Concatenate(m_LiveWireContour); // Clear the LiveWire contour and reset the corresponding DataNode m_LiveWireContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); + m_LiveWireFilterClosure->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_Contour); m_LiveWireFilter->SetUseDynamicCostMap(true); + + m_LiveWireFilterClosure->CreateDynamicCostMap(m_Contour); + m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Compute LiveWire segment from last control point to current mouse position auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); + UpdateClosureContour(positionEvent->GetPositionInWorld()); + this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } +void mitk::LiveWireTool2D::UpdateClosureContour(mitk::Point3D endpoint) +{ + if (m_SnapClosureContour) + { + m_LiveWireFilterClosure->SetStartPoint(endpoint); + m_LiveWireFilterClosure->Update(); + } + else + { + if (m_ClosureContour->GetNumberOfVertices() > 2) + { + m_ClosureContour = this->CreateNewContour(); + } + + if (m_ClosureContour->GetNumberOfVertices() == 0) + { + m_ClosureContour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates); + m_ClosureContour->Update(); + } + + if (m_ClosureContour->GetNumberOfVertices() == 2) + { + m_ClosureContour->RemoveVertexAt(0); + } + + m_ClosureContour->AddVertexAtFront(endpoint); + } +} + void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); + m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); + m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; mitk::Point3D click = positionEvent->GetPositionInWorld(); mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction + m_Contour->Concatenate(m_ClosureContour); auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; - // Remove last control point added by double click - m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); + if (m_PlaneGeometry.IsNotNull()) + { + // Check if the point is in the correct slice + if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) + return; + } + + //m_Contour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates, false); + // Remove last control point added by double click, if double click was performed on first point + //if (OnCheckPoint(interactionEvent)) + // m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); + + // remove green connection between mouse position and start point + m_ClosureContour->Clear(); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); m_LiveWireFilter->SetUseDynamicCostMap(false); + m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); this->GetToolManager()->GetDataStorage()->Remove(m_LiveWireContourNode); m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); m_ContourInteractor->SetEditingContourModelNode(this->m_EditingContourNode); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); this->m_LiveWireInteractors.push_back(m_ContourInteractor); } void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) { // If last point of current contour will be removed go to start state and remove nodes if (m_Contour->GetNumberOfVertices() <= 1) { auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Remove(m_LiveWireContourNode); dataStorage->Remove(m_ContourNode); dataStorage->Remove(m_EditingContourNode); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_Contour = this->CreateNewContour(); m_ContourNode->SetData(m_Contour); this->ResetToStartState(); } else // Remove last segment from contour and reset LiveWire contour { m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); auto newContour = this->CreateNewContour(); auto begin = m_Contour->IteratorBegin(); // Iterate from last point to next active point auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); // Go at least one down if (newLast != begin) --newLast; // Search next active control point while (newLast != begin && !((*newLast)->IsControlPoint)) --newLast; // Set position of start point for LiveWire filter to coordinates of the new last point m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); + //m_LiveWireFilterClosure->SetStartPoint((*newLast)->Coordinates); auto it = m_Contour->IteratorBegin(); // Fll new Contour while (it <= newLast) { newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); ++it; } newContour->SetClosed(m_Contour->IsClosed()); m_ContourNode->SetData(newContour); m_Contour = newContour; mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h index dff07f7a0b..755e9eedc2 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,139 +1,148 @@ /*============================================================================ 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 mitkLiveWireTool2D_h #define mitkLiveWireTool2D_h #include #include namespace mitk { /** \brief A 2D segmentation tool based on a LiveWire approach. The contour between the last point and the current mouse position is computed by searching the shortest path according to specific features of the image. The contour thus tends to snap to the boundary of objects. The tool always assumes that unconfirmed contours are always defined for the current time point. So the time step in which the contours will be stored as segmentations will be determined when the contours got confirmed. Then they will be transfered to the slices of the currently selected time step. Changing the time point/time step while tool is active will updated the working slice the live wire filter. So the behavior of the active live wire contour is always WYSIWYG (What you see is what you get). \sa SegTool2D \sa ImageLiveWireContourModelFilter \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ class MITKSEGMENTATION_EXPORT LiveWireTool2D : public SegTool2D { public: mitkClassMacro(LiveWireTool2D, SegTool2D); itkFactorylessNewMacro(Self); us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; const char **GetXPM() const override; /// \brief Convert all current contours to binary segmentations. void ConfirmSegmentation(); /// \brief Delete all current contours. void ClearSegmentation(); + void SetSnapClosureContour(bool snap); + protected: LiveWireTool2D(); ~LiveWireTool2D() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; void UpdateLiveWireContour(); void OnTimePointChanged() override; private: /// \brief Initialize tool. void OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Actual LiveWire computation. void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Check double click on first control point to finish the LiveWire tool. bool OnCheckPoint(const InteractionEvent *interactionEvent); /// \brief Finish LiveWire tool. void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Close the contour. void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Don't use dynamic cost map for LiveWire calculation. void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. void FinishTool(); void EnableContourLiveWireInteraction(bool on); bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); void ReleaseInteractors(); void ReleaseHelperObjects(); void RemoveHelperObjects(); template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); ContourModel::Pointer CreateNewContour() const; + void UpdateClosureContour(mitk::Point3D endpoint); + mitk::ContourModel::Pointer m_Contour; mitk::DataNode::Pointer m_ContourNode; mitk::ContourModel::Pointer m_LiveWireContour; mitk::DataNode::Pointer m_LiveWireContourNode; + mitk::ContourModel::Pointer m_ClosureContour; + mitk::DataNode::Pointer m_ClosureContourNode; + bool m_SnapClosureContour; + mitk::ContourModel::Pointer m_EditingContour; mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModelLiveWireInteractor::Pointer m_ContourInteractor; /** Slice of the reference data the tool is currently actively working on to define contours.*/ mitk::Image::Pointer m_ReferenceDataSlice; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; + mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilterClosure; bool m_CreateAndUseDynamicCosts; std::vector> m_WorkingContours; std::vector> m_EditingContours; std::vector m_LiveWireInteractors; PlaneGeometry::ConstPointer m_PlaneGeometry; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp new file mode 100644 index 0000000000..d65969bb8a --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp @@ -0,0 +1,153 @@ +/*============================================================================ + +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 "mitkProcessExecutor.h" + +#include +#include +#include +#include + +namespace mitk +{ + std::string ProcessExecutor::GetOSDependendExecutableName(const std::string &name) + { +#if defined(_WIN32) + + if (itksys::SystemTools::GetFilenameLastExtension(name).empty()) + { + return name + ".exe"; + } + + return name; + +#else + auto result = itksys::SystemTools::GetFilenamePath(name); + if (EnsureCorrectOSPathSeparator(result).empty()) + { + return "./" + name; + } + else + { + return name; + } + +#endif + } + + std::string ProcessExecutor::EnsureCorrectOSPathSeparator(const std::string &path) + { + std::string ret = path; + +#ifdef _WIN32 + const std::string curSep = "\\"; + const char wrongSep = '/'; +#else + const std::string curSep = "/"; + const char wrongSep = '\\'; +#endif + + std::string::size_type pos = ret.find_first_of(wrongSep); + + while (pos != std::string::npos) + { + ret.replace(pos, 1, curSep); + + pos = ret.find_first_of(wrongSep); + } + + return ret; + } + + int ProcessExecutor::GetExitValue() { return this->m_ExitValue; }; + + bool ProcessExecutor::Execute(const std::string &executionPath, const ArgumentListType &argumentList) + { + std::vector pArguments_(argumentList.size() + 1); + + for (ArgumentListType::size_type index = 0; index < argumentList.size(); ++index) + { + pArguments_[index] = argumentList[index].c_str(); + } + pArguments_.push_back(nullptr); //terminating null element as required by ITK + + bool normalExit = false; + + try + { + itksysProcess *processID = itksysProcess_New(); + itksysProcess_SetCommand(processID, pArguments_.data()); + + itksysProcess_SetWorkingDirectory(processID, executionPath.c_str()); + + if (this->m_SharedOutputPipes) + { + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDOUT, 1); + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDERR, 1); + } + + itksysProcess_Execute(processID); + + char *rawOutput = nullptr; + int outputLength = 0; + while (true) + { + int dataStatus = itksysProcess_WaitForData(processID, &rawOutput, &outputLength, nullptr); + + if (dataStatus == itksysProcess_Pipe_STDOUT) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdOutEvent(data)); + } + else if (dataStatus == itksysProcess_Pipe_STDERR) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdErrEvent(data)); + } + else + { + break; + } + } + + itksysProcess_WaitForExit(processID, nullptr); + + auto state = static_cast(itksysProcess_GetState(processID)); + + normalExit = (state == itksysProcess_State_Exited); + this->m_ExitValue = itksysProcess_GetExitValue(processID); + } + catch (...) + { + throw; + } + return normalExit; + }; + + bool ProcessExecutor::Execute(const std::string &executionPath, + const std::string &executableName, + ArgumentListType argumentList) + { + std::string executableName_OS = GetOSDependendExecutableName(executableName); + argumentList.insert(argumentList.begin(), executableName_OS); + + return Execute(executionPath, argumentList); + } + + ProcessExecutor::ProcessExecutor() + { + this->m_ExitValue = 0; + this->m_SharedOutputPipes = false; + } + + ProcessExecutor::~ProcessExecutor() = default; +} // namespace mitk diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.h b/Modules/Segmentation/Interactions/mitkProcessExecutor.h new file mode 100644 index 0000000000..79856889b4 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.h @@ -0,0 +1,110 @@ +/*============================================================================ + +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. + +============================================================================*/ + +// Class is adapted from MatchPoint ProcessExecutor + +#ifndef __MITK_PROCESS_EXECUTOR_H +#define __MITK_PROCESS_EXECUTOR_H + +#include +#include +#include + +namespace mitk +{ + class ExternalProcessOutputEvent : public itk::AnyEvent + { + public: + typedef ExternalProcessOutputEvent Self; + typedef itk::AnyEvent Superclass; + + explicit ExternalProcessOutputEvent(const std::string &output = "") : m_Output(output) {} + ~ExternalProcessOutputEvent() override {} + + const char *GetEventName() const override { return "ExternalProcessOutputEvent"; } + bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } + itk::EventObject *MakeObject() const override { return new Self(m_Output); } + std::string GetOutput() const { return m_Output; } + + private: + std::string m_Output; + }; + +#define mitkProcessExecutorEventMacro(classname) \ + class classname : public ExternalProcessOutputEvent \ + { \ + public: \ + typedef classname Self; \ + typedef ExternalProcessOutputEvent Superclass; \ + \ + explicit classname(const std::string &output) : Superclass(output) {} \ + ~classname() override {} \ + \ + virtual const char *GetEventName() const { return #classname; } \ + virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ + virtual ::itk::EventObject *MakeObject() const { return new Self(this->GetOutput()); } \ + }; + + mitkProcessExecutorEventMacro(ExternalProcessStdOutEvent); + mitkProcessExecutorEventMacro(ExternalProcessStdErrEvent); + + /** + * @brief You may register an observer for an ExternalProcessOutputEvent, ExternalProcessStdOutEvent or + * ExternalProcessStdErrEvent in order to get notified of any output. + * @remark The events will only be invoked if the pipes are NOT(!) shared. By default the pipes are not shared. + * + */ + class MITKSEGMENTATION_EXPORT ProcessExecutor : public itk::Object + { + public: + using Self = ProcessExecutor; + using Superclass = ::itk::Object; + using Pointer = ::itk::SmartPointer; + using ConstPointer = ::itk::SmartPointer; + + itkTypeMacro(ProcessExecutor, ::itk::Object); + itkFactorylessNewMacro(Self); + + itkSetMacro(SharedOutputPipes, bool); + itkGetConstMacro(SharedOutputPipes, bool); + + using ArgumentListType = std::vector; + + bool Execute(const std::string &executionPath, const std::string &executableName, ArgumentListType argumentList); + + /** + * @brief Executes the process. This version assumes that the executable name is the first argument in the argument + * list and has already been converted to its OS dependent name via the static convert function of this class. + */ + bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); + + int GetExitValue(); + static std::string EnsureCorrectOSPathSeparator(const std::string &); + + static std::string GetOSDependendExecutableName(const std::string &name); + + protected: + ProcessExecutor(); + ~ProcessExecutor() override; + + int m_ExitValue; + + /** + * @brief Specifies if the child process should share the output pipes (true) or not (false). + * If pipes are not shared the output will be passed by invoking ExternalProcessOutputEvents + * @remark The events will only be invoked if the pipes are NOT(!) shared. + */ + bool m_SharedOutputPipes; + }; + +} // namespace mitk +#endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp new file mode 100644 index 0000000000..c2db0bcca3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -0,0 +1,322 @@ +/*============================================================================ + +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 "mitknnUnetTool.h" + +#include "mitkIOUtil.h" +#include "mitkProcessExecutor.h" +#include +#include +#include +#include +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); +} + +mitk::nnUNetTool::nnUNetTool() +{ + this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); +} + +mitk::nnUNetTool::~nnUNetTool() +{ + itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); +} + +void mitk::nnUNetTool::Activated() +{ + Superclass::Activated(); + m_InputOutputPair = std::make_pair(nullptr, nullptr); +} + +void mitk::nnUNetTool::UpdateCleanUp() +{ + // This overriden method is intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. +} + +void mitk::nnUNetTool::RenderOutputBuffer() +{ + if (this->m_OutputBuffer != nullptr) + { + Superclass::SetNodeProperties(this->m_OutputBuffer); + this->ClearOutputBuffer(); + try + { + if (nullptr != this->GetPreviewSegmentationNode()) + { + this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); + } + if (this->GetSelectedLabels().empty()) + { + this->ResetPreviewNode(); + } + } + catch (const mitk::Exception &e) + { + MITK_INFO << e.GetDescription(); + } + } +} + +void mitk::nnUNetTool::SetNodeProperties(LabelSetImage::Pointer segmentation) +{ + // This overriden method doesn't set node properties. Intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. + this->m_OutputBuffer = segmentation; +} + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() +{ + return this->m_OutputBuffer; +} + +void mitk::nnUNetTool::ClearOutputBuffer() +{ + this->m_OutputBuffer = nullptr; +} + +us::ModuleResource mitk::nnUNetTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); + return resource; +} + +const char **mitk::nnUNetTool::GetXPM() const +{ + return nullptr; +} + +const char *mitk::nnUNetTool::GetName() const +{ + return "nnUNet"; +} + +mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() +{ + return this->GetToolManager()->GetDataStorage(); +} + +mitk::DataNode *mitk::nnUNetTool::GetRefNode() +{ + return this->GetToolManager()->GetReferenceData(0); +} + +namespace +{ + void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) + { + std::string testCOUT; + std::string testCERR; + const auto *pEvent = dynamic_cast(&e); + + if (pEvent) + { + testCOUT = testCOUT + pEvent->GetOutput(); + MITK_INFO << testCOUT; + } + + const auto *pErrEvent = dynamic_cast(&e); + + if (pErrEvent) + { + testCERR = testCERR + pErrEvent->GetOutput(); + MITK_ERROR << testCERR; + } + } +} // namespace + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType /*timeStep*/) +{ + if (m_InputOutputPair.first == inputAtTimeStep) + { + return m_InputOutputPair.second; + } + std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; + std::string templateFilename = "XXXXXX_000_0000.nii.gz"; + + ProcessExecutor::Pointer spExec = ProcessExecutor::New(); + itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&onPythonProcessEvent); + spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + ProcessExecutor::ArgumentListType args; + + inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); + std::ofstream tmpStream; + inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, templateFilename, inDir + IOUtil::GetDirectorySeparator()); + tmpStream.close(); + std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); + std::string fileName = inputImagePath.substr(found + 1); + std::string token = fileName.substr(0, fileName.find("_")); + + if (this->GetNoPip()) + { + scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; + } + + try + { + IOUtil::Save(inputAtTimeStep, inputImagePath); + + if (this->GetMultiModal()) + { + for (size_t i = 0; i < this->m_OtherModalPaths.size(); ++i) + { + mitk::Image::ConstPointer modalImage = this->m_OtherModalPaths[i]; + std::string outModalFile = + inDir + IOUtil::GetDirectorySeparator() + token + "_000_000" + std::to_string(i + 1) + ".nii.gz"; + IOUtil::Save(modalImage.GetPointer(), outModalFile); + } + } + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + // Code calls external process + std::string command = "nnUNet_predict"; + if (this->GetNoPip()) + { +#ifdef _WIN32 + command = "python"; +#else + command = "python3"; +#endif + } + for (ModelParams &modelparam : m_ParamQ) + { + outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + modelparam.outputDir = outDir; + args.clear(); + if (this->GetNoPip()) + { + args.push_back(scriptPath); + } + args.push_back("-i"); + args.push_back(inDir); + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-t"); + args.push_back(modelparam.task); + + if (modelparam.model.find("cascade") != std::string::npos) + { + args.push_back("-ctr"); + } + else + { + args.push_back("-tr"); + } + args.push_back(modelparam.trainer); + + args.push_back("-m"); + args.push_back(modelparam.model); + + args.push_back("-p"); + args.push_back(modelparam.planId); + + if (!modelparam.folds.empty()) + { + args.push_back("-f"); + for (auto fold : modelparam.folds) + { + args.push_back(fold); + } + } + + args.push_back("--num_threads_nifti_save"); + args.push_back("1"); // fixing to 1 + + if (!this->GetMirror()) + { + args.push_back("--disable_tta"); + } + + if (!this->GetMixedPrecision()) + { + args.push_back("--disable_mixed_precision"); + } + + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.push_back("--save_npz"); + } + + try + { + std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); + itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); + std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); + itksys::SystemTools::PutEnv(cudaEnv.c_str()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + } + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.clear(); + command = "nnUNet_ensemble"; + outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + + args.push_back("-f"); + for (ModelParams &modelparam : m_ParamQ) + { + args.push_back(modelparam.outputDir); + } + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-pp"); + args.push_back(this->GetPostProcessingJsonDirectory()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + try + { + LabelSetImage::Pointer resultImage = LabelSetImage::New(); + Image::Pointer outputImage = IOUtil::Load(outputImagePath); + resultImage->InitializeByLabeledImage(outputImage); + resultImage->SetGeometry(inputAtTimeStep->GetGeometry()); + m_InputOutputPair = std::make_pair(inputAtTimeStep, resultImage); + return resultImage; + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } +} diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h new file mode 100644 index 0000000000..9e5a31cac3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -0,0 +1,196 @@ +/*============================================================================ + +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 mitknnUnetTool_h_Included +#define mitknnUnetTool_h_Included + +#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkCommon.h" +#include "mitkToolManager.h" +#include +#include +#include + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + /** + * @brief nnUNet parameter request object holding all model parameters for input. + * Also holds output temporary directory path. + */ + struct ModelParams + { + std::string task; + std::vector folds; + std::string model; + std::string trainer; + std::string planId; + std::string outputDir; + }; + + /** + \brief nnUNet segmentation tool. + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + */ + class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoMLSegmentationWithPreviewTool + { + public: + mitkClassMacro(nnUNetTool, AutoMLSegmentationWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + const char **GetXPM() const override; + const char *GetName() const override; + us::ModuleResource GetIconResource() const override; + + void Activated() override; + + itkSetMacro(nnUNetDirectory, std::string); + itkGetConstMacro(nnUNetDirectory, std::string); + + itkSetMacro(ModelDirectory, std::string); + itkGetConstMacro(ModelDirectory, std::string); + + itkSetMacro(PythonPath, std::string); + itkGetConstMacro(PythonPath, std::string); + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + itkSetMacro(PostProcessingJsonDirectory, std::string); + itkGetConstMacro(PostProcessingJsonDirectory, std::string); + + itkSetMacro(MixedPrecision, bool); + itkGetConstMacro(MixedPrecision, bool); + itkBooleanMacro(MixedPrecision); + + itkSetMacro(Mirror, bool); + itkGetConstMacro(Mirror, bool); + itkBooleanMacro(Mirror); + + itkSetMacro(MultiModal, bool); + itkGetConstMacro(MultiModal, bool); + itkBooleanMacro(MultiModal); + + itkSetMacro(NoPip, bool); + itkGetConstMacro(NoPip, bool); + itkBooleanMacro(NoPip); + + itkSetMacro(Ensemble, bool); + itkGetConstMacro(Ensemble, bool); + itkBooleanMacro(Ensemble); + + itkSetMacro(Predict, bool); + itkGetConstMacro(Predict, bool); + itkBooleanMacro(Predict); + + itkSetMacro(GpuId, unsigned int); + itkGetConstMacro(GpuId, unsigned int); + + /** + * @brief vector of ModelParams. + * Size > 1 only for ensemble prediction. + */ + std::vector m_ParamQ; + + /** + * @brief Holds paths to other input image modalities. + * + */ + std::vector m_OtherModalPaths; + + std::pair m_InputOutputPair; + + /** + * @brief Renders the output LabelSetImage. + * To called in the main thread. + */ + void RenderOutputBuffer(); + + /** + * @brief Get the Output Buffer object + * + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer GetOutputBuffer(); + + /** + * @brief Sets the outputBuffer to nullptr + * + */ + void ClearOutputBuffer(); + + /** + * @brief Returns the DataStorage from the ToolManager + */ + mitk::DataStorage *GetDataStorage(); + + mitk::DataNode *GetRefNode(); + + protected: + /** + * @brief Construct a new nnUNet Tool object and temp directory. + * + */ + nnUNetTool(); + + /** + * @brief Destroy the nnUNet Tool object and deletes the temp directory. + * + */ + ~nnUNetTool(); + + /** + * @brief Overriden method from the tool manager to execute the segmentation + * Implementation: + * 1. Saves the inputAtTimeStep in a temporary directory. + * 2. Copies other modalities, renames and saves in the temporary directory, if required. + * 3. Sets RESULTS_FOLDER and CUDA_VISIBLE_DEVICES variables in the environment. + * 3. Iterates through the parameter queue (m_ParamQ) and executes "nnUNet_predict" command with the parameters + * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as + * LabelSetImage and returns. + * + * @param inputAtTimeStep + * @param timeStep + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType timeStep) override; + void UpdateCleanUp() override; + void SetNodeProperties(LabelSetImage::Pointer) override; + + private: + std::string m_MitkTempDir; + std::string m_nnUNetDirectory; + std::string m_ModelDirectory; + std::string m_PythonPath; + std::string m_PostProcessingJsonDirectory; + // bool m_UseGPU; kept for future + // bool m_AllInGPU; + bool m_MixedPrecision; + bool m_Mirror; + bool m_NoPip; + bool m_MultiModal; + bool m_Ensemble = false; + bool m_Predict; + LabelSetImage::Pointer m_OutputBuffer; + unsigned int m_GpuId; + }; +} // namespace mitk +#endif diff --git a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml index e64b4c810b..27d02bc15d 100644 --- a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml +++ b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml @@ -1,34 +1,30 @@ - - - - diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index c4c3643e85..02b759d7b4 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,115 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingBaseTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp + Interactions/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO + Interactions/mitkProcessExecutor.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml Interactions/PickingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp index 34eb6dfb82..19887dc125 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp @@ -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. ============================================================================*/ #include "QmitkLiveWireTool2DGUI.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkLiveWireTool2DGUI, "") QmitkLiveWireTool2DGUI::QmitkLiveWireTool2DGUI() : QmitkToolGUI() { m_Controls.setupUi(this); m_Controls.m_Information->hide(); connect(m_Controls.m_ConfirmButton, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); connect(m_Controls.m_ClearButton, SIGNAL(clicked()), this, SLOT(OnClearSegmentation())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); + connect(m_Controls.m_SnapClosureCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnSnapClosureContour(bool))); connect(m_Controls.m_InformationCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnShowInformation(bool))); } QmitkLiveWireTool2DGUI::~QmitkLiveWireTool2DGUI() { } void QmitkLiveWireTool2DGUI::OnNewToolAssociated(mitk::Tool *tool) { m_LiveWireTool = dynamic_cast(tool); } void QmitkLiveWireTool2DGUI::OnConfirmSegmentation() { if (m_LiveWireTool.IsNotNull()) m_LiveWireTool->ConfirmSegmentation(); } void QmitkLiveWireTool2DGUI::OnClearSegmentation() { if (m_LiveWireTool.IsNotNull()) m_LiveWireTool->ClearSegmentation(); } +void QmitkLiveWireTool2DGUI::OnSnapClosureContour(bool snap) +{ + m_LiveWireTool->SetSnapClosureContour(snap); +} + void QmitkLiveWireTool2DGUI::OnShowInformation(bool on) { m_Controls.m_Information->setVisible(on); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h index f0c2a6b102..74cb48476f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.h @@ -1,64 +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 QmitkLiveWireTool2DGUI_h_Included #define QmitkLiveWireTool2DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkLiveWireTool2D.h" #include "ui_QmitkLiveWireTool2DGUIControls.h" #include class QSlider; class QLabel; class QFrame; class QPushButton; #include #include "QmitkStepperAdapter.h" class QmitkLiveWireTool2DGUIControls; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::LiveWireTool. \sa mitk::LiveWireTool2D */ class MITKSEGMENTATIONUI_EXPORT QmitkLiveWireTool2DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkLiveWireTool2DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnNewToolAssociated(mitk::Tool *); void OnConfirmSegmentation(); void OnClearSegmentation(); + void OnSnapClosureContour(bool snap); + void OnShowInformation(bool on); protected: QmitkLiveWireTool2DGUI(); ~QmitkLiveWireTool2DGUI() override; Ui::QmitkLiveWireTool2DGUIControls m_Controls; mitk::LiveWireTool2D::Pointer m_LiveWireTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui index b14b68769b..fe7bcd00a6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUIControls.ui @@ -1,115 +1,131 @@ QmitkLiveWireTool2DGUIControls 0 0 421 355 0 0 0 0 QmitkLiveWireTool2DGUIControls Confirm all previous contour. - + + 0 + + + 0 + + + 0 + + 0 Confirm Segmentation Clear Segmentation Qt::Vertical QSizePolicy::Fixed 20 15 + + + + Snap closing contour + + + Show usage information 0 150 12 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:12pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The tool is started by a double-click near the object of interest.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">You can fix preview segments by adding ancor points with a single left mouse button click. Pressing &quot;Delete&quot; will remove the last segment. To close a contour and finish current segmentation perform a double click on the first ancor point.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">After finishing a segmentation the contour will be still editable. You can move, insert and delete its ancor points.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Push the &quot;Confirm Segmentation&quot; button or deactivate the Live Wire Tool to fill all contours in the working image. Push the &quot;Clear Segmentation&quot; button to delete all unconfirmed contours.</span></p></body></html> diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h new file mode 100644 index 0000000000..881cb52567 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h @@ -0,0 +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.s + +============================================================================*/ + +#ifndef QmitknnUNetEnsembleLayout_h_Included +#define QmitknnUNetEnsembleLayout_h_Included + + +#include +#include +#include +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetTaskParamsUITemplate +{ + +public: + QLabel* trainerLabel; + ctkComboBox* trainerBox; + QLabel* plannerLabel; + ctkComboBox* plannerBox; + QLabel* foldLabel; + ctkCheckableComboBox* foldBox; + QLabel* modelLabel; + ctkComboBox* modelBox; + QWidget* parent; + + QmitknnUNetTaskParamsUITemplate(QWidget* inputGroupBox_1) + { + this->parent = inputGroupBox_1; + QVBoxLayout* verticalLayout_x = new QVBoxLayout(inputGroupBox_1); + verticalLayout_x->setObjectName(QString::fromUtf8("verticalLayout_x")); + QGridLayout* g_x = new QGridLayout(); +#ifndef Q_OS_MAC + g_x->setSpacing(6); +#endif +#ifndef Q_OS_MAC + g_x->setContentsMargins(0, 0, 0, 0); +#endif + g_x->setObjectName(QString::fromUtf8("g_2")); + + modelLabel = new QLabel("Configuration", inputGroupBox_1); + g_x->addWidget(modelLabel, 0, 0, 1, 1); + trainerLabel = new QLabel("Trainer", inputGroupBox_1); + g_x->addWidget(trainerLabel, 0, 1, 1, 1); + + modelBox = new ctkComboBox(inputGroupBox_1); + modelBox->setObjectName(QString::fromUtf8("modelBox_1")); + g_x->addWidget(modelBox, 1, 0, 1, 1); + trainerBox = new ctkComboBox(inputGroupBox_1); + trainerBox->setObjectName(QString::fromUtf8("trainerBox_1")); + g_x->addWidget(trainerBox, 1, 1, 1, 1); + + plannerLabel = new QLabel("Planner", inputGroupBox_1); + g_x->addWidget(plannerLabel, 2, 0, 1, 1); + foldLabel = new QLabel("Fold", inputGroupBox_1); + g_x->addWidget(foldLabel, 2, 1, 1, 1); + + plannerBox = new ctkComboBox(inputGroupBox_1); + plannerBox->setObjectName(QString::fromUtf8("plannerBox_1")); + g_x->addWidget(plannerBox, 3, 0, 1, 1); + foldBox = new ctkCheckableComboBox(inputGroupBox_1); + foldBox->setObjectName(QString::fromUtf8("foldBox_1")); + g_x->addWidget(foldBox, 3, 1, 1, 1); + + verticalLayout_x->addLayout(g_x); + } +}; +QT_END_NAMESPACE + +#endif + diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h new file mode 100644 index 0000000000..d005f62eb3 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h @@ -0,0 +1,281 @@ +/*============================================================================ + +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.s + +============================================================================*/ + +#ifndef QmitknnUNetFolderParser_h_Included +#define QmitknnUNetFolderParser_h_Included + +#include "QmitknnUNetToolGUI.h" +#include +#include +#include + +/** + * @brief Struct to store each (Folder) Node of the hierarchy tree structure. + * + */ +struct FolderNode +{ + QString name; + QString path; // parent + std::vector> subFolders; +}; + +/** + * @brief Class to store and retreive folder hierarchy information + * of RESULTS_FOLDER. Only Root node is explicitly stored in m_RootNode. + * No. of sub levels in the hierachry is defined in the LEVEL constant. + * + */ +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetFolderParser +{ +public: + + /** + * @brief Construct a new QmitknnUNetFolderParser object + * Initializes root folder node object pointer calls + * @param parentFolder + */ + QmitknnUNetFolderParser(const QString parentFolder) + { + m_RootNode = std::make_shared(); + m_RootNode->path = parentFolder; + m_RootNode->name = QString("nnUNet"); + m_RootNode->subFolders.clear(); + InitDirs(m_RootNode, 0); + } + + /** + * @brief Destroy the QmitknnUNetFolderParser object + * + */ + ~QmitknnUNetFolderParser() = default; /*{ DeleteDirs(m_RootNode, LEVEL); }*/ + + /** + * @brief Returns the "Results Folder" string which is parent path of the root node. + * + * @return QString + */ + QString getResultsFolder() { return m_RootNode->path; } + + /** + * @brief Returns the Model Names from root node. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelNames() + { + auto models = GetSubFolderNamesFromNode(m_RootNode); + return models; + } + + /** + * @brief Returns the task names for a given model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTasksForModel(const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + auto tasks = GetSubFolderNamesFromNode(modelNode); + return tasks; + } + + /** + * @brief Returns the models names for a given task. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelsForTask(const QString &taskName) + { + T modelsForTask; + auto models = GetSubFolderNamesFromNode(m_RootNode); + foreach (QString model, models) + { + QStringList taskList = getTasksForModel(model); + if (taskList.contains(taskName, Qt::CaseInsensitive)) + { + modelsForTask << model; + } + } + return modelsForTask; + } + + /** + * @brief Returns the trainer / planner names for a given task & model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTrainerPlannersForTask(const QString &taskName, const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + auto tps = GetSubFolderNamesFromNode(taskNode); + return tps; + } + + /** + * @brief Returns the Folds names for a given trainer,planner,task & model name. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param trainer + * @param planner + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getFoldsForTrainerPlanner(const QString &trainer, + const QString &planner, + const QString &taskName, + const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + QString trainerPlanner = trainer + QString("__") + planner; + std::shared_ptr tpNode = GetSubNodeMatchingNameCrietria(trainerPlanner, taskNode); + auto folds = GetSubFolderNamesFromNode(tpNode); + return folds; + } + +private: + const int m_LEVEL = 4; + std::shared_ptr m_RootNode; + + /** + * @brief Iterates through the root node and returns the sub FolderNode object Matching Name Crietria + * + * @param queryName + * @param parentNode + * @return std::shared_ptr + */ + std::shared_ptr GetSubNodeMatchingNameCrietria(const QString &queryName, + std::shared_ptr parentNode) + { + std::shared_ptr retNode; + std::vector> subNodes = parentNode->subFolders; + for (std::shared_ptr node : subNodes) + { + if (node->name == queryName) + { + retNode = node; + break; + } + } + return retNode; + } + + /** + * @brief Returns the sub folder names for a folder node object. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param std::shared_ptr + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T GetSubFolderNamesFromNode(const std::shared_ptr parent) + { + T folders; + std::vector> subNodes = parent->subFolders; + for (std::shared_ptr folder : subNodes) + { + folders.push_back(folder->name); + } + return folders; + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and create a tree structure. + * + * @param parent + * @param level + */ + void InitDirs(std::shared_ptr parent, int level) + { + QString searchFolder = parent->path + QDir::separator() + parent->name; + auto subFolders = FetchFoldersFromDir(searchFolder); + level++; + foreach (QString folder, subFolders) + { + std::shared_ptr fp = std::make_shared(); + fp->path = searchFolder; + fp->name = folder; + if (level < this->m_LEVEL) + { + InitDirs(fp, level); + } + parent->subFolders.push_back(fp); + } + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and clears the sub folder std::vector from each node. + * + * @param parent + * @param level + */ + void DeleteDirs(std::shared_ptr parent, int level) + { + level++; + for (std::shared_ptr subFolder : parent->subFolders) + { + if (level < m_LEVEL) + { + DeleteDirs(subFolder, level); + } + parent->subFolders.clear(); + } + } + + /** + * @brief Template function to fetch all folders inside a given path. + * The type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param path + * @return T + */ + template + T FetchFoldersFromDir(const QString &path) + { + T folders; + for (QDirIterator it(path, QDir::AllDirs, QDirIterator::NoIteratorFlags); it.hasNext();) + { + it.next(); + if (!it.fileName().startsWith('.')) + { + folders.push_back(it.fileName()); + } + } + return folders; + } +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h new file mode 100644 index 0000000000..6cd9532f46 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h @@ -0,0 +1,83 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGPU_h_Included +#define QmitknnUNetToolGPU_h_Included + +#include +#include +#include +#include + +/** + * @brief Struct to store GPU info. + * + */ +struct QmitkGPUSpec +{ + QString name; + int memoryFree; + unsigned int id; +}; + +/** + * @brief Class to load and save GPU information + * for further validation + */ +class QmitkGPULoader : public QObject +{ + Q_OBJECT + +private: + std::vector m_Gpus; + +public: + /** + * @brief Construct a new Qmitk GPU Loader object. + * Parses GPU info using `nvidia-smi` command and saves it as QmitkGPUSpec objects. + */ + QmitkGPULoader() + { + QProcess process; + process.start("nvidia-smi --query-gpu=name,memory.free --format=csv"); + process.waitForFinished(-1); + QStringList infoStringList; + while (process.canReadLine()) + { + QString line = process.readLine(); + if (!line.startsWith("name")) + { + infoStringList << line; + } + } + foreach (QString infoString, infoStringList) + { + QmitkGPUSpec spec; + QStringList gpuDetails; + gpuDetails = infoString.split(","); + spec.name = gpuDetails.at(0); + // spec.id = id; + spec.memoryFree = gpuDetails.at(1).split(" ")[0].toInt(); + this->m_Gpus.push_back(spec); + } + } + ~QmitkGPULoader() = default; + + /** + * @brief Returns the number of GPUs parsed and saved as QmitkGPUSpec objects. + * + * @return int + */ + int GetGPUCount() { return static_cast(m_Gpus.size()); } +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp new file mode 100644 index 0000000000..ea0b15c38b --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -0,0 +1,328 @@ +/*============================================================================ + +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 "QmitknnUNetToolGUI.h" + +#include "mitknnUnetTool.h" +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") + +QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkAutoMLSegmentationToolGUIBase() +{ + // Nvidia-smi command returning zero doesn't alway mean lack of GPUs. + // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. + if (m_GpuLoader.GetGPUCount() == 0) + { + std::string warning = "WARNING: No GPUs were detected on your machine. The nnUNet tool might not work."; + ShowErrorMessage(warning); + } + + // define predicates for multi modal data selection combobox + auto imageType = mitk::TNodePredicateDataType::New(); + auto labelSetImageType = mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New()); + this->m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); +} + +void QmitknnUNetToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) +{ + Superclass::ConnectNewTool(newTool); + newTool->IsTimePointChangeAwareOff(); +} + +void QmitknnUNetToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); +#ifndef _WIN32 + m_Controls.pythonEnvComboBox->addItem("/usr/bin"); +#endif + m_Controls.pythonEnvComboBox->addItem("Select"); + AutoParsePythonPaths(); + connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewRequested())); + connect(m_Controls.modeldirectoryBox, + SIGNAL(directoryChanged(const QString &)), + this, + SLOT(OnDirectoryChanged(const QString &))); + connect( + m_Controls.modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); + connect(m_Controls.taskBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTaskChanged(const QString &))); + connect( + m_Controls.plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); + connect(m_Controls.nopipBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalitiesNumberChanged(int))); + connect(m_Controls.posSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalPositionChanged(int))); + connect(m_Controls.pythonEnvComboBox, +#if QT_VERSION >= 0x050F00 // 5.15 + SIGNAL(textActivated(const QString &)), +#elif QT_VERSION >= 0x050C00 // 5.12 + SIGNAL(activated(const QString &)), +#endif + this, + SLOT(OnPythonPathChanged(const QString &))); + connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); + + m_Controls.codedirectoryBox->setVisible(false); + m_Controls.nnUnetdirLabel->setVisible(false); + m_Controls.multiModalSpinBox->setVisible(false); + m_Controls.multiModalSpinLabel->setVisible(false); + m_Controls.posSpinBoxLabel->setVisible(false); + m_Controls.posSpinBox->setVisible(false); + m_Controls.previewButton->setEnabled(false); + + QSize sz(16, 16); + m_Controls.stopButton->setVisible(false); + + QIcon refreshIcon; + refreshIcon.addPixmap(style()->standardIcon(QStyle::SP_BrowserReload).pixmap(sz), QIcon::Normal, QIcon::Off); + m_Controls.refreshdirectoryBox->setIcon(refreshIcon); + m_Controls.refreshdirectoryBox->setEnabled(true); + + m_Controls.statusLabel->setTextFormat(Qt::RichText); + m_Controls.statusLabel->setText("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + + " GPUs were detected."); + + if (m_GpuLoader.GetGPUCount() != 0) + { + m_Controls.gpuSpinBox->setMaximum(m_GpuLoader.GetGPUCount() - 1); + } + mainLayout->addLayout(m_Controls.verticalLayout); + Superclass::InitializeUI(mainLayout); + m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. +} + +void QmitknnUNetToolGUI::OnPreviewRequested() +{ + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + try + { + m_Controls.previewButton->setEnabled(false); // To prevent misclicked back2back prediction. + qApp->processEvents(); + tool->PredictOn(); // purposefully placed to make tool->GetMTime different than before. + QString modelName = m_Controls.modelBox->currentText(); + if (modelName.startsWith("ensemble", Qt::CaseInsensitive)) + { + ProcessEnsembleModelsParams(tool); + } + else + { + ProcessModelParams(tool); + } + QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); + QString pythonPath = pythonPathTextItem.mid(pythonPathTextItem.indexOf(" ") + 1); + bool isNoPip = m_Controls.nopipBox->isChecked(); +#ifdef _WIN32 + if (!isNoPip && !(pythonPath.endsWith("Scripts", Qt::CaseInsensitive) || + pythonPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(pythonPath.endsWith("bin", Qt::CaseInsensitive) || pythonPath.endsWith("bin/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("bin"); + } +#endif + std::string nnUNetDirectory; + if (isNoPip) + { + nnUNetDirectory = m_Controls.codedirectoryBox->directory().toStdString(); + } + else if (!IsNNUNetInstalled(pythonPath)) + { + throw std::runtime_error("nnUNet is not detected in the selected python environment. Please select a valid " + "python environment or install nnUNet."); + } + + tool->SetnnUNetDirectory(nnUNetDirectory); + tool->SetPythonPath(pythonPath.toStdString()); + tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); + // checkboxes + tool->SetMirror(m_Controls.mirrorBox->isChecked()); + tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); + tool->SetNoPip(isNoPip); + tool->SetMultiModal(m_Controls.multiModalBox->isChecked()); + // Spinboxes + tool->SetGpuId(static_cast(m_Controls.gpuSpinBox->value())); + // Multi-Modal + tool->MultiModalOff(); + if (m_Controls.multiModalBox->isChecked()) + { + tool->m_OtherModalPaths.clear(); + tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); + tool->MultiModalOn(); + } + tool->m_InputOutputPair = std::make_pair(nullptr, nullptr); + m_Controls.statusLabel->setText("STATUS: Starting Segmentation task... This might take a while."); + tool->UpdatePreview(); + if (tool->GetOutputBuffer() == nullptr) + { + SegmentationProcessFailed(); + } + SegmentationResultHandler(tool); + tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. + m_Controls.previewButton->setEnabled(true); + } + catch (const std::exception &e) + { + std::stringstream errorMsg; + errorMsg << "Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); + ShowErrorMessage(errorMsg.str()); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + catch (...) + { + std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; + ShowErrorMessage(errorMsg); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + } +} + +std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() +{ + std::vector paths; + if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) + { + for (QmitkDataStorageComboBox *modality : m_Modalities) + { + if (modality->objectName() != "multiModal_0") + { + paths.push_back(dynamic_cast(modality->GetSelectedNode()->GetData())); + } + } + } + return paths; +} + +bool QmitknnUNetToolGUI::IsNNUNetInstalled(const QString &pythonPath) +{ + QString fullPath = pythonPath; +#ifdef _WIN32 + if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + } +#endif + fullPath = fullPath.mid(fullPath.indexOf(" ") + 1); + return QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")); +} + +void QmitknnUNetToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) +{ + this->setCursor(Qt::ArrowCursor); + QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); + messageBox->exec(); + delete messageBox; + MITK_WARN << message; +} + +void QmitknnUNetToolGUI::ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer tool) +{ + if (m_EnsembleParams[0]->modelBox->currentText() == m_EnsembleParams[1]->modelBox->currentText()) + { + throw std::runtime_error("Both models you have selected for ensembling are the same."); + } + QString taskName = m_Controls.taskBox->currentText(); + std::vector requestQ; + QString ppDirFolderNamePart1 = "ensemble_"; + QStringList ppDirFolderNameParts; + for (std::unique_ptr &layout : m_EnsembleParams) + { + QStringList ppDirFolderName; + QString modelName = layout->modelBox->currentText(); + ppDirFolderName << modelName; + ppDirFolderName << "__"; + QString trainer = layout->trainerBox->currentText(); + ppDirFolderName << trainer; + ppDirFolderName << "__"; + QString planId = layout->plannerBox->currentText(); + ppDirFolderName << planId; + + if (!IsModelExists(modelName, taskName, QString(trainer + "__" + planId))) + { + std::string errorMsg = "The configuration " + modelName.toStdString() + + " you have selected doesn't exist. Check your Results Folder again."; + throw std::runtime_error(errorMsg); + } + std::vector testfold; // empty vector to consider all folds for inferencing. + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); + requestQ.push_back(modelObject); + ppDirFolderNameParts << ppDirFolderName.join(QString("")); + } + tool->EnsembleOn(); + + QString ppJsonFilePossibility1 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.first() + "--" + + ppDirFolderNameParts.last() + QDir::separator() + "postprocessing.json"); + QString ppJsonFilePossibility2 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.last() + "--" + + ppDirFolderNameParts.first() + QDir::separator() + "postprocessing.json"); + + if (QFile(ppJsonFilePossibility1).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility1.toStdString()); + } + else if (QFile(ppJsonFilePossibility2).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); + } + else + { + // warning message + } + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +void QmitknnUNetToolGUI::ProcessModelParams(mitk::nnUNetTool::Pointer tool) +{ + tool->EnsembleOff(); + std::vector requestQ; + QString modelName = m_Controls.modelBox->currentText(); + QString taskName = m_Controls.taskBox->currentText(); + QString trainer = m_Controls.trainerBox->currentText(); + QString planId = m_Controls.plannerBox->currentText(); + std::vector fetchedFolds = FetchSelectedFoldsFromUI(); + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, fetchedFolds); + requestQ.push_back(modelObject); + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +bool QmitknnUNetToolGUI::IsModelExists(const QString &modelName, const QString &taskName, const QString &trainerPlanner) +{ + QString modelSearchPath = + QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + modelName + + QDir::separator() + taskName + QDir::separator() + trainerPlanner); + if (QDir(modelSearchPath).exists()) + { + return true; + } + return false; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h new file mode 100644 index 0000000000..583151c47a --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h @@ -0,0 +1,222 @@ +/*============================================================================ + +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.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGUI_h_Included +#define QmitknnUNetToolGUI_h_Included + +#include "QmitkAutoMLSegmentationToolGUIBase.h" +#include "QmitknnUNetFolderParser.h" +#include "QmitknnUNetGPU.h" +#include "mitknnUnetTool.h" +#include "ui_QmitknnUNetToolGUIControls.h" +#include +#include +#include +#include + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetToolGUI : public QmitkAutoMLSegmentationToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitknnUNetToolGUI, QmitkAutoMLSegmentationToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots: + + /** + * @brief Qt slot + * + */ + void OnPreviewRequested(); + + /** + * @brief Qt slot + * + */ + void OnDirectoryChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnModelChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTaskChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTrainerChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnCheckBoxChanged(int); + + /** + * @brief Qthread slot to capture failures from thread worker and + * shows error message + * + */ + void SegmentationProcessFailed(); + + /** + * @brief Qthread to capture sucessfull nnUNet segmentation. + * Further, renders the LabelSet image + */ + void SegmentationResultHandler(mitk::nnUNetTool *); + + /** + * @brief Qt Slot + * + */ + void OnModalitiesNumberChanged(int); + + /** + * @brief Qt Slot + * + */ + void OnPythonPathChanged(const QString &); + + /** + * @brief Qt Slot + * + */ + void OnModalPositionChanged(int); + + /** + * @brief Qt slot + * + */ + void OnRefreshPresssed(); + +protected: + QmitknnUNetToolGUI(); + ~QmitknnUNetToolGUI() = default; + + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) override; + void InitializeUI(QBoxLayout *mainLayout) override; + void EnableWidgets(bool enabled) override; + +private: + /** + * @brief Parses the ensemble UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Parses the UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessModelParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Creates and renders QmitknnUNetTaskParamsUITemplate layout for ensemble input. + */ + void ShowEnsembleLayout(bool visible = true); + + /** + * @brief Creates a QMessage object and shows on screen. + */ + void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); + + /** + * @brief Searches and parses paths of python virtual enviroments + * from predefined lookout locations + */ + void AutoParsePythonPaths(); + + /** + * @brief Check if pretrained model sub folder inside RESULTS FOLDER exist. + * + */ + bool IsModelExists(const QString&, const QString&, const QString&); + + /** + * @brief Clears all combo boxes + * Any new combo box added in the future can be featured here for clearance. + * + */ + void ClearAllComboBoxes(); + + /** + * @brief Checks if nnUNet_predict command is valid in the selected python virtual environment. + * + * @return bool + */ + bool IsNNUNetInstalled(const QString &); + + /** + * @brief Mapper function to map QString entries from UI to ModelParam attributes. + * + * @return mitk::ModelParams + */ + mitk::ModelParams MapToRequest( + const QString &, const QString &, const QString &, const QString &, const std::vector &); + + /** + * @brief Returns checked fold names from the ctk-Checkable-ComboBox. + * + * @return std::vector + */ + std::vector FetchSelectedFoldsFromUI(); + + /** + * @brief Returns all paths from the dynamically generated ctk-path-line-edit boxes. + * + * @return std::vector + */ + std::vector FetchMultiModalImagesFromUI(); + + Ui_QmitknnUNetToolGUIControls m_Controls; + QmitkGPULoader m_GpuLoader; + + /** + * @brief Stores all dynamically added ctk-path-line-edit UI elements. + * + */ + std::vector m_Modalities; + + std::vector> m_EnsembleParams; + + mitk::NodePredicateBase::Pointer m_MultiModalPredicate; + + /** + * @brief Stores row count of the "advancedSettingsLayout" layout element. This value helps dynamically add + * ctk-path-line-edit UI elements at the right place. Forced to initialize in the InitializeUI method since there is + * no guarantee of retrieving exact row count anywhere else. + * + */ + int m_UI_ROWS; + + /** + * @brief Stores path of the model director (RESULTS_FOLDER appended by "nnUNet"). + * + */ + std::shared_ptr m_ParentFolder = nullptr; + + /** + * @brief Valid list of models supported by nnUNet + * + */ + const QStringList m_VALID_MODELS = {"2d", "3d_lowres", "3d_fullres", "3d_cascade_fullres", "ensembles"}; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui new file mode 100644 index 0000000000..534b398ddf --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui @@ -0,0 +1,440 @@ + + + QmitknnUNetToolGUIControls + + + + 0 + 0 + 192 + 352 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitknnUNetToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + nnUNet Results Folder: + + + + + + + + + + + + Refresh Results Folder + + + + + + + + + Configuration: + + + + + + + + + + + 0 + 0 + + + + Task: + + + + + + + + + + + + + 0 + 0 + + + + Trainer: + + + + + + + + + + + + 0 + 0 + + + + Plan: + + + + + + + + + + + Fold: + + + + + + + + + + Stop + + + + + + + + + 0 + 0 + + + + Multi-Modal: + + + + + + + + + + + 0 + 0 + + + + No. of Extra Modalities: + + + + + + + + + + + + + 0 + 0 + + + + Reference Image Position + + + + + + + + + + + + + + + + 0 + 0 + + + + 5 + + + Advanced + + + true + + + true + + + Qt::AlignRight + + + + + + + 6 + + + 0 + + + + + + + 0 + 0 + + + + No Pip + + + + + + + + + + + 0 + 0 + + + + Mixed Precision + + + + + + + true + + + + + + + + + 0 + 0 + + + + GPU Id: + + + + + + + + + + + + 0 + 0 + + + + Enable Mirroring + + + + + + + true + + + + + + + + + 0 + 0 + + + + Python Path: + + + + + + + + + + + + 0 + 0 + + + + nnUNet Path: + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Preview + + + + + + + + 0 + 0 + + + + + + + + + + + ctkDirectoryButton + QWidget +
ctkDirectoryButton.h
+ 1 +
+ + ctkComboBox + QComboBox +
ctkComboBox.h
+ 1 +
+ + ctkCheckableComboBox + QComboBox +
ctkCheckableComboBox.h
+ 1 +
+ + ctkCheckBox + QCheckBox +
ctkCheckBox.h
+ 1 +
+ + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+
+ + +
diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp new file mode 100644 index 0000000000..b04034d042 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp @@ -0,0 +1,434 @@ +#include "QmitknnUNetToolGUI.h" +#include +#include +#include +#include +#include + +void QmitknnUNetToolGUI::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); +} + +void QmitknnUNetToolGUI::ClearAllComboBoxes() +{ + m_Controls.modelBox->clear(); + m_Controls.taskBox->clear(); + m_Controls.foldBox->clear(); + m_Controls.trainerBox->clear(); + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + } +} + +void QmitknnUNetToolGUI::OnRefreshPresssed() +{ + const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); + OnDirectoryChanged(resultsFolder); +} + +void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) +{ + m_Controls.previewButton->setEnabled(false); + this->ClearAllComboBoxes(); + m_ParentFolder = std::make_shared(resultsFolder); + auto models = m_ParentFolder->getModelNames(); + std::for_each(models.begin(), + models.end(), + [this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + m_Controls.modelBox->addItem(model); + }); +} + +void QmitknnUNetToolGUI::OnModelChanged(const QString &model) +{ + if (model.isEmpty()) + { + return; + } + m_Controls.taskBox->clear(); + auto tasks = m_ParentFolder->getTasksForModel(model); + std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); +} + +void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) +{ + if (task.isEmpty()) + { + return; + } + m_Controls.trainerBox->clear(); + m_Controls.plannerBox->clear(); + QStringList trainerPlanners = + m_ParentFolder->getTrainerPlannersForTask(task, m_Controls.modelBox->currentText()); + if (m_Controls.modelBox->currentText() == "ensembles") + { + m_Controls.trainerBox->setVisible(false); + m_Controls.trainerLabel->setVisible(false); + m_Controls.plannerBox->setVisible(false); + m_Controls.plannerLabel->setVisible(false); + m_Controls.foldBox->setVisible(false); + m_Controls.foldLabel->setVisible(false); + ShowEnsembleLayout(true); + QString splitterString = "--"; + QStringList models, trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + QStringList trainerSplitParts = trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts); + foreach (QString modelSet, trainerSplitParts) + { + modelSet.remove("ensemble_", Qt::CaseInsensitive); + QStringList splitParts = modelSet.split("__", QString::SplitBehavior::SkipEmptyParts); + QString modelName = splitParts.first(); + QString trainer = splitParts.at(1); + QString planId = splitParts.at(2); + models << modelName; + trainers << trainer; + planners << planId; + } + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + models.removeDuplicates(); + + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + std::for_each(models.begin(), + models.end(), + [&layout, this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + layout->modelBox->addItem(model); + }); + std::for_each( + trainers.begin(), trainers.end(), [&layout](QString trainer) { layout->trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [&layout](QString planner) { layout->plannerBox->addItem(planner); }); + } + m_Controls.previewButton->setEnabled(true); + } + else + { + m_Controls.trainerBox->setVisible(true); + m_Controls.trainerLabel->setVisible(true); + m_Controls.plannerBox->setVisible(true); + m_Controls.plannerLabel->setVisible(true); + m_Controls.foldBox->setVisible(true); + m_Controls.foldLabel->setVisible(true); + m_Controls.previewButton->setEnabled(false); + ShowEnsembleLayout(false); + QString splitterString = "__"; + QStringList trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + trainers << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).first(); + planners << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).last(); + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + std::for_each( + trainers.begin(), trainers.end(), [this](QString trainer) { m_Controls.trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [this](QString planner) { m_Controls.plannerBox->addItem(planner); }); + } +} + +void QmitknnUNetToolGUI::OnTrainerChanged(const QString &plannerSelected) +{ + if (plannerSelected.isEmpty()) + { + return; + } + m_Controls.foldBox->clear(); + if (m_Controls.modelBox->currentText() != "ensembles") + { + auto selectedTrainer = m_Controls.trainerBox->currentText(); + auto selectedTask = m_Controls.taskBox->currentText(); + auto selectedModel = m_Controls.modelBox->currentText(); + auto folds = m_ParentFolder->getFoldsForTrainerPlanner( + selectedTrainer, plannerSelected, selectedTask, selectedModel); + std::for_each(folds.begin(), + folds.end(), + [this](QString fold) + { + if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet + m_Controls.foldBox->addItem(fold); + }); + if (m_Controls.foldBox->count() != 0) + { + // Now recalling all added items to check-mark it. + const QAbstractItemModel *qaim = m_Controls.foldBox->checkableModel(); + for (int i = 0; i < folds.size(); ++i) + { + const QModelIndex mi = qaim->index(i, 0); + m_Controls.foldBox->setCheckState(mi, Qt::Checked); + } + m_Controls.previewButton->setEnabled(true); + } + } + else + { + m_Controls.previewButton->setEnabled(true); + } +} + +void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) +{ + if (pyEnv == QString("Select")) + { + QString path = + QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + m_Controls.pythonEnvComboBox->insertItem(0, path); + m_Controls.pythonEnvComboBox->setCurrentIndex(0); + } + } + else if (!IsNNUNetInstalled(pyEnv)) + { + std::string warning = + "WARNING: nnUNet is not detected on the Python environment you selected. Please select another " + "environment or create one. For more info refer https://github.com/MIC-DKFZ/nnUNet"; + ShowErrorMessage(warning); + } +} + +void QmitknnUNetToolGUI::OnCheckBoxChanged(int state) +{ + bool visibility = false; + if (state == Qt::Checked) + { + visibility = true; + } + ctkCheckBox *box = qobject_cast(sender()); + if (box != nullptr) + { + if (box->objectName() == QString("nopipBox")) + { + m_Controls.codedirectoryBox->setVisible(visibility); + m_Controls.nnUnetdirLabel->setVisible(visibility); + } + else if (box->objectName() == QString("multiModalBox")) + { + m_Controls.multiModalSpinLabel->setVisible(visibility); + m_Controls.multiModalSpinBox->setVisible(visibility); + m_Controls.posSpinBoxLabel->setVisible(visibility); + m_Controls.posSpinBox->setVisible(visibility); + if (visibility) + { + QmitkDataStorageComboBox *defaultImage = new QmitkDataStorageComboBox(this, true); + defaultImage->setObjectName(QString("multiModal_" + QString::number(0))); + defaultImage->SetPredicate(this->m_MultiModalPredicate); + defaultImage->setDisabled(true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (tool != nullptr) + { + defaultImage->SetDataStorage(tool->GetDataStorage()); + defaultImage->SetSelectedNode(tool->GetRefNode()); + } + m_Controls.advancedSettingsLayout->addWidget(defaultImage, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(defaultImage); + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_UI_ROWS++; + } + else + { + OnModalitiesNumberChanged(0); + m_Controls.multiModalSpinBox->setValue(0); + m_Controls.posSpinBox->setMaximum(0); + delete this->m_Modalities[0]; + m_Modalities.pop_back(); + } + } + } +} + +void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) +{ + while (num > static_cast(this->m_Modalities.size() - 1)) + { + QmitkDataStorageComboBox *multiModalBox = new QmitkDataStorageComboBox(this, true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + multiModalBox->SetDataStorage(tool->GetDataStorage()); + multiModalBox->SetPredicate(this->m_MultiModalPredicate); + multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(multiModalBox); + } + while (num < static_cast(this->m_Modalities.size() - 1) && !m_Modalities.empty()) + { + QmitkDataStorageComboBox *child = m_Modalities.back(); + if (child->objectName() == "multiModal_0") + { + std::iter_swap(this->m_Modalities.end() - 2, this->m_Modalities.end() - 1); + child = m_Modalities.back(); + } + delete child; // delete the layout item + m_Modalities.pop_back(); + } + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_Controls.advancedSettingsLayout->update(); +} + +void QmitknnUNetToolGUI::OnModalPositionChanged(int posIdx) +{ + if (posIdx < static_cast(m_Modalities.size())) + { + int currPos = 0; + bool stopCheck = false; + // for-loop clears all widgets from the QGridLayout and also, finds the position of loaded-image widget. + for (QmitkDataStorageComboBox *multiModalBox : m_Modalities) + { + m_Controls.advancedSettingsLayout->removeWidget(multiModalBox); + multiModalBox->setParent(nullptr); + if (multiModalBox->objectName() != "multiModal_0" && !stopCheck) + { + currPos++; + } + else + { + stopCheck = true; + } + } + // moving the loaded-image widget to the required position + std::iter_swap(this->m_Modalities.begin() + currPos, m_Modalities.begin() + posIdx); + // re-adding all widgets in the order + for (int i = 0; i < static_cast(m_Modalities.size()); ++i) + { + QmitkDataStorageComboBox *multiModalBox = m_Modalities[i]; + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + i + 1, 1, 1, 3); + } + m_Controls.advancedSettingsLayout->update(); + } +} + +void QmitknnUNetToolGUI::AutoParsePythonPaths() +{ + QString homeDir = QDir::homePath(); + std::vector searchDirs; +#ifdef _WIN32 + searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + + QString("anaconda3")); +#else + // Add search locations for possible standard python paths here + searchDirs.push_back(homeDir + QDir::separator() + "environments"); + searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); +#endif + for (QString searchDir : searchDirs) + { + if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) + { + if (QDir(searchDir).exists()) + { + m_Controls.pythonEnvComboBox->insertItem(0, "(base): " + searchDir); + searchDir.append((QDir::separator() + QString("envs"))); + } + } + for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) + { + subIt.next(); + QString envName = subIt.fileName(); + if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. + { + m_Controls.pythonEnvComboBox->insertItem(0, "(" + envName + "): " + subIt.filePath()); + } + } + } + m_Controls.pythonEnvComboBox->setCurrentIndex(-1); +} + +std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI() +{ + std::vector folds; + if (!(m_Controls.foldBox->allChecked() || m_Controls.foldBox->noneChecked())) + { + QModelIndexList foldList = m_Controls.foldBox->checkedIndexes(); + foreach (QModelIndex index, foldList) + { + QString foldQString = + m_Controls.foldBox->itemText(index.row()).split("_", QString::SplitBehavior::SkipEmptyParts).last(); + folds.push_back(foldQString.toStdString()); + } + } + return folds; +} + +mitk::ModelParams QmitknnUNetToolGUI::MapToRequest(const QString &modelName, + const QString &taskName, + const QString &trainer, + const QString &planId, + const std::vector &folds) +{ + mitk::ModelParams requestObject; + requestObject.model = modelName.toStdString(); + requestObject.trainer = trainer.toStdString(); + requestObject.planId = planId.toStdString(); + requestObject.task = taskName.toStdString(); + requestObject.folds = folds; + return requestObject; +} + +void QmitknnUNetToolGUI::SegmentationProcessFailed() +{ + m_Controls.statusLabel->setText( + "STATUS: Error in the segmentation process. No resulting segmentation can be loaded."); + this->setCursor(Qt::ArrowCursor); + std::stringstream stream; + stream << "Error in the segmentation process. No resulting segmentation can be loaded."; + ShowErrorMessage(stream.str()); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool) +{ + tool->RenderOutputBuffer(); + this->SetLabelSetPreview(tool->GetMLPreview()); + m_Controls.statusLabel->setText("STATUS: Segmentation task finished successfully. Please Confirm the " + "segmentation else will result in data loss"); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) +{ + if (m_EnsembleParams.empty()) + { + ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); + auto lay1 = std::make_unique(groupBoxModel1); + m_EnsembleParams.push_back(std::move(lay1)); + groupBoxModel1->setObjectName(QString::fromUtf8("model_1_Box")); + groupBoxModel1->setTitle(QString::fromUtf8("Model 1")); + groupBoxModel1->setMinimumSize(QSize(0, 0)); + groupBoxModel1->setCollapsedHeight(5); + groupBoxModel1->setCollapsed(false); + groupBoxModel1->setFlat(true); + groupBoxModel1->setAlignment(Qt::AlignRight); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel1, 3, 0, 1, 2); + + ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); + auto lay2 = std::make_unique(groupBoxModel2); + m_EnsembleParams.push_back(std::move(lay2)); + groupBoxModel2->setObjectName(QString::fromUtf8("model_2_Box")); + groupBoxModel2->setTitle(QString::fromUtf8("Model 2")); + groupBoxModel2->setMinimumSize(QSize(0, 0)); + groupBoxModel2->setCollapsedHeight(5); + groupBoxModel2->setCollapsed(false); + groupBoxModel2->setFlat(true); + groupBoxModel2->setAlignment(Qt::AlignLeft); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel2, 3, 2, 1, 2); + } + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->parent->setVisible(visible); + } +} diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index e71eb77792..63a50b2923 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,90 +1,97 @@ set( CPP_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp Qmitk/QmitkAutoSegmentationToolGUIBase.cpp Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkFastMarchingToolGUIBase.cpp Qmitk/QmitkFastMarchingTool3DGUI.cpp Qmitk/QmitkFastMarchingToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkNewSegmentationDialog.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitkWatershedToolGUI.cpp +Qmitk/QmitknnUNetToolGUI.cpp +Qmitk/QmitknnUNetToolSlots.cpp #Added from ML Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSearchLabelDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h Qmitk/QmitkAutoSegmentationToolGUIBase.h Qmitk/QmitkAutoMLSegmentationToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkFastMarchingToolGUIBase.h Qmitk/QmitkFastMarchingTool3DGUI.h Qmitk/QmitkFastMarchingToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkNewSegmentationDialog.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitkWatershedToolGUI.h +Qmitk/QmitknnUNetToolGUI.h +Qmitk/QmitknnUNetGPU.h +Qmitk/QmitknnUNetEnsembleLayout.h +Qmitk/QmitknnUNetFolderParser.h #Added from ML Qmitk/QmitkLabelSetWidget.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSearchLabelDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkWatershedToolGUIControls.ui #Added from ML Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSearchLabelDialogGUI.ui +Qmitk/QmitknnUNetToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp index f3a4196c61..98ccf8a397 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp @@ -1,1432 +1,1432 @@ /*============================================================================ 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 "QmitkExtWorkbenchWindowAdvisor.h" #include "QmitkExtActionBarAdvisor.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include // UGLYYY #include "internal/QmitkExtWorkbenchWindowAdvisorHack.h" #include "internal/QmitkCommonExtPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include QmitkExtWorkbenchWindowAdvisorHack* QmitkExtWorkbenchWindowAdvisorHack::undohack = new QmitkExtWorkbenchWindowAdvisorHack(); QString QmitkExtWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; static bool USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS = false; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForViewNavigator: public berry::IPartListener { public: PartListenerForViewNavigator(QAction* act) : viewNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } private: QAction* viewNavigatorAction; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED; } else { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } //GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->closeProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->viewNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(true); } } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->closeProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->viewNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(false); } } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; QmitkExtWorkbenchWindowAdvisor::QmitkExtWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showPerspectiveToolbar(false) , showVersionInfo(true) , showMitkVersionInfo(true) , showViewMenuItem(true) , showNewWindowMenuItem(false) , showClosePerspectiveMenuItem(true) , viewNavigatorFound(false) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); - viewExcludeList.push_back("org.mitk.views.viewnavigatorview"); + viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkExtWorkbenchWindowAdvisor::~QmitkExtWorkbenchWindowAdvisor() { } berry::ActionBarAdvisor::Pointer QmitkExtWorkbenchWindowAdvisor::CreateActionBarAdvisor(berry::IActionBarConfigurer::Pointer configurer) { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { berry::ActionBarAdvisor::Pointer actionBarAdvisor(new QmitkExtActionBarAdvisor(configurer)); return actionBarAdvisor; } else { return berry::WorkbenchWindowAdvisor::CreateActionBarAdvisor(configurer); } } QWidget* QmitkExtWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkExtWorkbenchWindowAdvisor::ShowClosePerspectiveMenuItem(bool show) { showClosePerspectiveMenuItem = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowClosePerspectiveMenuItem() { return showClosePerspectiveMenuItem; } void QmitkExtWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkExtWorkbenchWindowAdvisor::ShowNewWindowMenuItem(bool show) { showNewWindowMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewMenuItem(bool show) { showViewMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowPerspectiveToolbar(bool show) { showPerspectiveToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkExtWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkExtWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); auto fileOpenAction = new QmitkFileOpenAction(berry::QtStyleManager::ThemeIcon(basePath + "document-open.svg"), window); fileOpenAction->setShortcut(QKeySequence::Open); auto fileSaveAction = new QmitkFileSaveAction(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg"), window); fileSaveAction->setShortcut(QKeySequence::Save); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); closeProjectAction = new QmitkCloseProjectAction(window); closeProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "edit-delete.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; - if ((*iter)->GetId() == "org.mitk.views.viewnavigatorview") + if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } if (!USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileOpenAction); fileMenu->addAction(fileSaveAction); fileMenu->addAction(fileSaveProjectAction); fileMenu->addAction(closeProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); if (showNewWindowMenuItem) { windowMenu->addAction("&New Window", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onNewWindow())); windowMenu->addSeparator(); } QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); QMenu* viewMenu = nullptr; if (showViewMenuItem) { viewMenu = windowMenu->addMenu("Show &View"); viewMenu->setObjectName("Show View"); } windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); if(showClosePerspectiveMenuItem) closePerspAction = windowMenu->addAction("&Close Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onClosePerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); if (showViewMenuItem) { for (auto viewAction : qAsConst(viewActions)) { viewMenu->addAction(viewAction); } } // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== } else { undoAction = new QmitkUndoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), nullptr); undoAction->setShortcut(QKeySequence::Undo); redoAction = new QmitkRedoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), nullptr); redoAction->setShortcut(QKeySequence::Redo); } // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { openDicomEditorAction = new QmitkOpenDicomEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "dicom.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { openStdMultiWidgetEditorAction = new QmitkOpenStdMultiWidgetEditorAction(QIcon(":/org.mitk.gui.qt.ext/Editor.png"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { openMxNMultiWidgetEditorAction = new QmitkOpenMxNMultiWidgetEditorAction(QIcon(":/org.mitk.gui.qt.ext/Editor.png"), window); } if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } viewNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.ext/view-manager.svg")),"&View Navigator", nullptr); - viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigatorview"); + viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigator"); if (viewNavigatorFound) { QObject::connect(viewNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onViewNavigator())); viewNavigatorAction->setCheckable(true); // add part listener for view navigator viewNavigatorPartListener.reset(new PartListenerForViewNavigator(viewNavigatorAction)); window->GetPartService()->AddPartListener(viewNavigatorPartListener.data()); - berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigatorview"); + berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigator"); viewNavigatorAction->setChecked(false); if (viewnavigatorview) { bool isViewNavigatorVisible = window->GetActivePage()->IsPartVisible(viewnavigatorview); if (isViewNavigatorVisible) viewNavigatorAction->setChecked(true); } viewNavigatorAction->setToolTip("Toggle View Navigator"); } mainActionsToolBar->addAction(fileOpenAction); mainActionsToolBar->addAction(fileSaveProjectAction); mainActionsToolBar->addAction(closeProjectAction); mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if(this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { mainActionsToolBar->addAction(openDicomEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { mainActionsToolBar->addAction(openStdMultiWidgetEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { mainActionsToolBar->addAction(openMxNMultiWidgetEditorAction); } if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } if (viewNavigatorFound) { mainActionsToolBar->addAction(viewNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== Perspective Toolbar ================================== auto qPerspectiveToolbar = new QToolBar; qPerspectiveToolbar->setObjectName("perspectiveToolBar"); if (showPerspectiveToolbar) { qPerspectiveToolbar->addActions(perspGroup->actions()); mainWindow->addToolBar(qPerspectiveToolbar); } else delete qPerspectiveToolbar; if (showViewToolbar) { auto prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); berry::IPreferences::Pointer stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (const auto &labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (const auto &category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); if (!viewDescriptorsInCurrentCategory.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (const auto &viewDescriptor : qAsConst(viewDescriptorsInCurrentCategory)) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); // progBar->AddStepsToDo(2); // progBar->Progress(1); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkExtWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); // show the shortcut bar and progress indicator, which are hidden by // default //configurer->SetShowPerspectiveBar(true); //configurer->SetShowFastViewBars(true); //configurer->SetShowProgressIndicator(true); // // add the drag and drop support for the editor area // configurer.addEditorAreaTransfer(EditorInputTransfer.getInstance()); // configurer.addEditorAreaTransfer(ResourceTransfer.getInstance()); // configurer.addEditorAreaTransfer(FileTransfer.getInstance()); // configurer.addEditorAreaTransfer(MarkerTransfer.getInstance()); // configurer.configureEditorAreaDropListener(new EditorAreaDropAdapter( // configurer.getWindow())); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkExtWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } auto introPart = configurer->GetWindow()->GetWorkbench()->GetIntroManager()->GetIntro(); if (introPart.IsNotNull()) { configurer->GetWindow()->GetWorkbench()->GetIntroManager()->ShowIntro(GetWindowConfigurer()->GetWindow(), false); } } void QmitkExtWorkbenchWindowAdvisor::onIntro() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkExtWorkbenchWindowAdvisor::onHelp() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkExtWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkExtWorkbenchWindowAdvisor::onAbout() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onAbout(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkExtWorkbenchWindowAdvisorHack::QmitkExtWorkbenchWindowAdvisorHack() : QObject() { } QmitkExtWorkbenchWindowAdvisorHack::~QmitkExtWorkbenchWindowAdvisorHack() { } void QmitkExtWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkExtWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if( wbench == nullptr ) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if( wbench_window.IsNull() ) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if( wbench_page.IsNull() ) return; auto wbench_view = wbench_page->FindView( view_query_name ); if( wbench_view.IsNotNull() ) { bool isViewVisible = wbench_page->IsPartVisible( wbench_view ); if( isViewVisible ) { wbench_page->HideView( wbench_view ); return; } } wbench_page->ShowView( view_query_name ); } void QmitkExtWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onViewNavigator() { // show/hide viewnavigatorView - SafeHandleNavigatorView("org.mitk.views.viewnavigatorview"); + SafeHandleNavigatorView("org.mitk.views.viewnavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkExtWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkExtWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkExtWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkExtWorkbenchWindowAdvisorHack::onNewWindow() { berry::PlatformUI::GetWorkbench()->OpenWorkbenchWindow(nullptr); } void QmitkExtWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkExtWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(),nullptr); aboutDialog->open(); } void QmitkExtWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkExtWorkbenchWindowAdvisor>(this, &QmitkExtWorkbenchWindowAdvisor::PropertyChange)); // configurer.getWindow().addPageListener(new IPageListener() { // public void pageActivated(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageClosed(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageOpened(IWorkbenchPage page) { // // do nothing // } // }); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkExtWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkExtWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkExtWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } if (!lastActiveEditor.Expired()) { lastActiveEditor.Lock()->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkExtWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { if (!lastActiveEditor.Expired()) { QString newTitle = lastActiveEditor.Lock()->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkExtWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkExtWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkExtWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); QSettings settings(GetQSettingsFile(), QSettings::IniFormat); settings.setValue("ToolbarPosition", mainWindow->saveState()); } QString QmitkExtWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkCommonExtPlugin::getContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } diff --git a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp index 826357b8b4..7b2e41bc87 100644 --- a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp @@ -1,1146 +1,1146 @@ /*============================================================================ 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 "QmitkFlowApplicationWorkbenchWindowAdvisor.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 #include #include "QmitkExtFileSaveProjectAction.h" #include #include #include #include #include #include #include #include // UGLYYY #include "QmitkFlowApplicationWorkbenchWindowAdvisorHack.h" #include "QmitkFlowApplicationPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include QmitkFlowApplicationWorkbenchWindowAdvisorHack* QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack = new QmitkFlowApplicationWorkbenchWindowAdvisorHack(); QString QmitkFlowApplicationWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; QmitkFlowApplicationWorkbenchWindowAdvisor::QmitkFlowApplicationWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showVersionInfo(true) , showMitkVersionInfo(true) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); - viewExcludeList.push_back("org.mitk.views.viewnavigatorview"); + viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkFlowApplicationWorkbenchWindowAdvisor::~QmitkFlowApplicationWorkbenchWindowAdvisor() { } QWidget* QmitkFlowApplicationWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkFlowApplicationWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; - if ((*iter)->GetId() == "org.mitk.views.viewnavigatorview") + if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileSaveProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== View Toolbar ================================== if (showViewToolbar) { auto prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); berry::IPreferences::Pointer stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (auto labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (auto category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); QList > relevantViewDescriptors; for (auto viewDescriptor : viewDescriptorsInCurrentCategory) { if (viewDescriptor->GetId() != "org.mitk.views.flow.control") { relevantViewDescriptors.push_back(viewDescriptor); } } if (!relevantViewDescriptors.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (auto viewDescriptor : relevantViewDescriptors) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::onIntro() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelp() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onAbout() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onAbout(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkFlowApplicationWorkbenchWindowAdvisor>(this, &QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange)); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkFlowApplicationWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkFlowApplicationWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } if (!lastActiveEditor.Expired()) { lastActiveEditor.Lock()->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { if (!lastActiveEditor.Expired()) { QString newTitle = lastActiveEditor.Lock()->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); QSettings settings(GetQSettingsFile(), QSettings::IniFormat); settings.setValue("ToolbarPosition", mainWindow->saveState()); } QString QmitkFlowApplicationWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkFlowApplicationWorkbenchWindowAdvisorHack::QmitkFlowApplicationWorkbenchWindowAdvisorHack() : QObject() { } QmitkFlowApplicationWorkbenchWindowAdvisorHack::~QmitkFlowApplicationWorkbenchWindowAdvisorHack() { } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if (wbench == nullptr) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if (wbench_window.IsNull()) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if (wbench_page.IsNull()) return; auto wbench_view = wbench_page->FindView(view_query_name); if (wbench_view.IsNotNull()) { bool isViewVisible = wbench_page->IsPartVisible(wbench_view); if (isViewVisible) { wbench_page->HideView(wbench_view); return; } } wbench_page->ShowView(view_query_name); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(), nullptr); aboutDialog->open(); } 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 60f918782e..ada96a36f3 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,473 +1,469 @@ /*============================================================================ 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 "mitkPlanarFigureMaskGenerator.h" #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); 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_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); m_DataGenerator->SetDataStorage(this->GetDataStorage()); m_DataGenerator->SetAutoUpdate(true); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); m_Controls.imageNodesSelector->SetDataStorage(this->GetDataStorage()); m_Controls.imageNodesSelector->SetNodePredicate(mitk::GetImageStatisticsImagePredicate()); m_Controls.imageNodesSelector->SetSelectionCheckFunction(this->CheckForSameGeometry()); m_Controls.imageNodesSelector->SetSelectionIsOptional(false); m_Controls.imageNodesSelector->SetInvalidInfo(QStringLiteral("Please select images for statistics")); m_Controls.imageNodesSelector->SetPopUpTitel(QStringLiteral("Select input images")); m_Controls.roiNodesSelector->SetPopUpHint(QStringLiteral("You may select multiple images for the statistics computation. But all selected images must have the same geometry.")); m_Controls.roiNodesSelector->SetDataStorage(this->GetDataStorage()); m_Controls.roiNodesSelector->SetNodePredicate(this->GenerateROIPredicate()); m_Controls.roiNodesSelector->SetSelectionIsOptional(true); m_Controls.roiNodesSelector->SetEmptyInfo(QStringLiteral("Please select ROIs")); m_Controls.roiNodesSelector->SetPopUpTitel(QStringLiteral("Select ROIs for statistics computation")); m_Controls.roiNodesSelector->SetPopUpHint(QStringLiteral("You may select ROIs (e.g. planar figures, segmentations) that should be used for the statistics computation. The statistics will only computed for the image parts defined by the ROIs.")); CreateConnections(); this->m_TimePointChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_TimePointChangeListener, &QmitkSliceNavigationListener::SelectedTimePointChanged, this, & QmitkImageStatisticsView::OnSelectedTimePointChanged); } void QmitkImageStatisticsView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { this->m_TimePointChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkImageStatisticsView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { this->m_TimePointChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkImageStatisticsView::CreateConnections() { - connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, - this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); + connect(m_Controls.widget_statistics, &QmitkImageStatisticsWidget::IgnoreZeroValuedVoxelStateChanged, + this, &QmitkImageStatisticsView::OnIgnoreZeroValuedVoxelStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::DataGenerationStarted, this, &QmitkImageStatisticsView::OnGenerationStarted); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, this, &QmitkImageStatisticsView::OnGenerationFinished); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, this, &QmitkImageStatisticsView::OnJobError); connect(m_Controls.imageNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageStatisticsView::OnImageSelectionChanged); connect(m_Controls.roiNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageStatisticsView::OnROISelectionChanged); connect(m_Controls.sliderWidget_intensityProfile, &ctkSliderWidget::valueChanged, this, &QmitkImageStatisticsView::UpdateIntensityProfile); } void QmitkImageStatisticsView::UpdateIntensityProfile() { m_Controls.groupBox_intensityProfile->setVisible(false); const auto selectedImageNodes = m_Controls.imageNodesSelector->GetSelectedNodes(); const auto selectedROINodes = m_Controls.roiNodesSelector->GetSelectedNodes(); if (selectedImageNodes.size()==1 && selectedROINodes.size()==1) { //only supported for one image and roi currently auto image = dynamic_cast(selectedImageNodes.front()->GetData()); auto maskPlanarFigure = dynamic_cast(selectedROINodes.front()->GetData()); if (maskPlanarFigure != nullptr) { if (!maskPlanarFigure->IsClosed()) { 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); m_Controls.groupBox_intensityProfile->setVisible(true); m_Controls.widget_intensityProfile->Reset(); m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), "Intensity Profile of " + selectedImageNodes.front()->GetName()); } } } } void QmitkImageStatisticsView::UpdateHistogramWidget() { - bool visibility = false; + m_Controls.groupBox_histogram->setVisible(false); const auto selectedImageNodes = m_Controls.imageNodesSelector->GetSelectedNodes(); const auto selectedMaskNodes = m_Controls.roiNodesSelector->GetSelectedNodes(); if (selectedImageNodes.size() == 1 && selectedMaskNodes.size()<=1) { //currently only supported for one image and roi due to histogram widget limitations. auto imageNode = selectedImageNodes.front(); const mitk::DataNode* roiNode = nullptr; const mitk::PlanarFigure* planarFigure = nullptr; if (!selectedMaskNodes.empty()) { roiNode = selectedMaskNodes.front(); planarFigure = dynamic_cast(roiNode->GetData()); } if ((planarFigure == nullptr || planarFigure->IsClosed()) && imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(m_TimePointChangeListener.GetCurrentSelectedTimePoint())) { //if a planar figure is not closed, we show the intensity profile instead of the histogram. auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode, true); if (statisticsNode.IsNotNull()) { auto statistics = dynamic_cast(statisticsNode->GetData()); if (statistics) { const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(m_TimePointChangeListener.GetCurrentSelectedTimePoint()); - std::stringstream label; - label << imageNode->GetName(); - if (imageNode->GetData()->GetTimeSteps() > 1) + if (statistics->TimeStepExists(timeStep)) { - label << "["<< timeStep <<"]"; + std::stringstream label; + label << imageNode->GetName(); + if (imageNode->GetData()->GetTimeSteps() > 1) + { + label << "[" << timeStep << "]"; + } + + if (roiNode) + { + label << " with " << roiNode->GetName(); + } + + //Hardcoded labels are currently needed because the current histogram widget (and ChartWidget) + //do not allow correct removal or sound update/insertion of serveral charts. + //only thing that works for now is always to update/overwrite the same data label + //This is a quick fix for T28223 and T28221 + m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(timeStep), "histogram"); + m_Controls.groupBox_histogram->setVisible(true); } - - if (roiNode) - { - label << " with " << roiNode->GetName(); - } - - //Hardcoded labels are currently needed because the current histogram widget (and ChartWidget) - //do not allow correct removal or sound update/insertion of serveral charts. - //only thing that works for now is always to update/overwrite the same data label - //This is a quick fix for T28223 and T28221 - m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(timeStep), "histogram"); - - visibility = true; } } } } - - if (visibility != m_Controls.groupBox_histogram->isVisible()) - { - m_Controls.groupBox_histogram->setVisible(visibility); - } - } 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); m_Controls.widget_histogram->SetTheme(GetColorTheme()); } void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { m_Controls.label_currentlyComputingStatistics->setVisible(true); } void QmitkImageStatisticsView::OnGenerationFinished() { m_Controls.label_currentlyComputingStatistics->setVisible(false); mitk::StatusBar::GetInstance()->Clear(); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnSelectedTimePointChanged(const mitk::TimePointType& /*newTimePoint*/) { this->UpdateHistogramWidget(); } 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 nbins) { m_Controls.widget_statistics->SetHistogramNBins(nbins); m_DataGenerator->SetHistogramNBins(nbins); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } -void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) +void QmitkImageStatisticsView::OnIgnoreZeroValuedVoxelStateChanged(int state) { auto ignoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; m_Controls.widget_statistics->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); m_DataGenerator->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { auto images = m_Controls.imageNodesSelector->GetSelectedNodesStdVector(); m_Controls.widget_statistics->SetImageNodes(images); m_Controls.widget_statistics->setEnabled(!images.empty()); m_Controls.roiNodesSelector->SetNodePredicate(this->GenerateROIPredicate()); m_DataGenerator->SetAutoUpdate(false); m_DataGenerator->SetImageNodes(images); m_DataGenerator->Generate(); m_DataGenerator->SetAutoUpdate(true); this->UpdateHistogramWidget(); this->UpdateIntensityProfile(); } void QmitkImageStatisticsView::OnROISelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { auto rois = m_Controls.roiNodesSelector->GetSelectedNodesStdVector(); m_Controls.widget_statistics->SetMaskNodes(rois); m_DataGenerator->SetAutoUpdate(false); m_DataGenerator->SetROINodes(rois); m_DataGenerator->Generate(); m_DataGenerator->SetAutoUpdate(true); this->UpdateHistogramWidget(); this->UpdateIntensityProfile(); } 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_Controls.imageNodesSelector->GetSelectedNodes()+m_Controls.roiNodesSelector->GetSelectedNodes()); if (dialog->exec()) { auto selectedNodeList = dialog->GetSelectedNodes(); m_Controls.imageNodesSelector->SetCurrentSelection(selectedNodeList); m_Controls.roiNodesSelector->SetCurrentSelection(selectedNodeList); } delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto lambda = [isMaskPredicate](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { auto castedData = dynamic_cast(node->GetData()); if (castedData != nullptr && !isMaskPredicate->CheckNode(node)) { imageNodeData = castedData; break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

"; return ss.str(); } auto imageGeoPredicate = mitk::NodePredicateGeometry::New(imageNodeData->GetGeometry()); auto maskGeoPredicate = mitk::NodePredicateSubGeometry::New(imageNodeData->GetGeometry()); for (auto& rightNode : nodes) { if (imageNodeData != rightNode->GetData()) { bool validGeometry = true; if (isMaskPredicate->CheckNode(rightNode)) { validGeometry = maskGeoPredicate->CheckNode(rightNode); } else if (dynamic_cast(rightNode->GetData())) { validGeometry = imageGeoPredicate->CheckNode(rightNode); } else { const mitk::PlanarFigure* planar2 = dynamic_cast(rightNode->GetData()); if (planar2) { validGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), imageNodeData->GetGeometry()); } } if (!validGeometry) { 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; } mitk::NodePredicateBase::Pointer QmitkImageStatisticsView::GenerateROIPredicate() const { auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); mitk::NodePredicateBase::Pointer result = isMaskOrPlanarFigurePredicate.GetPointer(); if(!m_Controls.imageNodesSelector->GetSelectedNodes().empty()) { auto image = m_Controls.imageNodesSelector->GetSelectedNodes().front()->GetData(); auto imageGeoPredicate = mitk::NodePredicateSubGeometry::New(image->GetGeometry()); auto lambda = [image, imageGeoPredicate](const mitk::DataNode* node) { bool sameGeometry = true; if (dynamic_cast(node->GetData()) != nullptr) { sameGeometry = imageGeoPredicate->CheckNode(node); } else { const auto planar2 = dynamic_cast(node->GetData()); if (planar2) { sameGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), image->GetGeometry()); } } return sameGeometry; }; result = mitk::NodePredicateAnd::New(isMaskOrPlanarFigurePredicate, mitk::NodePredicateFunction::New(lambda)).GetPointer(); } return result; } 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 3d5e633e39..c6637f5fed 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,95 +1,95 @@ /*============================================================================ 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 #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, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default destructor */ ~QmitkImageStatisticsView() override; protected: /*! \brief Creates the widget containing the application controls, like sliders, buttons etc.*/ void CreateQtPartControl(QWidget* parent) override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; using HistogramType = mitk::ImageStatisticsContainer::HistogramType; void SetFocus() override { }; virtual void CreateConnections(); void UpdateIntensityProfile(); void UpdateHistogramWidget(); QmitkChartWidget::ColorTheme GetColorTheme() const; void ResetGUI(); 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 OnIgnoreZeroValuedVoxelStateChanged(int state); void OnButtonSelectionPressed(); void OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList nodes); void OnROISelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList nodes); void OnSelectedTimePointChanged(const mitk::TimePointType& newTimePoint); // member variable Ui::QmitkImageStatisticsViewControls m_Controls; private: QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry() const; mitk::NodePredicateBase::Pointer GenerateROIPredicate() const; std::vector m_StatisticsForSelection; QmitkImageStatisticsDataGenerator* m_DataGenerator = nullptr; QmitkSliceNavigationListener m_TimePointChangeListener; }; #endif // QMITKIMAGESTATISTICSVIEW_H diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui index 1a34302c5b..60c490ee6a 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,295 +1,288 @@ QmitkImageStatisticsViewControls true 0 0 419 1016 Form 3 3 3 3 0 Statistics 5 5 5 5 Quick selector button for input data. + true 0 200 Statistics 3 3 3 3 0 0 0 0 Calculating statistics... - - - - Ignore zero-valued voxels - - - 0 200 Histogram Qt::Vertical 20 40 0 200 Intensity Profile Input data 5 5 5 5 0 0 Images 0 0 ROIs (segmentations, planarfigures...) Qt::Vertical 20 40 QmitkImageStatisticsWidget QWidget
QmitkImageStatisticsWidget.h
1
QmitkHistogramVisualizationWidget QWidget
QmitkHistogramVisualizationWidget.h
1
QmitkIntensityProfileVisualizationWidget QWidget
QmitkIntensityProfileVisualizationWidget.h
1
ctkSliderWidget QWidget
ctkSliderWidget.h
1
QmitkMultiNodeSelectionWidget QWidget
QmitkMultiNodeSelectionWidget.h
1
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 98a6bb780f..23872a6f83 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,877 +1,888 @@ /*============================================================================ 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) + m_EndInteractionObserverTag(0), + m_DuringInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; + unsigned int m_DuringInteractionObserverTag; }; 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; QLabel* m_SelectedImageLabel; 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->setEnabled(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_SelectedImageLabel = new QLabel("Selected Image"); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(d->m_SelectedImageLabel, 0, 0); d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 1, 1, 1); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); // placed after CreateConnections to trigger update of the current selection d->m_SingleNodeSelectionWidget->SetAutoSelectNewNodes(true); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); + d->m_FixedParameterBox->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); + d->m_FixedParameterBox->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 + // add observer for event when interaction with figure ends auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); + // add observer for event during interaction when a point is moved + auto duringInteractionCommand = SimpleCommandType::New(); + duringInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::UpdateMeasurementText); + data.m_DuringInteractionObserverTag = planarFigure->AddObserver(mitk::PointMovedPlanarFigureEvent(), duringInteractionCommand); + // 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); + data.m_Figure->RemoveObserver(data.m_DuringInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { mitk::CoreServicePointer propertyFilters(mitk::CoreServices::GetPropertyFilters()); mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp index 0d02206bb5..762e8e5dee 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp @@ -1,1213 +1,1168 @@ /*============================================================================ 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 "QmitkMultiLabelSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include // Qmitk #include "QmitkCreateMultiLabelPresetAction.h" #include "QmitkLoadMultiLabelPresetAction.h" #include #include #include #include // us #include #include #include #include #include // Qt #include #include #include #include #include const std::string QmitkMultiLabelSegmentationView::VIEW_ID = "org.mitk.views.multilabelsegmentation"; QmitkMultiLabelSegmentationView::QmitkMultiLabelSegmentationView() : m_Parent(nullptr), m_RenderWindowPart(nullptr), m_ToolManager(nullptr), m_ReferenceNode(nullptr), m_WorkingNode(nullptr), m_AutoSelectionEnabled(false), m_MouseCursorSet(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkMultiLabelSegmentationView::~QmitkMultiLabelSegmentationView() { OnLooseLabelSetConnection(); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls.m_WorkingNodeSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); + // Remove observer if one was registered + auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); + if (finding != m_WorkingDataObserverTags.end()) + { + m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); + m_WorkingDataObserverTags.erase(m_WorkingNode); + } + if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } if (m_WorkingNode.IsNotNull()) { this->OnLooseLabelSetConnection(); } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); this->OnEstablishLabelSetConnection(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); + m_WorkingDataObserverTags.insert(std::pair(m_WorkingNode, + m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); bool isVisible = false; workingNode->GetBoolProperty("visible", isVisible); workingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkMultiLabelSegmentationView::OnNewLabel() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { QMessageBox::information( m_Parent, "New Segmentation", "Please load and select an image before starting some action."); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Reference data needs to be an image in order to create a new segmentation."); return; } mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select a multilabel segmentation before starting some action."); return; } mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select a multilabel segmentation before starting some action."); return; } // ask about the name and organ type of the new segmentation auto dialog = new QmitkNewSegmentationDialog(m_Parent); QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); dialog->SetSuggestionList(organColors); dialog->setWindowTitle("New Label"); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } QString segName = dialog->GetSegmentationName(); if (segName.isEmpty()) { segName = "Unnamed"; } workingImage->GetActiveLabelSet()->AddLabel(segName.toStdString(), dialog->GetColor()); UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - - this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkMultiLabelSegmentationView::OnSavePreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkCreateMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnLoadPreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkLoadMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnShowLabelTable(bool value) { if (value) m_Controls.m_LabelSetWidget->show(); else m_Controls.m_LabelSetWidget->hide(); } void QmitkMultiLabelSegmentationView::OnNewSegmentationSession() { mitk::DataNode::Pointer referenceNode = m_ToolManager->GetReferenceData(0); if (referenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information(m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The selected image has multiple time steps.\n\nDo you want to create a static " "segmentation that is identical for all time steps or do you want to create a " "dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); bool ok = false; newName = QInputDialog::getText(m_Parent, "New segmentation", "New name:", QLineEdit::Normal, newName, &ok); if (!ok) { return; } if (newName.isEmpty()) { newName = "Unnamed"; } this->WaitCursorOn(); mitk::LabelSetImage::Pointer workingImage = mitk::LabelSetImage::New(); try { workingImage->Initialize(segTemplateImage); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Parent, tr("New segmentation"), tr("Could not create a new segmentation.\n")); return; } this->WaitCursorOff(); mitk::DataNode::Pointer workingNode = mitk::DataNode::New(); workingNode->SetData(workingImage); workingNode->SetName(newName.toStdString()); workingImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(referenceNode->GetName().c_str())); workingImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New(newName.toStdString().c_str())); this->GetDataStorage()->Add(workingNode, referenceNode); m_Controls.m_WorkingNodeSelector->SetCurrentSelectedNode(workingNode); OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) m_RenderWindowPart->SetSelectedPosition(pos); } void QmitkMultiLabelSegmentationView::OnResetView() { if (m_RenderWindowPart) m_RenderWindowPart->ForceImmediateUpdate(); } void QmitkMultiLabelSegmentationView::OnAddLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } QString question = "Do you really want to add a layer to the current segmentation session?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Add layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) return; try { WaitCursorOn(); workingImage->AddLayer(); WaitCursorOff(); } catch ( mitk::Exception& e ) { WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( m_Controls.m_LabelSetWidget, "Add Layer", "Could not add a new layer. See error log for details.\n"); return; } OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnDeleteLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } if (workingImage->GetNumberOfLayers() < 2) return; QString question = "Do you really want to delete the current layer?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } try { this->WaitCursorOn(); workingImage->RemoveLayer(); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Controls.m_LabelSetWidget, "Delete Layer", "Could not delete the currently active layer. See error log for details.\n"); return; } UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnPreviousLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } OnChangeLayer(workingImage->GetActiveLayer() - 1); } void QmitkMultiLabelSegmentationView::OnNextLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } OnChangeLayer(workingImage->GetActiveLayer() + 1); } void QmitkMultiLabelSegmentationView::OnChangeLayer(int layer) { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->SetActiveLayer(layer); this->WaitCursorOff(); UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnLockExteriorToggled(bool checked) { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } workingImage->GetLabel(0)->SetLocked(checked); } void QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged(int index) { if (index == 1) { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false);//OnToggleWidgetActivation(false); m_Controls.m_swInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->show(); } else if (index == 2) { m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(1); m_Controls.m_swInterpolation->show(); } else { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(2); m_Controls.m_swInterpolation->hide(); } } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkMultiLabelSegmentationView::CreateQtPartControl(QWidget *parent) { m_Parent = parent; m_Controls.setupUi(parent); m_Controls.m_tbSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls.m_tbLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); // *------------------------ // * Shortcuts // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls.m_ReferenceNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_ReferenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls.m_ReferenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls.m_WorkingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls.m_WorkingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls.m_ReferenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged); connect(m_Controls.m_WorkingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * ToolManager // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); m_Controls.m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls.m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); // *------------------------ // * LabelSetWidget // *------------------------ m_Controls.m_LabelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls.m_LabelSetWidget->SetOrganColors(mitk::OrganNamesHandling::GetDefaultOrganColorString()); m_Controls.m_LabelSetWidget->hide(); // *------------------------ // * Interpolation // *------------------------ m_Controls.m_SurfaceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); m_Controls.m_SliceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); connect(m_Controls.m_cbInterpolation, QOverload::of(&QComboBox::activated), this, &QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged); m_Controls.m_cbInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->hide(); m_Controls.m_gbInterpolation->hide(); // See T27436 QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold"); std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // *------------------------ // * ToolSelection 2D // *------------------------ m_Controls.m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer2D); m_Controls.m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls.m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls.m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkMultiLabelSegmentationView::OnManualTool2DSelected); // *------------------------ // * ToolSelection 3D // *------------------------ m_Controls.m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer3D); m_Controls.m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); // todo add : FastMarching3D RegionGrowing Watershed m_Controls.m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // *------------------------* // * Connect Buttons // *------------------------* connect(m_Controls.m_pbNewLabel, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNewLabel); connect(m_Controls.m_tbSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnSavePreset); connect(m_Controls.m_tbLoadPreset, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnLoadPreset); connect(m_Controls.m_pbNewSegmentationSession, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNewSegmentationSession); connect(m_Controls.m_pbShowLabelTable, &QToolButton::toggled, this, &QmitkMultiLabelSegmentationView::OnShowLabelTable); // *------------------------* // * Connect LabelSetWidget // *------------------------* connect(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkMultiLabelSegmentationView::OnGoToLabel); connect(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::resetView, this, &QmitkMultiLabelSegmentationView::OnResetView); // *------------------------* // * DATA SLECTION WIDGET // *------------------------* connect(m_Controls.m_btAddLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnAddLayer); connect(m_Controls.m_btDeleteLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnDeleteLayer); connect(m_Controls.m_btPreviousLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnPreviousLayer); connect(m_Controls.m_btNextLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNextLayer); connect(m_Controls.m_btLockExterior, &QToolButton::toggled, this, &QmitkMultiLabelSegmentationView::OnLockExteriorToggled); connect(m_Controls.m_cbActiveLayer, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiLabelSegmentationView::OnChangeLayer); - m_Controls.m_btAddLayer->setEnabled(false); - m_Controls.m_btDeleteLayer->setEnabled(false); - m_Controls.m_btNextLayer->setEnabled(false); - m_Controls.m_btPreviousLayer->setEnabled(false); - m_Controls.m_cbActiveLayer->setEnabled(false); - - m_Controls.m_pbNewLabel->setEnabled(false); - m_Controls.m_btLockExterior->setEnabled(false); - m_Controls.m_tbSavePreset->setEnabled(false); - m_Controls.m_tbLoadPreset->setEnabled(false); - m_Controls.m_pbShowLabelTable->setEnabled(false); - - // set callback function for already existing segmentation nodes - mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(m_SegmentationPredicate); - for (mitk::DataStorage::SetOfObjects::const_iterator iter = allSegmentations->begin(); iter != allSegmentations->end(); ++iter) - { - mitk::DataNode* node = *iter; - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - } - auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls.m_ReferenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls.m_WorkingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part QList controllers; controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } void QmitkMultiLabelSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkMultiLabelSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { bool slimView = prefs->GetBool("slim view", false); m_Controls.m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls.m_ManualToolSelectionBox3D->SetShowNames(!slimView); m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); this->ApplyDisplayOptions(); } void QmitkMultiLabelSegmentationView::NodeAdded(const mitk::DataNode* node) { - if (!m_SegmentationPredicate->CheckNode(node)) + if (m_SegmentationPredicate->CheckNode(node)) { - return; + this->ApplyDisplayOptions(const_cast(node)); } - - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - - ApplyDisplayOptions(const_cast(node)); } void QmitkMultiLabelSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext *context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService *service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; } - - mitk::DataNode* tempNode = const_cast(node); - //Remove observer if one was registered - auto finding = m_WorkingDataObserverTags.find(tempNode); - if (finding != m_WorkingDataObserverTags.end()) - { - node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); - m_WorkingDataObserverTags.erase(tempNode); - } } void QmitkMultiLabelSegmentationView::ApplyDisplayOptions() { if (!m_Parent) { return; } mitk::DataNode::Pointer workingData = m_ToolManager->GetWorkingData(0); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is a multi label segmentation node->SetProperty("labelset.contour.active", drawOutline); // force render window update to show outline node->GetData()->Modified(); } else if (nullptr != node->GetData()) { // node is actually a 'single label' segmentation, // but its outline property can be set in the 'multi label' segmentation preference page as well bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkMultiLabelSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { // node is a "single label" / "binary" image --> no label set return; } workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateGUI); } void QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { // data (type) was changed in-place, e.g. LabelSetImage -> (binary) image return; } // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateGUI); } void QmitkMultiLabelSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkMultiLabelSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkMultiLabelSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls.m_pbNewSegmentationSession->setEnabled(false); - m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_gbInterpolation->setEnabled(false); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(false); - m_Controls.m_LabelSetWidget->setEnabled(false); - m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btLockExterior->setChecked(false); - m_Controls.m_btLockExterior->setEnabled(false); - m_Controls.m_tbSavePreset->setEnabled(false); - m_Controls.m_tbLoadPreset->setEnabled(false); m_Controls.m_pbShowLabelTable->setChecked(false); - m_Controls.m_pbShowLabelTable->setEnabled(false); if (hasReferenceNode) { m_Controls.m_pbNewSegmentationSession->setEnabled(true); } if (hasWorkingNode) { - m_Controls.m_pbNewLabel->setEnabled(true); - m_Controls.m_btLockExterior->setEnabled(true); - m_Controls.m_tbSavePreset->setEnabled(true); - m_Controls.m_tbLoadPreset->setEnabled(true); - m_Controls.m_pbShowLabelTable->setEnabled(true); - m_Controls.m_LabelSetWidget->setEnabled(true); - m_Controls.m_btAddLayer->setEnabled(true); - m_Controls.m_cbActiveLayer->blockSignals(true); m_Controls.m_cbActiveLayer->clear(); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (nullptr != workingImage) { int numberOfLayers = workingImage->GetNumberOfLayers(); for (unsigned int lidx = 0; lidx < workingImage->GetNumberOfLayers(); ++lidx) { m_Controls.m_cbActiveLayer->addItem(QString::number(lidx)); } int activeLayer = workingImage->GetActiveLayer(); m_Controls.m_cbActiveLayer->setCurrentIndex(activeLayer); m_Controls.m_cbActiveLayer->blockSignals(false); - m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btDeleteLayer->setEnabled(numberOfLayers > 1); + m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btPreviousLayer->setEnabled(activeLayer > 0); m_Controls.m_btNextLayer->setEnabled(activeLayer != numberOfLayers - 1); m_Controls.m_btLockExterior->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); m_Controls.m_pbShowLabelTable->setChecked(workingImage->GetNumberOfLabels() > 1 /*1st is exterior*/); } } if (hasWorkingNode && hasReferenceNode) { m_Controls.m_gbInterpolation->setEnabled(true); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(true); int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkMultiLabelSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); + m_Controls.groupBox_Layer->setEnabled(false); + m_Controls.groupBox_Labels->setEnabled(false); + m_Controls.m_LabelSetWidget->setEnabled(false); // the argument is actually not used // enable status depends on the tool manager selection m_Controls.m_ManualToolSelectionBox2D->setEnabled(false); m_Controls.m_ManualToolSelectionBox3D->setEnabled(false); mitk::DataNode* referenceNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls.m_WorkingNodeSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); - auto referenceNodeIsVisible = renderWindowPart && - referenceNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); - if (!referenceNodeIsVisible) - { - this->UpdateWarningLabel(tr("The selected reference image is currently not visible!")); - return; - } - auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry *worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); + m_Controls.groupBox_Layer->setEnabled(true); + m_Controls.groupBox_Labels->setEnabled(true); + m_Controls.m_LabelSetWidget->setEnabled(true); m_Controls.m_ManualToolSelectionBox2D->setEnabled(true); m_Controls.m_ManualToolSelectionBox3D->setEnabled(true); return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkMultiLabelSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls.lblSegmentationWarnings->hide(); } else { m_Controls.lblSegmentationWarnings->show(); } m_Controls.lblSegmentationWarnings->setText("" + text + ""); } diff --git a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp index 70bc475007..a6c3dd90d8 100644 --- a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp +++ b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp @@ -1,371 +1,395 @@ /*============================================================================ 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 "QmitkAddNewPropertyDialog.h" #include "QmitkPropertyItemDelegate.h" #include "QmitkPropertyItemModel.h" #include "QmitkPropertyItemSortFilterProxyModel.h" #include "QmitkPropertyTreeView.h" #include #include #include #include #include #include #include #include +namespace +{ + QmitkAbstractNodeSelectionWidget::NodeList GetInitialSelection(berry::ISelection::ConstPointer selection) + { + if (selection.IsNotNull() && !selection->IsEmpty()) + { + auto* dataNodeSelection = dynamic_cast(selection.GetPointer()); + + if (nullptr != dataNodeSelection) + { + auto firstSelectedDataNode = dataNodeSelection->GetSelectedDataNodes().front(); + + if (firstSelectedDataNode.IsNotNull()) + { + QmitkAbstractNodeSelectionWidget::NodeList initialSelection; + initialSelection.push_back(firstSelectedDataNode); + + return initialSelection; + } + } + } + + return QmitkAbstractNodeSelectionWidget::NodeList(); + } +} + const std::string QmitkPropertyTreeView::VIEW_ID = "org.mitk.views.properties"; QmitkPropertyTreeView::QmitkPropertyTreeView() : m_PropertyAliases(mitk::CoreServices::GetPropertyAliases(nullptr), nullptr), m_PropertyDescriptions(mitk::CoreServices::GetPropertyDescriptions(nullptr), nullptr), m_PropertyPersistence(mitk::CoreServices::GetPropertyPersistence(nullptr), nullptr), m_ProxyModel(nullptr), m_Model(nullptr), m_Delegate(nullptr), m_Renderer(nullptr) { } QmitkPropertyTreeView::~QmitkPropertyTreeView() { } void QmitkPropertyTreeView::SetFocus() { m_Controls.filterLineEdit->setFocus(); } void QmitkPropertyTreeView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_Controls.propertyListComboBox->count() == 2) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); Q_FOREACH(QString renderWindow, renderWindows.keys()) { m_Controls.propertyListComboBox->insertItem(m_Controls.propertyListComboBox->count() - 1, QString("Data node: ") + renderWindow); } } } void QmitkPropertyTreeView::RenderWindowPartDeactivated(mitk::IRenderWindowPart*) { if (m_Controls.propertyListComboBox->count() > 2) { m_Controls.propertyListComboBox->clear(); m_Controls.propertyListComboBox->addItem("Data node: common"); m_Controls.propertyListComboBox->addItem("Base data"); } } void QmitkPropertyTreeView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.propertyListComboBox->addItem("Data node: common"); mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); if (renderWindowPart != nullptr) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); for(const auto& renderWindow : renderWindows.keys()) { m_Controls.propertyListComboBox->addItem(QString("Data node: ") + renderWindow); } } m_Controls.propertyListComboBox->addItem("Base data"); m_Controls.newButton->setEnabled(false); this->HideAllIcons(); m_ProxyModel = new QmitkPropertyItemSortFilterProxyModel(m_Controls.treeView); m_Model = new QmitkPropertyItemModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_Model); m_ProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setDynamicSortFilter(true); m_Delegate = new QmitkPropertyItemDelegate(m_Controls.treeView); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); - m_Controls.singleSlot->SetAutoSelectNewNodes(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); m_Controls.filterLineEdit->setClearButtonEnabled(true); m_Controls.treeView->setItemDelegateForColumn(1, m_Delegate); m_Controls.treeView->setModel(m_ProxyModel); m_Controls.treeView->setColumnWidth(0, 160); m_Controls.treeView->sortByColumn(0, Qt::AscendingOrder); m_Controls.treeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.treeView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.treeView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked); const int ICON_SIZE = 32; auto icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tags.svg")); m_Controls.tagsLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tag.svg")); m_Controls.tagLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg")); m_Controls.saveLabel->setPixmap(icon.pixmap(ICON_SIZE)); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkPropertyTreeView::OnCurrentSelectionChanged); connect(m_Controls.filterLineEdit, &QLineEdit::textChanged, this, &QmitkPropertyTreeView::OnFilterTextChanged); connect(m_Controls.propertyListComboBox, static_cast(&QComboBox::currentIndexChanged), this, &QmitkPropertyTreeView::OnPropertyListChanged); connect(m_Controls.newButton, &QPushButton::clicked, this, &QmitkPropertyTreeView::OnAddNewProperty); connect(m_Controls.treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QmitkPropertyTreeView::OnCurrentRowChanged); connect(m_Model, &QmitkPropertyItemModel::modelReset, this, &QmitkPropertyTreeView::OnModelReset); + + auto selection = this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + auto currentSelection = GetInitialSelection(selection); + + if (!currentSelection.isEmpty()) + m_Controls.singleSlot->SetCurrentSelection(currentSelection); } void QmitkPropertyTreeView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } QString QmitkPropertyTreeView::GetPropertyNameOrAlias(const QModelIndex& index) { QString propertyName; if (index.isValid()) { QModelIndex current = index; while (current.isValid()) { QString name = m_ProxyModel->data(m_ProxyModel->index(current.row(), 0, current.parent())).toString(); propertyName.prepend(propertyName.isEmpty() ? name : name.append('.')); current = current.parent(); } } return propertyName; } void QmitkPropertyTreeView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; this->SetPartName("Properties"); m_Model->SetPropertyList(nullptr); m_Delegate->SetPropertyList(nullptr); m_Controls.newButton->setEnabled(false); return; } // node is selected, create tree with node properties m_SelectedNode = nodes.front(); mitk::PropertyList* propertyList = m_Model->GetPropertyList(); if (m_Renderer == nullptr && m_Controls.propertyListComboBox->currentText() == "Base data") { propertyList = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetPropertyList() : nullptr; } else { propertyList = m_SelectedNode->GetPropertyList(m_Renderer); } QString selectionClassName = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetNameOfClass() : ""; m_SelectionClassName = selectionClassName.toStdString(); m_Model->SetPropertyList(propertyList, selectionClassName); m_Delegate->SetPropertyList(propertyList); m_Controls.newButton->setEnabled(true); - - if (!m_ProxyModel->filterRegExp().isEmpty()) - { - m_Controls.treeView->expandAll(); - } + m_Controls.treeView->expandAll(); } void QmitkPropertyTreeView::HideAllIcons() { m_Controls.tagLabel->hide(); m_Controls.tagsLabel->hide(); m_Controls.saveLabel->hide(); } void QmitkPropertyTreeView::OnCurrentRowChanged(const QModelIndex& current, const QModelIndex&) { if (current.isValid()) { QString name = this->GetPropertyNameOrAlias(current); if (!name.isEmpty()) { QString alias; bool isTrueName = true; std::string trueName = m_PropertyAliases->GetPropertyName(name.toStdString()); if (trueName.empty() && !m_SelectionClassName.empty()) trueName = m_PropertyAliases->GetPropertyName(name.toStdString(), m_SelectionClassName); if (!trueName.empty()) { alias = name; name = QString::fromStdString(trueName); isTrueName = false; } QString description = QString::fromStdString(m_PropertyDescriptions->GetDescription(name.toStdString())); std::vector aliases; if (!isTrueName) { aliases = m_PropertyAliases->GetAliases(name.toStdString(), m_SelectionClassName); if (aliases.empty() && !m_SelectionClassName.empty()) aliases = m_PropertyAliases->GetAliases(name.toStdString()); } bool isPersistent = m_PropertyPersistence->HasInfo(name.toStdString()); if (!description.isEmpty() || !aliases.empty() || isPersistent) { QString customizedDescription; if (!aliases.empty()) { customizedDescription = "

" + name + "

"; std::size_t numAliases = aliases.size(); std::size_t lastAlias = numAliases - 1; for (std::size_t i = 0; i < numAliases; ++i) { customizedDescription += i != lastAlias ? "
" : "
"; customizedDescription += QString::fromStdString(aliases[i]) + "
"; } } else { customizedDescription = "

" + name + "

"; } if (!description.isEmpty()) customizedDescription += "

" + description + "

"; m_Controls.tagsLabel->setVisible(!aliases.empty() && aliases.size() > 1); m_Controls.tagLabel->setVisible(!aliases.empty() && aliases.size() == 1); m_Controls.saveLabel->setVisible(isPersistent); m_Controls.descriptionLabel->setText(customizedDescription); m_Controls.descriptionLabel->show(); return; } } } m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } void QmitkPropertyTreeView::OnPropertyListChanged(int index) { if (index == -1) return; QString renderer = m_Controls.propertyListComboBox->itemText(index); if (renderer.startsWith("Data node: ")) renderer = QString::fromStdString(renderer.toStdString().substr(11)); m_Renderer = nullptr; if (renderer != "common" && renderer != "Base data") { auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) m_Renderer = renderWindowPart->GetQmitkRenderWindow(renderer)->GetRenderer(); } QList nodes; if (m_SelectedNode.IsNotNull()) nodes << m_SelectedNode; this->OnCurrentSelectionChanged(nodes); } void QmitkPropertyTreeView::OnAddNewProperty() { std::unique_ptr dialog(m_Controls.propertyListComboBox->currentText() != "Base data" ? new QmitkAddNewPropertyDialog(m_SelectedNode, m_Renderer) : new QmitkAddNewPropertyDialog(m_SelectedNode->GetData())); if (dialog->exec() == QDialog::Accepted) this->m_Model->Update(); } void QmitkPropertyTreeView::OnFilterTextChanged(const QString& filter) { m_ProxyModel->setFilterWildcard(filter); - - if (filter.isEmpty()) - m_Controls.treeView->collapseAll(); - else - m_Controls.treeView->expandAll(); + m_Controls.treeView->expandAll(); } void QmitkPropertyTreeView::OnModelReset() { + m_Controls.treeView->expandAll(); m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index efcc12808f..ec740d3f74 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,285 +1,321 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the segmentation view",2.00} Some of the features described below are closed source additions to the open source toolkit MITK and are not available in every application. \tableofcontents \section org_mitk_views_segmentationUserManualOverview Overview Segmentation is the act of partitioning an image into subsets by either manual or automated delineation to create i.e. a distinction between foreground and background. The MITK segmentation plugin allows you to create segmentations of anatomical and pathological structures in medical images. The plugin consists of a number of views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Segmentation post-processing
In this documentation, the features and usage of the segmentation view will be described. For an introduction to the segmentation utilities and clipping plane views, please be referred to the respective documentation pages. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation plugin overview", 16.00} \section org_mitk_views_segmentationPreferences Preferences The segmentation plugin offers a number of preferences which can be set via the MITK Workbench application preferences: \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Slim view: Allows you to show or hide the tool button description of the segmentation view
  • 2D display: Specify whether the segmentation is drawn as outline or as a transparent overlay
  • 3D display: Activate 3D volume rendering for your segmentation
  • Data node selection mode: If activated the segmentation image is automatically chosen from the data manager selection.
  • Smoothed surface creation: Set certain smoothing parameters for surface creation
\section org_mitk_views_segmentationUserManualTechnical Technical Issues The segmentation plugin makes a number of assumptions:
  • Images must be 2D, 3D, or 3D+t.
  • Images must be single-values, i.e. CT, MRI or "normal" ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations are handled as binary images of the same extent as the original image.
\section org_mitk_views_segmentationUserManualImageSelection Data Selection & Creating New Segmentations To select a reference image for the segmentation, click on the Patient Image selection widget and choose a suitable image from the selection available in the data manager. By default the auto selection mode is enabled (see \ref org_mitk_views_segmentationPreferences).\n Once a patient image is selected, a new segmentation can be created on this reference image by clicking the New... button to the right of the Segmentation selection widget. An input field will appear which allows you to set the name and display color of the segmentation. Notice that the input field suggests names once you start typing and that it also suggests colors for known organ names. Once generated the segmentation will be added with "binary mask" icon to the data manager as sub-node of the reference image. This item is automatically selected for you, allowing you to start editing the new segmentation right away. \subsection org_mitk_views_segmentationUserManualManualKringeling2 Selecting Segmentations for Editing Alternatively to creating a new segmentation, an existing one can be edited as well. As you might have segmented multiple structures within a single image, the application needs to know which of them to use for editing. For that you click the segmentation selection widget and a selection field will open, containing all suitable segmentations for the parent dataset available in the data manager. \section org_mitk_views_segmentationUserManualToolOverview Segmentation Tool Overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting/adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentationUserManualManualKringeling 2D Segmentation Tools With 2D manual contouring you define which voxels are part of the segmentation and which ones are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. \subsection org_mitk_views_segmentationUserManualManualKringeling3 Selecting Editing Tools To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). \subsection org_mitk_views_segmentationUserManualManualKringeling4 Using Editing Tools All of the editing tools work by the same principle: you use the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), move the mouse while holding the mouse button and release to finish the editing action. Multi-step undo and redo is fully supported by all editing tools. Use the application-wide undo button in the toolbar to revert erroneous %actions. Remark: If you are familiar with the MITK Workbench, you know that clicking and moving the mouse in any of the 2D render windows will move around the crosshair that defines what part of the image is displayed. This behavior is disabled whilst any of the manual segmentation tools are active- otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationUserManualAddSubtractTools Add and Subtract Tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and Subtract Tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationUserManualPaintWipeTools Paint and Wipe Tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and Wipe Tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationUserManualRegionGrowingTool Region Growing Tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region Growing Tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This enables you to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up/down is different from left/right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_IMGLeakage.png,"Leakage correction feature of the Region Growing tool",11.28} \endif \subsection org_mitk_views_segmentationUserManualFillTool Fill Tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill Tool",3.81} Left-click inside a segmentation with holes to completely fill all holes. Left-click inside a hole to fill only this specific hole. \subsection org_mitk_views_segmentationUserManualEraseTool Erase Tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase Tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmentations (left-click on specific segmentation) or to clear a whole slice at once (left-click outside a segmentation). \subsection org_mitk_views_segmentationUserManualLiveWireTool Live Wire Tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live Wire Tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00}
  • (1) To start the tool you have to double-click near the edge of the object you want to segment. The initial anchor point will snap to the edge within a 3x3 region.
  • (2) Move the mouse. You don't have trace the edge of the object. The contour will automatically snap to it.
  • (3) To fix a segment you can set anchor points by single left mouse button click.
  • (4) Go on with moving the mouse and setting anchor points.
  • (5) To close the contour double-click on the initial anchor point.
  • (6) After closing, the contour can be edited by moving, inserting and deleting anchor points.
The contour will be transferred to its binary image representation by deactivating the tool. \subsection org_mitk_views_segmentationUserManual2DFastMarchingTool 2D Fast Marching Tool \imageMacro{QmitkSegmentation_IMG2DFastMarchingUsage.png,"2D Fast Marching Tool",3.01} Provides a fast marching based 2D interaction segmentation tool. You start with setting seed points in an image slice. Via several sliders you can adapt parameters and see the fast marching result instantly. \subsection org_mitk_views_segmentationUserManualManualKringeling5 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D Interpolation Usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D Interpolation Usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since Redo/Undo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentationUserManual3DSegmentationTools 3D Segmentation Tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentationUserManual3DThresholdTool 3D Threshold Tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentationUserManual3DULTool 3D Upper/Lower Threshold Tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D Upper/Lower Threshold tool",10.00} \subsection org_mitk_views_segmentationUserManual3DOtsuTool 3D Otsu Tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentationUserManual3DFMTool 3D Fast Marching Tool The 3D Fast Marching tool works similar to the 2D pendant but on the whole image. Depending on your image size the calculation might take some time. You can interactively set the parameters of the algorithm via the GUI. The resulting segmentation will be automatically updated. \imageMacro{QmitkSegmentation_3DFMTool.png,"3D Fast Marching tool",10.00} \subsection org_mitk_views_segmentationUserManual3DRGTool 3D Region Growing Tool The 3D Region Growing tool works similar to the 2D pendant. At the beginning you have to place a seedpoint and define a threshold interval. If you press Run Segmentation a preview is calculated. By moving the Adapt region growing slider you can interactively adapt the segmentation result. \imageMacro{QmitkSegmentation_3DRGTool.png,"3D Region Growing tool",10.00} \subsection org_mitk_views_segmentationUserManual3DWatershedTool 3D Watershed Tool This tool provides a watershed based segmentation algorithm. For a detailed explanation of the parameters level and threshold, please be referred to https://itk.org/Doxygen/html/classitk_1_1WatershedImageFilter.html . Remark: The tool is (due to its implementation) faster if you use lower levels. So in doubt you should start with high levels (supposed to undersegment) and then lower the levels until you are happy. \imageMacro{QmitkSegmentation_3DWatershedTool.png,"3D Watershed tool",10.00} \subsection org_mitk_views_segmentationUserManualPickingTool Picking Tool The Picking tool allows you to select islands within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} +\subsection org_mitk_views_segmentationUserManualnnUNetTool nnU-Net Tool (Windows & Ubuntu only) + +This tool provides a GUI to the deep learning-based segmentation algorithm called the nnUNet. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) +for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
+Remark: The tool assumes that you have a Python3 environment with nnUNet (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. +\imageMacro{QmitkSegmentation_3DWatershedTool.png,"nnUNet tool",10.00} + +\subsubsection org_mitk_views_segmentationUserManualnnUNetToolWorkflow Workflow: + -# Click on the "nnUNet Results Folder" directory icon and navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as + per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. + \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} + -# Choose your required configuration-task-trainer-fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). + Note that, if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. + -# For ensemble predictions, you will get the option to select parameters based on postprocessing files available in the ensembles configuration of RESULTS_FOLDER. + Note that, all folds for both configurations will be considered for ensemble inferencing. + \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} + -# If your task requires multi-modal inputs, then check the "Multi-Modal" checkbox and enter the no.of modalities in the "No. of Extra Modalities" spinbox you'd like to refer to. + Instantly, as much node selectors should appear below to select other image nodes from the Data Manager along with a deactivated selector with preselected with the reference node. + Certain pre-trained configurations are trained in a particular modal order. Please be aware of the order beforehand & select the image nodes in the node selectors accordingly for accurate inferencing. + If the reference image node for segmentation is not the first in the multi-modal order, then you may reposition it using the "Reference Image Position" spinbox. + \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} + -# Click on "Advanced" to choose a Python environment. Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. + Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder. + No need to select all the way until "../myenv/bin/python", for example. + -# Click on "Preview". + \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} + -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. + -# Use "GPU Id" to change the preferred GPU for inferencing. This is equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. + +\subsubsection org_mitk_views_segmentationUserManualnnUNetToolMisc Miscellaneous: + -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. + -# If you have a nnUNet code-base yet to be pip-installed into any environment (eg. developer modified nnUNet forks), click checkbox "No pip". + Once the checkbox is checked a new directory selector appears in the section. You may click and navigate to your nnUNet fork folder to select the code from which you want inference. + \imageMacro{QmitkSegmentation_nnUNet_nopip.png,"nnUNet Advanced Settings with option to select nnUNet local folder",10.00} + + \section org_mitk_views_segmentationUserManualPostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations.",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3DTImages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationUserManualTechnicalDetail Technical Information for Developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions . */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png new file mode 100644 index 0000000000..f084fba26e Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png new file mode 100644 index 0000000000..5736d3d8da Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png new file mode 100644 index 0000000000..7354cb28e8 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png new file mode 100644 index 0000000000..803d126ec7 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png new file mode 100644 index 0000000000..593c91f57f Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml index a72db4b004..a628b56818 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml +++ b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml @@ -1,80 +1,81 @@ Allows the segmentation of images using different tools. Edit segmentations using standard operations. + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp index 936217f330..9035c9faa8 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp @@ -1,236 +1,242 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkDataSelectionWidget.h" #include "../mitkPluginActivator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mitk::NodePredicateBase::Pointer CreatePredicate(QmitkDataSelectionWidget::Predicate predicate) { auto imageType = mitk::TNodePredicateDataType::New(); auto labelSetImageType = mitk::TNodePredicateDataType::New(); auto surfaceType = mitk::TNodePredicateDataType::New(); auto contourModelType = mitk::TNodePredicateDataType::New(); auto contourModelSetType = mitk::TNodePredicateDataType::New(); auto nonLabelSetImageType = mitk::NodePredicateAnd::New(imageType, mitk::NodePredicateNot::New(labelSetImageType)); auto nonHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( mitk::NodePredicateProperty::New("helper object"), mitk::NodePredicateProperty::New("hidden object"))); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isSegmentation = mitk::NodePredicateProperty::New("segmentation", mitk::BoolProperty::New(true)); auto isBinaryOrSegmentation = mitk::NodePredicateOr::New(isBinary, isSegmentation); mitk::NodePredicateBase::Pointer returnValue; switch(predicate) { case QmitkDataSelectionWidget::ImagePredicate: returnValue = mitk::NodePredicateAnd::New( mitk::NodePredicateNot::New(isBinaryOrSegmentation), nonLabelSetImageType).GetPointer(); break; case QmitkDataSelectionWidget::SegmentationPredicate: returnValue = mitk::NodePredicateOr::New( mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), labelSetImageType).GetPointer(); break; case QmitkDataSelectionWidget::SurfacePredicate: returnValue = surfaceType.GetPointer(); break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: returnValue = imageType.GetPointer(); break; case QmitkDataSelectionWidget::ContourModelPredicate: returnValue = mitk::NodePredicateOr::New( contourModelSetType, contourModelSetType).GetPointer(); break; case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: returnValue = mitk::NodePredicateOr::New( mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), labelSetImageType).GetPointer(); returnValue = mitk::NodePredicateOr::New(returnValue, surfaceType).GetPointer(); break; default: assert(false && "Unknown predefined predicate!"); return nullptr; } return mitk::NodePredicateAnd::New(returnValue, nonHelperObject).GetPointer(); } QmitkDataSelectionWidget::QmitkDataSelectionWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_Controls.helpLabel->hide(); } QmitkDataSelectionWidget::~QmitkDataSelectionWidget() { } unsigned int QmitkDataSelectionWidget::AddDataSelection(QmitkDataSelectionWidget::Predicate predicate) { QString hint = "Select node"; switch (predicate) { case QmitkDataSelectionWidget::ImagePredicate: hint = "Select an image"; break; case QmitkDataSelectionWidget::SegmentationPredicate: hint = "Select a segmentation"; break; case QmitkDataSelectionWidget::SurfacePredicate: hint = "Select a surface"; break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: hint = "Select an image or segmentation"; break; case QmitkDataSelectionWidget::ContourModelPredicate: hint = "Select a contour model"; break; case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: hint = "Select a segmentation or surface"; break; } return this->AddDataSelection("", hint, hint, "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(mitk::NodePredicateBase* predicate) { return this->AddDataSelection("", "Select a node", "Select a node", "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, QmitkDataSelectionWidget::Predicate predicate) { return this->AddDataSelection(labelText, info, popupHint, popupTitel, CreatePredicate(predicate)); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate) { int row = m_Controls.gridLayout->rowCount(); if (!labelText.isEmpty()) { QLabel* label = new QLabel(labelText, m_Controls.dataSelectionWidget); label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_Controls.gridLayout->addWidget(label, row, 0); } QmitkSingleNodeSelectionWidget* nodeSelection = new QmitkSingleNodeSelectionWidget(m_Controls.dataSelectionWidget); nodeSelection->SetSelectionIsOptional(false); nodeSelection->SetInvalidInfo(info); nodeSelection->SetPopUpTitel(popupTitel); nodeSelection->SetPopUpHint(popupHint); nodeSelection->SetDataStorage(this->GetDataStorage()); nodeSelection->SetNodePredicate(predicate); nodeSelection->SetAutoSelectNewNodes(true); nodeSelection->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); nodeSelection->setMinimumSize(0, 40); connect(nodeSelection, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDataSelectionWidget::OnSelectionChanged); m_Controls.gridLayout->addWidget(nodeSelection, row, 1); m_NodeSelectionWidgets.push_back(nodeSelection); return static_cast(m_NodeSelectionWidgets.size() - 1); } mitk::DataStorage::Pointer QmitkDataSelectionWidget::GetDataStorage() const { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); assert(ref == true); mitk::IDataStorageService* service = mitk::PluginActivator::getContext()->getService(ref); assert(service); return service->GetDefaultDataStorage()->GetDataStorage(); } mitk::DataNode::Pointer QmitkDataSelectionWidget::GetSelection(unsigned int index) { assert(index < m_NodeSelectionWidgets.size()); return m_NodeSelectionWidgets[index]->GetSelectedNode(); } void QmitkDataSelectionWidget::SetPredicate(unsigned int index, Predicate predicate) { this->SetPredicate(index, CreatePredicate(predicate)); } -void QmitkDataSelectionWidget::SetPredicate(unsigned int index, mitk::NodePredicateBase* predicate) +void QmitkDataSelectionWidget::SetPredicate(unsigned int index, const mitk::NodePredicateBase* predicate) { assert(index < m_NodeSelectionWidgets.size()); m_NodeSelectionWidgets[index]->SetNodePredicate(predicate); } +const mitk::NodePredicateBase *QmitkDataSelectionWidget::GetPredicate(unsigned int index) const +{ + assert(index < m_NodeSelectionWidgets.size()); + return m_NodeSelectionWidgets[index]->GetNodePredicate(); +} + void QmitkDataSelectionWidget::SetHelpText(const QString& text) { if (!text.isEmpty()) { m_Controls.helpLabel->setText(text); if (!m_Controls.helpLabel->isVisible()) m_Controls.helpLabel->show(); } else { m_Controls.helpLabel->hide(); } } void QmitkDataSelectionWidget::OnSelectionChanged(QList selection) { std::vector::iterator it = std::find(m_NodeSelectionWidgets.begin(), m_NodeSelectionWidgets.end(), sender()); assert(it != m_NodeSelectionWidgets.end()); const mitk::DataNode* result = nullptr; if (!selection.empty()) { result = selection.front(); } emit SelectionChanged(std::distance(m_NodeSelectionWidgets.begin(), it), result); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.h index f702654857..2586454d2d 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.h @@ -1,68 +1,69 @@ /*============================================================================ 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 QmitkDataSelectionWidget_h #define QmitkDataSelectionWidget_h #include #include #include #include namespace mitk { class NodePredicateBase; } class QmitkSingleNodeSelectionWidget; class QmitkDataSelectionWidget : public QWidget { Q_OBJECT public: enum Predicate { ImagePredicate, SegmentationPredicate, SurfacePredicate, ImageAndSegmentationPredicate, ContourModelPredicate, SegmentationOrSurfacePredicate }; explicit QmitkDataSelectionWidget(QWidget* parent = nullptr); ~QmitkDataSelectionWidget() override; unsigned int AddDataSelection(Predicate predicate); unsigned int AddDataSelection(mitk::NodePredicateBase* predicate = nullptr); unsigned int AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, Predicate predicate); unsigned int AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate = nullptr); mitk::DataStorage::Pointer GetDataStorage() const; mitk::DataNode::Pointer GetSelection(unsigned int index); void SetPredicate(unsigned int index, Predicate predicate); - void SetPredicate(unsigned int index, mitk::NodePredicateBase* predicate); + void SetPredicate(unsigned int index, const mitk::NodePredicateBase* predicate); + const mitk::NodePredicateBase *GetPredicate(unsigned int index) const; void SetHelpText(const QString& text); signals: void SelectionChanged(unsigned int index, const mitk::DataNode* selection); private slots: void OnSelectionChanged(QList selection); private: Ui::QmitkDataSelectionWidgetControls m_Controls; std::vector m_NodeSelectionWidgets; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 1c1181fdf1..b26097ebce 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,822 +1,809 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_AutoSelectionEnabled(false) , m_MouseCursorSet(false) { mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls->segImageSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); + // Remove observer if one was registered + auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); + if (finding != m_WorkingDataObserverTags.end()) + { + m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); + m_WorkingDataObserverTags.erase(m_WorkingNode); + } + if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); + + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); + m_WorkingDataObserverTags.insert(std::pair(m_WorkingNode, + m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer referenceNode = m_ToolManager->GetReferenceData(0); if (referenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information(m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } m_ToolManager->ActivateTool(-1); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The selected image has multiple time steps.\n\nDo you want to create a static " "segmentation that is identical for all time steps or do you want to create a " "dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); // ask about the name and organ type of the new segmentation auto dialog = new QmitkNewSegmentationDialog(m_Parent); QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); dialog->SetSuggestionList(organColors); dialog->SetSegmentationName(newName); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } std::string newNodeName = dialog->GetSegmentationName().toStdString(); if (newNodeName.empty()) { newNodeName = "Unnamed"; } // create a new image of the same dimensions and smallest possible pixel type auto firstTool = m_ToolManager->GetToolById(0); if (nullptr == firstTool) { return; } mitk::DataNode::Pointer emptySegmentation = nullptr; try { emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); } catch (const std::bad_alloc &) { QMessageBox::warning(m_Parent, tr("New segmentation"), tr("Could not allocate memory for new segmentation")); } if (nullptr == emptySegmentation) { return; // could have been aborted by user } // initialize "showVolume"-property to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); // escape ';' here (replace by '\;') QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->Put("Organ-Color-List", stringForStorage); this->GetPreferences()->Flush(); this->GetDataStorage()->Add(emptySegmentation, referenceNode); if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } emptySegmentation->SetSelected(true); m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); - - this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (manualSegmentationTool) { if (state == true) { manualSegmentationTool->SetShowMarkerNodes(true); } else { manualSegmentationTool->SetShowMarkerNodes(false); } } } } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->patImageSelector->SetDataStorage(GetDataStorage()); m_Controls->patImageSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->patImageSelector->SetInvalidInfo("Select an image"); m_Controls->patImageSelector->SetPopUpTitel("Select an image"); m_Controls->patImageSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->segImageSelector->SetDataStorage(GetDataStorage()); m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->segImageSelector->SetInvalidInfo("Select a segmentation"); m_Controls->segImageSelector->SetPopUpTitel("Select a segmentation"); m_Controls->segImageSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->patImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->segImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire' '2D Fast Marching'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Fast Marching 3D' 'Region Growing 3D' Watershed Picking"); - +#ifdef __linux__ + segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows +#endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // all part of open source MITK m_Controls->m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer2D); m_Controls->m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); //setup 3D Tools m_Controls->m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer3D); m_Controls->m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // create signal/slot connections connect(m_Controls->btnNewSegmentation, &QToolButton::clicked, this, &QmitkSegmentationView::CreateNewSegmentation); connect(m_Controls->m_SlicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); - // set callback function for already existing segmentation nodes - mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(m_SegmentationPredicate); - for (mitk::DataStorage::SetOfObjects::const_iterator iter = allSegmentations->begin(); iter != allSegmentations->end(); ++iter) - { - mitk::DataNode* node = *iter; - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - } - auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->patImageSelector->SetAutoSelectNewNodes(true); m_Controls->segImageSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part if (m_Controls) { m_Controls->m_SlicesInterpolator->SetDataStorage(this->GetDataStorage()); QList controllers; controllers.push_back(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->m_SlicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Controls != nullptr) { bool slimView = prefs->GetBool("slim view", false); m_Controls->m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls->m_ManualToolSelectionBox3D->SetShowNames(!slimView); } m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { - if (!m_SegmentationPredicate->CheckNode(node)) + if (m_SegmentationPredicate->CheckNode(node)) { - return; + this->ApplyDisplayOptions(const_cast(node)); } - - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - - this->ApplyDisplayOptions(const_cast(node)); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } - - mitk::DataNode* tempNode = const_cast(node); - //Remove observer if one was registered - auto finding = m_WorkingDataObserverTags.find(tempNode); - if (finding != m_WorkingDataObserverTags.end()) - { - node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); - m_WorkingDataObserverTags.erase(tempNode); - } } void QmitkSegmentationView::ApplyDisplayOptions() { if (!m_Parent) { return; } if (!m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataNode::Pointer workingData = m_ToolManager->GetWorkingData(0); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is actually a multi label segmentation, // but its outline property can be set in the 'single label' segmentation preference page as well node->SetProperty("labelset.contour.active", drawOutline); // force render window update to show outline node->GetData()->Modified(); } else { // node is a 'single label' segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* _3DRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, _3DRenderWindow->GetRenderer())) { selectedRenderWindow = _3DRenderWindow; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; { ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); } selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (nodes.size() != 0) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->btnNewSegmentation->setEnabled(false); m_Controls->m_SlicesInterpolator->setEnabled(false); if (hasReferenceNode) { m_Controls->btnNewSegmentation->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { m_Controls->m_SlicesInterpolator->setEnabled(true); int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->m_ManualToolSelectionBox2D->setEnabled(false); m_Controls->m_ManualToolSelectionBox3D->setEnabled(false); mitk::DataNode* referenceNode = m_Controls->patImageSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->segImageSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry *worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->m_ManualToolSelectionBox2D->setEnabled(true); m_Controls->m_ManualToolSelectionBox3D->setEnabled(true); return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls->lblSegmentationWarnings->hide(); } else { m_Controls->lblSegmentationWarnings->show(); } m_Controls->lblSegmentationWarnings->setText("" + text + ""); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp index 0ee6636348..86a5468a8c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp @@ -1,356 +1,382 @@ /*============================================================================ 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 "QmitkImageMaskingWidget.h" #include "mitkImage.h" #include "../../Common/QmitkDataSelectionWidget.h" #include #include #include #include #include #include #include #include +#include +#include +#include +#include #include #include namespace { bool IsSurface(const mitk::DataNode* dataNode) { if (nullptr != dataNode) { if (nullptr != dynamic_cast(dataNode->GetData())) return true; } return false; } } static const char* const HelpText = "Select an image and a segmentation or surface"; QmitkImageMaskingWidget::QmitkImageMaskingWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent) : QmitkSegmentationUtilityWidget(timeNavigationController, parent) { m_Controls.setupUi(this); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ImagePredicate); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); m_Controls.dataSelectionWidget->SetHelpText(HelpText); + // T28795: Disable 2-d reference images since they do not work yet (segmentations are at least 3-d images with a single slice) + m_Controls.dataSelectionWidget->SetPredicate(0, mitk::NodePredicateAnd::New( + mitk::NodePredicateNot::New(mitk::NodePredicateDimension::New(2)), + m_Controls.dataSelectionWidget->GetPredicate(0))); + this->EnableButtons(false); connect(m_Controls.btnMaskImage, SIGNAL(clicked()), this, SLOT(OnMaskImagePressed())); connect(m_Controls.rbnCustom, SIGNAL(toggled(bool)), this, SLOT(OnCustomValueButtonToggled(bool))); connect(m_Controls.dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); if( m_Controls.dataSelectionWidget->GetSelection(0).IsNotNull() && m_Controls.dataSelectionWidget->GetSelection(1).IsNotNull() ) { this->OnSelectionChanged(0, m_Controls.dataSelectionWidget->GetSelection(0)); } } QmitkImageMaskingWidget::~QmitkImageMaskingWidget() { } -void QmitkImageMaskingWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode* selection) +void QmitkImageMaskingWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode *selection) { - QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; - mitk::DataNode::Pointer node0 = dataSelectionWidget->GetSelection(0); - mitk::DataNode::Pointer node1 = dataSelectionWidget->GetSelection(1); + auto *dataSelectionWidget = m_Controls.dataSelectionWidget; + auto node0 = dataSelectionWidget->GetSelection(0); + + if (index == 0) + { + dataSelectionWidget->SetPredicate(1, QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); + + if (node0.IsNotNull()) + { + dataSelectionWidget->SetPredicate(1, mitk::NodePredicateAnd::New( + mitk::NodePredicateGeometry::New(node0->GetData()->GetGeometry()), + dataSelectionWidget->GetPredicate(1))); + } + } - if (node0.IsNull() || node1.IsNull() ) + auto node1 = dataSelectionWidget->GetSelection(1); + + if (node0.IsNull() || node1.IsNull()) { dataSelectionWidget->SetHelpText(HelpText); this->EnableButtons(false); } else { this->SelectionControl(index, selection); } } void QmitkImageMaskingWidget::SelectionControl(unsigned int index, const mitk::DataNode* selection) { QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node = dataSelectionWidget->GetSelection(index); //if Image-Masking is enabled, check if image-dimension of reference and binary image is identical if( !IsSurface(dataSelectionWidget->GetSelection(1)) ) { if( dataSelectionWidget->GetSelection(0) == dataSelectionWidget->GetSelection(1) ) { dataSelectionWidget->SetHelpText("Select two different images above"); this->EnableButtons(false); return; } else if( node.IsNotNull() && selection ) { mitk::Image::Pointer referenceImage = dynamic_cast ( dataSelectionWidget->GetSelection(0)->GetData() ); mitk::Image::Pointer maskImage = dynamic_cast ( dataSelectionWidget->GetSelection(1)->GetData() ); - if( maskImage.IsNull() || referenceImage->GetLargestPossibleRegion().GetSize() != maskImage->GetLargestPossibleRegion().GetSize() ) + if (maskImage.IsNull()) { dataSelectionWidget->SetHelpText("Different image sizes cannot be masked"); this->EnableButtons(false); return; } } else { dataSelectionWidget->SetHelpText(HelpText); return; } } dataSelectionWidget->SetHelpText(""); this->EnableButtons(); } void QmitkImageMaskingWidget::EnableButtons(bool enable) { m_Controls.grpBackgroundValue->setEnabled(enable); m_Controls.btnMaskImage->setEnabled(enable); } template void GetRange(const itk::Image*, double& bottom, double& top) { bottom = std::numeric_limits::lowest(); top = std::numeric_limits::max(); } void QmitkImageMaskingWidget::OnCustomValueButtonToggled(bool checked) { m_Controls.txtCustom->setEnabled(checked); } void QmitkImageMaskingWidget::OnMaskImagePressed() { //Disable Buttons during calculation and initialize Progressbar this->EnableButtons(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(4); mitk::ProgressBar::GetInstance()->Progress(); QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; //create result image, get mask node and reference image mitk::Image::Pointer resultImage(nullptr); mitk::DataNode::Pointer maskingNode = dataSelectionWidget->GetSelection(1); mitk::Image::Pointer referenceImage = static_cast(dataSelectionWidget->GetSelection(0)->GetData()); if(referenceImage.IsNull() || maskingNode.IsNull() ) { MITK_ERROR << "Selection does not contain an image"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain an image", QMessageBox::Ok ); m_Controls.btnMaskImage->setEnabled(true); return; } //Do Image-Masking if (!IsSurface(maskingNode)) { mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer maskImage = dynamic_cast ( maskingNode->GetData() ); if(maskImage.IsNull() ) { MITK_ERROR << "Selection does not contain a segmentation"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a segmentation", QMessageBox::Ok ); this->EnableButtons(); return; } - if( referenceImage->GetLargestPossibleRegion().GetSize() == maskImage->GetLargestPossibleRegion().GetSize() ) - { - resultImage = this->MaskImage( referenceImage, maskImage ); - } + resultImage = this->MaskImage(referenceImage, maskImage); } //Do Surface-Masking else { mitk::ProgressBar::GetInstance()->Progress(); //1. convert surface to image mitk::Surface::Pointer surface = dynamic_cast ( maskingNode->GetData() ); //TODO Get 3D Surface of current time step if(surface.IsNull()) { MITK_ERROR << "Selection does not contain a surface"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a surface", QMessageBox::Ok ); this->EnableButtons(); return; } mitk::Image::Pointer maskImage = this->ConvertSurfaceToImage( referenceImage, surface ); //2. mask reference image with mask image if(maskImage.IsNotNull() && referenceImage->GetLargestPossibleRegion().GetSize() == maskImage->GetLargestPossibleRegion().GetSize() ) { resultImage = this->MaskImage( referenceImage, maskImage ); } } mitk::ProgressBar::GetInstance()->Progress(); if( resultImage.IsNull() ) { MITK_ERROR << "Masking failed"; QMessageBox::information( this, "Image and Surface Masking", "Masking failed. For more information please see logging window.", QMessageBox::Ok ); this->EnableButtons(); mitk::ProgressBar::GetInstance()->Progress(4); return; } //Add result to data storage this->AddToDataStorage( dataSelectionWidget->GetDataStorage(), resultImage, dataSelectionWidget->GetSelection(0)->GetName() + "_" + dataSelectionWidget->GetSelection(1)->GetName(), dataSelectionWidget->GetSelection(0)); this->EnableButtons(); mitk::ProgressBar::GetInstance()->Progress(); } mitk::Image::Pointer QmitkImageMaskingWidget::MaskImage(mitk::Image::Pointer referenceImage, mitk::Image::Pointer maskImage ) { mitk::ScalarType backgroundValue = 0.0; if (m_Controls.rbnMinimum->isChecked()) { backgroundValue = referenceImage->GetStatistics()->GetScalarValueMin(); } else if (m_Controls.rbnCustom->isChecked()) { auto warningTitle = QStringLiteral("Invalid custom pixel value"); bool ok = false; auto originalBackgroundValue = m_Controls.txtCustom->text().toDouble(&ok); if (!ok) { // Input is not even a number QMessageBox::warning(nullptr, warningTitle, "Please enter a valid number as custom pixel value."); return nullptr; } else { // Clamp to the numerical limits of the pixel/component type double bottom, top; - AccessByItk_n(referenceImage, GetRange, (bottom, top)); + if (referenceImage->GetDimension() == 4) + { + AccessFixedDimensionByItk_n(referenceImage, GetRange, 4, (bottom, top)); + } + else + { + AccessByItk_n(referenceImage, GetRange, (bottom, top)); + } backgroundValue = std::max(bottom, std::min(originalBackgroundValue, top)); // Get rid of decimals for integral numbers auto type = referenceImage->GetPixelType().GetComponentType(); if (type != itk::ImageIOBase::FLOAT && type != itk::ImageIOBase::DOUBLE) backgroundValue = std::round(backgroundValue); } // Ask the user for permission before correcting their input if (std::abs(originalBackgroundValue - backgroundValue) > 1e-4) { auto warningText = QString( "

The custom pixel value %1 lies not within the range of valid pixel values for the selected image.

" "

Apply the closest valid pixel value %2 instead?

").arg(originalBackgroundValue).arg(backgroundValue); auto ret = QMessageBox::warning( nullptr, warningTitle, warningText, QMessageBox::StandardButton::Apply | QMessageBox::StandardButton::Cancel, QMessageBox::StandardButton::Apply); if (QMessageBox::StandardButton::Apply != ret) return nullptr; m_Controls.txtCustom->setText(QString("%1").arg(backgroundValue)); } } auto maskFilter = mitk::MaskImageFilter::New(); maskFilter->SetInput(referenceImage); maskFilter->SetMask(maskImage); maskFilter->OverrideOutsideValueOn(); maskFilter->SetOutsideValue(backgroundValue); try { maskFilter->Update(); } catch(const itk::ExceptionObject& e) { MITK_ERROR << e.GetDescription(); return nullptr; } return maskFilter->GetOutput(); } mitk::Image::Pointer QmitkImageMaskingWidget::ConvertSurfaceToImage( mitk::Image::Pointer image, mitk::Surface::Pointer surface ) { mitk::ProgressBar::GetInstance()->AddStepsToDo(2); mitk::ProgressBar::GetInstance()->Progress(); mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(surface); surfaceToImageFilter->SetImage(image); try { surfaceToImageFilter->Update(); } catch(itk::ExceptionObject& excpt) { MITK_ERROR << excpt.GetDescription(); return nullptr; } mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer resultImage = mitk::Image::New(); resultImage = surfaceToImageFilter->GetOutput(); return resultImage; } void QmitkImageMaskingWidget::AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent ) { auto dataNode = mitk::DataNode::New(); dataNode->SetName(name); dataNode->SetData(segmentation); if (parent.IsNotNull()) { mitk::LevelWindow levelWindow; parent->GetLevelWindow(levelWindow); dataNode->SetLevelWindow(levelWindow); } dataStorage->Add(dataNode, parent); } diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake b/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake index 5b58d0228e..a4e8dac4c9 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake +++ b/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake @@ -1,47 +1,45 @@ set(SRC_CPP_FILES QmitkViewNavigatorWidget.cpp - -# mitkQtPerspectiveItem.h -# mitkQtViewItem.h + QmitkPerspectiveItem.h + QmitkViewItem.h ) set(INTERNAL_CPP_FILES - org_mitk_gui_qt_viewnavigator_Activator.cpp - ViewNavigatorView.cpp + mitkPluginActivator.cpp + QmitkViewNavigatorView.cpp ) set(UI_FILES src/QmitkViewNavigatorWidgetControls.ui ) set(MOC_H_FILES - src/internal/org_mitk_gui_qt_viewnavigator_Activator.h - src/internal/ViewNavigatorView.h - + src/internal/mitkPluginActivator.h + src/internal/QmitkViewNavigatorView.h src/QmitkViewNavigatorWidget.h ) # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench set(CACHED_RESOURCE_FILES resources/view-manager.svg plugin.xml ) # list of Qt .qrc files which contain additional resources # specific to this plugin set(QRC_FILES ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml b/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml index c2a48b96b9..f08d78ca51 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml +++ b/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml @@ -1,12 +1,12 @@ - diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h similarity index 51% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h index 644aa14611..10ecc84c24 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h @@ -1,44 +1,38 @@ /*============================================================================ 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 _PerspectiveItem -#define _PerspectiveItem +#ifndef QMITKPERSPECTIVEITEM_H +#define QMITKPERSPECTIVEITEM_H #include #include -namespace mitk -{ - -class QtPerspectiveItem : public QStandardItem +class QmitkPerspectiveItem : public QStandardItem { public: - QtPerspectiveItem(QString string) : - QStandardItem(string) - { - } - QtPerspectiveItem(const QIcon& icon, QString string) : - QStandardItem(icon, string) - { - } - berry::IPerspectiveDescriptor::Pointer m_Perspective; + QmitkPerspectiveItem(const QString& string) + : QStandardItem(string) + { + } - QStringList m_Tags; - QString m_Description; -private: + QmitkPerspectiveItem(const QIcon& icon, const QString& string) + : QStandardItem(icon, string) + { + } + berry::IPerspectiveDescriptor::Pointer m_ItemDescriptor; + QStringList m_Tags; + QString m_Description; }; -} - -#endif +#endif // QMITKPERSPECTIVEITEM_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h similarity index 53% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h index 34e71b487d..26dd93c9c5 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h @@ -1,45 +1,38 @@ /*============================================================================ 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 _ViewItem -#define _ViewItem +#ifndef QMITKVIEWITEM_H +#define QMITKVIEWITEM_H #include -#include #include -namespace mitk -{ - -class QtViewItem : public QStandardItem +class QmitkViewItem : public QStandardItem { public: - QtViewItem(QString string) : - QStandardItem(string) - { - } - QtViewItem(const QIcon& icon, QString string) : - QStandardItem(icon, string) - { - } - berry::IViewDescriptor::Pointer m_View; - QStringList m_Tags; - QString m_Description; + QmitkViewItem(const QString& string) + : QStandardItem(string) + { + } -private: + QmitkViewItem(const QIcon& icon, const QString& string) + : QStandardItem(icon, string) + { + } + berry::IViewDescriptor::Pointer m_ItemDescriptor; + QStringList m_Tags; + QString m_Description; }; -} - -#endif +#endif // QMITKVIEWITEM_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp index fc58de6095..1b378a4c41 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp @@ -1,697 +1,699 @@ /*============================================================================ 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 headers -#include "QmitkViewNavigatorWidget.h" +// View navigator plugin +#include +#include +#include // Blueberry -#include #include #include -#include #include #include #include #include #include #include // MITK #include // Qt #include #include -#include #include #include class KeywordRegistry { public: + KeywordRegistry() { berry::IExtensionRegistry* extensionPointService = berry::Platform::GetExtensionRegistry(); auto keywordExts = extensionPointService->GetConfigurationElementsFor("org.blueberry.ui.keywords"); for (auto keywordExtsIt = keywordExts.begin(); keywordExtsIt != keywordExts.end(); ++keywordExtsIt) { QString keywordId = (*keywordExtsIt)->GetAttribute("id"); QString keywordLabels = (*keywordExtsIt)->GetAttribute("label"); m_Keywords[keywordId].push_back(keywordLabels); } } QStringList GetKeywords(const QString& id) { return m_Keywords[id]; } QStringList GetKeywords(const QStringList& ids) { QStringList result; - for (int i = 0; i < ids.size(); ++i) + for (const auto& id : ids) { - result.append(this->GetKeywords(ids[i])); + result.append(this->GetKeywords(id)); } return result; } private: + QHash m_Keywords; }; - class ClassFilterProxyModel : public QSortFilterProxyModel { -private : - bool hasToBeDisplayed(const QModelIndex index) const; - bool displayElement(const QModelIndex index) const; public: - ClassFilterProxyModel(QObject *parent = nullptr); - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; -}; -ClassFilterProxyModel::ClassFilterProxyModel(QObject *parent): - QSortFilterProxyModel(parent) -{ -} -bool ClassFilterProxyModel::filterAcceptsRow(int sourceRow, - const QModelIndex &sourceParent) const -{ + ClassFilterProxyModel(QObject* parent = nullptr) + : QSortFilterProxyModel(parent) + { + } + + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); return hasToBeDisplayed(index); -} + } -bool ClassFilterProxyModel::displayElement(const QModelIndex index) const -{ - bool result = false; +private: + + bool displayElement(const QModelIndex index) const + { QString type = sourceModel()->data(index, Qt::DisplayRole).toString(); - QStandardItem * item = dynamic_cast(sourceModel())->itemFromIndex(index); + QStandardItem* item = dynamic_cast(sourceModel())->itemFromIndex(index); if (type.contains(filterRegExp())) { return true; } + + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - mitk::QtViewItem* viewItem = dynamic_cast(item); - if (viewItem) + for (const auto& tag : viewItem->m_Tags) { - for (int i = 0; i < viewItem->m_Tags.size(); ++i) - { - if (viewItem->m_Tags[i].contains(filterRegExp())) - { - return true; - } - } - if (viewItem->m_Description.contains(filterRegExp())) + if (tag.contains(filterRegExp())) { return true; } } + if (viewItem->m_Description.contains(filterRegExp())) + { + return true; + } } + + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { - mitk::QtPerspectiveItem* viewItem = dynamic_cast(item); - if (viewItem) + for (const auto& tag : perspectiveItem->m_Tags) { - for (int i = 0; i < viewItem->m_Tags.size(); ++i) - { - if (viewItem->m_Tags[i].contains(filterRegExp())) - { - return true; - } - } - if (viewItem->m_Description.contains(filterRegExp())) + if (tag.contains(filterRegExp())) { return true; } } + if (perspectiveItem->m_Description.contains(filterRegExp())) + { + return true; + } } - return result; -} + return false; + } -bool ClassFilterProxyModel::hasToBeDisplayed(const QModelIndex index) const -{ + bool hasToBeDisplayed(const QModelIndex index) const + { bool result = false; - if ( sourceModel()->rowCount(index) > 0 ) + if (sourceModel()->rowCount(index) > 0) { - for( int ii = 0; ii < sourceModel()->rowCount(index); ii++) + for (int i = 0; i < sourceModel()->rowCount(index); i++) { - QModelIndex childIndex = sourceModel()->index(ii,0,index); - if ( ! childIndex.isValid() ) + QModelIndex childIndex = sourceModel()->index(i, 0, index); + if (!childIndex.isValid()) + { break; + } + result = hasToBeDisplayed(childIndex); result |= displayElement(index); + if (result) { break; } } } else { result = displayElement(index); } return result; -} + } +}; class ViewNavigatorPerspectiveListener: public berry::IPerspectiveListener { public: - ViewNavigatorPerspectiveListener(QmitkViewNavigatorWidget* p) : - parentWidget(p) + ViewNavigatorPerspectiveListener(QmitkViewNavigatorWidget* parent) + : m_ParentWidget(parent) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED // remove the following line when command framework is finished | Events::CLOSED | Events::OPENED | Events::PART_CHANGED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { - parentWidget->m_ActivePerspective = perspective; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = perspective; + m_ParentWidget->UpdateTreeList(); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& newPerspective) override { - parentWidget->m_ActivePerspective = newPerspective; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = newPerspective; + m_ParentWidget->UpdateTreeList(); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->m_ActivePerspective = nullptr; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = nullptr; } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->UpdateTreeList(); + m_ParentWidget->UpdateTreeList(); } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->m_ActivePerspective = nullptr; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = nullptr; } using IPerspectiveListener::PerspectiveChanged; - void PerspectiveChanged(const berry::IWorkbenchPage::Pointer&, - const berry::IPerspectiveDescriptor::Pointer&, + void PerspectiveChanged(const berry::IWorkbenchPage::Pointer& /*page*/, + const berry::IPerspectiveDescriptor::Pointer& /*perspective*/, const berry::IWorkbenchPartReference::Pointer& partRef, const std::string& changeId) { - if (changeId=="viewHide" && partRef->GetId()=="org.mitk.views.viewnavigatorview") - berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->RemovePerspectiveListener(parentWidget->m_PerspectiveListener.data()); + if (changeId == "viewHide" && partRef->GetId() == "org.mitk.views.viewnavigator") + berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->RemovePerspectiveListener(m_ParentWidget->m_PerspectiveListener.data()); else - parentWidget->UpdateTreeList(nullptr, partRef.GetPointer(), changeId); + m_ParentWidget->UpdateTreeList(); } private: - QmitkViewNavigatorWidget* parentWidget; + + QmitkViewNavigatorWidget* m_ParentWidget; +}; + +class ViewNavigatorViewListener: public berry::IPartListener +{ +public: + + ViewNavigatorViewListener(QmitkViewNavigatorWidget* parent) + : m_ParentWidget(parent) + { + } + + Events::Types GetPartEventTypes() const override + { + return Events::OPENED | Events::CLOSED; + } + + void PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) override + { + if (partRef->GetId() != "org.mitk.views.viewnavigator") + { + m_ParentWidget->UpdateTreeList((partRef->GetPart(false)).GetPointer()); + } + else + { + m_ParentWidget->FillTreeList(); + m_ParentWidget->UpdateTreeList(); + } + } + + void PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) override + { + if (partRef->GetId() != "org.mitk.views.viewnavigator") + { + m_ParentWidget->UpdateTreeList(); + } + } + +private: + + QmitkViewNavigatorWidget* m_ParentWidget; }; bool compareViews(const berry::IViewDescriptor::Pointer& a, const berry::IViewDescriptor::Pointer& b) { if (a.IsNull() || b.IsNull()) + { return false; + } + return a->GetLabel().compare(b->GetLabel()) < 0; } bool comparePerspectives(const berry::IPerspectiveDescriptor::Pointer& a, const berry::IPerspectiveDescriptor::Pointer& b) { if (a.IsNull() || b.IsNull()) + { return false; + } + return a->GetLabel().compare(b->GetLabel()) < 0; } bool compareQStandardItems(const QStandardItem* a, const QStandardItem* b) { - if (a==nullptr || b==nullptr) + if (nullptr == a || nullptr== b) + { return false; + } + return a->text().compare(b->text()) < 0; } QmitkViewNavigatorWidget::QmitkViewNavigatorWidget(berry::IWorkbenchWindow::Pointer window, - QWidget * parent, Qt::WindowFlags ) + QWidget* parent, + Qt::WindowFlags) : QWidget(parent) , m_Window(window) { - m_Generated = false; this->CreateQtPartControl(this); } QmitkViewNavigatorWidget::~QmitkViewNavigatorWidget() { m_Window->RemovePerspectiveListener(m_PerspectiveListener.data()); + m_Window->GetPartService()->RemovePartListener(m_ViewPartListener.data()); } -void QmitkViewNavigatorWidget::setFocus() +void QmitkViewNavigatorWidget::SetFocus() { m_Controls.lineEdit->setFocus(); } -void QmitkViewNavigatorWidget::CreateQtPartControl( QWidget *parent ) -{ - // create GUI widgets from the Qt Designer's .ui file - m_PerspectiveListener.reset(new ViewNavigatorPerspectiveListener(this)); - m_Window->AddPerspectiveListener(m_PerspectiveListener.data()); - - m_Parent = parent; - m_Controls.setupUi( parent ); - connect( m_Controls.m_PluginTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(CustomMenuRequested(QPoint))); - connect( m_Controls.m_PluginTreeView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(ItemClicked(const QModelIndex&))); - connect( m_Controls.lineEdit, SIGNAL(textChanged(QString)), SLOT(FilterChanged())); - - m_ContextMenu = new QMenu(m_Controls.m_PluginTreeView); - m_Controls.m_PluginTreeView->setContextMenuPolicy(Qt::CustomContextMenu); - - // Create a new TreeModel for the data - m_TreeModel = new QStandardItemModel(); - m_FilterProxyModel = new ClassFilterProxyModel(this); - m_FilterProxyModel->setSourceModel(m_TreeModel); - //proxyModel->setFilterFixedString("Diff"); - m_Controls.m_PluginTreeView->setModel(m_FilterProxyModel); - - this->UpdateTreeList(); -} - -void QmitkViewNavigatorWidget::UpdateTreeList(QStandardItem* root, berry::IWorkbenchPartReference *partRef, const std::string &changeId) +void QmitkViewNavigatorWidget::UpdateTreeList(berry::IWorkbenchPart* workbenchPart) { - berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); - if (page.IsNull()) - return; + berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); + if (page.IsNull()) + { + return; + } - if( !m_Generated ) - { - m_Generated = FillTreeList(); - } + berry::IPerspectiveDescriptor::Pointer currentPerspective = page->GetPerspective(); + QList viewParts = page->GetViews(); - if (root==nullptr) - root = m_TreeModel->invisibleRootItem(); - for (int i=0; irowCount(); i++) + // iterate over all tree items + for (const auto& item : m_TreeModel->findItems("*", Qt::MatchWildcard | Qt::MatchRecursive)) + { + QFont font; + // check if the item is a view item and if it is equal to any opened view + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - QStandardItem* item = root->child(i); - QFont font; - if (dynamic_cast(item)) + if (nullptr != workbenchPart && workbenchPart->GetPartName() == viewItem->m_ItemDescriptor->GetLabel()) + { + font.setBold(true); + } + else + { + for (const auto& viewPart : viewParts) { - mitk::QtPerspectiveItem* pItem = dynamic_cast(item); - berry::IPerspectiveDescriptor::Pointer currentPersp = page->GetPerspective(); - - if (currentPersp.IsNotNull() && currentPersp->GetId()==pItem->m_Perspective->GetId()) - font.setBold(true); - pItem->setFont(font); + if (viewPart->GetPartName() == viewItem->m_ItemDescriptor->GetLabel()) + { + font.setBold(true); + break; + } } - mitk::QtViewItem* vItem = dynamic_cast(item); - if (vItem) + } + + viewItem->setFont(font); + } + else + { + // check if the item is a perspective item and if it is equal to the current perspective + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) + { + if (currentPerspective.IsNotNull() && currentPerspective->GetId() == perspectiveItem->m_ItemDescriptor->GetId()) { - QList viewParts(page->GetViews()); - for (int i=0; iGetPartName()==vItem->m_View->GetLabel()) - { - font.setBold(true); - break; - } - - if( partRef!=nullptr && partRef->GetId()==vItem->m_View->GetId() && changeId=="viewHide") - font.setBold(false); - - vItem->setFont(font); + font.setBold(true); } - UpdateTreeList(item, partRef, changeId); + + perspectiveItem->setFont(font); + } } + } } bool QmitkViewNavigatorWidget::FillTreeList() { - // active workbench window available? - if (m_Window.IsNull()) - return false; - - // active page available? - berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); - if (page.IsNull()) - return false; - - // everything is fine and we can remove the window listener - if (m_WindowListener != nullptr) - berry::PlatformUI::GetWorkbench()->RemoveWindowListener(m_WindowListener.data()); - // initialize tree model m_TreeModel->clear(); - QStandardItem *treeRootItem = m_TreeModel->invisibleRootItem(); - - // get all available perspectives - berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - QList perspectiveDescriptors(perspRegistry->GetPerspectives()); - qSort(perspectiveDescriptors.begin(), perspectiveDescriptors.end(), comparePerspectives); - // get all Keywords - KeywordRegistry keywordRegistry; + // add all available perspectives + this->AddPerspectivesToTree(); - berry::IPerspectiveDescriptor::Pointer currentPersp = page->GetPerspective(); - //QStringList perspectiveExcludeList = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetPerspectiveExcludeList(); - - std::vector< QStandardItem* > categoryItems; - QStandardItem *perspectiveRootItem = new QStandardItem("Perspectives"); - perspectiveRootItem->setEditable(false); - perspectiveRootItem->setFont(QFont("", 12, QFont::Normal)); - treeRootItem->appendRow(perspectiveRootItem); - - for (int i=0; iGetId()) - { - skipPerspective = true; - break; - } - if (skipPerspective) - continue; -*/ - //QIcon* pIcon = static_cast(p->GetImageDescriptor()->CreateImage()); - mitk::QtPerspectiveItem* pItem = new mitk::QtPerspectiveItem(p->GetLabel()); - pItem->m_Perspective = p; - pItem->m_Description = p->GetDescription(); - QStringList keylist = p->GetKeywordReferences(); - pItem->m_Tags = keywordRegistry.GetKeywords(keylist); - pItem->setEditable(false); - - QFont font; font.setBold(true); - if (currentPersp.IsNotNull() && currentPersp->GetId()==p->GetId()) - pItem->setFont(font); - - QStringList catPath = p->GetCategoryPath(); - if (catPath.isEmpty()) - { - perspectiveRootItem->appendRow(pItem); - } - else - { - QStandardItem* categoryItem = nullptr; - - for (unsigned int c=0; ctext() == catPath.front()) - { - categoryItem = categoryItems.at(c); - break; - } - } - - if (categoryItem==nullptr) - { - categoryItem = new QStandardItem(QIcon(),catPath.front()); - categoryItems.push_back(categoryItem); - } - categoryItem->setEditable(false); - categoryItem->appendRow(pItem); - categoryItem->setFont(QFont("", 12, QFont::Normal)); - } - } - std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); - for (unsigned int i=0; iappendRow(categoryItems.at(i)); - - // get all available views - berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); - QList viewDescriptors(viewRegistry->GetViews()); - QList viewParts(page->GetViews()); - qSort(viewDescriptors.begin(), viewDescriptors.end(), compareViews); - auto emptyItem = new QStandardItem(); - emptyItem->setFlags(Qt::ItemIsEnabled); - treeRootItem->appendRow(emptyItem); - //QStringList viewExcludeList = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetViewExcludeList(); - - //There currently is no way to get the list of excluded views at application start - QStringList viewExcludeList; - // internal view used for the intro screen, will crash when opened directly, see T22352 - viewExcludeList.append(QString("org.blueberry.ui.internal.introview")); - viewExcludeList.append(QString("org.mitk.views.controlvisualizationpropertiesview")); - viewExcludeList.append(QString("org.mitk.views.modules")); - viewExcludeList.append(QString("org.mitk.views.viewnavigatorview")); - - QStandardItem* viewRootItem = new QStandardItem(QIcon(),"Views"); - viewRootItem->setFont(QFont("", 12, QFont::Normal)); - viewRootItem->setEditable(false); - treeRootItem->appendRow(viewRootItem); - - categoryItems.clear(); - QStandardItem* noCategoryItem = new QStandardItem(QIcon(),"Miscellaneous"); - noCategoryItem->setEditable(false); - noCategoryItem->setFont(QFont("", 12, QFont::Normal)); - - for (int i = 0; i < viewDescriptors.size(); ++i) - { - berry::IViewDescriptor::Pointer v = viewDescriptors[i]; - bool skipView = false; - for(int e=0; eGetId()) - { - skipView = true; - break; - } - if (skipView) - continue; - - QStringList catPath = v->GetCategoryPath(); - - QIcon icon = v->GetImageDescriptor(); - mitk::QtViewItem* vItem = new mitk::QtViewItem(icon, v->GetLabel()); - vItem->m_View = v; - vItem->setToolTip(v->GetDescription()); - vItem->m_Description = v->GetDescription(); - - QStringList keylist = v->GetKeywordReferences(); - vItem->m_Tags = keywordRegistry.GetKeywords(keylist); - vItem->setEditable(false); - - for (int i=0; iGetPartName()==v->GetLabel()) - { - QFont font; font.setBold(true); - vItem->setFont(font); - break; - } - - if (catPath.empty()) - noCategoryItem->appendRow(vItem); - else - { - QStandardItem* categoryItem = nullptr; - - for (unsigned int c=0; ctext() == catPath.front()) - { - categoryItem = categoryItems.at(c); - break; - } - - if (categoryItem==nullptr) - { - categoryItem = new QStandardItem(QIcon(),catPath.front()); - categoryItems.push_back(categoryItem); - } - categoryItem->setEditable(false); - categoryItem->appendRow(vItem); - categoryItem->setFont(QFont("", 12, QFont::Normal)); - } - } - std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); - - for (unsigned int i=0; iappendRow(categoryItems.at(i)); - if (noCategoryItem->hasChildren()) - viewRootItem->appendRow(noCategoryItem); + // add all available views + this->AddViewsToTree(); m_Controls.m_PluginTreeView->expandAll(); return true; } void QmitkViewNavigatorWidget::FilterChanged() { QString filterString = m_Controls.lineEdit->text(); -// if (filterString.size() > 0 ) m_Controls.m_PluginTreeView->expandAll(); -// else -// m_Controls.m_PluginTreeView->collapseAll(); - // QRegExp::PatternSyntax syntax = QRegExp::RegExp; Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; QString strPattern = "^*" + filterString; QRegExp regExp(strPattern, caseSensitivity); m_FilterProxyModel->setFilterRegExp(regExp); } void QmitkViewNavigatorWidget::ItemClicked(const QModelIndex &index) { QStandardItem* item = m_TreeModel->itemFromIndex(m_FilterProxyModel->mapToSource(index)); - if ( dynamic_cast< mitk::QtPerspectiveItem* >(item) ) + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { try { - mitk::QtPerspectiveItem* pItem = dynamic_cast< mitk::QtPerspectiveItem* >(item); - berry::PlatformUI::GetWorkbench()->ShowPerspective( pItem->m_Perspective->GetId(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); + berry::PlatformUI::GetWorkbench()->ShowPerspective( + perspectiveItem->m_ItemDescriptor->GetId(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } catch (...) { - QMessageBox::critical(nullptr, "Opening Perspective Failed", QString("The requested perspective could not be opened.\nSee the log for details.")); + QMessageBox::critical(nullptr, "Opening Perspective Failed", + QString("The requested perspective could not be opened.\nSee the log for details.")); } + + return; } - else if ( dynamic_cast< mitk::QtViewItem* >(item) ) + + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); + berry::IWorkbenchPage::Pointer page = + berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); if (page.IsNotNull()) { try { - mitk::QtViewItem* vItem = dynamic_cast< mitk::QtViewItem* >(item); - page->ShowView(vItem->m_View->GetId()); + page->ShowView(viewItem->m_ItemDescriptor->GetId()); } catch (const berry::PartInitException& e) { BERRY_ERROR << "Error: " << e.what() << std::endl; } } } } void QmitkViewNavigatorWidget::SaveCurrentPerspectiveAs() { berry::IHandlerService* handlerService = m_Window->GetService(); try { handlerService->ExecuteCommand(berry::IWorkbenchCommandConstants::WINDOW_SAVE_PERSPECTIVE_AS, berry::UIElement::Pointer()); FillTreeList(); } catch(const berry::NotHandledException&) - {} + { + } catch(const berry::CommandException& e) { MITK_ERROR << e.what(); } } void QmitkViewNavigatorWidget::ResetCurrentPerspective() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to reset the current perspective?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to reset the current perspective?", + QMessageBox::Yes | QMessageBox::No).exec()) + { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); + } } void QmitkViewNavigatorWidget::ClosePerspective() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to close the current perspective?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to close the current perspective?", + QMessageBox::Yes | QMessageBox::No).exec()) { berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); - - // if ( page->GetPerspective().IsNull() ) - // { - // berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - // berry::PlatformUI::GetWorkbench()->ShowPerspective( perspRegistry->GetDefaultPerspective(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); - // } } } void QmitkViewNavigatorWidget::CloseAllPerspectives() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to close all perspectives?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to close all perspectives?", + QMessageBox::Yes | QMessageBox::No).exec()) { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->CloseAllPerspectives(true, true); - - // berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - // berry::PlatformUI::GetWorkbench()->ShowPerspective( perspRegistry->GetDefaultPerspective(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); } } void QmitkViewNavigatorWidget::ExpandAll() { m_Controls.m_PluginTreeView->expandAll(); } void QmitkViewNavigatorWidget::CollapseAll() { m_Controls.m_PluginTreeView->collapseAll(); } void QmitkViewNavigatorWidget::CustomMenuRequested(QPoint pos) { QModelIndex index = m_Controls.m_PluginTreeView->indexAt(pos); QStandardItem* item = m_TreeModel->itemFromIndex(m_FilterProxyModel->mapToSource(index)); - if (m_ContextMenu==nullptr) + if (nullptr == m_ContextMenu) return; m_ContextMenu->clear(); - QAction* expandAction = new QAction("Expand tree", this); - m_ContextMenu->addAction(expandAction); - connect(expandAction, SIGNAL(triggered()), SLOT(ExpandAll())); - - QAction* collapseAction = new QAction("Collapse tree", this); - m_ContextMenu->addAction(collapseAction); - connect(collapseAction, SIGNAL(triggered()), SLOT(CollapseAll())); - - m_ContextMenu->addSeparator(); - - - if ( item!=nullptr && dynamic_cast< mitk::QtPerspectiveItem* >(item) ) + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { - berry::IPerspectiveDescriptor::Pointer persp = dynamic_cast< mitk::QtPerspectiveItem* >(item)->m_Perspective; - if (this->m_ActivePerspective.IsNotNull() && this->m_ActivePerspective == persp) + berry::IPerspectiveDescriptor::Pointer perspectiveDescriptor = perspectiveItem->m_ItemDescriptor; + if (this->m_ActivePerspective.IsNotNull() && this->m_ActivePerspective == perspectiveDescriptor) { - //m_ContextMenu->addSeparator(); - QAction* saveAsAction = new QAction("Save As...", this); m_ContextMenu->addAction(saveAsAction); connect(saveAsAction, SIGNAL(triggered()), SLOT(SaveCurrentPerspectiveAs())); m_ContextMenu->addSeparator(); } } QAction* resetAction = new QAction("Reset current perspective", this); m_ContextMenu->addAction(resetAction); connect(resetAction, SIGNAL(triggered()), SLOT(ResetCurrentPerspective())); QAction* closeAction = new QAction("Close perspective", this); m_ContextMenu->addAction(closeAction); connect(closeAction, SIGNAL(triggered()), SLOT(ClosePerspective())); - m_ContextMenu->addSeparator(); - QAction* closeAllAction = new QAction("Close all perspectives", this); m_ContextMenu->addAction(closeAllAction); connect(closeAllAction, SIGNAL(triggered()), SLOT(CloseAllPerspectives())); + m_ContextMenu->addSeparator(); + + QAction* expandAction = new QAction("Expand tree", this); + m_ContextMenu->addAction(expandAction); + connect(expandAction, SIGNAL(triggered()), SLOT(ExpandAll())); + + QAction* collapseAction = new QAction("Collapse tree", this); + m_ContextMenu->addAction(collapseAction); + connect(collapseAction, SIGNAL(triggered()), SLOT(CollapseAll())); + m_ContextMenu->popup(m_Controls.m_PluginTreeView->viewport()->mapToGlobal(pos)); } + +void QmitkViewNavigatorWidget::CreateQtPartControl(QWidget* parent) +{ + // active workbench window available? + if (m_Window.IsNull()) + { + return; + } + + m_Parent = parent; + m_Controls.setupUi(parent); + connect(m_Controls.m_PluginTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(CustomMenuRequested(QPoint))); + connect(m_Controls.m_PluginTreeView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(ItemClicked(const QModelIndex&))); + connect(m_Controls.lineEdit, SIGNAL(textChanged(QString)), SLOT(FilterChanged())); + + m_ContextMenu = new QMenu(m_Controls.m_PluginTreeView); + m_Controls.m_PluginTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + + // Create a new TreeModel for the data + m_TreeModel = new QStandardItemModel(); + m_FilterProxyModel = new ClassFilterProxyModel(this); + m_FilterProxyModel->setSourceModel(m_TreeModel); + m_Controls.m_PluginTreeView->setModel(m_FilterProxyModel); + + m_PerspectiveListener.reset(new ViewNavigatorPerspectiveListener(this)); + m_Window->AddPerspectiveListener(m_PerspectiveListener.data()); + + m_ViewPartListener.reset(new ViewNavigatorViewListener(this)); + m_Window->GetPartService()->AddPartListener(m_ViewPartListener.data()); +} + +void QmitkViewNavigatorWidget::AddPerspectivesToTree() +{ + berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); + QList perspectiveDescriptors(perspRegistry->GetPerspectives()); + qSort(perspectiveDescriptors.begin(), perspectiveDescriptors.end(), comparePerspectives); + + QStandardItem* perspectiveRootItem = new QStandardItem("Perspectives"); + perspectiveRootItem->setFont(QFont("", 12, QFont::Normal)); + perspectiveRootItem->setEditable(false); + QStandardItem* treeRootItem = m_TreeModel->invisibleRootItem(); + treeRootItem->appendRow(perspectiveRootItem); + + this->AddItemsToTree, QmitkPerspectiveItem>( + perspectiveDescriptors, perspectiveRootItem); +} + +void QmitkViewNavigatorWidget::AddViewsToTree() +{ + berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); + QList viewDescriptors(viewRegistry->GetViews()); + qSort(viewDescriptors.begin(), viewDescriptors.end(), compareViews); + + QStandardItem* viewRootItem = new QStandardItem("Views"); + viewRootItem->setFont(QFont("", 12, QFont::Normal)); + viewRootItem->setEditable(false); + QStandardItem* treeRootItem = m_TreeModel->invisibleRootItem(); + treeRootItem->appendRow(viewRootItem); + + QStandardItem* miscellaneousCategoryItem = new QStandardItem("Miscellaneous"); + miscellaneousCategoryItem->setFont(QFont("", 12, QFont::Normal)); + miscellaneousCategoryItem->setEditable(false); + + QStringList viewExcludeList; + // internal view used for the intro screen, will crash when opened directly, see T22352 + viewExcludeList.append(QString("org.blueberry.ui.internal.introview")); + viewExcludeList.append(QString("org.mitk.views.controlvisualizationpropertiesview")); + viewExcludeList.append(QString("org.mitk.views.modules")); + viewExcludeList.append(QString("org.mitk.views.viewnavigator")); + + this->AddItemsToTree, QmitkViewItem>( + viewDescriptors, viewRootItem, miscellaneousCategoryItem, viewExcludeList); +} + +template +void QmitkViewNavigatorWidget::AddItemsToTree(D itemDescriptors, QStandardItem* rootItem, + QStandardItem* miscellaneousItem, const QStringList& itemExcludeList) +{ + KeywordRegistry keywordRegistry; + std::vector categoryItems; + + for (const auto& itemDescriptor : itemDescriptors) + { + bool excludeView = itemExcludeList.contains(itemDescriptor->GetId()); + if (excludeView) + { + continue; + } + + QIcon icon = itemDescriptor->GetImageDescriptor(); + I* item = new I(icon, itemDescriptor->GetLabel()); + item->m_ItemDescriptor = itemDescriptor; + item->m_Description = itemDescriptor->GetDescription(); + item->setToolTip(itemDescriptor->GetDescription()); + + QStringList keylist = itemDescriptor->GetKeywordReferences(); + item->m_Tags = keywordRegistry.GetKeywords(keylist); + item->setEditable(false); + + QStringList categoryPath = itemDescriptor->GetCategoryPath(); + if (categoryPath.empty()) + { + // If a root item for general / non-categorized item views is given, use it. + // Otherwise put the non-categorized item views into the top root item. + if (nullptr != miscellaneousItem) + { + miscellaneousItem->appendRow(item); + } + else + { + rootItem->appendRow(item); + } + } + else + { + QStandardItem* categoryItem = nullptr; + for (const auto& currentCategoryItem : categoryItems) + { + if (currentCategoryItem->text() == categoryPath.front()) + { + categoryItem = currentCategoryItem; + break; + } + } + + if (nullptr == categoryItem) + { + categoryItem = new QStandardItem(QIcon(), categoryPath.front()); + categoryItems.push_back(categoryItem); + } + + categoryItem->setFont(QFont("", 12, QFont::Normal)); + categoryItem->setEditable(false); + categoryItem->appendRow(item); + } + } + + std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); + for (const auto& categoryItem : categoryItems) + { + rootItem->appendRow(categoryItem); + } + + if (nullptr != miscellaneousItem && miscellaneousItem->hasChildren()) + { + rootItem->appendRow(miscellaneousItem); + } +} diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h index 2e81f31644..d55bcedba4 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h @@ -1,88 +1,87 @@ /*============================================================================ 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 _QMITKViewNavigatorWidget_H_INCLUDED -#define _QMITKViewNavigatorWidget_H_INCLUDED +#ifndef QMIKVIEWNAVIGATORWIDGET_H +#define QMIKVIEWNAVIGATORWIDGET_H -//QT headers -#include -#include - -#include #include "ui_QmitkViewNavigatorWidgetControls.h" -#include -#include -#include -#include -#include +//QT headers +#include #include -#include #include -#include -#include + +#include +#include +#include class ClassFilterProxyModel; -/** @brief - */ +/** +* @brief +* +*/ class QmitkViewNavigatorWidget : public QWidget { - //this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: - QmitkViewNavigatorWidget (berry::IWorkbenchWindow::Pointer window, - QWidget* parent = nullptr, Qt::WindowFlags f = nullptr); + QmitkViewNavigatorWidget(berry::IWorkbenchWindow::Pointer window, + QWidget* parent = nullptr, + Qt::WindowFlags f = nullptr); ~QmitkViewNavigatorWidget() override; - virtual void CreateQtPartControl(QWidget *parent); - void setFocus(); - - bool FillTreeList(); - void UpdateTreeList(QStandardItem* item = nullptr, berry::IWorkbenchPartReference* partRef=nullptr, const std::string& changeId=""); - - QScopedPointer m_PerspectiveListener; - QScopedPointer m_WindowListener; + void SetFocus(); -public slots: +public Q_SLOTS: - void CustomMenuRequested(QPoint pos); - void ItemClicked(const QModelIndex &index); + void FilterChanged(); + void ItemClicked(const QModelIndex& index); void SaveCurrentPerspectiveAs(); void ResetCurrentPerspective(); - void CloseAllPerspectives(); void ClosePerspective(); + void CloseAllPerspectives(); void ExpandAll(); void CollapseAll(); - void FilterChanged(); + void CustomMenuRequested(QPoint pos); protected: friend class ViewNavigatorPerspectiveListener; + friend class ViewNavigatorViewListener; - // member variables Ui::QmitkViewNavigatorWidgetControls m_Controls; QWidget* m_Parent; QStandardItemModel* m_TreeModel; ClassFilterProxyModel* m_FilterProxyModel; QMenu* m_ContextMenu; berry::IPerspectiveDescriptor::Pointer m_ActivePerspective; - bool m_Generated; private: + void CreateQtPartControl(QWidget* parent); + bool FillTreeList(); + void UpdateTreeList(berry::IWorkbenchPart* workbenchPart = nullptr); + + void AddPerspectivesToTree(); + void AddViewsToTree(); + template + void AddItemsToTree(D itemDescriptors, QStandardItem* rootItem, + QStandardItem* miscellaneousItem = nullptr, const QStringList& itemExcludeList = QStringList()); + + QScopedPointer m_PerspectiveListener; + QScopedPointer m_ViewPartListener; + berry::IWorkbenchWindow::Pointer m_Window; }; -#endif // _QMITKViewNavigatorWidget_H_INCLUDED - +#endif // QMIKVIEWNAVIGATORWIDGET_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp similarity index 74% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp index 11e625d938..3443fe6ba3 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp @@ -1,36 +1,34 @@ /*============================================================================ 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 "ViewNavigatorView.h" - +#include "QmitkViewNavigatorView.h" #include "QmitkViewNavigatorWidget.h" // BlueBerry #include #include -const std::string ViewNavigatorView::VIEW_ID = "org.mitk.views.viewnavigatorview"; +const std::string QmitkViewNavigatorView::VIEW_ID = "org.mitk.views.viewnavigator"; -void ViewNavigatorView::SetFocus() +void QmitkViewNavigatorView::SetFocus() { - m_ViewNavigatorWidget->setFocus(); + m_ViewNavigatorWidget->SetFocus(); } -void ViewNavigatorView::CreateQtPartControl( QWidget *parent ) +void QmitkViewNavigatorView::CreateQtPartControl(QWidget* parent) { parent->setLayout(new QVBoxLayout); parent->layout()->setContentsMargins(0, 0, 0, 0); m_ViewNavigatorWidget = new QmitkViewNavigatorWidget(this->GetSite()->GetWorkbenchWindow(), parent); parent->layout()->addWidget(m_ViewNavigatorWidget); } diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h similarity index 58% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h index 62c7f24120..18f72929ad 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h @@ -1,50 +1,44 @@ /*============================================================================ 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 ViewNavigatorView_h -#define ViewNavigatorView_h +#ifndef QMITKVIEWNAVIGATORVIEW_H +#define QMITKVIEWNAVIGATORVIEW_H #include class QmitkViewNavigatorWidget; /** - \brief ViewNavigatorView - - \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. - - \sa QmitkAbstractView - \ingroup ${plugin_target}_internal +* @brief +* */ -class ViewNavigatorView : public QmitkAbstractView +class QmitkViewNavigatorView : public QmitkAbstractView { - // this is needed for all Qt objects that should have a Qt meta-object - // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; protected: void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; private: QmitkViewNavigatorWidget* m_ViewNavigatorWidget; + }; -#endif // ViewNavigatorView_h +#endif // QMITKVIEWNAVIGATORVIEW_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp similarity index 56% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp index 2f2991cbbf..c0312d1037 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp @@ -1,29 +1,24 @@ /*============================================================================ 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 "QmitkViewNavigatorView.h" -#include "org_mitk_gui_qt_viewnavigator_Activator.h" -#include "ViewNavigatorView.h" - -namespace mitk { - -void org_mitk_gui_qt_viewnavigator_Activator::start(ctkPluginContext* context) +void mitk::PluginActivator::start(ctkPluginContext* context) { - BERRY_REGISTER_EXTENSION_CLASS(ViewNavigatorView, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkViewNavigatorView, context) } -void org_mitk_gui_qt_viewnavigator_Activator::stop(ctkPluginContext* context) +void mitk::PluginActivator::stop(ctkPluginContext* context) { Q_UNUSED(context) } - -} diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h new file mode 100644 index 0000000000..c2daea8f73 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h @@ -0,0 +1,33 @@ +/*============================================================================ + +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 MITKPLUGINACTIVATOR_H +#define MITKPLUGINACTIVATOR_H + +#include + +namespace mitk +{ + class PluginActivator : public QObject, public ctkPluginActivator + { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_viewnavigator") + Q_INTERFACES(ctkPluginActivator) + + public: + void start(ctkPluginContext *context) override; + void stop(ctkPluginContext *context) override; + + }; +} + +#endif // MITKPLUGINACTIVATOR_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h deleted file mode 100644 index a14a2a6286..0000000000 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h +++ /dev/null @@ -1,37 +0,0 @@ -/*============================================================================ - -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 org_mitk_gui_qt_viewnavigator_Activator_h -#define org_mitk_gui_qt_viewnavigator_Activator_h - -#include - -namespace mitk { - -class org_mitk_gui_qt_viewnavigator_Activator : - public QObject, public ctkPluginActivator -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_viewnavigator") - Q_INTERFACES(ctkPluginActivator) - -public: - - void start(ctkPluginContext* context) override; - void stop(ctkPluginContext* context) override; - -}; // org_mitk_gui_qt_viewnavigator_Activator - -} - -#endif // org_mitk_gui_qt_viewnavigator_Activator_h diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp index b9b80e8e0b..90e0d7ecf0 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp @@ -1,293 +1,313 @@ /*============================================================================ 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 "QmitkVolumeVisualizationView.h" #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkVolumeVisualizationView::VIEW_ID = "org.mitk.views.volumevisualization"; enum { DEFAULT_RENDERMODE = 0, RAYCAST_RENDERMODE = 1, GPU_RENDERMODE = 2 }; QmitkVolumeVisualizationView::QmitkVolumeVisualizationView() : QmitkAbstractView() , m_Controls(nullptr) { } void QmitkVolumeVisualizationView::SetFocus() { } void QmitkVolumeVisualizationView::CreateQtPartControl(QWidget* parent) { m_Controls = new Ui::QmitkVolumeVisualizationViewControls; m_Controls->setupUi(parent); m_Controls->volumeSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls->volumeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls->volumeSelectionWidget->SetSelectionIsOptional(true); - m_Controls->volumeSelectionWidget->SetAutoSelectNewNodes(true); m_Controls->volumeSelectionWidget->SetEmptyInfo(QString("Please select a 3D / 4D image volume")); m_Controls->volumeSelectionWidget->SetPopUpTitel(QString("Select image volume")); // Fill the transfer function presets in the generator widget std::vector names; mitk::TransferFunctionInitializer::GetPresetNames(names); for (const auto& name : names) { m_Controls->transferFunctionGeneratorWidget->AddPreset(QString::fromStdString(name)); } // see enum in vtkSmartVolumeMapper m_Controls->renderMode->addItem("Default"); m_Controls->renderMode->addItem("RayCast"); m_Controls->renderMode->addItem("GPU"); // see vtkVolumeMapper::BlendModes m_Controls->blendMode->addItem("Comp"); m_Controls->blendMode->addItem("Max"); m_Controls->blendMode->addItem("Min"); m_Controls->blendMode->addItem("Avg"); m_Controls->blendMode->addItem("Add"); connect(m_Controls->volumeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkVolumeVisualizationView::OnCurrentSelectionChanged); connect(m_Controls->enableRenderingCB, SIGNAL(toggled(bool)), this, SLOT(OnEnableRendering(bool))); connect(m_Controls->renderMode, SIGNAL(activated(int)), this, SLOT(OnRenderMode(int))); connect(m_Controls->blendMode, SIGNAL(activated(int)), this, SLOT(OnBlendMode(int))); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalUpdateCanvas()), m_Controls->transferFunctionWidget, SLOT(OnUpdateCanvas())); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalTransferFunctionModeChanged(int)), SLOT(OnMitkInternalPreset(int))); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); m_Controls->volumeSelectionWidget->SetAutoSelectNewNodes(true); + + this->m_TimePointChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); + connect(&m_TimePointChangeListener, + &QmitkSliceNavigationListener::SelectedTimePointChanged, + this, + &QmitkVolumeVisualizationView::OnSelectedTimePointChanged); +} + +void QmitkVolumeVisualizationView::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) +{ + this->m_TimePointChangeListener.RenderWindowPartActivated(renderWindowPart); +} + +void QmitkVolumeVisualizationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) +{ + this->m_TimePointChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkVolumeVisualizationView::OnMitkInternalPreset(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto node = m_SelectedNode.Lock(); mitk::TransferFunctionProperty::Pointer transferFuncProp; if (node->GetProperty(transferFuncProp, "TransferFunction")) { // first item is only information if (--mode == -1) return; // -- Creat new TransferFunction mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(transferFuncProp->GetValue()); tfInit->SetTransferFunctionMode(mode); RequestRenderWindowUpdate(); m_Controls->transferFunctionWidget->OnUpdateCanvas(); } } void QmitkVolumeVisualizationView::OnCurrentSelectionChanged(QList nodes) { m_SelectedNode = nullptr; if (nodes.empty() || nodes.front().IsNull()) { UpdateInterface(); return; } auto selectedNode = nodes.front(); auto image = dynamic_cast(selectedNode->GetData()); if (nullptr != image) { m_SelectedNode = selectedNode; } UpdateInterface(); } void QmitkVolumeVisualizationView::OnEnableRendering(bool state) { if (m_SelectedNode.IsExpired()) { return; } m_SelectedNode.Lock()->SetProperty("volumerendering", mitk::BoolProperty::New(state)); UpdateInterface(); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnRenderMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usegpu = false; bool useray = false; if (DEFAULT_RENDERMODE == mode) { useray = true; usegpu = true; } else if (GPU_RENDERMODE == mode) { usegpu = true; } else if (RAYCAST_RENDERMODE == mode) { useray = true; } selectedNode->SetProperty("volumerendering.usegpu", mitk::BoolProperty::New(usegpu)); selectedNode->SetProperty("volumerendering.useray", mitk::BoolProperty::New(useray)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnBlendMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usemip = false; if (vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND == mode) { usemip = true; } selectedNode->SetProperty("volumerendering.usemip", mitk::BoolProperty::New(usemip)); selectedNode->SetProperty("volumerendering.blendmode", mitk::IntProperty::New(mode)); RequestRenderWindowUpdate(); } +void QmitkVolumeVisualizationView::OnSelectedTimePointChanged(const mitk::TimePointType & /*newTimePoint*/) +{ + this->UpdateInterface(); +} + void QmitkVolumeVisualizationView::UpdateInterface() { if (m_SelectedNode.IsExpired()) { // turnoff all m_Controls->enableRenderingCB->setChecked(false); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } bool enabled = false; auto selectedNode = m_SelectedNode.Lock(); selectedNode->GetBoolProperty("volumerendering", enabled); m_Controls->enableRenderingCB->setEnabled(true); m_Controls->enableRenderingCB->setChecked(enabled); if (!enabled) { // turnoff all except volumerendering checkbox m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } // otherwise we can activate em all m_Controls->blendMode->setEnabled(true); m_Controls->renderMode->setEnabled(true); // Determine Combo Box mode { bool usegpu = false; bool useray = false; bool usemip = false; selectedNode->GetBoolProperty("volumerendering.usegpu", usegpu); selectedNode->GetBoolProperty("volumerendering.useray", useray); selectedNode->GetBoolProperty("volumerendering.usemip", usemip); int blendMode; if (selectedNode->GetIntProperty("volumerendering.blendmode", blendMode)) m_Controls->blendMode->setCurrentIndex(blendMode); if (usemip) m_Controls->blendMode->setCurrentIndex(vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND); int mode = DEFAULT_RENDERMODE; if (useray) mode = RAYCAST_RENDERMODE; else if (usegpu) mode = GPU_RENDERMODE; m_Controls->renderMode->setCurrentIndex(mode); } - - m_Controls->transferFunctionWidget->SetDataNode(selectedNode); + auto time = this->GetRenderWindowPart()->GetTimeNavigationController()->GetSelectedTimeStep(); + m_Controls->transferFunctionWidget->SetDataNode(selectedNode, time); m_Controls->transferFunctionWidget->setEnabled(true); - m_Controls->transferFunctionGeneratorWidget->SetDataNode(selectedNode); + m_Controls->transferFunctionGeneratorWidget->SetDataNode(selectedNode, time); m_Controls->transferFunctionGeneratorWidget->setEnabled(true); } diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h index 5d413576f2..658d5fc133 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h @@ -1,61 +1,69 @@ /*============================================================================ 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 QMITKVOLUMEVISUALIZATIONVIEW_H #define QMITKVOLUMEVISUALIZATIONVIEW_H #include "ui_QmitkVolumeVisualizationViewControls.h" // mitk core #include #include #include +#include +#include /** * @brief */ -class QmitkVolumeVisualizationView : public QmitkAbstractView +class QmitkVolumeVisualizationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkVolumeVisualizationView(); ~QmitkVolumeVisualizationView() override = default; void SetFocus() override; private Q_SLOTS: void OnCurrentSelectionChanged(QList nodes); void OnMitkInternalPreset(int mode); void OnEnableRendering(bool state); void OnRenderMode(int mode); void OnBlendMode(int mode); + void OnSelectedTimePointChanged(const mitk::TimePointType &); + +protected: + void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) override; + void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) override; private: void CreateQtPartControl(QWidget* parent) override; void UpdateInterface(); Ui::QmitkVolumeVisualizationViewControls* m_Controls; mitk::WeakPointer m_SelectedNode; + QmitkSliceNavigationListener m_TimePointChangeListener; }; #endif // QMITKVOLUMEVISUALIZATIONVIEW_H