diff --git a/CMake/BuildConfigurations/Minimal.cmake b/CMake/BuildConfigurations/Minimal.cmake index 65855e9aeb..e382ebc434 100644 --- a/CMake/BuildConfigurations/Minimal.cmake +++ b/CMake/BuildConfigurations/Minimal.cmake @@ -1,6 +1,7 @@ set(BUILD_TESTING OFF CACHE BOOL "" FORCE) set(MITK_USE_BLUEBERRY OFF CACHE BOOL "" FORCE) set(MITK_USE_CTK OFF CACHE BOOL "" FORCE) +set(MITK_USE_httplib OFF CACHE BOOL "" FORCE) set(MITK_USE_Qt6 OFF CACHE BOOL "" FORCE) set(MITK_USE_Qt6Qwt6 OFF CACHE BOOL "" FORCE) diff --git a/CMake/PackageDepends/MITK_cpprestsdk_Config.cmake b/CMake/PackageDepends/MITK_cpprestsdk_Config.cmake deleted file mode 100644 index 7764cec563..0000000000 --- a/CMake/PackageDepends/MITK_cpprestsdk_Config.cmake +++ /dev/null @@ -1 +0,0 @@ -set(ALL_LIBRARIES cpprestsdk::cpprest) diff --git a/CMake/mitkInstallRules.cmake b/CMake/mitkInstallRules.cmake index 2aa56b530d..18716c46ad 100644 --- a/CMake/mitkInstallRules.cmake +++ b/CMake/mitkInstallRules.cmake @@ -1,184 +1,188 @@ # Install MITK icon and logo MITK_INSTALL(FILES "${MITK_SOURCE_DIR}/mitk.ico" "${MITK_SOURCE_DIR}/mitk.bmp") # Helper vars if(WIN32) set(_prefix "") set(_ext ".dll") elseif(UNIX) set(_prefix "lib") if(APPLE) set(_ext ".dylib") else() set(_ext ".so") endif() endif() # Install MITK executables including auto-load modules get_property(_mitk_executable_targets GLOBAL PROPERTY MITK_EXECUTABLE_TARGETS) if(_mitk_executable_targets) get_property(_mitk_module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) foreach(_mitk_module_target ${_mitk_module_targets}) if(TARGET ${_mitk_module_target}) get_target_property(_mitk_autoload_targets ${_mitk_module_target} MITK_AUTOLOAD_TARGETS) if (_mitk_autoload_targets) foreach(_mitk_autoload_target ${_mitk_autoload_targets}) get_target_property(_mitk_autoload_directory ${_mitk_autoload_target} MITK_AUTOLOAD_DIRECTORY) if (_mitk_autoload_directory) if(WIN32) get_target_property(_target_location ${_mitk_autoload_target} RUNTIME_OUTPUT_DIRECTORY) else() get_target_property(_target_location ${_mitk_autoload_target} LIBRARY_OUTPUT_DIRECTORY) endif() if(NOT CMAKE_CFG_INTDIR STREQUAL ".") set(_target_location "${_target_location}/Release") endif() set(_mitk_autoload_target_filename "${_prefix}${_mitk_autoload_target}${_ext}") set(_mitk_autoload_target_filepath "${_target_location}/${_mitk_autoload_target_filename}") set(_install_DESTINATION "${_mitk_autoload_directory}") MITK_INSTALL(FILES ${_mitk_autoload_target_filepath}) if(UNIX AND NOT APPLE) install(CODE "file(RPATH_REMOVE FILE \"\${CMAKE_INSTALL_PREFIX}/bin/${_mitk_autoload_directory}/${_mitk_autoload_target_filename}\")") endif() endif() endforeach() endif() endif() endforeach() set(_install_DESTINATION "") foreach(_mitk_executable_target ${_mitk_executable_targets}) get_target_property(_no_install ${_mitk_executable_target} NO_INSTALL) if(_no_install) continue() endif() MITK_INSTALL_TARGETS(EXECUTABLES ${_mitk_executable_target} GLOB_PLUGINS) get_target_property(_command_line_app ${_mitk_executable_target} COMMAND_LINE_APP) if(_command_line_app) set(_source "RunInstalledCmdLineApp") set(_destination "apps") else() set(_source "RunInstalledApp") set(_destination ".") endif() if(UNIX AND NOT APPLE) install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/${_source}.sh" DESTINATION "${_destination}" RENAME "${_mitk_executable_target}.sh") elseif(WIN32) get_target_property(_win32_exec ${_mitk_executable_target} WIN32_EXECUTABLE) if(_win32_exec) install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/RunInstalledWin32App.bat" DESTINATION "${_destination}" RENAME "${_mitk_executable_target}.bat") else() install(PROGRAMS "${MITK_SOURCE_DIR}/CMake/${_source}.bat" DESTINATION "${_destination}" RENAME "${_mitk_executable_target}.bat") endif() endif() endforeach() endif() # Install PythonQt if(MITK_USE_Python3 AND PythonQt_DIR) set(_python_qt_lib "${PythonQt_DIR}/") if(WIN32) set(_python_qt_lib "${_python_qt_lib}bin") else() set(_python_qt_lib "${_python_qt_lib}lib") endif() set(_python_qt_lib "${_python_qt_lib}/${_prefix}PythonQt${_ext}") MITK_INSTALL(FILES ${_python_qt_lib}) endif() # Install Qt plugins if(MITK_USE_Qt6) get_filename_component(_qmake_path "${QT_QMAKE_EXECUTABLE}" DIRECTORY) set(_install_DESTINATION "plugins/sqldrivers") MITK_INSTALL(FILES "${_qmake_path}/../plugins/sqldrivers/${_prefix}qsqlite${_ext}") set(_install_DESTINATION "plugins/imageformats") MITK_INSTALL(FILES "${_qmake_path}/../plugins/imageformats/${_prefix}qsvg${_ext}") set(_install_DESTINATION "plugins/iconengines") MITK_INSTALL(FILES "${_qmake_path}/../plugins/iconengines/${_prefix}qsvgicon${_ext}") # Install platform-specific Qt plugins set(_install_DESTINATION "plugins/platforms") if(WIN32) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/qwindows.dll") elseif(APPLE) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/libqcocoa.dylib") elseif(UNIX) MITK_INSTALL(FILES "${_qmake_path}/../plugins/platforms/libqxcb.so") set(_install_DESTINATION "plugins/xcbglintegrations") MITK_INSTALL(FILES "${_qmake_path}/../plugins/xcbglintegrations/libqxcb-glx-integration.so") endif() # Install platform-specific Qt styles set(_install_DESTINATION "plugins/styles") if(WIN32) - MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/qwindowsvistastyle.dll") + if(Qt6_VERSION VERSION_LESS 6.7) + MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/qwindowsvistastyle.dll") + else() + MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/qmodernwindowsstyle.dll") + endif() elseif(APPLE) MITK_INSTALL(FILES "${_qmake_path}/../plugins/styles/libqmacstyle.dylib") endif() # Install Qt WebEngine if(APPLE) set(_install_DESTINATION "../Frameworks/QtWebEngineCore.framework") get_filename_component(_real_path "${_qmake_path}/../lib/QtWebEngineCore.framework/Helpers" REALPATH) MITK_INSTALL(DIRECTORY ${_real_path} USE_SOURCE_PERMISSIONS) # Translations are included in the Resources directory of # QtWebEngineCore.framework and are installed by default. else() set(_install_DESTINATION "") if(WIN32) MITK_INSTALL(PROGRAMS "${_qmake_path}/QtWebEngineProcess.exe") elseif(UNIX) MITK_INSTALL(PROGRAMS "${_qmake_path}/../libexec/QtWebEngineProcess") endif() # make sure resources and translations exist and try system location as well if(EXISTS "${_qmake_path}/../resources") MITK_INSTALL(DIRECTORY "${_qmake_path}/../resources") elseif(EXISTS "/usr/share/qt6/resources") MITK_INSTALL(DIRECTORY "/usr/share/qt6/resources") else() message(WARNING "No webengine resources found!") endif() set(_install_DESTINATION "translations") if(EXISTS "${_qmake_path}/../translations/qtwebengine_locales") MITK_INSTALL(DIRECTORY "${_qmake_path}/../translations/qtwebengine_locales") elseif(EXISTS "/usr/share/qt6/translations/qtwebengine_locales") MITK_INSTALL(DIRECTORY "/usr/share/qt6/translations/qtwebengine_locales") else() message(WARNING "No webengine translations found!") endif() endif() endif() set(_install_DESTINATION "") # Install MatchPoint binaries that are not auto-detected if(MITK_USE_MatchPoint) MITK_INSTALL(DIRECTORY "${MITK_EXTERNAL_PROJECT_PREFIX}/bin/" FILES_MATCHING PATTERN "MapUtilities*") MITK_INSTALL(DIRECTORY "${MITK_EXTERNAL_PROJECT_PREFIX}/bin/" FILES_MATCHING PATTERN "MapAlgorithms*") endif() # IMPORTANT: Restore default install destination! Do not edit this file beyond this line! set(_install_DESTINATION "") diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index dfd6ab5826..b72dc68825 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,30 +1,29 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) mitkFunctionAddExternalProject(NAME ANN ON ADVANCED DOC "Use Approximate Nearest Neighbor Library") mitkFunctionAddExternalProject(NAME CppUnit ON ADVANCED DOC "Use CppUnit for unit tests") mitkFunctionAddExternalProject(NAME HDF5 ON ADVANCED) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) mitkFunctionAddExternalProject(NAME ZLIB OFF ADVANCED) mitkFunctionAddExternalProject(NAME lz4 ON ADVANCED) -mitkFunctionAddExternalProject(NAME cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) mitkFunctionAddExternalProject(NAME ACVD OFF DOC "Use Approximated Centroidal Voronoi Diagrams") mitkFunctionAddExternalProject(NAME CTK ON DEPENDS Qt6 DCMTK DOC "Use CTK in MITK") mitkFunctionAddExternalProject(NAME DCMQI ON DEPENDS DCMTK ITK DOC "Use dcmqi in MITK") mitkFunctionAddExternalProject(NAME MatchPoint OFF ADVANCED DEPENDS Boost ITK DOC "Use the MatchPoint translation image registration library") mitkFunctionAddExternalProject(NAME nlohmann_json ON ADVANCED) mitkFunctionAddExternalProject(NAME httplib ON DEPENDS ZLIB) if(MITK_USE_Qt6) mitkFunctionAddExternalProject(NAME Qt6Qwt6 ON ADVANCED DEPENDS Qt6) endif() if(UNIX AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/CMakeExternals/cpprestsdk.cmake b/CMakeExternals/cpprestsdk.cmake deleted file mode 100644 index 3c172eab55..0000000000 --- a/CMakeExternals/cpprestsdk.cmake +++ /dev/null @@ -1,41 +0,0 @@ -set(proj cpprestsdk) -set(proj_DEPENDENCIES Boost ZLIB) - -if(MITK_USE_${proj}) - set(${proj}_DEPENDS ${proj}) - - if(DEFINED ${proj}_DIR AND NOT EXISTS ${${proj}_DIR}) - message(FATAL_ERROR "${proj}_DIR variable is defined but corresponds to non-existing directory!") - endif() - - if(NOT DEFINED ${proj}_DIR) - set(cmake_cache_args - ${ep_common_cache_args} - -DBUILD_SAMPLES:BOOL=OFF - -DBUILD_TESTS:BOOL=OFF - -DWERROR:BOOL=OFF - ) - - if(OPENSSL_ROOT_DIR) - list(APPEND cmake_cache_args - -DOPENSSL_ROOT_DIR:PATH=${OPENSSL_ROOT_DIR} - ) - endif() - - ExternalProject_Add(${proj} - GIT_REPOSITORY https://github.com/MITK/cpprestsdk.git - GIT_TAG v2.10.19-patched - SOURCE_SUBDIR Release - CMAKE_ARGS - "-DBoost_DIR:PATH=${Boost_DIR}" - ${ep_common_args} - CMAKE_CACHE_ARGS ${cmake_cache_args} - CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} - DEPENDS ${proj_DEPENDENCIES} - ) - - set(${proj}_DIR ${ep_prefix}) - else() - mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") - endif() -endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 07ce3cfc0b..c6c7a03c1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1415 +1,1404 @@ #[[ 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) if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) # https://cmake.org/cmake/help/v3.24/policy/CMP0135.html endif() #----------------------------------------------------------------------------- # 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 2024.06.00) + project(MITK VERSION 2024.06.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() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionGetGccVersion(${CMAKE_CXX_COMPILER} GCC_VERSION) else() set(GCC_VERSION 0) endif() set(MITK_CXX_STANDARD 17) 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++ flag for targets. # However, compile flag checks also need to be done with -std=c++. # The MITK_CXX_FLAG variable is also used for external projects # build during the MITK super-build. mitkFunctionCheckCompilerFlags("-std=c++${MITK_CXX_STANDARD}" MITK_CXX${MITK_CXX_STANDARD}_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_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 "WorkbenchRelease" 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_Qt6 "Use Qt 6 library" ON) if(MITK_USE_Qt6) set(MITK_QT6_MINIMUM_VERSION 6.6) set(MITK_QT6_COMPONENTS Concurrent Core Core5Compat CoreTools Designer DesignerComponentsPrivate Gui Help LinguistTools Network OpenGL OpenGLWidgets Qml Sql StateMachine Svg ToolsTools UiTools WebEngineCore WebEngineWidgets Widgets Xml ) if(APPLE) list(APPEND MITK_QT6_COMPONENTS DBus) endif() # Hint at default install locations of Qt if(NOT Qt6_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 2022) list(APPEND _compilers "msvc2019") # 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() if(APPLE) list(APPEND _compilers macos) endif() foreach(_dir_candidate ${_dir_candidates}) get_filename_component(_dir_candidate ${_dir_candidate} REALPATH) foreach(_compiler ${_compilers}) set(_glob_expression "${_dir_candidate}/6.*/${_compiler}") file(GLOB _hints ${_glob_expression}) list(SORT _hints) list(APPEND MITK_QT6_HINTS ${_hints}) endforeach() endforeach() endif() find_package(Qt6 ${MITK_QT6_MINIMUM_VERSION} COMPONENTS ${MITK_QT6_COMPONENTS} REQUIRED HINTS ${MITK_QT6_HINTS}) get_target_property(QT_QMAKE_EXECUTABLE Qt6::qmake LOCATION) get_target_property(QT_HELPGENERATOR_EXECUTABLE Qt6::qhelpgenerator LOCATION) endif() if(Qt6_DIR) list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}/../../..") list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH) endif() # ----------------------------------------- # Custom dependency logic if(WIN32 AND Qt6_DIR) set(_dir_candidate "${Qt6_DIR}/../../../../../Tools/OpenSSLv3/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 3) if(NOT OpenSSL_FOUND) find_package(OpenSSL 1.1.1) endif() 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 OR MITK_USE_httplib) AND NOT OpenSSL_FOUND) - set(openssl_message "Could not find OpenSSL (dependency of C++ REST SDK and cpp-httplib).\n") +if(MITK_USE_httplib AND NOT OpenSSL_FOUND) + set(openssl_message "Could not find OpenSSL (dependency of cpp-httplib).\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 (recommended):\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() -if(MITK_USE_cpprestsdk) - 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) set(python3_mininum_version 3.11) else() set(python3_mininum_version 3.8) endif() find_package(Python3 ${python3_mininum_version} REQUIRED COMPONENTS Interpreter Development NumPy) 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(mitkFunctionAddManifest) 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) #----------------------------------------------------------------------------- # Global CMake variables #----------------------------------------------------------------------------- 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) #[[ On Windows, CMake is able to locate the link libraries for OpenSSL but it does not look for the corresponding DLLs that we need to copy to our binary directories and include in packaging. Setting these paths manually is cumbersome so we try to use a simple heuristic to automatically set them: - Based on the link libraries (usually located in a lib folder), try to find the "../bin" binary directory. - Use the base file names of the link libraries to find corresponding DLLs like "*.dll", that usually are named like "-1_1-x64.dll" or similar. ]] set(openssl_ssl_dll "") set(openssl_crypto_dll "") if(OPENSSL_SSL_LIBRARY AND EXISTS "${OPENSSL_SSL_LIBRARY}") get_filename_component(openssl_bin_dir "${OPENSSL_SSL_LIBRARY}" DIRECTORY) get_filename_component(openssl_bin_dir "${openssl_bin_dir}" DIRECTORY) set(openssl_bin_dir "${openssl_bin_dir}/bin") if(EXISTS "${openssl_bin_dir}") get_filename_component(openssl_ssl_basename "${OPENSSL_SSL_LIBRARY}" NAME_WE) file(GLOB openssl_ssl_dll "${openssl_bin_dir}/${openssl_ssl_basename}*.dll") list(LENGTH openssl_ssl_dll num_findings) if(num_findings GREATER 1) set(openssl_ssl_dll "") endif() get_filename_component(openssl_crypto_basename "${OPENSSL_CRYPTO_LIBRARY}" NAME_WE) file(GLOB openssl_crypto_dll "${openssl_bin_dir}/${openssl_crypto_basename}*.dll") list(LENGTH openssl_crypto_dll num_findings) if(num_findings GREATER 1) set(openssl_crypto_dll "") endif() endif() endif() set(MITK_OPENSSL_SSL_DLL "${openssl_ssl_dll}" CACHE FILEPATH "") if(DEFINED CACHE{MITK_OPENSSL_SSL_DLL} AND NOT MITK_OPENSSL_SSL_DLL AND openssl_ssl_dll) set(MITK_OPENSSL_SSL_DLL "${openssl_ssl_dll}" CACHE FILEPATH "" FORCE) endif() set(MITK_OPENSSL_CRYPTO_DLL "${openssl_crypto_dll}" CACHE FILEPATH "") if(DEFINED CACHE{MITK_OPENSSL_CRYPTO_DLL} AND NOT MITK_OPENSSL_CRYPTO_DLL AND openssl_crypto_dll) set(MITK_OPENSSL_CRYPTO_DLL "${openssl_crypto_dll}" CACHE FILEPATH "" FORCE) endif() 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_CXX${MITK_CXX_STANDARD}_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} -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-deprecated-copy -Wno-array-bounds -Wno-cast-function-type -Wno-maybe-uninitialized -Wno-error=stringop-overread -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 "externally 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 explicitly 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_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_Qt6) 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_Qt6) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because MITK_USE_Qt6 is OFF.") 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) # 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 #----------------------------------------------------------------------------- set(MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS) set(MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS) set(MITK_DOXYGEN_ADDITIONAL_EXAMPLE_PATHS) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS "${MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS} \"${MITK_EXTENSION_DIR}\"") set(MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS "${MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS} \"${MITK_EXTENSION_DIR}\"") # MITK_DOXYGEN_ADDITIONAL_EXAMPLE_PATHS should be modified by MITK extensions as needed endforeach() if(DOXYGEN_FOUND) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_DOCUMENTATION_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Documentation") file(GLOB MITK_DOCUMENTATION_EXTENSION_FILES CONFIGURE_DEPENDS "${MITK_DOCUMENTATION_EXTENSION_DIR}/*.cmake") foreach(doc_file ${MITK_DOCUMENTATION_EXTENSION_FILES}) include("${doc_file}") endforeach() endforeach() # Transform list of example paths into space-separated string of quoted paths unset(example_paths) foreach(example_path ${MITK_DOXYGEN_ADDITIONAL_EXAMPLE_PATHS}) set(example_paths "${example_paths} \"${example_path}\"") endforeach() set(MITK_DOXYGEN_ADDITIONAL_EXAMPLE_PATHS "${example_paths}") 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/MITKModuleManualsList.dox b/Documentation/Doxygen/3-DeveloperManual/MITKModuleManualsList.dox index 81e21568b8..e5b9cb45b2 100644 --- a/Documentation/Doxygen/3-DeveloperManual/MITKModuleManualsList.dox +++ b/Documentation/Doxygen/3-DeveloperManual/MITKModuleManualsList.dox @@ -1,11 +1,10 @@ /** \page MITKModuleManualsList List of Module Manuals \li \subpage AnnotationModulePage \li \subpage ChartModule \li \subpage LegacyGLModule \li \subpage mitkPython_Overview - \li \subpage RESTModule */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox index 1a2190b2e0..b383f62666 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox @@ -1,95 +1,95 @@ /** \page thirdpartylibs Third-party libraries The following third-party libraries can be used with MITK by default and can, in part, be automatically downloaded during superbuild. \par ACVD https://www.creatis.insa-lyon.fr/~valette/public/project/acvd/ \par ANN https://www.cs.umd.edu/~mount/ANN/ \par Boost https://www.boost.org/ -\par C++ REST SDK +\par cpp-httplib -https://github.com/Microsoft/cpprestsdk/ +https://github.com/yhirose/cpp-httplib \par CppUnit https://sourceforge.net/projects/cppunit/ \par CTK https://commontk.org/ \par DCMTK https://dicom.offis.de/dcmtk \par GDCM https://gdcm.sourceforge.net/ \par HDF5 https://support.hdfgroup.org/HDF5/ \par ITK https://itk.org/ \par JSON for Modern C++ https://github.com/nlohmann/json \par lz4 https://github.com/lz4/lz4 \par MatchPoint https://www.dkfz.de/en/sidt/projects/MatchPoint/info.html \par PCRE https://www.pcre.org/ \par POCO https://pocoproject.org/ \par Python https://www.python.org/ \par Qt https://www.qt.io/ \par Qwt https://qwt.sourceforge.io/ \par SWIG https://www.swig.org/ \par TinyXML-2 https://www.grinninglizard.com/tinyxml2/ \par VTK https://vtk.org/ \par zlib https://zlib.net/ For copyright information on any of the above toolkits see the corresponding home page or the corresponding source folder. */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Toolkit/ModuleManuals/MITKModuleManualsList.dox b/Documentation/Doxygen/3-DeveloperManual/Toolkit/ModuleManuals/MITKModuleManualsList.dox index d30358657b..b8cafb4a07 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Toolkit/ModuleManuals/MITKModuleManualsList.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Toolkit/ModuleManuals/MITKModuleManualsList.dox @@ -1,29 +1,28 @@ /** \page MITKModuleManualsListPage MITK Module Manuals Overview The modules are shared libraries that provide functionality that can be used by developers. \subpage MITKModuleManualsList List of Module Manuals
  • \ref AnnotationModulePage
  • \ref ChartModule
  • \ref LegacyGLModule
  • \ref mitkPython_Overview -
  • \ref RESTModule
\subpage MITKModuleManualsListPageAdditionalInformation Additional Information on Certain Modules
  • \ref PlanarPropertiesPage
\subpage MITKMigrationGuides Migration Guides
  • \ref GeometryMigration
  • \ref InteractionMigration
  • \ref OverlayMigration
*/ diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index f1bd7b5ec4..ea75df2801 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,330 +1,331 @@ 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/mitkCrosshairManager.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSliceNavigationHelper.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkTimeNavigationController.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkAffineTransform3D.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/mitkCrosshairData.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/mitkIPropertyDeserialization.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp DataManagement/mitkITKEventObserverGuard.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp 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/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.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/mitkPropertyDeserialization.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/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkWeakPointerProperty.cpp DataManagement/mitkIPropertyRelations.cpp DataManagement/mitkPropertyRelations.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.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/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/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp + IO/mitkIOVolumeSplitReason.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLogBackend.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/mitkUtf8Util.cpp IO/mitkVtkLoggingAdapter.cpp IO/mitkPreferenceListReaderOptionsFunctor.cpp IO/mitkIOMetaInformationPropertyConstants.cpp IO/mitkIPreferences.cpp IO/mitkPreferences.cpp IO/mitkIPreferencesService.cpp IO/mitkPreferencesService.cpp IO/mitkIPreferencesStorage.cpp IO/mitkXMLPreferencesStorage.cpp Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp Rendering/mitkBaseRendererHelper.cpp Rendering/mitkCrosshairVtkMapper2D.cpp 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/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVideoRecorder.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/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/DisplayConfigBlockLMB.xml Interactions/PointSet.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml mitkAnatomicalStructureColorPresets.xml ) diff --git a/Modules/Core/include/mitkIOMetaInformationPropertyConstants.h b/Modules/Core/include/mitkIOMetaInformationPropertyConstants.h index 854d6cd673..41991a3f02 100644 --- a/Modules/Core/include/mitkIOMetaInformationPropertyConstants.h +++ b/Modules/Core/include/mitkIOMetaInformationPropertyConstants.h @@ -1,44 +1,46 @@ /*============================================================================ 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 mitkIOMetaInformationPropertyConstants_h #define mitkIOMetaInformationPropertyConstants_h #include #include "mitkPropertyKeyPath.h" namespace mitk { /** * @ingroup IO * @brief The IOMetaInformationPropertyConstants struct */ struct MITKCORE_EXPORT IOMetaInformationPropertyConstants { //Path to the property containing the name of the reader used static PropertyKeyPath READER_DESCRIPTION(); //Path to the property containing the version of mitk used to read the data static PropertyKeyPath READER_VERSION(); //Path to the property containing the mine name detected used to read the data static PropertyKeyPath READER_MIME_NAME(); //Path to the property containing the mime category detected to read the data static PropertyKeyPath READER_MIME_CATEGORY(); //Path to the property containing the input location if loaded by file used to read the data static PropertyKeyPath READER_INPUTLOCATION(); + //Path to the property containing split reason information for the read volume + static PropertyKeyPath VOLUME_SPLIT_REASON(); //Path to the properties containing the reader options used to read the data static PropertyKeyPath READER_OPTION_ROOT(); static PropertyKeyPath READER_OPTIONS_ANY(); }; } #endif diff --git a/Modules/Core/include/mitkIOVolumeSplitReason.h b/Modules/Core/include/mitkIOVolumeSplitReason.h new file mode 100644 index 0000000000..b27a9e96ef --- /dev/null +++ b/Modules/Core/include/mitkIOVolumeSplitReason.h @@ -0,0 +1,105 @@ +/*============================================================================ + +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 mitkIOVolumeSplitReason_h +#define mitkIOVolumeSplitReason_h + +#include +#include +#include + +#include "MitkCoreExports.h" + +namespace mitk +{ + + class MITKCORE_EXPORT IOVolumeSplitReason + { + public: + using Self = IOVolumeSplitReason; + using Pointer = std::shared_ptr; + using ConstPointer = std::shared_ptr; + + enum class ReasonType + { + Unknown = 0, + ValueSplitDifference, //*< split due to different values in splitting relevant dicom tags + ValueSortDistance, //*< split due value distance of sort criterion too large for relevant dicom tag(s) + ImagePostionMissing, //*< split because image position tag was missing in one of the compared files + OverlappingSlices, //*< split because at least two input files are overlapping in world coordinate space + GantryTiltDifference, //*< split because the gantry tilts of at least two input files were different + SliceDistanceInconsistency, //*< split because the distance between slices were inconsistent. + // This can either be evoked by volumes with heterogeneous z spacing or by missing slices. + // Details for this reason will contain the detected slice distance inconsistency + MissingSlices //*< Indicates that is a split was done due to missing slices. (It is a sub class of SliceDistanceInconsistency + // as all SliceDistanceInconsistency with a positive distance inconsistency greater then one times the slice + // thickness are deemed also missing slices as split reason. This sub class was introduced to make it easier + // for parsing applications to react on this important split reason. + // Details for this reason will contain the assumed/detected number of missing slices + }; + + void AddReason(ReasonType type, const std::string& detail = ""); + void RemoveReason(ReasonType type); + + bool HasReasons() const; + bool HasReason(ReasonType type) const; + std::string GetReasonDetails(ReasonType type) const; + + /** This methods generates a clone of this instances and extends the clone by the reason types and details + provided by another IOVolumeSplitReason instance. + @remark If the other instance contains a reason type that is already existing it will be ignored. + Therefor only new types and details will be added to the extension. + @pre otherReason must point to a valid instance. + @return Pointer to the cloned and extended instance.*/ + Pointer ExtendReason(ConstPointer otherReason) const; + + static nlohmann::json ToJSON(ConstPointer); + static Pointer FromJSON(const nlohmann::json& j); + + static std::string TypeToString(ReasonType reasonType); + static IOVolumeSplitReason::ReasonType StringToType(const std::string& reasonStr); + + Pointer Clone() const; + static Pointer New(); + + protected: + using ReasonMapType = std::map; + ReasonMapType m_ReasonMap; + }; + + template + inline void to_json(BasicJsonType& j, const IOVolumeSplitReason::ReasonType& e) + { + static_assert(std::is_enum::value, + "IOVolumeSplitReason::ReasonType" + " must be an enum!"); + j = IOVolumeSplitReason::TypeToString(e); + } + + template + inline void from_json(const BasicJsonType& j, IOVolumeSplitReason::ReasonType& e) + { + static_assert(std::is_enum::value, + "IOVolumeSplitReason::ReasonType" + " must be an enum!"); + e = IOVolumeSplitReason::StringToType(j.template get()); + } + + + MITKCORE_EXPORT void to_json(nlohmann::json& j, IOVolumeSplitReason::ConstPointer reason); + MITKCORE_EXPORT void to_json(nlohmann::json& j, IOVolumeSplitReason::Pointer reason); + + MITKCORE_EXPORT void from_json(const nlohmann::json& j, IOVolumeSplitReason& reason); + +} + +#endif diff --git a/Modules/Core/include/mitkTemporoSpatialStringProperty.h b/Modules/Core/include/mitkTemporoSpatialStringProperty.h index 8f6f090f93..015a14320d 100644 --- a/Modules/Core/include/mitkTemporoSpatialStringProperty.h +++ b/Modules/Core/include/mitkTemporoSpatialStringProperty.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 mitkTemporoSpatialStringProperty_h #define mitkTemporoSpatialStringProperty_h #include #include "mitkBaseProperty.h" #include #include "mitkTimeGeometry.h" #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief Property for time and space resolved string values * @ingroup DataManagement */ class MITKCORE_EXPORT TemporoSpatialStringProperty : public BaseProperty { public: typedef ::itk::IndexValueType IndexValueType; typedef std::string ValueType; mitkClassMacro(TemporoSpatialStringProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); mitkNewMacro1Param(TemporoSpatialStringProperty, const char*); mitkNewMacro1Param(TemporoSpatialStringProperty, const std::string &); /**Returns the value of the first time point in the first slice. * If now value is set it returns an empty string.*/ ValueType GetValue() const; /**Returns the value of the passed time step and slice. If it does not exist and allowedClosed is true * it will look for the closest value. If nothing could be found an empty string will be returned.*/ ValueType GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; ValueType GetValueBySlice(const IndexValueType &zSlice, bool allowClose = false) const; ValueType GetValueByTimeStep(const TimeStepType &timeStep, bool allowClose = false) const; bool HasValue() const; bool HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; bool HasValueBySlice(const IndexValueType &zSlice, bool allowClose = false) const; bool HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose = false) const; /** return all slices stored for the specified timestep.*/ std::vector GetAvailableSlices(const TimeStepType& timeStep) const; /** return all time steps stored for the specified slice.*/ std::vector GetAvailableTimeSteps(const IndexValueType& slice) const; /** return all time steps stored in the property.*/ std::vector GetAvailableTimeSteps() const; /** return all slices stored in the property. @remark not all time steps may contain all slices.*/ std::vector GetAvailableSlices() const; void SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value); void SetValue(const ValueType &value); std::string GetValueAsString() const override; /** Indicates of all values (all time steps, all slices) are the same, or if at least one value stored in the property is different. If IsUniform==true one can i.a. use GetValueAsString() without the loss of information to retrieve the stored value.*/ bool IsUniform() const; bool ToJSON(nlohmann::json& j) const override; bool FromJSON(const nlohmann::json& j) override; using BaseProperty::operator=; protected: typedef std::map SliceMapType; typedef std::map TimeMapType; TimeMapType m_Values; TemporoSpatialStringProperty(const char *string = nullptr); TemporoSpatialStringProperty(const std::string &s); TemporoSpatialStringProperty(const TemporoSpatialStringProperty &); std::pair CheckValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime = false, bool allowCloseSlice = false) const; private: // purposely not implemented TemporoSpatialStringProperty &operator=(const TemporoSpatialStringProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; namespace PropertyPersistenceSerialization { /** Serialization of a TemporoSpatialStringProperty into a JSON string.*/ MITKCORE_EXPORT std::string serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop); } namespace PropertyPersistenceDeserialization { /**Deserialize a passed JSON string into a TemporoSpatialStringProperty.*/ MITKCORE_EXPORT mitk::BaseProperty::Pointer deserializeJSONToTemporoSpatialStringProperty(const std::string &value); } + /** Helper function that extracts the information of a time step out of a TemporoSpatialStringProperty + * and returns a TemporoSpatialStringProperty that only contains that time step. + * @param tsProperty The source property from which the values should be extracted. + * @param ts The time point that should be extracted. + * @pre tsProperty must point to a valid instance. + * @pre ts must indicate a time step that exists in tsProperty. + * @result Returns a TemporoSpatialStringProperty instance that only contains the values of the indicated time step. In the result the time step is always time step 0.*/ + TemporoSpatialStringProperty::Pointer MITKCORE_EXPORT ExtractTimeStepFromTemporoSpatialStringProperty(const TemporoSpatialStringProperty* tsProperty, TimeStepType ts); + #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp b/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp index 811f2029c8..c30160b06e 100644 --- a/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp +++ b/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp @@ -1,348 +1,359 @@ /*============================================================================ 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 "mitkImageStatisticsHolder.h" #include "mitkHistogramGenerator.h" #include #include "mitkImageAccessByItk.h" //#define BOUNDINGOBJECT_IGNORE mitk::ImageStatisticsHolder::ImageStatisticsHolder(mitk::Image *image) : m_Image(image) { m_CountOfMinValuedVoxels.resize(1, 0); m_CountOfMaxValuedVoxels.resize(1, 0); m_ScalarMin.resize(1, itk::NumericTraits::max()); m_ScalarMax.resize(1, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.resize(1, itk::NumericTraits::max()); m_Scalar2ndMax.resize(1, itk::NumericTraits::NonpositiveMin()); mitk::HistogramGenerator::Pointer generator = mitk::HistogramGenerator::New(); m_HistogramGeneratorObject = generator; } mitk::ImageStatisticsHolder::~ImageStatisticsHolder() { m_HistogramGeneratorObject = nullptr; } const mitk::ImageStatisticsHolder::HistogramType *mitk::ImageStatisticsHolder::GetScalarHistogram( int t, unsigned int /*component*/) { mitk::ImageTimeSelector *timeSelector = this->GetTimeSelector(); if (timeSelector != nullptr) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); auto *generator = static_cast(m_HistogramGeneratorObject.GetPointer()); generator->SetImage(timeSelector->GetOutput()); generator->ComputeHistogram(); return static_cast(generator->GetHistogram()); } return nullptr; } bool mitk::ImageStatisticsHolder::IsValidTimeStep(int t) const { return m_Image->IsValidTimeStep(t); } mitk::ImageTimeSelector::Pointer mitk::ImageStatisticsHolder::GetTimeSelector() { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); return timeSelector; } void mitk::ImageStatisticsHolder::Expand(unsigned int timeSteps) { if (!m_Image->IsValidTimeStep(timeSteps - 1)) return; // The BaseData needs to be expanded, call the mitk::Image::Expand() method m_Image->Expand(timeSteps); if (timeSteps > m_ScalarMin.size()) { m_ScalarMin.resize(timeSteps, itk::NumericTraits::max()); m_ScalarMax.resize(timeSteps, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.resize(timeSteps, itk::NumericTraits::max()); m_Scalar2ndMax.resize(timeSteps, itk::NumericTraits::NonpositiveMin()); m_CountOfMinValuedVoxels.resize(timeSteps, 0); m_CountOfMaxValuedVoxels.resize(timeSteps, 0); } } void mitk::ImageStatisticsHolder::ResetImageStatistics() { m_ScalarMin.assign(1, itk::NumericTraits::max()); m_ScalarMax.assign(1, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.assign(1, itk::NumericTraits::max()); m_Scalar2ndMax.assign(1, itk::NumericTraits::NonpositiveMin()); m_CountOfMinValuedVoxels.assign(1, 0); m_CountOfMaxValuedVoxels.assign(1, 0); } /// \cond SKIP_DOXYGEN template void mitk::_ComputeExtremaInItkImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t) { typename ItkImageType::RegionType region; region = itkImage->GetBufferedRegion(); if (region.Crop(itkImage->GetRequestedRegion()) == false) return; if (region != itkImage->GetRequestedRegion()) return; itk::ImageRegionConstIterator it(itkImage, region); - typedef typename ItkImageType::PixelType TPixel; - TPixel value = 0; + ScalarType value = 0; if (statisticsHolder == nullptr || !statisticsHolder->IsValidTimeStep(t)) return; statisticsHolder->Expand(t + 1); // make sure we have initialized all arrays statisticsHolder->m_CountOfMinValuedVoxels[t] = 0; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 0; statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t] = itk::NumericTraits::max(); statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t] = itk::NumericTraits::NonpositiveMin(); + bool foundValidValue = false; + while (!it.IsAtEnd()) { value = it.Get(); + + if (!foundValidValue && !std::isnan(value)) + foundValidValue = true; + #ifdef BOUNDINGOBJECT_IGNORE if (value > -32765) { #endif // update min if (value < statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t]; statisticsHolder->m_ScalarMin[t] = value; statisticsHolder->m_CountOfMinValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMin[t]) { ++statisticsHolder->m_CountOfMinValuedVoxels[t]; } else if (value < statisticsHolder->m_Scalar2ndMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = value; } // update max if (value > statisticsHolder->m_ScalarMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t]; statisticsHolder->m_ScalarMax[t] = value; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMax[t]) { ++statisticsHolder->m_CountOfMaxValuedVoxels[t]; } else if (value > statisticsHolder->m_Scalar2ndMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = value; } #ifdef BOUNDINGOBJECT_IGNORE } #endif ++it; } + if (!foundValidValue) + { + statisticsHolder->m_ScalarMax[t] = 0; + statisticsHolder->m_ScalarMin[t] = 0; + } + //// guard for wrong 2dMin/Max on single constant value images if (statisticsHolder->m_ScalarMax[t] == statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMax[t]; } statisticsHolder->m_LastRecomputeTimeStamp.Modified(); } /// \endcond SKIP_DOXYGEN /// \cond SKIP_DOXYGEN template void mitk::_ComputeExtremaInItkVectorImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t, unsigned int component) { typename ItkImageType::RegionType region; region = itkImage->GetBufferedRegion(); if (region.Crop(itkImage->GetRequestedRegion()) == false) return; if (region != itkImage->GetRequestedRegion()) return; itk::ImageRegionConstIterator it(itkImage, region); if (statisticsHolder == nullptr || !statisticsHolder->IsValidTimeStep(t)) return; statisticsHolder->Expand(t + 1); // make sure we have initialized all arrays statisticsHolder->m_CountOfMinValuedVoxels[t] = 0; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 0; statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t] = itk::NumericTraits::max(); statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t] = itk::NumericTraits::NonpositiveMin(); while (!it.IsAtEnd()) { double value = it.Get()[component]; #ifdef BOUNDINGOBJECT_IGNORE if (value > -32765) { #endif // update min if (value < statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t]; statisticsHolder->m_ScalarMin[t] = value; statisticsHolder->m_CountOfMinValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMin[t]) { ++statisticsHolder->m_CountOfMinValuedVoxels[t]; } else if (value < statisticsHolder->m_Scalar2ndMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = value; } // update max if (value > statisticsHolder->m_ScalarMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t]; statisticsHolder->m_ScalarMax[t] = value; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMax[t]) { ++statisticsHolder->m_CountOfMaxValuedVoxels[t]; } else if (value > statisticsHolder->m_Scalar2ndMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = value; } #ifdef BOUNDINGOBJECT_IGNORE } #endif ++it; } //// guard for wrong 2dMin/Max on single constant value images if (statisticsHolder->m_ScalarMax[t] == statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMax[t]; } statisticsHolder->m_LastRecomputeTimeStamp.Modified(); } /// \endcond SKIP_DOXYGEN void mitk::ImageStatisticsHolder::ComputeImageStatistics(int t, unsigned int component) { // timestep valid? if (!m_Image->IsValidTimeStep(t)) return; // image modified? if (this->m_Image->GetMTime() > m_LastRecomputeTimeStamp.GetMTime()) this->ResetImageStatistics(); Expand(t + 1); // do we have valid information already? if (m_ScalarMin[t] != itk::NumericTraits::max() || m_Scalar2ndMin[t] != itk::NumericTraits::max()) return; // Values already calculated before... // used to avoid statistics calculation on Odf images. property will be replaced as soons as bug 17928 is merged and // the diffusion image refactoring is complete. mitk::BoolProperty *isSh = dynamic_cast(m_Image->GetProperty("IsShImage").GetPointer()); mitk::BoolProperty *isOdf = dynamic_cast(m_Image->GetProperty("IsOdfImage").GetPointer()); const mitk::PixelType pType = m_Image->GetPixelType(0); if (pType.GetNumberOfComponents() == 1 && (pType.GetPixelType() != itk::IOPixelEnum::UNKNOWNPIXELTYPE) && (pType.GetPixelType() != itk::IOPixelEnum::VECTOR)) { // recompute mitk::ImageTimeSelector::Pointer timeSelector = this->GetTimeSelector(); if (timeSelector.IsNotNull()) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); const mitk::Image *image = timeSelector->GetOutput(); AccessByItk_2(image, _ComputeExtremaInItkImage, this, t); } } else if (pType.GetPixelType() == itk::IOPixelEnum::VECTOR && (!isOdf || !isOdf->GetValue()) && (!isSh || !isSh->GetValue())) // we have a vector image { // recompute mitk::ImageTimeSelector::Pointer timeSelector = this->GetTimeSelector(); if (timeSelector.IsNotNull()) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); const mitk::Image *image = timeSelector->GetOutput(); AccessVectorPixelTypeByItk_n(image, _ComputeExtremaInItkVectorImage, (this, t, component)); } } else { m_ScalarMin[t] = 0; m_ScalarMax[t] = 255; m_Scalar2ndMin[t] = 0; m_Scalar2ndMax[t] = 255; } } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValueMin(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_ScalarMin[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValueMax(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_ScalarMax[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValue2ndMin(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_Scalar2ndMin[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValue2ndMax(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_Scalar2ndMax[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetCountOfMinValuedVoxels(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_CountOfMinValuedVoxels[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetCountOfMaxValuedVoxels(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_CountOfMaxValuedVoxels[t]; } diff --git a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp index c269481332..d8368a9ef8 100644 --- a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp +++ b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp @@ -1,548 +1,568 @@ /*============================================================================ 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 "mitkLevelWindow.h" #include "mitkImage.h" #include "mitkImageSliceSelector.h" #include "mitkImageStatisticsHolder.h" #include #include void mitk::LevelWindow::EnsureConsistency() { // Check if total range is ok { if (m_RangeMin > m_RangeMax) std::swap(m_RangeMin, m_RangeMax); if (m_RangeMin == m_RangeMax) m_RangeMin = m_RangeMax - 1; } // Check if current window is ok { if (m_LowerWindowBound > m_UpperWindowBound) std::swap(m_LowerWindowBound, m_UpperWindowBound); if (m_LowerWindowBound <= m_RangeMin) m_LowerWindowBound = m_RangeMin; if (m_UpperWindowBound <= m_RangeMin) m_UpperWindowBound = m_RangeMin + 1; if (m_LowerWindowBound >= m_RangeMax) m_LowerWindowBound = m_RangeMax - 1; if (m_UpperWindowBound >= m_RangeMax) m_UpperWindowBound = m_RangeMax; if (m_LowerWindowBound == m_UpperWindowBound) { m_UpperWindowBound += 0.5; m_LowerWindowBound -= 0.5; m_UpperWindowBound = std::min(m_UpperWindowBound, m_RangeMax); m_LowerWindowBound = std::max(m_LowerWindowBound, m_RangeMin); } } } mitk::LevelWindow::LevelWindow(mitk::ScalarType level, mitk::ScalarType window) : m_LowerWindowBound(level - window / 2.0), m_UpperWindowBound(level + window / 2.0), m_RangeMin(-2048.0), m_RangeMax(4096.0), m_DefaultLowerBound(-2048.0), m_DefaultUpperBound(4096.0), m_IsFloatingImage(false), m_Fixed(false) { SetDefaultLevelWindow(level, window); SetLevelWindow(level, window, true); } mitk::LevelWindow::LevelWindow(const mitk::LevelWindow &levWin) : m_LowerWindowBound(levWin.GetLowerWindowBound()), m_UpperWindowBound(levWin.GetUpperWindowBound()), m_RangeMin(levWin.GetRangeMin()), m_RangeMax(levWin.GetRangeMax()), m_DefaultLowerBound(levWin.GetDefaultLowerBound()), m_DefaultUpperBound(levWin.GetDefaultUpperBound()), m_IsFloatingImage(levWin.IsFloatingValues()), m_Fixed(levWin.GetFixed()) { } mitk::LevelWindow::~LevelWindow() { } mitk::ScalarType mitk::LevelWindow::GetLevel() const { return (m_UpperWindowBound - m_LowerWindowBound) / 2.0 + m_LowerWindowBound; } mitk::ScalarType mitk::LevelWindow::GetWindow() const { return (m_UpperWindowBound - m_LowerWindowBound); } mitk::ScalarType mitk::LevelWindow::GetDefaultLevel() const { return ((m_DefaultUpperBound + m_DefaultLowerBound) / 2.0); } mitk::ScalarType mitk::LevelWindow::GetDefaultWindow() const { return ((m_DefaultUpperBound - m_DefaultLowerBound)); } void mitk::LevelWindow::ResetDefaultLevelWindow() { SetLevelWindow(GetDefaultLevel(), GetDefaultWindow()); } mitk::ScalarType mitk::LevelWindow::GetLowerWindowBound() const { return m_LowerWindowBound; } mitk::ScalarType mitk::LevelWindow::GetUpperWindowBound() const { return m_UpperWindowBound; } void mitk::LevelWindow::SetDefaultLevelWindow(mitk::ScalarType level, mitk::ScalarType window) { SetDefaultBoundaries((level - (window / 2.0)), (level + (window / 2.0))); } void mitk::LevelWindow::SetLevelWindow(mitk::ScalarType level, mitk::ScalarType window, bool expandRangesIfNecessary) { SetWindowBounds((level - (window / 2.0)), (level + (window / 2.0)), expandRangesIfNecessary); } void mitk::LevelWindow::SetWindowBounds(mitk::ScalarType lowerBound, mitk::ScalarType upperBound, bool expandRangesIfNecessary) { if (IsFixed()) return; upperBound = std::clamp(upperBound, -1e300, 1e300); lowerBound = std::clamp(lowerBound, -1e300, 1e300); m_LowerWindowBound = lowerBound; m_UpperWindowBound = upperBound; if (expandRangesIfNecessary) { /* if caller is sure he wants exactly that level/window, we make sure the limits match */ if (m_LowerWindowBound > m_UpperWindowBound) std::swap(m_LowerWindowBound, m_UpperWindowBound); if (m_LowerWindowBound < m_RangeMin) { m_RangeMin = m_LowerWindowBound; } if (m_UpperWindowBound > m_RangeMax) { m_RangeMax = m_UpperWindowBound; } } EnsureConsistency(); } void mitk::LevelWindow::SetRangeMinMax(mitk::ScalarType min, mitk::ScalarType max) { if (IsFixed()) return; m_RangeMin = min; m_RangeMax = max; EnsureConsistency(); } void mitk::LevelWindow::SetDefaultBoundaries(mitk::ScalarType low, mitk::ScalarType up) { if (IsFixed()) return; m_DefaultLowerBound = low; m_DefaultUpperBound = up; // Check if default window is ok { if (m_DefaultLowerBound > m_DefaultUpperBound) std::swap(m_DefaultLowerBound, m_DefaultUpperBound); if (m_DefaultLowerBound == m_DefaultUpperBound) m_DefaultLowerBound--; } EnsureConsistency(); } void mitk::LevelWindow::SetToMaxWindowSize() { SetWindowBounds(m_RangeMin, m_RangeMax); } mitk::ScalarType mitk::LevelWindow::GetRangeMin() const { return m_RangeMin; } mitk::ScalarType mitk::LevelWindow::GetRangeMax() const { return m_RangeMax; } mitk::ScalarType mitk::LevelWindow::GetRange() const { return m_RangeMax - m_RangeMin; } mitk::ScalarType mitk::LevelWindow::GetDefaultUpperBound() const { return m_DefaultUpperBound; } mitk::ScalarType mitk::LevelWindow::GetDefaultLowerBound() const { return m_DefaultLowerBound; } void mitk::LevelWindow::ResetDefaultRangeMinMax() { SetRangeMinMax(m_DefaultLowerBound, m_DefaultUpperBound); } /*! This method initializes a mitk::LevelWindow from an mitk::Image. The algorithm is as follows: Default to taking the central image slice for quick analysis. Compute the smallest (minValue), second smallest (min2ndValue), second largest (max2ndValue), and largest (maxValue) data value by traversing the pixel values only once. In the same scan it also computes the count of minValue values and maxValue values. After that a basic histogram with specific information about the extremes is complete. If minValue == maxValue, the center slice is uniform and the above scan is repeated for the complete image, not just one slice Next, special cases of images with only 1, 2 or 3 distinct data values have hand assigned level window ranges. Next the level window is set relative to the inner range IR = lengthOf([min2ndValue, max2ndValue]) For count(minValue) > 20% the smallest values are frequent and should be distinct from the min2ndValue and larger values (minValue may be std:min, may signify something special) hence the lower end of the level window is set to min2ndValue - 0.5 * IR For count(minValue) <= 20% the smallest values are not so important and can blend with the next ones => min(level window) = min2ndValue And analog for max(level window): count(max2ndValue) > 20%: max(level window) = max2ndValue + 0.5 * IR count(max2ndValue) < 20%: max(level window) = max2ndValue In both 20%+ cases the level window bounds are clamped to the [minValue, maxValue] range In consequence the level window maximizes contrast with minimal amount of computation and does do useful things if the data contains std::min or std:max values or has only 1 or 2 or 3 data values. */ void mitk::LevelWindow::SetAuto(const mitk::Image *image, bool /*tryPicTags*/, bool guessByCentralSlice, unsigned selectedComponent) { if (IsFixed()) return; if (image == nullptr || !image->IsInitialized()) return; if (itk::IOComponentEnum::FLOAT == image->GetPixelType().GetComponentType() || itk::IOComponentEnum::DOUBLE == image->GetPixelType().GetComponentType()) { m_IsFloatingImage = true; } else { m_IsFloatingImage = false; } const mitk::Image *wholeImage = image; ScalarType minValue = 0.0; ScalarType maxValue = 0.0; ScalarType min2ndValue = 0.0; ScalarType max2ndValue = 0.0; mitk::ImageSliceSelector::Pointer sliceSelector = mitk::ImageSliceSelector::New(); if (guessByCentralSlice) { sliceSelector->SetInput(image); sliceSelector->SetSliceNr(image->GetDimension(2) / 2); sliceSelector->SetTimeNr(image->GetDimension(3) / 2); sliceSelector->SetChannelNr(image->GetDimension(4) / 2); sliceSelector->Update(); image = sliceSelector->GetOutput(); if (image == nullptr || !image->IsInitialized()) return; minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent); maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(); min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(); max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(); if (minValue == maxValue) { // guessByCentralSlice seems to have failed, lets look at all data image = wholeImage; minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent); maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(); min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(); max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(); + + if (minValue == maxValue) + { + // Same result, also look at data at other time steps if present... + auto numTimeSteps = image->GetTimeGeometry()->CountTimeSteps(); + + if (numTimeSteps > 1) + { + for (TimeStepType t = 1; t < numTimeSteps; ++t) + { + minValue = image->GetStatistics()->GetScalarValueMin(t, selectedComponent); + maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(t); + min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(t); + max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(t); + + if (minValue != maxValue) + break; // We found a time step with valid data + } + } + } } } else { const_cast(image)->Update(); minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent); maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(0); min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0); max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0); for (unsigned int i = 1; i < image->GetDimension(3); ++i) { ScalarType minValueTemp = image->GetStatistics()->GetScalarValueMin(i, selectedComponent); if (minValue > minValueTemp) minValue = minValueTemp; ScalarType maxValueTemp = image->GetStatistics()->GetScalarValueMaxNoRecompute(i); if (maxValue < maxValueTemp) maxValue = maxValueTemp; ScalarType min2ndValueTemp = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(i); if (min2ndValue > min2ndValueTemp) min2ndValue = min2ndValueTemp; ScalarType max2ndValueTemp = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(i); if (max2ndValue > max2ndValueTemp) max2ndValue = max2ndValueTemp; } } // Fix for bug# 344 Level Window wird bei Eris Cut bildern nicht richtig gesetzt if (image->GetPixelType().GetPixelType() == itk::IOPixelEnum::SCALAR && image->GetPixelType().GetComponentType() == itk::IOComponentEnum::INT && image->GetPixelType().GetBpe() >= 8) { // the windows compiler complains about ambiguous 'pow' call, therefore static casting to (double, int) if (minValue == -(pow((double)2.0, static_cast(image->GetPixelType().GetBpe() / 2)))) { minValue = min2ndValue; } } // End fix //// uniform image if (minValue == maxValue) { minValue = maxValue - 1; } else { // Due to bug #8690 level window now is no longer of fixed range by default but the range adapts according to // levelwindow interaction // This is done because the range should be a little bit larger from the beginning so that the scale doesn't start // to resize right from the beginning double additionalRange = 0.15 * (maxValue - minValue); minValue -= additionalRange; maxValue += additionalRange; } if (!std::isfinite(minValue)) { minValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0); } if (!std::isfinite(maxValue)) { maxValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0); } SetRangeMinMax(minValue, maxValue); SetDefaultBoundaries(minValue, maxValue); size_t numPixelsInDataset = image->GetDimensions()[0]; for (decltype(image->GetDimension()) k = 1; k < image->GetDimension(); ++k) numPixelsInDataset *= image->GetDimensions()[k]; const auto minCount = image->GetStatistics()->GetCountOfMinValuedVoxelsNoRecompute(); const auto maxCount = image->GetStatistics()->GetCountOfMaxValuedVoxelsNoRecompute(); const auto minCountFraction = minCount / static_cast(numPixelsInDataset); const auto maxCountFraction = maxCount / static_cast(numPixelsInDataset); //// binary image if (min2ndValue == maxValue) { // noop; full range is fine } //// triple value image, put middle value in center of gray level ramp else if (min2ndValue == max2ndValue) { ScalarType minDelta = std::min(min2ndValue - minValue, maxValue - min2ndValue); minValue = min2ndValue - minDelta; maxValue = min2ndValue + minDelta; } // now we can assume more than three distinct scalar values else { ScalarType innerRange = max2ndValue - min2ndValue; if (minCountFraction > 0.2) //// lots of min values -> make different from rest, but not miles away { ScalarType halfInnerRangeGapMinValue = min2ndValue - innerRange / 2.0; minValue = std::max(minValue, halfInnerRangeGapMinValue); } else //// few min values -> focus on innerRange { minValue = min2ndValue; } if (maxCountFraction > 0.2) //// lots of max values -> make different from rest { ScalarType halfInnerRangeGapMaxValue = max2ndValue + innerRange / 2.0; maxValue = std::min(maxValue, halfInnerRangeGapMaxValue); } else //// few max values -> focus on innerRange { maxValue = max2ndValue; } } SetWindowBounds(minValue, maxValue); SetDefaultLevelWindow((maxValue - minValue) / 2 + minValue, maxValue - minValue); } void mitk::LevelWindow::SetToImageRange(const mitk::Image *image) { if (IsFixed()) return; if (image == nullptr || !image->IsInitialized()) return; ScalarType minValue = image->GetStatistics()->GetScalarValueMin(0); if (!std::isfinite(minValue)) { minValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0); } ScalarType maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(0); if (!std::isfinite(maxValue)) { maxValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0); } SetRangeMinMax(minValue, maxValue); SetDefaultBoundaries(minValue, maxValue); SetWindowBounds(minValue, maxValue); SetDefaultLevelWindow((maxValue - minValue) / 2 + minValue, maxValue - minValue); } void mitk::LevelWindow::SetFixed(bool fixed) { m_Fixed = fixed; } bool mitk::LevelWindow::GetFixed() const { return m_Fixed; } bool mitk::LevelWindow::IsFixed() const { return m_Fixed; } bool mitk::LevelWindow::IsFloatingValues() const { return m_IsFloatingImage; } void mitk::LevelWindow::SetFloatingValues(bool value) { m_IsFloatingImage = value; } bool mitk::LevelWindow::operator==(const mitk::LevelWindow &levWin) const { return mitk::Equal(this->m_RangeMin, levWin.m_RangeMin, mitk::sqrteps) && mitk::Equal(this->m_RangeMax, levWin.m_RangeMax, mitk::sqrteps) && mitk::Equal(this->m_DefaultLowerBound, levWin.m_DefaultLowerBound, mitk::sqrteps) && mitk::Equal(this->m_DefaultUpperBound, levWin.m_DefaultUpperBound, mitk::sqrteps) && mitk::Equal(this->m_LowerWindowBound, levWin.m_LowerWindowBound, mitk::sqrteps) && mitk::Equal(this->m_UpperWindowBound, levWin.m_UpperWindowBound, mitk::sqrteps) && m_Fixed == levWin.IsFixed() && m_IsFloatingImage == levWin.IsFloatingValues(); } bool mitk::LevelWindow::operator!=(const mitk::LevelWindow &levWin) const { return !((*this) == levWin); } mitk::LevelWindow &mitk::LevelWindow::operator=(const mitk::LevelWindow &levWin) { if (this == &levWin) { return *this; } else { m_RangeMin = levWin.GetRangeMin(); m_RangeMax = levWin.GetRangeMax(); m_LowerWindowBound = levWin.GetLowerWindowBound(); m_UpperWindowBound = levWin.GetUpperWindowBound(); m_DefaultLowerBound = levWin.GetDefaultLowerBound(); m_DefaultUpperBound = levWin.GetDefaultUpperBound(); m_Fixed = levWin.GetFixed(); m_IsFloatingImage = levWin.IsFloatingValues(); return *this; } } namespace mitk { void to_json(nlohmann::json& j, const LevelWindow& lw) { j = nlohmann::json{ {"Fixed", lw.IsFixed()}, {"IsFloatingImage", lw.IsFloatingValues()}, {"CurrentSettings", { {"Level", lw.GetLevel()}, {"Window", lw.GetWindow()}}}, {"DefaultSettings", { {"Level", lw.GetDefaultLevel()}, {"Window", lw.GetDefaultWindow()}}}, {"CurrentRange", { {"Min", lw.GetRangeMin()}, {"Max", lw.GetRangeMax()}}}}; } void from_json(const nlohmann::json& j, LevelWindow& lw) { lw.SetRangeMinMax( j["CurrentRange"]["Min"].get(), j["CurrentRange"]["Max"].get()); lw.SetDefaultLevelWindow( j["DefaultSettings"]["Level"].get(), j["DefaultSettings"]["Window"].get()); lw.SetLevelWindow( j["CurrentSettings"]["Level"].get(), j["CurrentSettings"]["Window"].get()); lw.SetFixed(j["Fixed"].get()); lw.SetFloatingValues(j["IsFloatingImage"].get()); } } diff --git a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp index ac53fc9dd6..99501017ba 100644 --- a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp @@ -1,425 +1,444 @@ /*============================================================================ 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 "mitkTemporoSpatialStringProperty.h" #include using CondensedTimeKeyType = std::pair; using CondensedTimePointsType = std::map; using CondensedSliceKeyType = std::pair; using CondensedSlicesType = std::map; namespace { /** Helper function that checks if between an ID and a successive ID is no gap.*/ template bool isGap(const TValue& value, const TValue& successor) { return value successor + 1; } template void CheckAndCondenseElement(const TNewKey& newKeyMinID, const TNewValue& newValue, TMasterKey& masterKey, TMasterValue& masterValue, TCondensedContainer& condensedContainer) { if (newValue != masterValue || isGap(newKeyMinID, masterKey.second)) { condensedContainer[masterKey] = masterValue; masterValue = newValue; masterKey.first = newKeyMinID; } masterKey.second = newKeyMinID; } /** Helper function that tries to condense the values of time points for a slice as much as possible and returns all slices with condensed timepoint values.*/ CondensedSlicesType CondenseTimePointValuesOfProperty(const mitk::TemporoSpatialStringProperty* tsProp) { CondensedSlicesType uncondensedSlices; auto zs = tsProp->GetAvailableSlices(); for (const auto z : zs) { CondensedTimePointsType condensedTimePoints; auto timePointIDs = tsProp->GetAvailableTimeSteps(z); CondensedTimeKeyType condensedKey = { timePointIDs.front(),timePointIDs.front() }; auto refValue = tsProp->GetValue(timePointIDs.front(), z); for (const auto timePointID : timePointIDs) { const auto& newVal = tsProp->GetValue(timePointID, z); CheckAndCondenseElement(timePointID, newVal, condensedKey, refValue, condensedTimePoints); } condensedTimePoints[condensedKey] = refValue; uncondensedSlices[{ z, z }] = condensedTimePoints; } return uncondensedSlices; } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const char *s) { if (s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const std::string &s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const TemporoSpatialStringProperty &other) : BaseProperty(other), m_Values(other.m_Values) { } bool mitk::TemporoSpatialStringProperty::IsEqual(const BaseProperty &property) const { return this->m_Values == static_cast(property).m_Values; } bool mitk::TemporoSpatialStringProperty::Assign(const BaseProperty &property) { this->m_Values = static_cast(property).m_Values; return true; } std::string mitk::TemporoSpatialStringProperty::GetValueAsString() const { return GetValue(); } bool mitk::TemporoSpatialStringProperty::IsUniform() const { auto refValue = this->GetValue(); for (const auto& timeStep : m_Values) { auto finding = std::find_if_not(timeStep.second.begin(), timeStep.second.end(), [&refValue](const mitk::TemporoSpatialStringProperty::SliceMapType::value_type& val) { return val.second == refValue; }); if (finding != timeStep.second.end()) { return false; } } return true; } itk::LightObject::Pointer mitk::TemporoSpatialStringProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue() const { std::string result = ""; if (!m_Values.empty()) { if (!m_Values.begin()->second.empty()) { result = m_Values.begin()->second.begin()->second; } } return result; }; std::pair mitk::TemporoSpatialStringProperty::CheckValue( const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { std::string value = ""; bool found = false; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd && allowCloseTime) { // search for closest time step (earlier preverd) timeIter = m_Values.upper_bound(timeStep); if (timeIter != m_Values.begin()) { // there is a key lower than time step timeIter = std::prev(timeIter); } } if (timeIter != timeEnd) { const SliceMapType &slices = timeIter->second; auto sliceIter = slices.find(zSlice); auto sliceEnd = slices.end(); if (sliceIter == sliceEnd && allowCloseSlice) { // search for closest slice (earlier preverd) sliceIter = slices.upper_bound(zSlice); if (sliceIter != slices.begin()) { // there is a key lower than slice sliceIter = std::prev(sliceIter); } } if (sliceIter != sliceEnd) { value = sliceIter->second; found = true; } } return std::make_pair(found, value); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).second; }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueBySlice( const IndexValueType &zSlice, bool allowClose) const { return GetValue(0, zSlice, true, allowClose); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueByTimeStep( const TimeStepType &timeStep, bool allowClose) const { return GetValue(timeStep, 0, allowClose, true); }; bool mitk::TemporoSpatialStringProperty::HasValue() const { return !m_Values.empty(); }; bool mitk::TemporoSpatialStringProperty::HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).first; }; bool mitk::TemporoSpatialStringProperty::HasValueBySlice(const IndexValueType &zSlice, bool allowClose) const { return HasValue(0, zSlice, true, allowClose); }; bool mitk::TemporoSpatialStringProperty::HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose) const { return HasValue(timeStep, 0, allowClose, true); }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices() const { std::set uniqueSlices; for (const auto& timeStep : m_Values) { for (const auto& slice : timeStep.second) { uniqueSlices.insert(slice.first); } } return std::vector(std::begin(uniqueSlices), std::end(uniqueSlices)); } std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices( const TimeStepType &timeStep) const { std::vector result; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter != timeEnd) { for (auto const &element : timeIter->second) { result.push_back(element.first); } } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps() const { std::vector result; for (auto const &element : m_Values) { result.push_back(element.first); } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps(const IndexValueType& slice) const { std::vector result; for (const auto& timeStep : m_Values) { if (timeStep.second.find(slice) != std::end(timeStep.second)) { result.push_back(timeStep.first); } } return result; } void mitk::TemporoSpatialStringProperty::SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value) { auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd) { SliceMapType slices{{zSlice, value}}; m_Values.insert(std::make_pair(timeStep, slices)); } else { timeIter->second[zSlice] = value; } this->Modified(); }; void mitk::TemporoSpatialStringProperty::SetValue(const ValueType &value) { m_Values.clear(); this->SetValue(0, 0, value); }; bool mitk::TemporoSpatialStringProperty::ToJSON(nlohmann::json& j) const { // We condense the content of the property to have a compact serialization. // We start with condensing time points and then slices (in difference to the // internal layout). Reason: There is more entropy in slices (looking at DICOM) // than across time points for one slice, so we can "compress" at a higher rate. // We didn't want to change the internal structure of the property as it would // introduce API inconvenience and subtle changes in behavior. auto uncondensedSlices = CondenseTimePointValuesOfProperty(this); CondensedSlicesType condensedSlices; if(!uncondensedSlices.empty()) { auto& masterSlice = uncondensedSlices.begin()->second; auto masterSliceKey = uncondensedSlices.begin()->first; for (const auto& uncondensedSlice : uncondensedSlices) { const auto& uncondensedSliceID = uncondensedSlice.first.first; CheckAndCondenseElement(uncondensedSliceID, uncondensedSlice.second, masterSliceKey, masterSlice, condensedSlices); } condensedSlices[masterSliceKey] = masterSlice; } auto values = nlohmann::json::array(); for (const auto& z : condensedSlices) { for (const auto& t : z.second) { const auto& minSliceID = z.first.first; const auto& maxSliceID = z.first.second; const auto& minTimePointID = t.first.first; const auto& maxTimePointID = t.first.second; auto value = nlohmann::json::object(); value["z"] = minSliceID; if (minSliceID != maxSliceID) value["zmax"] = maxSliceID; value["t"] = minTimePointID; if (minTimePointID != maxTimePointID) value["tmax"] = maxTimePointID; value["value"] = t.second; values.push_back(value); } } j = nlohmann::json{{"values", values}}; return true; } bool mitk::TemporoSpatialStringProperty::FromJSON(const nlohmann::json& j) { for (const auto& element : j["values"]) { auto value = element.value("value", ""); auto z = element.value("z", 0); auto zmax = element.value("zmax", z); auto t = element.value("t", 0); auto tmax = element.value("tmax", t); for (auto currentT = t; currentT <= tmax; ++currentT) { for (auto currentZ = z; currentZ <= zmax; ++currentZ) { this->SetValue(currentT, currentZ, value); } } } return true; } std::string mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop) { const auto *tsProp = dynamic_cast(prop); if (!tsProp) mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty."; nlohmann::json j; tsProp->ToJSON(j); return j.dump(); } mitk::BaseProperty::Pointer mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(const std::string &value) { if (value.empty()) return nullptr; auto prop = mitk::TemporoSpatialStringProperty::New(); auto root = nlohmann::json::parse(value); prop->FromJSON(root); return prop.GetPointer(); } + +mitk::TemporoSpatialStringProperty::Pointer mitk::ExtractTimeStepFromTemporoSpatialStringProperty(const TemporoSpatialStringProperty* tsProperty, TimeStepType ts) +{ + if (nullptr == tsProperty) + mitkThrow() << "Cannot extract time step. Passed TemporoSpatialStringProperty pointer is invalid."; + + if (!tsProperty->HasValueByTimeStep(ts)) + mitkThrow() << "Cannot extract time step. TemporoSpatialStringProperty does not contain values for that passed time step. Invalid time step: " << ts; + + auto slices = tsProperty->GetAvailableSlices(ts); + + auto result = mitk::TemporoSpatialStringProperty::New(); + + for (const auto& slice : slices) + { + result->SetValue(0, slice, tsProperty->GetValue(ts, slice)); + } + return result; +} diff --git a/Modules/Core/src/IO/mitkIOMetaInformationPropertyConstants.cpp b/Modules/Core/src/IO/mitkIOMetaInformationPropertyConstants.cpp index a57c640107..dd0b2e1c04 100644 --- a/Modules/Core/src/IO/mitkIOMetaInformationPropertyConstants.cpp +++ b/Modules/Core/src/IO/mitkIOMetaInformationPropertyConstants.cpp @@ -1,52 +1,57 @@ /*============================================================================ 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 "mitkIOMetaInformationPropertyConstants.h" namespace mitk { PropertyKeyPath IOMetaInformationPropertyConstants::READER_DESCRIPTION() { return PropertyKeyPath({ "MITK", "IO", "reader", "description" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_VERSION() { return PropertyKeyPath({ "MITK", "IO", "reader", "version" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_MIME_NAME() { return PropertyKeyPath({ "MITK", "IO", "reader", "mime", "name" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_MIME_CATEGORY() { return PropertyKeyPath({ "MITK", "IO", "reader", "mime", "category" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_INPUTLOCATION() { return PropertyKeyPath({ "MITK", "IO", "reader", "inputlocation" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_OPTION_ROOT() { return PropertyKeyPath({ "MITK", "IO", "reader", "option" }); } PropertyKeyPath IOMetaInformationPropertyConstants::READER_OPTIONS_ANY() { return READER_OPTION_ROOT().AddAnyElement(); } + + PropertyKeyPath IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON() + { + return PropertyKeyPath({ "MITK", "IO", "reader", "VolumeSplitReason" }); + } } diff --git a/Modules/Core/src/IO/mitkIOMimeTypes.cpp b/Modules/Core/src/IO/mitkIOMimeTypes.cpp index 01a3d23c15..17b007a6f1 100644 --- a/Modules/Core/src/IO/mitkIOMimeTypes.cpp +++ b/Modules/Core/src/IO/mitkIOMimeTypes.cpp @@ -1,365 +1,380 @@ /*============================================================================ 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 "mitkIOMimeTypes.h" #include "mitkCustomMimeType.h" #include "mitkLog.h" #include #include "itkGDCMImageIO.h" #include "itkMetaDataObject.h" #include #include namespace mitk { IOMimeTypes::BaseDicomMimeType::BaseDicomMimeType(const std::string& name) : CustomMimeType(name) { this->AddExtension("gdcm"); this->AddExtension("dcm"); this->AddExtension("DCM"); this->AddExtension("dc3"); this->AddExtension("DC3"); this->AddExtension("ima"); this->AddExtension("img"); this->SetCategory(CATEGORY_IMAGES()); this->SetComment("DICOM"); } + bool IOMimeTypes::BaseDicomMimeType::AppliesTo(const std::string &path) const { // check whether directory or file // if directory try to find first file within it instead bool pathIsDirectory = itksys::SystemTools::FileIsDirectory(Utf8Util::Local8BitToUtf8(path)); std::string filepath = path; + itk::GDCMImageIO::Pointer gdcmIO = itk::GDCMImageIO::New(); + if (pathIsDirectory) { itksys::Directory input; input.Load(path.c_str()); - std::vector files; + bool dcmFound = false; for (unsigned long idx = 0; idxMatchesExtension(filename)) + std::string fullpath = path + "/" + filename; + + if (!itksys::SystemTools::FileIsDirectory(fullpath)) { - std::string fullpath = path + "/" + std::string(input.GetFile(idx)); - files.push_back(fullpath.c_str()); + + gdcmIO->SetFileName(fullpath); + try + { + gdcmIO->ReadImageInformation(); + dcmFound = true; + filepath = fullpath; + break; + } + catch (const itk::ExceptionObject& /*err*/) + { + } } } - if (!files.empty()) - { - filepath = files.front(); + if (!dcmFound) return false; + } + else + { + // Ask the GDCM ImageIO class directly + gdcmIO->SetFileName(filepath); + try { + gdcmIO->ReadImageInformation(); + } + catch (const itk::ExceptionObject& /*err*/) { + return false; } } - // Ask the GDCM ImageIO class directly - itk::GDCMImageIO::Pointer gdcmIO = itk::GDCMImageIO::New(); - gdcmIO->SetFileName(filepath); - try { - gdcmIO->ReadImageInformation(); - } - catch (const itk::ExceptionObject & /*err*/) { - return false; - } + //If we reached that point, there is at least one file (stored under filepath) that is a DICOM //DICOMRT modalities have specific reader, don't read with normal DICOM readers std::string modality; itk::MetaDataDictionary& dict = gdcmIO->GetMetaDataDictionary(); itk::ExposeMetaData(dict, "0008|0060", modality); MITK_DEBUG << "DICOM Modality detected by MimeType "<< this->GetName() << " is " << modality; if (modality == "RTSTRUCT" || modality == "RTDOSE" || modality == "RTPLAN") { return false; } else { return gdcmIO->CanReadFile(filepath.c_str()); } } IOMimeTypes::BaseDicomMimeType*IOMimeTypes::BaseDicomMimeType::Clone() const { return new BaseDicomMimeType(*this); } IOMimeTypes::DicomMimeType::DicomMimeType() : BaseDicomMimeType(DICOM_MIMETYPE_NAME()) { } IOMimeTypes::DicomMimeType* IOMimeTypes::DicomMimeType::Clone() const { return new DicomMimeType(*this); } std::vector IOMimeTypes::Get() { std::vector mimeTypes; // order matters here (descending rank for mime types) mimeTypes.push_back(NRRD_MIMETYPE().Clone()); mimeTypes.push_back(NIFTI_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(DICOM_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(STEREOLITHOGRAPHY_MIMETYPE().Clone()); mimeTypes.push_back(WAVEFRONT_OBJ_MIMETYPE().Clone()); mimeTypes.push_back(STANFORD_PLY_MIMETYPE().Clone()); mimeTypes.push_back(RAW_MIMETYPE().Clone()); mimeTypes.push_back(POINTSET_MIMETYPE().Clone()); return mimeTypes; } CustomMimeType IOMimeTypes::VTK_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_NAME()); mimeType.AddExtension("vti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_IMAGE_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Legacy Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_IMAGE_NAME()); mimeType.AddExtension("pvti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Parallel Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_NAME()); mimeType.AddExtension("vtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Legacy PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_POLYDATA_NAME()); mimeType.AddExtension("pvtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Parallel PolyData"); return mimeType; } CustomMimeType IOMimeTypes::STEREOLITHOGRAPHY_MIMETYPE() { CustomMimeType mimeType(STEREOLITHOGRAPHY_NAME()); mimeType.AddExtension("stl"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stereolithography"); return mimeType; } CustomMimeType IOMimeTypes::WAVEFRONT_OBJ_MIMETYPE() { CustomMimeType mimeType(WAVEFRONT_OBJ_NAME()); mimeType.AddExtension("obj"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Wavefront OBJ"); return mimeType; } CustomMimeType IOMimeTypes::STANFORD_PLY_MIMETYPE() { CustomMimeType mimeType(STANFORD_PLY_NAME()); mimeType.AddExtension("ply"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stanford PLY"); return mimeType; } std::string IOMimeTypes::STEREOLITHOGRAPHY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".stl"; return name; } std::string IOMimeTypes::WAVEFRONT_OBJ_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".obj"; return name; } std::string IOMimeTypes::STANFORD_PLY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".ply"; return name; } std::string IOMimeTypes::DEFAULT_BASE_NAME() { static std::string name = "application/vnd.mitk"; return name; } std::string IOMimeTypes::CATEGORY_IMAGES() { static std::string cat = "Images"; return cat; } std::string IOMimeTypes::CATEGORY_SURFACES() { static std::string cat = "Surfaces"; return cat; } std::string IOMimeTypes::VTK_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image"; return name; } std::string IOMimeTypes::VTK_IMAGE_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.image"; return name; } std::string IOMimeTypes::VTK_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata"; return name; } std::string IOMimeTypes::VTK_POLYDATA_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.polydata"; return name; } CustomMimeType IOMimeTypes::NRRD_MIMETYPE() { CustomMimeType mimeType(NRRD_MIMETYPE_NAME()); mimeType.AddExtension("nrrd"); mimeType.AddExtension("nhdr"); mimeType.SetCategory("Images"); mimeType.SetComment("NRRD"); return mimeType; } CustomMimeType IOMimeTypes::NIFTI_MIMETYPE() { CustomMimeType mimeType(NIFTI_MIMETYPE_NAME()); mimeType.AddExtension("nii"); mimeType.AddExtension("nii.gz"); mimeType.AddExtension("hdr"); mimeType.AddExtension("hdr.gz"); mimeType.AddExtension("img"); mimeType.AddExtension("img.gz"); mimeType.AddExtension("nia"); mimeType.SetCategory("Images"); mimeType.SetComment("Nifti"); return mimeType; } CustomMimeType IOMimeTypes::RAW_MIMETYPE() { CustomMimeType mimeType(RAW_MIMETYPE_NAME()); mimeType.AddExtension("raw"); mimeType.SetCategory("Images"); mimeType.SetComment("Raw data"); return mimeType; } IOMimeTypes::DicomMimeType IOMimeTypes::DICOM_MIMETYPE() { return DicomMimeType(); } std::string IOMimeTypes::NRRD_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nrrd"; return name; } std::string IOMimeTypes::NIFTI_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nifti"; return name; } std::string IOMimeTypes::RAW_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.raw"; return name; } std::string IOMimeTypes::DICOM_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.dicom"; return name; } CustomMimeType IOMimeTypes::POINTSET_MIMETYPE() { CustomMimeType mimeType(POINTSET_MIMETYPE_NAME()); mimeType.AddExtension("mps"); mimeType.SetCategory("Point Sets"); mimeType.SetComment("MITK Point Set"); return mimeType; } std::string IOMimeTypes::POINTSET_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".pointset"; return name; } CustomMimeType IOMimeTypes::GEOMETRY_DATA_MIMETYPE() { mitk::CustomMimeType mimeType(DEFAULT_BASE_NAME() + ".geometrydata"); mimeType.AddExtension("mitkgeometry"); mimeType.SetCategory("Geometries"); mimeType.SetComment("GeometryData object"); return mimeType; } } diff --git a/Modules/Core/src/IO/mitkIOVolumeSplitReason.cpp b/Modules/Core/src/IO/mitkIOVolumeSplitReason.cpp new file mode 100644 index 0000000000..0f7c5dea40 --- /dev/null +++ b/Modules/Core/src/IO/mitkIOVolumeSplitReason.cpp @@ -0,0 +1,169 @@ +/*============================================================================ + +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 + +void mitk::IOVolumeSplitReason::AddReason(ReasonType type, const std::string& detail) +{ + m_ReasonMap[type] = detail; +} + +void mitk::IOVolumeSplitReason::RemoveReason(ReasonType type) +{ + m_ReasonMap.erase(type); +} + +bool mitk::IOVolumeSplitReason::HasReasons() const +{ + return !m_ReasonMap.empty(); +} + +bool mitk::IOVolumeSplitReason::HasReason(ReasonType type) const +{ + return m_ReasonMap.cend() != m_ReasonMap.find(type); +} + +std::string mitk::IOVolumeSplitReason::GetReasonDetails(ReasonType type) const +{ + auto finding = m_ReasonMap.find(type); + if (m_ReasonMap.cend() == finding) + mitkThrow() << "Cannot get details for inexistent type."; + + return finding->second; +}; + +mitk::IOVolumeSplitReason::Pointer mitk::IOVolumeSplitReason::ExtendReason(ConstPointer otherReason) const +{ + if (nullptr == otherReason) + mitkThrow() << "Cannot extend reason. Passed other reason is in valid."; + + Pointer result = this->Clone(); + + result->m_ReasonMap.insert(otherReason->m_ReasonMap.cbegin(), otherReason->m_ReasonMap.cend()); + + return result; +} + +std::string mitk::IOVolumeSplitReason::TypeToString(ReasonType reasonType) +{ + switch (reasonType) + { + case ReasonType::GantryTiltDifference: + return "gantry_tilt_difference"; + case ReasonType::ImagePostionMissing: + return "image_position_missing"; + case ReasonType::OverlappingSlices: + return "overlapping_slices"; + case ReasonType::SliceDistanceInconsistency: + return "slice_distance_inconsistency"; + case ReasonType::ValueSortDistance: + return "value_sort_distance"; + case ReasonType::ValueSplitDifference: + return "value_split_difference"; + case ReasonType::MissingSlices: + return "missing_slices"; + default: return "unknown"; + } +} + +mitk::IOVolumeSplitReason::ReasonType mitk::IOVolumeSplitReason::StringToType(const std::string& reasonStr) +{ + if (reasonStr == "gantry_tilt_difference") + return ReasonType::GantryTiltDifference; + else if (reasonStr == "image_position_missing") + return ReasonType::ImagePostionMissing; + else if (reasonStr == "overlapping_slices") + return ReasonType::OverlappingSlices; + else if (reasonStr == "slice_distance_inconsistency") + return ReasonType::SliceDistanceInconsistency; + else if (reasonStr == "value_sort_distance") + return ReasonType::ValueSortDistance; + else if (reasonStr == "value_split_difference") + return ReasonType::ValueSplitDifference; + else if (reasonStr == "missing_slices") + return ReasonType::MissingSlices; + + return ReasonType::Unknown; +} + +mitk::IOVolumeSplitReason::Pointer mitk::IOVolumeSplitReason::Clone() const +{ + return std::make_shared(*this); +} + +mitk::IOVolumeSplitReason::Pointer mitk::IOVolumeSplitReason::New() +{ + return std::make_shared(); +} + + +nlohmann::json mitk::IOVolumeSplitReason::ToJSON(ConstPointer reason) +{ + if (nullptr == reason) + mitkThrow() << "Cannot extend reason. Passed other reason is in valid."; + + auto data = nlohmann::json::array(); + + for (const auto& [type, detail] : reason->m_ReasonMap) + { + auto details = nlohmann::json::array(); + + details.push_back(type); + if (!detail.empty()) + { + details.push_back(detail); + } + data.push_back(details); + } + + return data; +} + +mitk::IOVolumeSplitReason::Pointer mitk::IOVolumeSplitReason::FromJSON(const nlohmann::json& j) +{ + auto reason = std::make_shared(); + + for (const auto& jItem : j) + { + if (!jItem.empty()) + { + ReasonType reasonType = jItem.at(0).get(); + + std::string detail; + if (jItem.size() > 1) + { + detail = jItem.at(1).get(); + } + reason->AddReason(reasonType, detail); + } + } + + return reason; +} + + +void mitk::to_json(nlohmann::json& j, IOVolumeSplitReason::ConstPointer reason) +{ + j = IOVolumeSplitReason::ToJSON(reason); +} + +void mitk::to_json(nlohmann::json& j, IOVolumeSplitReason::Pointer reason) +{ + j = IOVolumeSplitReason::ToJSON(reason); +} + +void mitk::from_json(const nlohmann::json& j, IOVolumeSplitReason& e) +{ + e = *IOVolumeSplitReason::FromJSON(j); +} diff --git a/Modules/Core/src/mitkCoreActivator.cpp b/Modules/Core/src/mitkCoreActivator.cpp index a5c00479ad..00cc3e159b 100644 --- a/Modules/Core/src/mitkCoreActivator.cpp +++ b/Modules/Core/src/mitkCoreActivator.cpp @@ -1,437 +1,438 @@ /*============================================================================ 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 "mitkCoreActivator.h" #include #include // File IO #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkLegacyFileWriterService.h" #include #include #include // PropertyRelationRules #include // Micro Services #include #include #include #include #include #include #include #include #include #include // Properties #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ITK "injects" static initialization code for IO factories // via the itkImageIOFactoryRegisterManager.h header (which // is generated in the application library build directory). // To ensure that the code is called *before* the CppMicroServices // static initialization code (which triggers the Activator::Start // method), we include the ITK header here. #include namespace { void HandleMicroServicesMessages(us::MsgType type, const char* msg) { switch (type) { case us::DebugMsg: MITK_DEBUG << msg; break; case us::InfoMsg: MITK_INFO << msg; break; case us::WarningMsg: MITK_WARN << msg; break; case us::ErrorMsg: MITK_ERROR << msg; break; } } void AddMitkAutoLoadPaths(const std::string& programPath) { us::ModuleSettings::AddAutoLoadPath(programPath); #ifdef __APPLE__ // Walk up three directories since that is where the .dylib files are located // for build trees. std::string additionalPath = programPath; bool addPath = true; for (int i = 0; i < 3; ++i) { std::size_t index = additionalPath.find_last_of('/'); if (index != std::string::npos) { additionalPath = additionalPath.substr(0, index); } else { addPath = false; break; } } if (addPath) { us::ModuleSettings::AddAutoLoadPath(additionalPath); } #endif } void AddPropertyPersistence(const mitk::PropertyKeyPath& propPath) { mitk::CoreServicePointer persistenceService(mitk::CoreServices::GetPropertyPersistence()); auto info = mitk::PropertyPersistenceInfo::New(); if (propPath.IsExplicit()) { std::string name = mitk::PropertyKeyPathToPropertyName(propPath); std::string key = name; std::replace(key.begin(), key.end(), '.', '_'); info->SetNameAndKey(name, key); } else { std::string key = mitk::PropertyKeyPathToPersistenceKeyRegEx(propPath); std::string keyTemplate = mitk::PropertyKeyPathToPersistenceKeyTemplate(propPath); std::string propRegEx = mitk::PropertyKeyPathToPropertyRegEx(propPath); std::string propTemplate = mitk::PropertyKeyPathToPersistenceNameTemplate(propPath); info->UseRegEx(propRegEx, propTemplate, key, keyTemplate); } persistenceService->AddInfo(info); } void RegisterProperties() { mitk::CoreServicePointer service(mitk::CoreServices::GetPropertyDeserialization()); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); service->RegisterProperty(); } } class FixedNiftiImageIO : public itk::NiftiImageIO { public: /** Standard class typedefs. */ typedef FixedNiftiImageIO Self; typedef itk::NiftiImageIO Superclass; typedef itk::SmartPointer Pointer; /** Method for creation through the object factory. */ itkNewMacro(Self) /** Run-time type information (and related methods). */ itkTypeMacro(FixedNiftiImageIO, Superclass) bool SupportsDimension(unsigned long dim) override { return dim > 1 && dim < 5; } }; void MitkCoreActivator::Load(us::ModuleContext *context) { // Handle messages from CppMicroServices us::installMsgHandler(HandleMicroServicesMessages); this->m_Context = context; // Add the current application directory to the auto-load paths. // This is useful for third-party executables. std::string programPath = mitk::IOUtil::GetProgramPath(); if (programPath.empty()) { MITK_WARN << "Could not get the program path."; } else { AddMitkAutoLoadPaths(programPath); } // m_RenderingManager = mitk::RenderingManager::New(); // context->RegisterService(renderingManager.GetPointer()); m_PlanePositionManager.reset(new mitk::PlanePositionManagerService); context->RegisterService(m_PlanePositionManager.get()); m_PropertyAliases.reset(new mitk::PropertyAliases); context->RegisterService(m_PropertyAliases.get()); m_PropertyDescriptions.reset(new mitk::PropertyDescriptions); context->RegisterService(m_PropertyDescriptions.get()); m_PropertyDeserialization.reset(new mitk::PropertyDeserialization); context->RegisterService(m_PropertyDeserialization.get()); m_PropertyExtensions.reset(new mitk::PropertyExtensions); context->RegisterService(m_PropertyExtensions.get()); m_PropertyFilters.reset(new mitk::PropertyFilters); context->RegisterService(m_PropertyFilters.get()); m_PropertyPersistence.reset(new mitk::PropertyPersistence); context->RegisterService(m_PropertyPersistence.get()); m_PropertyRelations.reset(new mitk::PropertyRelations); context->RegisterService(m_PropertyRelations.get()); m_PreferencesService.reset(new mitk::PreferencesService); context->RegisterService(m_PreferencesService.get()); m_MimeTypeProvider.reset(new mitk::MimeTypeProvider); m_MimeTypeProvider->Start(); m_MimeTypeProviderReg = context->RegisterService(m_MimeTypeProvider.get()); this->RegisterDefaultMimeTypes(); this->RegisterItkReaderWriter(); this->RegisterVtkReaderWriter(); // Add custom Reader / Writer Services m_FileReaders.push_back(new mitk::PointSetReaderService()); m_FileWriters.push_back(new mitk::PointSetWriterService()); m_FileReaders.push_back(new mitk::GeometryDataReaderService()); m_FileWriters.push_back(new mitk::GeometryDataWriterService()); m_FileReaders.push_back(new mitk::RawImageFileReaderService()); //add properties that should be persistent (if possible/supported by the writer) AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_DESCRIPTION()); AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_INPUTLOCATION()); AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_MIME_CATEGORY()); AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_MIME_NAME()); AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_VERSION()); AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::READER_OPTIONS_ANY()); + AddPropertyPersistence(mitk::IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON()); AddPropertyPersistence(mitk::PropertyRelationRuleBase::GetRIIDestinationUIDPropertyKeyPath()); AddPropertyPersistence(mitk::PropertyRelationRuleBase::GetRIIRelationUIDPropertyKeyPath()); AddPropertyPersistence(mitk::PropertyRelationRuleBase::GetRIIRuleIDPropertyKeyPath()); AddPropertyPersistence(mitk::PropertyRelationRuleBase::GetRIIPropertyKeyPath("","").AddAnyElement()); RegisterProperties(); /* There IS an option to exchange ALL vtkTexture instances against vtkNeverTranslucentTextureFactory. This code is left here as a reminder, just in case we might need to do that some time. vtkNeverTranslucentTextureFactory* textureFactory = vtkNeverTranslucentTextureFactory::New(); vtkObjectFactory::RegisterFactory( textureFactory ); textureFactory->Delete(); */ this->RegisterLegacyWriter(); } void MitkCoreActivator::Unload(us::ModuleContext *) { for (auto &elem : m_FileReaders) { delete elem; } for (auto &elem : m_FileWriters) { delete elem; } for (auto &elem : m_FileIOs) { delete elem; } for (auto &elem : m_LegacyWriters) { delete elem; } // The mitk::ModuleContext* argument of the Unload() method // will always be 0 for the Mitk library. It makes no sense // to use it at this stage anyway, since all libraries which // know about the module system have already been unloaded. // we need to close the internal service tracker of the // MimeTypeProvider class here. Otherwise it // would hold on to the ModuleContext longer than it is // actually valid. m_MimeTypeProviderReg.Unregister(); m_MimeTypeProvider->Stop(); for (std::vector::const_iterator mimeTypeIter = m_DefaultMimeTypes.begin(), iterEnd = m_DefaultMimeTypes.end(); mimeTypeIter != iterEnd; ++mimeTypeIter) { delete *mimeTypeIter; } } void MitkCoreActivator::RegisterDefaultMimeTypes() { // Register some default mime-types std::vector mimeTypes = mitk::IOMimeTypes::Get(); for (std::vector::const_iterator mimeTypeIter = mimeTypes.begin(), iterEnd = mimeTypes.end(); mimeTypeIter != iterEnd; ++mimeTypeIter) { m_DefaultMimeTypes.push_back(*mimeTypeIter); m_Context->RegisterService(m_DefaultMimeTypes.back()); } } void MitkCoreActivator::RegisterItkReaderWriter() { std::list allobjects = itk::ObjectFactoryBase::CreateAllInstance("itkImageIOBase"); for (auto &allobject : allobjects) { auto *io = dynamic_cast(allobject.GetPointer()); // NiftiImageIO does not provide a correct "SupportsDimension()" methods // and the supported read/write extensions are not ordered correctly if (dynamic_cast(io)) continue; // Use a custom mime-type for GDCMImageIO below if (dynamic_cast(allobject.GetPointer())) { // MITK provides its own DICOM reader (which internally uses GDCMImageIO). continue; } if (io) { m_FileIOs.push_back(new mitk::ItkImageIO(io)); } else { MITK_WARN << "Error ImageIO factory did not return an ImageIOBase: " << (allobject)->GetNameOfClass(); } } FixedNiftiImageIO::Pointer itkNiftiIO = FixedNiftiImageIO::New(); mitk::ItkImageIO *niftiIO = new mitk::ItkImageIO(mitk::IOMimeTypes::NIFTI_MIMETYPE(), itkNiftiIO.GetPointer(), 0); m_FileIOs.push_back(niftiIO); } void MitkCoreActivator::RegisterVtkReaderWriter() { m_FileIOs.push_back(new mitk::SurfaceVtkXmlIO()); m_FileIOs.push_back(new mitk::SurfaceStlIO()); m_FileIOs.push_back(new mitk::SurfaceVtkLegacyIO()); m_FileIOs.push_back(new mitk::ImageVtkXmlIO()); m_FileIOs.push_back(new mitk::ImageVtkLegacyIO()); } void MitkCoreActivator::RegisterLegacyWriter() { std::list allobjects = itk::ObjectFactoryBase::CreateAllInstance("IOWriter"); for (auto i = allobjects.begin(); i != allobjects.end(); ++i) { mitk::FileWriter::Pointer io = dynamic_cast(i->GetPointer()); if (io) { std::string description = std::string("Legacy ") + io->GetNameOfClass() + " Writer"; mitk::IFileWriter *writer = new mitk::LegacyFileWriterService(io, description); m_LegacyWriters.push_back(writer); } else { MITK_ERROR << "Error IOWriter override is not of type mitk::FileWriter: " << (*i)->GetNameOfClass() << std::endl; } } } US_EXPORT_MODULE_ACTIVATOR(MitkCoreActivator) // Call CppMicroservices initialization code at the end of the file. // This especially ensures that VTK object factories have already // been registered (VTK initialization code is injected by implicitly // include VTK header files at the top of this file). US_INITIALIZE_MODULE diff --git a/Modules/Core/test/files.cmake b/Modules/Core/test/files.cmake index b4b926c612..5d53b55f50 100644 --- a/Modules/Core/test/files.cmake +++ b/Modules/Core/test/files.cmake @@ -1,199 +1,200 @@ # tests with no extra command line parameter set(MODULE_TESTS # IMPORTANT: If you plan to deactivate / comment out a test please write a bug number to the commented out line of code. # # Example: #mitkMyTest #this test is commented out because of bug 12345 # # It is important that the bug is open and that the test will be activated again before the bug is closed. This assures that # no test is forgotten after it was commented out. If there is no bug for your current problem, please add a new one and # mark it as critical. ################## DISABLED TESTS ################################################# #mitkAbstractTransformGeometryTest.cpp #seems as tested class mitkExternAbstractTransformGeometry doesn't exist any more #mitkStateMachineContainerTest.cpp #rewrite test, indirect since no longer exported Bug 14529 #mitkRegistrationBaseTest.cpp #tested class mitkRegistrationBase doesn't exist any more #mitkSegmentationInterpolationTest.cpp #file doesn't exist! #mitkPipelineSmartPointerCorrectnessTest.cpp #file doesn't exist! #mitkITKThreadingTest.cpp #test outdated because itk::Semaphore was removed from ITK #mitkAbstractTransformPlaneGeometryTest.cpp #mitkVtkAbstractTransformPlaneGeometry doesn't exist any more #mitkTestUtilSharedLibrary.cpp #Linker problem with this test... #mitkTextOverlay2DSymbolsRenderingTest.cpp #Implementation of the tested feature is not finished yet. Ask Christoph or see bug 15104 for details. ################# RUNNING TESTS ################################################### mitkAccessByItkTest.cpp mitkCoreObjectFactoryTest.cpp mitkDataNodeTest.cpp mitkMaterialTest.cpp mitkActionTest.cpp mitkDispatcherTest.cpp mitkEnumerationPropertyTest.cpp mitkFileReaderRegistryTest.cpp #mitkFileWriterRegistryTest.cpp mitkFloatToStringTest.cpp mitkGenericPropertyTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataIOTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkImageCastTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkIOUtilTest.cpp mitkITKEventObserverGuardTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetOnEmptyTest.cpp mitkPointSetLocaleTest.cpp mitkPointSetWriterTest.cpp mitkPointSetPointOperationsTest.cpp mitkProgressBarTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkPropertyPersistenceTest.cpp mitkPropertyPersistenceInfoTest.cpp mitkPropertyRelationRuleBaseTest.cpp mitkPropertyRelationsTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkProportionalTimeGeometryTest.cpp mitkUndoControllerTest.cpp mitkVtkWidgetRenderingTest.cpp mitkVerboseLimitedLinearUndoTest.cpp mitkWeakPointerTest.cpp mitkTransferFunctionTest.cpp mitkStepperTest.cpp mitkRenderingManagerTest.cpp mitkCompositePixelValueToStringTest.cpp vtkMitkThickSlicesFilterTest.cpp mitkNodePredicateDataPropertyTest.cpp mitkNodePredicateFunctionTest.cpp mitkVectorTest.cpp mitkClippedSurfaceBoundsCalculatorTest.cpp mitkExceptionTest.cpp mitkExtractSliceFilterTest.cpp mitkLogTest.cpp mitkImageDimensionConverterTest.cpp mitkLoggingAdapterTest.cpp mitkUIDGeneratorTest.cpp mitkPlanePositionManagerTest.cpp mitkAffineTransformBaseTest.cpp mitkPropertyAliasesTest.cpp mitkPropertyDescriptionsTest.cpp mitkPropertyExtensionsTest.cpp mitkPropertyFiltersTest.cpp mitkPropertyKeyPathTest.cpp mitkTinyXMLTest.cpp mitkRawImageFileReaderTest.cpp mitkInteractionEventTest.cpp mitkLookupTableTest.cpp mitkSTLFileReaderTest.cpp mitkPointTypeConversionTest.cpp mitkVectorTypeConversionTest.cpp mitkMatrixTypeConversionTest.cpp mitkArrayTypeConversionTest.cpp mitkSurfaceToImageFilterTest.cpp mitkBaseGeometryTest.cpp mitkImageToSurfaceFilterTest.cpp mitkEqualTest.cpp mitkLineTest.cpp mitkArbitraryTimeGeometryTest.cpp mitkItkImageIOTest.cpp mitkLevelWindowManagerTest.cpp mitkVectorPropertyTest.cpp mitkTemporoSpatialStringPropertyTest.cpp mitkPropertyNameHelperTest.cpp mitkNodePredicateGeometryTest.cpp mitkNodePredicateSubGeometryTest.cpp mitkPreferenceListReaderOptionsFunctorTest.cpp mitkGenericIDRelationRuleTest.cpp mitkSourceImageRelationRuleTest.cpp mitkTemporalJoinImagesFilterTest.cpp mitkPreferencesTest.cpp + mitkIOVolumeSplitReasonTest.cpp ) set(MODULE_RENDERING_TESTS mitkPointSetDataInteractorTest.cpp mitkSurfaceVtkMapper2DTest.cpp mitkSurfaceVtkMapper2D3DTest.cpp ) # test with image filename as an extra command line parameter set(MODULE_IMAGE_TESTS mitkImageTimeSelectorTest.cpp #only runs on images mitkImageAccessorTest.cpp #only runs on images ) set(MODULE_SURFACE_TESTS mitkSurfaceVtkWriterTest.cpp #only runs on surfaces ) # list of images for which the tests are run set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACE binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDataNodeTest.cpp mitkEventConfigTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DOpacityTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkSurfaceVtkMapper3DTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp mitkPointSetReaderTest.cpp mitkImageEqualTest.cpp mitkRotatedSlice4DTest.cpp mitkPlaneGeometryDataMapper2DTest.cpp ) # Currently not working on windows because of a rendering timing issue # see bug 18083 for details if(NOT WIN32) set(MODULE_CUSTOM_TESTS ${MODULE_CUSTOM_TESTS} mitkSurfaceDepthSortingTest.cpp) endif() set(RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) diff --git a/Modules/Core/test/mitkIOVolumeSplitReasonTest.cpp b/Modules/Core/test/mitkIOVolumeSplitReasonTest.cpp new file mode 100644 index 0000000000..a16d37af78 --- /dev/null +++ b/Modules/Core/test/mitkIOVolumeSplitReasonTest.cpp @@ -0,0 +1,180 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkTestingMacros.h" +#include +#include + +#include +#include + +#include + +class mitkIOVolumeSplitReasonTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkIOVolumeSplitReasonTestSuite); + MITK_TEST(TestAddNRemoveMethods); + MITK_TEST(TestSerializeReasonType); + MITK_TEST(TestSerializeReason); + MITK_TEST(TestExtendReason); + CPPUNIT_TEST_SUITE_END(); + +private: + std::string m_ReasonStr; + mitk::IOVolumeSplitReason::Pointer m_Reason1; + mitk::IOVolumeSplitReason::Pointer m_Reason2; + mitk::IOVolumeSplitReason::Pointer m_EmptyReason; + +public: + void setUp() override + { + m_Reason1 = mitk::IOVolumeSplitReason::New(); + m_Reason2 = mitk::IOVolumeSplitReason::New(); + m_EmptyReason = mitk::IOVolumeSplitReason::New(); + + m_Reason1->AddReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices); + m_Reason1->AddReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices,"1"); + m_Reason2->AddReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing); + m_Reason2->AddReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices, "2"); + + m_ReasonStr = "[[\"value_sort_distance\",\"detail\"],[\"overlapping_slices\"],[\"missing_slices\",\"3\"]]"; + } + + void TestAddNRemoveMethods() + { + CPPUNIT_ASSERT(!m_EmptyReason->HasReasons()); + CPPUNIT_ASSERT(m_Reason1->HasReasons()); + CPPUNIT_ASSERT(m_Reason2->HasReasons()); + + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(!m_Reason2->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + + m_Reason1->RemoveReason(mitk::IOVolumeSplitReason::ReasonType::Unknown); + + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + + m_Reason1->RemoveReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices); + + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + + m_Reason1->AddReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance); + m_Reason1->AddReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference); + + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(m_Reason1->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + } + + static bool CheckType(mitk::IOVolumeSplitReason::ReasonType reasontype, const std::string& reasonStr) + { + if (mitk::IOVolumeSplitReason::TypeToString(reasontype) != reasonStr) + return false; + if (mitk::IOVolumeSplitReason::StringToType(reasonStr) != reasontype) + return false; + + return true; + } + + static bool CheckReason(const std::string& refStr, const nlohmann::json& j) + { + auto refJ = nlohmann::json::parse(refStr); + + return refJ == j; + } + + + void TestSerializeReasonType() + { + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference,"gantry_tilt_difference")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing, "image_position_missing")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::MissingSlices, "missing_slices")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices, "overlapping_slices")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency, "slice_distance_inconsistency")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::Unknown, "unknown")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance, "value_sort_distance")); + CPPUNIT_ASSERT(CheckType(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference, "value_split_difference")); + } + + void TestSerializeReason() + { + auto serializedReason1 = mitk::IOVolumeSplitReason::ToJSON(m_Reason1); + auto serializedReason2 = mitk::IOVolumeSplitReason::ToJSON(m_Reason2); + auto serializedReasonEmpty = mitk::IOVolumeSplitReason::ToJSON(m_EmptyReason); + + CPPUNIT_ASSERT(CheckReason("[[\"overlapping_slices\"],[\"missing_slices\",\"1\"]]", serializedReason1)); + CPPUNIT_ASSERT(CheckReason("[[\"image_position_missing\"],[\"missing_slices\",\"2\"]]", serializedReason2)); + CPPUNIT_ASSERT(CheckReason("[]", serializedReasonEmpty)); + + auto newReason = mitk::IOVolumeSplitReason::FromJSON(nlohmann::json::parse(m_ReasonStr)); + CPPUNIT_ASSERT(!newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::GantryTiltDifference)); + CPPUNIT_ASSERT(!newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::ImagePostionMissing)); + CPPUNIT_ASSERT(newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + CPPUNIT_ASSERT(newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices)); + CPPUNIT_ASSERT(!newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency)); + CPPUNIT_ASSERT(!newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::Unknown)); + CPPUNIT_ASSERT(newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance)); + CPPUNIT_ASSERT(!newReason->HasReason(mitk::IOVolumeSplitReason::ReasonType::ValueSplitDifference)); + + CPPUNIT_ASSERT(newReason->GetReasonDetails(mitk::IOVolumeSplitReason::ReasonType::ValueSortDistance) == "detail"); + CPPUNIT_ASSERT(newReason->GetReasonDetails(mitk::IOVolumeSplitReason::ReasonType::MissingSlices) == "3"); + CPPUNIT_ASSERT(newReason->GetReasonDetails(mitk::IOVolumeSplitReason::ReasonType::OverlappingSlices) == ""); + } + + void TestExtendReason() + { + auto extendedReason = m_Reason1->ExtendReason(m_EmptyReason); + CPPUNIT_ASSERT(CheckReason("[[\"overlapping_slices\"],[\"missing_slices\",\"1\"]]", mitk::IOVolumeSplitReason::ToJSON(extendedReason))); + + extendedReason = m_EmptyReason->ExtendReason(m_Reason1); + CPPUNIT_ASSERT(CheckReason("[[\"overlapping_slices\"],[\"missing_slices\",\"1\"]]", mitk::IOVolumeSplitReason::ToJSON(extendedReason))); + + extendedReason = m_Reason2->ExtendReason(m_Reason1); + CPPUNIT_ASSERT(CheckReason("[[\"image_position_missing\"],[\"overlapping_slices\"],[\"missing_slices\",\"2\"]]", mitk::IOVolumeSplitReason::ToJSON(extendedReason))); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkIOVolumeSplitReason) diff --git a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp index 48db4613df..29c758999d 100644 --- a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp +++ b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp @@ -1,245 +1,273 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkTemporoSpatialStringProperty.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" #include class mitkTemporoSpatialStringPropertyTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkTemporoSpatialStringPropertyTestSuite); MITK_TEST(GetValue); MITK_TEST(HasValue); MITK_TEST(SetValue); MITK_TEST(IsUniform); MITK_TEST(serializeTemporoSpatialStringPropertyToJSON); MITK_TEST(deserializeJSONToTemporoSpatialStringProperty); + MITK_TEST(ExtractTimeStepFromTemporoSpatialStringProperty); + CPPUNIT_TEST_SUITE_END(); private: std::string refJSON; std::string refJSON_legacy; std::string refPartlyCondensibleJSON; std::string refCondensibleJSON; mitk::TemporoSpatialStringProperty::Pointer refProp; mitk::TemporoSpatialStringProperty::Pointer refPartlyCondensibleProp; mitk::TemporoSpatialStringProperty::Pointer refCondensibleProp; public: void setUp() override { refJSON_legacy = "{\"values\":[{\"t\":0, \"z\":0, \"value\":\"v_0_0\"}, {\"t\":3, \"z\":0, \"value\":\"v_3_0\"}, " "{\"t\":3, \"z\":2, \"value\":\"v_3_2\"}, {\"t\":6, \"z\":1, \"value\":\"v_6_1\"}]}"; refJSON = "{\"values\":[{\"z\":0, \"t\":0, \"value\":\"v_0_0\"}, {\"z\":0, \"t\":3, \"value\":\"v_3_0\"}, {\"z\":1, \"t\":6, \"value\":\"v_6_1\"}, {\"z\":2, \"t\":3, \"value\":\"v_3_2\"}]}"; refPartlyCondensibleJSON = "{\"values\":[{\"z\":0, \"t\":0, \"value\":\"0\"}, {\"z\":1, \"t\":0, \"tmax\":1, \"value\":\"0\"}, {\"z\":1, \"t\":3, \"tmax\":5, \"value\":\"0\"}, {\"z\":1, \"t\":6, \"value\":\"otherValue\"}, {\"z\":2, \"t\":6, \"value\":\"0\"}]}"; refCondensibleJSON = "{\"values\":[{\"z\":0, \"zmax\":2, \"t\":0, \"tmax\":1, \"value\":\"1\"}]}"; refProp = mitk::TemporoSpatialStringProperty::New(); refProp->SetValue(0, 0, "v_0_0"); refProp->SetValue(3, 0, "v_3_0"); refProp->SetValue(3, 2, "v_3_2"); refProp->SetValue(6, 1, "v_6_1"); refPartlyCondensibleProp = mitk::TemporoSpatialStringProperty::New(); refPartlyCondensibleProp->SetValue(0, 0, "0"); refPartlyCondensibleProp->SetValue(0, 1, "0"); refPartlyCondensibleProp->SetValue(1, 1, "0"); refPartlyCondensibleProp->SetValue(3, 1, "0"); refPartlyCondensibleProp->SetValue(4, 1, "0"); refPartlyCondensibleProp->SetValue(5, 1, "0"); refPartlyCondensibleProp->SetValue(6, 1, "otherValue"); refPartlyCondensibleProp->SetValue(6, 2, "0"); refCondensibleProp = mitk::TemporoSpatialStringProperty::New(); refCondensibleProp->SetValue(0, 0, "1"); refCondensibleProp->SetValue(1, 0, "1"); refCondensibleProp->SetValue(0, 1, "1"); refCondensibleProp->SetValue(1, 1, "1"); refCondensibleProp->SetValue(0, 2, "1"); refCondensibleProp->SetValue(1, 2, "1"); } void tearDown() override {} void GetValue() { CPPUNIT_ASSERT(refProp->GetValue() == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 0) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 2) == "v_3_2"); CPPUNIT_ASSERT(refProp->GetValue(3, 1, false, true) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValue(3, 5, false, true) == "v_3_2"); CPPUNIT_ASSERT(refProp->GetValueBySlice(0) == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValueBySlice(4, true) == "v_0_0"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(3) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(6) == "v_6_1"); CPPUNIT_ASSERT(refProp->GetValueByTimeStep(5, true) == "v_3_0"); CPPUNIT_ASSERT(refProp->GetValueAsString() == "v_0_0"); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps().size() == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[1] == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps()[2] == 6); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0).size() == 2); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(0)[1] == 3); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(1).size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(1)[0] == 6); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps(5).size() == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices().size() == 3); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[1] == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices()[2] == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0).size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3).size() == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3)[0] == 0); CPPUNIT_ASSERT(refProp->GetAvailableSlices(3)[1] == 2); CPPUNIT_ASSERT(refProp->GetAvailableSlices(2).size() == 0); } void HasValue() { CPPUNIT_ASSERT(refProp->HasValue()); CPPUNIT_ASSERT(refProp->HasValue(3, 0)); CPPUNIT_ASSERT(refProp->HasValue(3, 2)); CPPUNIT_ASSERT(refProp->HasValue(3, 1, false, true)); CPPUNIT_ASSERT(refProp->HasValue(3, 5, false, true)); CPPUNIT_ASSERT(!refProp->HasValue(3, 1)); CPPUNIT_ASSERT(!refProp->HasValue(3, 5)); CPPUNIT_ASSERT(refProp->HasValue(4, 2, true, true)); CPPUNIT_ASSERT(refProp->HasValue(4, 2, true, false)); CPPUNIT_ASSERT(!refProp->HasValue(4, 2, false, true)); CPPUNIT_ASSERT(refProp->HasValueBySlice(0)); CPPUNIT_ASSERT(refProp->HasValueBySlice(4, true)); CPPUNIT_ASSERT(!refProp->HasValueBySlice(4)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(3)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(6)); CPPUNIT_ASSERT(refProp->HasValueByTimeStep(5, true)); CPPUNIT_ASSERT(!refProp->HasValueByTimeStep(5)); } void SetValue() { CPPUNIT_ASSERT_NO_THROW(refProp->SetValue(8, 9, "v_8_9")); CPPUNIT_ASSERT(refProp->GetValue(8, 9) == "v_8_9"); CPPUNIT_ASSERT_NO_THROW(refProp->SetValue("newValue")); CPPUNIT_ASSERT(refProp->GetValue(0, 0) == "newValue"); CPPUNIT_ASSERT(refProp->GetAvailableTimeSteps().size() == 1); CPPUNIT_ASSERT(refProp->GetAvailableSlices(0).size() == 1); } void IsUniform() { CPPUNIT_ASSERT(!refProp->IsUniform()); CPPUNIT_ASSERT(!refPartlyCondensibleProp->IsUniform()); CPPUNIT_ASSERT(refCondensibleProp->IsUniform()); } void serializeTemporoSpatialStringPropertyToJSON() { auto data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refProp)); auto ref = nlohmann::json::parse(refJSON); CPPUNIT_ASSERT(ref == data); //"Testing serializeTemporoSpatialStringPropertyToJSON() producing correct string."); data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refPartlyCondensibleProp)); ref = nlohmann::json::parse(refPartlyCondensibleJSON); CPPUNIT_ASSERT(ref == data); data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refCondensibleProp)); ref = nlohmann::json::parse(refCondensibleJSON); CPPUNIT_ASSERT(ref == data); } void deserializeJSONToTemporoSpatialStringProperty() { mitk::BaseProperty::Pointer prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refJSON); auto *tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT( tsProp->GetValue(0, 0) == "v_0_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 1"); CPPUNIT_ASSERT( tsProp->GetValue(3, 0) == "v_3_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 2"); CPPUNIT_ASSERT( tsProp->GetValue(3, 2) == "v_3_2"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 3"); CPPUNIT_ASSERT( tsProp->GetValue(6, 1) == "v_6_1"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 4"); CPPUNIT_ASSERT(*tsProp == *refProp); //"Testing deserializeJSONToTemporoSpatialStringProperty()"); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refJSON_legacy); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT( tsProp->GetValue(0, 0) == "v_0_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 1"); CPPUNIT_ASSERT( tsProp->GetValue(3, 0) == "v_3_0"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 2"); CPPUNIT_ASSERT( tsProp->GetValue(3, 2) == "v_3_2"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 3"); CPPUNIT_ASSERT( tsProp->GetValue(6, 1) == "v_6_1"); //"Testing deserializeJSONToTemporoSpatialStringProperty() producing property with correct value 4"); CPPUNIT_ASSERT(*tsProp == *refProp); //"Testing deserializeJSONToTemporoSpatialStringProperty()"); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refPartlyCondensibleJSON); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT(tsProp->GetValue(0, 0) =="0"); CPPUNIT_ASSERT(tsProp->GetValue(0, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(1, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(3, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(4, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(5, 1) == "0"); CPPUNIT_ASSERT(tsProp->GetValue(6, 1) == "otherValue"); CPPUNIT_ASSERT(tsProp->GetValue(6, 2) == "0"); CPPUNIT_ASSERT(*tsProp == *refPartlyCondensibleProp); prop = mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(refCondensibleJSON); tsProp = dynamic_cast(prop.GetPointer()); CPPUNIT_ASSERT(tsProp->GetValue(0, 0) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 0) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(0, 1) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 1) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(0, 2) == "1"); CPPUNIT_ASSERT(tsProp->GetValue(1, 2) == "1"); CPPUNIT_ASSERT(*tsProp == *refCondensibleProp); } + + void ExtractTimeStepFromTemporoSpatialStringProperty() + { + CPPUNIT_ASSERT_THROW(mitk::ExtractTimeStepFromTemporoSpatialStringProperty(nullptr, 0), mitk::Exception); + CPPUNIT_ASSERT_THROW_MESSAGE("Test for accessing invalid time step failed.", mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 4), mitk::Exception); + + auto result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 0); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 1); + CPPUNIT_ASSERT(result->GetValue(0, 0) == "v_0_0"); + + result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 3); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 2); + CPPUNIT_ASSERT(result->GetValue(0, 0) == "v_3_0"); + CPPUNIT_ASSERT(result->GetValue(0, 2) == "v_3_2"); + + result = mitk::ExtractTimeStepFromTemporoSpatialStringProperty(refProp, 6); + + CPPUNIT_ASSERT(result->GetAvailableTimeSteps().size() == 1); + CPPUNIT_ASSERT(result->GetAvailableSlices(0).size() == 1); + CPPUNIT_ASSERT(result->GetValue(0, 1) == "v_6_1"); + + } }; MITK_TEST_SUITE_REGISTRATION(mitkTemporoSpatialStringProperty) diff --git a/Modules/CoreCmdApps/CMakeLists.txt b/Modules/CoreCmdApps/CMakeLists.txt index 5841e7e289..08120cb8a3 100644 --- a/Modules/CoreCmdApps/CMakeLists.txt +++ b/Modules/CoreCmdApps/CMakeLists.txt @@ -1,6 +1,8 @@ option(BUILD_CoreCmdApps "Build command-line apps of the MitkCore module" OFF) if(BUILD_CoreCmdApps OR MITK_BUILD_ALL_APPS) mitkFunctionCreateCommandLineApp(NAME FileConverter) mitkFunctionCreateCommandLineApp(NAME ImageTypeConverter) + mitkFunctionCreateCommandLineApp(NAME Fuse3Dto4DImage) + mitkFunctionCreateCommandLineApp(NAME Split4Dto3DImages) endif() diff --git a/Modules/CoreCmdApps/FileConverter.cpp b/Modules/CoreCmdApps/FileConverter.cpp index 197fa9fa67..f9f52f936d 100644 --- a/Modules/CoreCmdApps/FileConverter.cpp +++ b/Modules/CoreCmdApps/FileConverter.cpp @@ -1,116 +1,152 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkProperties.h" #include "mitkCommandLineParser.h" #include "mitkIOUtil.h" #include #include "mitkPreferenceListReaderOptionsFunctor.h" +#include "mitkIOMetaInformationPropertyConstants.h" +#include "mitkIOVolumeSplitReason.h" +#include "mitkPropertyKeyPath.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()); + parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file:", "Input path that should be loaded.",us::Any(),false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file:", "Output path where the result should be stored. If the input generates multiple outputs the index will be added for all but the first output (before the extension; starting with 0).", us::Any(), false, false, false, mitkCommandLineParser::Output); + parser.addArgument("reader", "r", mitkCommandLineParser::String, "Reader Name", "Enforce a certain reader to be used for loading the input.", us::Any()); + parser.addArgument("list-readers", "lr", mitkCommandLineParser::Bool, "List reader names", "Print names of all available readers.", 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; } mitk::IOUtil::Save(node, writeName); ++count; + + try + { + auto splitReasonProperty = node->GetProperty(mitk::PropertyKeyPathToPropertyName(mitk::IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON()).c_str()); + if (splitReasonProperty.IsNotNull()) + { + auto reasonStr = splitReasonProperty->GetValueAsString(); + auto reason = mitk::IOVolumeSplitReason::FromJSON(reasonStr); + if (nullptr != reason && reason->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)) + { + missingSlicesDetected += std::stoi(reason->GetReasonDetails(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + } + } + } + catch (const std::exception& e) + { + std::cerr << "Error while checking for existing split reasons in volume #" << count << "." << std::endl; + std::cerr << "Error details:" << e.what() << std::endl; + } + catch (...) + { + std::cerr << "Unknown error while checking for existing split reasons in volume #" << count << "." << std::endl; + } + } + + if (missingSlicesDetected > 0) + { + std::cout << std::endl; + std::cout << "\n!!! WARNING: MISSING SLICES !!!\n" + "Details: Reader indicated volume splitting due to missing slices. Converted data might be invalid/incomplete.\n" + "Estimated number of missing slices: " << missingSlicesDetected << std::endl; } return EXIT_SUCCESS; } diff --git a/Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp b/Modules/CoreCmdApps/Fuse3Dto4DImage.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/Fuse3Dto4DImageMiniApp.cpp rename to Modules/CoreCmdApps/Fuse3Dto4DImage.cpp diff --git a/Modules/CoreCmdApps/Split4Dto3DImages.cpp b/Modules/CoreCmdApps/Split4Dto3DImages.cpp new file mode 100644 index 0000000000..7ffe9524b3 --- /dev/null +++ b/Modules/CoreCmdApps/Split4Dto3DImages.cpp @@ -0,0 +1,170 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// std includes +#include +#include + +// itk includes +#include "itksys/SystemTools.hxx" + +// CTK includes +#include "mitkCommandLineParser.h" + +// MITK includes +#include +#include +#include +#include + +std::string inFileName; +std::string outFileNamePattern; + +void setupParser(mitkCommandLineParser& parser) +{ + // set general information + parser.setCategory("Dynamic Data Analysis Tools"); + parser.setTitle("Split 4D to 3D Image"); + parser.setDescription("CLI app that allows to split an image into the images per time point. Time resolved properties will be also split."); + parser.setContributor("DKFZ MIC"); + //! [create parser] + + //! [add arguments] + // how should arguments be prefixed + parser.setArgumentPrefix("--", "-"); + // add each argument, unless specified otherwise each argument is optional + // see mitkCommandLineParser::addArgument for more information + parser.beginGroup("Required I/O parameters"); + parser.addArgument( + "input", "i", mitkCommandLineParser::File, "Input file", "Path to the input image that should be splitted. If the image has only one time point it will be stored as output untouched.", us::Any(), false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", + "o", + mitkCommandLineParser::File, + "Output file(s) path", + "Path to the splitted images. If the input has multiple time points the path will be used as pattern and a suffix \"_[time step]\" will be added before the extension.", + us::Any(), + false, false, false, mitkCommandLineParser::Output); + parser.endGroup(); + + parser.beginGroup("Optional parameters"); + parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); + parser.endGroup(); + //! [add arguments] +} + +bool configureApplicationSettings(std::map parsedArgs) +{ + if (parsedArgs.size() == 0) + return false; + + inFileName = us::any_cast(parsedArgs["input"]); + outFileNamePattern = us::any_cast(parsedArgs["output"]); + + return true; +} + +void transferMetaProperties(const mitk::Image* sourceImage, mitk::Image* destinationImage, mitk::TimeStepType ts) +{ + auto props = sourceImage->GetPropertyList()->GetMap(); + + for (const auto& [name, prop] : *props) + { + auto tsProperty = dynamic_cast(prop.GetPointer()); + if (nullptr == tsProperty) + { + destinationImage->SetProperty(name, prop->Clone()); + } + else + { + auto extractedProp = ExtractTimeStepFromTemporoSpatialStringProperty(tsProperty, ts); + destinationImage->SetProperty(name, extractedProp); + } + } +} + +void saveResultImage(const mitk::Image* image, const std::string& fileName, mitk::TimeStepType ts) +{ + std::cout << "time step: "<< ts << " -> " <& parsedArgs = parser.parseArguments(argc, argv); + if (!configureApplicationSettings(parsedArgs)) + { + return EXIT_FAILURE; + }; + + // Show a help message + if (parsedArgs.count("help") || parsedArgs.count("h")) + { + std::cout << parser.helpText(); + return EXIT_SUCCESS; + } + + //! [do processing] + try + { + std::cout << "Load image:" << inFileName << std::endl; + auto image = mitk::IOUtil::Load(inFileName, &readerFilterFunctor); + + std::cout << "Split the image ..." << std::endl; + + if (image->GetTimeGeometry()->CountTimeSteps() == 1) + { + std::cout << "Input contains only one time step; will be passed through" << std::endl; + saveResultImage(image, outFileNamePattern, 0); + } + else + { + std::string extension = itksys::SystemTools::GetFilenameExtension(outFileNamePattern); + std::string filename = itksys::SystemTools::GetFilenameWithoutExtension(outFileNamePattern); + std::string path = itksys::SystemTools::GetFilenamePath(outFileNamePattern); + if (!path.empty()) + { + path = path + mitk::IOUtil::GetDirectorySeparator(); + } + + std::cout << "Process time steps:" << std::endl; + for (mitk::TimeStepType ts = 0; ts < image->GetTimeGeometry()->CountTimeSteps(); ++ts) + { + auto splitImage = mitk::SelectImageByTimeStep(image, ts)->Clone(); + transferMetaProperties(image, splitImage, ts); + + std::string writeName = path + filename + "_" + std::to_string(ts) + extension; + + saveResultImage(splitImage, writeName, ts); + } + std::cout << std::endl; + } + + std::cout << "Processing finished." << std::endl; + + return EXIT_SUCCESS; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + MITK_ERROR << "Unexpected error encountered."; + return EXIT_FAILURE; + } +} diff --git a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp index dc50c4c610..fa9ec00f29 100644 --- a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp +++ b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp @@ -1,199 +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 #include #include #include #include #include #include void InitializeCommandLineParser(mitkCommandLineParser& parser) { parser.setTitle("DICOM Volume Diagnostics"); parser.setCategory("DICOM"); parser.setDescription("Gives insights how MITK readers would convert a set of DICOM files into image volumes (e.g. number of volumes and the sorting of the files)"); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.addArgument("only-own-series", "s", mitkCommandLineParser::Bool, "Only own series", "Analyze only files in the same directory that have the same DICOM Series UID, if a file is provided as input.", us::Any()); parser.addArgument("check-3d", "d", mitkCommandLineParser::Bool, "Check 3D configs", "Analyze the input by using all known 3D configurations. If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); parser.addArgument("check-3d+t", "t", mitkCommandLineParser::Bool, "Check 3D+t configs", "Analyze the input by using all known 3D+t configurations (thus dynamic image configurations). If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file or path", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "Output file where the diagnostics results are stored as json.", us::Any()); } int main(int argc, char* argv[]) { int returnValue = EXIT_SUCCESS; mitkCommandLineParser parser; InitializeCommandLineParser(parser); auto args = parser.parseArguments(argc, argv); if (args.empty()) { std::cout << parser.helpText(); return EXIT_FAILURE; } nlohmann::json diagnosticsResult; try { + int missingSlicesDetected = 0; + auto inputFilename = us::any_cast(args["input"]); auto outputFilename = args.count("output")==0 ? std::string() : us::any_cast(args["output"]); bool onlyOwnSeries = args.count("only-own-series"); bool check3D = args.count("check-3d"); bool check3DPlusT = args.count("check-3d+t"); if (!check3D && !check3DPlusT) { //if no check option is selected all are activated by default. check3D = true; check3DPlusT = true; } diagnosticsResult["input"] = inputFilename; diagnosticsResult["only-own-series"] = onlyOwnSeries; diagnosticsResult["check-3d"] = check3D; diagnosticsResult["check-3d+t"] = check3DPlusT; mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(inputFilename); if (relevantFiles.empty()) { mitkThrow() << "DICOM Volume Diagnostics found no relevant files in specified location. No data is loaded. Location: " << inputFilename; } else { bool pathIsDirectory = fs::is_directory(inputFilename); if (!pathIsDirectory && onlyOwnSeries) { relevantFiles = mitk::FilterDICOMFilesForSameSeries(inputFilename, relevantFiles); } diagnosticsResult["analyzed_files"] = relevantFiles; auto selector = mitk::DICOMFileReaderSelector::New(); if (check3D) selector->LoadBuiltIn3DConfigs(); if (check3DPlusT) selector->LoadBuiltIn3DnTConfigs(); nlohmann::json readerInfos; for (const auto& reader : selector->GetAllConfiguredReaders()) { nlohmann::json readerInfo; readerInfo["class_name"] = reader->GetNameOfClass(); readerInfo["configuration_label"] = reader->GetConfigurationLabel(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); readerInfos.push_back(readerInfo); } diagnosticsResult["checked_readers"] = readerInfos; selector->SetInputFiles(relevantFiles); auto reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNull()) { mitkThrow() << "DICOM Volume Diagnostics service found no suitable reader configuration for relevant files."; } else { nlohmann::json readerInfo; readerInfo["class_name"] = reader->GetNameOfClass(); readerInfo["configuration_label"] = reader->GetConfigurationLabel(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); std::stringstream config; reader->PrintConfiguration(config); readerInfo["config_details"] = config.str(); diagnosticsResult["selected_reader"] = readerInfo; nlohmann::json outputInfos; unsigned int relevantOutputCount = 0; const auto nrOfOutputs = reader->GetNumberOfOutputs(); for (std::remove_const_t outputIndex = 0; outputIndex < nrOfOutputs; ++outputIndex) { bool isRelevantOutput = true; if (!pathIsDirectory) { const auto frameList = reader->GetOutput(outputIndex).GetImageFrameList(); auto finding = std::find_if(frameList.begin(), frameList.end(), [&](const mitk::DICOMImageFrameInfo::Pointer& frame) { fs::path framePath(frame->Filename); fs::path inputPath(inputFilename); return framePath == inputPath; }); isRelevantOutput = finding != frameList.end(); } if (isRelevantOutput) { ++relevantOutputCount; nlohmann::json outputInfo; const auto output = reader->GetOutput(outputIndex); const auto frameList = output.GetImageFrameList(); mitk::DICOMFilePathList outputFiles; outputFiles.resize(frameList.size()); std::transform(frameList.begin(), frameList.end(), outputFiles.begin(), [](const mitk::DICOMImageFrameInfo::Pointer& frame) { return frame->Filename; }); outputInfo["files"] = outputFiles; outputInfo["timesteps"] = output.GetNumberOfTimeSteps(); outputInfo["frames_per_timesteps"] = output.GetNumberOfFramesPerTimeStep(); + if (output.GetSplitReason()!=nullptr && output.GetSplitReason()->HasReasons()) + { + outputInfo["volume_split_reason"] = mitk::IOVolumeSplitReason::ToJSON(output.GetSplitReason()); + + try + { + if (output.GetSplitReason()->HasReason(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)) + { + missingSlicesDetected += std::stoi(output.GetSplitReason()->GetReasonDetails(mitk::IOVolumeSplitReason::ReasonType::MissingSlices)); + } + } + catch (const std::exception& e) + { + std::cerr << "Error while checking for missing slices split reasons in volume #" << relevantOutputCount << "." << std::endl; + std::cerr << "Error details:" << e.what() << std::endl; + } + catch (...) + { + std::cerr << "Unknown error while checking for missing slices split reasons in volume #" << relevantOutputCount << "." << std::endl; + } + } outputInfos.push_back(outputInfo); } } diagnosticsResult["volume_count"] = relevantOutputCount; diagnosticsResult["volumes"] = outputInfos; } } std::cout << "\n### DIAGNOSTICS REPORT ###\n" << std::endl; std::cout << std::setw(2) << diagnosticsResult << std::endl; + if (missingSlicesDetected > 0) + { + std::cout << std::endl; + std::cout << "\n!!! WARNING: MISSING SLICES !!!\n" + "Details: Reader indicated volume splitting due to missing slices. Converted data might be invalid/incomplete.\n" + "Estimated number of missing slices: " << missingSlicesDetected << std::endl; + } + if (!outputFilename.empty()) { std::ofstream fileout(outputFilename); fileout << diagnosticsResult; fileout.close(); } } catch (const mitk::Exception& e) { MITK_ERROR << e.GetDescription(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "An unknown error occurred!"; return EXIT_FAILURE; } return returnValue; } diff --git a/Modules/DICOM/include/mitkClassicDICOMSeriesReader.h b/Modules/DICOM/include/mitkClassicDICOMSeriesReader.h index 7e00602a37..be66ab02f1 100644 --- a/Modules/DICOM/include/mitkClassicDICOMSeriesReader.h +++ b/Modules/DICOM/include/mitkClassicDICOMSeriesReader.h @@ -1,78 +1,78 @@ /*============================================================================ 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 mitkClassicDICOMSeriesReader_h #define mitkClassicDICOMSeriesReader_h #include "mitkThreeDnTDICOMSeriesReader.h" #include "MitkDICOMExports.h" namespace mitk { /** \ingroup DICOMModule \brief Sorting and grouping like mitk::DicomSeriesReader until 2013. This class implements the same functionality as the legacy class DicomSeriesReader, except that it is 75 lines instead of 2500 lines. \warning Since the old class is known to have problems with some series, it is advised to use a good configuration of DICOMITKSeriesGDCMReader, which can be obtained by using DICOMFileReaderSelector. The following text documents the actual sorting logic of this reader. The class groups datasets that have different values in any of the following tags: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames - (0020,000e) Series Instance UID Within each of the groups, datasets are sorted by the value of the following tags (primary sorting first): - (0020,0032) %Image Position (Patient) (distance from zero along normal of (0020,0037) %Image Orientation (Patient)) - (0020,0012) Acquisition Number - (0008,0032) Acquisition Time - (0018,1060) Trigger Time - (0008,0018) SOP Instance UID (last resort, not really meaningful but decides clearly) If the series was acquired using a tilted gantry, this will be "fixed" by applying a shear transformation. If multiple images occupy the same position in space, it is assumed that this indicated a 3D+t image. */ class MITKDICOM_EXPORT ClassicDICOMSeriesReader : public ThreeDnTDICOMSeriesReader { public: mitkClassMacro( ClassicDICOMSeriesReader, DICOMITKSeriesGDCMReader ); - mitkCloneMacro( ClassicDICOMSeriesReader ); itkNewMacro( ClassicDICOMSeriesReader ); bool operator==(const DICOMFileReader& other) const override; protected: + mitkCloneMacro(ClassicDICOMSeriesReader); ClassicDICOMSeriesReader(); ~ClassicDICOMSeriesReader() override; ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other); ClassicDICOMSeriesReader& operator=(const ClassicDICOMSeriesReader& other); }; } #endif diff --git a/Modules/DICOM/include/mitkDICOMDatasetSorter.h b/Modules/DICOM/include/mitkDICOMDatasetSorter.h index c2b8ad4066..84898d35dd 100644 --- a/Modules/DICOM/include/mitkDICOMDatasetSorter.h +++ b/Modules/DICOM/include/mitkDICOMDatasetSorter.h @@ -1,91 +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 mitkDICOMDatasetSorter_h #define mitkDICOMDatasetSorter_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "mitkDICOMDatasetAccess.h" +#include "mitkIOVolumeSplitReason.h" namespace mitk { /** \ingroup DICOMModule \brief The sorting/splitting building-block of DICOMITKSeriesGDCMReader. This class describes the interface of the sorting/splitting process described as part of DICOMITKSeriesGDCMReader::AnalyzeInputFiles() (see \ref DICOMITKSeriesGDCMReader_LoadingStrategy). The procedure is simple: - take a list of input datasets (DICOMDatasetAccess) - sort them (to be defined by sub-classes, based on specific tags) - return the sorting result as outputs (the single input might be distributed into multiple outputs) The simplest and most generic form of sorting is implemented in sub-class DICOMTagBasedSorter. */ class MITKDICOM_EXPORT DICOMDatasetSorter : public itk::LightObject { public: mitkClassMacroItkParent( DICOMDatasetSorter, itk::LightObject ); /** \brief Return the tags of interest (to facilitate scanning) */ virtual DICOMTagList GetTagsOfInterest() = 0; /// \brief Input for sorting void SetInput(DICOMDatasetList filenames); /// \brief Input for sorting const DICOMDatasetList& GetInput() const; /// \brief Sort input datasets into one or multiple outputs. virtual void Sort() = 0; /// \brief Output of the sorting process. unsigned int GetNumberOfOutputs() const; /// \brief Output of the sorting process. const DICOMDatasetList& GetOutput(unsigned int index) const; /// \brief Output of the sorting process. DICOMDatasetList& GetOutput(unsigned int index); + IOVolumeSplitReason::ConstPointer GetSplitReason(unsigned int index) const; + /// \brief Print configuration details into stream. virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const = 0; virtual bool operator==(const DICOMDatasetSorter& other) const = 0; protected: DICOMDatasetSorter(); ~DICOMDatasetSorter() override; DICOMDatasetSorter(const DICOMDatasetSorter& other); DICOMDatasetSorter& operator=(const DICOMDatasetSorter& other); void ClearOutputs(); void SetNumberOfOutputs(unsigned int numberOfOutputs); - void SetOutput(unsigned int index, const DICOMDatasetList& output); + void SetOutput(unsigned int index, const DICOMDatasetList& output, IOVolumeSplitReason::ConstPointer splitReason = nullptr); private: DICOMDatasetList m_Input; std::vector< DICOMDatasetList > m_Outputs; + std::vector< IOVolumeSplitReason::Pointer > m_SplitReasons; }; } #endif diff --git a/Modules/DICOM/include/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOM/include/mitkDICOMITKSeriesGDCMReader.h index 62b242f8d3..e6c44f2ab5 100644 --- a/Modules/DICOM/include/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOM/include/mitkDICOMITKSeriesGDCMReader.h @@ -1,377 +1,378 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkDICOMITKSeriesGDCMReader_h #define mitkDICOMITKSeriesGDCMReader_h #include #include #include "mitkDICOMFileReader.h" #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMGDCMImageFrameInfo.h" #include "mitkEquiDistantBlocksSorter.h" #include "mitkNormalDirectionConsistencySorter.h" #include "MitkDICOMExports.h" namespace itk { class TimeProbesCollectorBase; } namespace mitk { /** \ingroup DICOMModule \brief Flexible reader based on itk::ImageSeriesReader and GDCM, for single-slice modalities like CT, MR, PET, CR, etc. Implements the loading processed as structured by DICOMFileReader offers configuration of its loading strategy. Documentation sections: - \ref DICOMITKSeriesGDCMReader_LoadingStrategy - \ref DICOMITKSeriesGDCMReader_ForcedConfiguration - \ref DICOMITKSeriesGDCMReader_UserConfiguration - \ref DICOMITKSeriesGDCMReader_GantryTilt - \ref DICOMITKSeriesGDCMReader_Testing - \ref DICOMITKSeriesGDCMReader_Internals - \ref DICOMITKSeriesGDCMReader_RelatedClasses - \ref DICOMITKSeriesGDCMReader_TiltInternals - \ref DICOMITKSeriesGDCMReader_Condensing \section DICOMITKSeriesGDCMReader_LoadingStrategy Loading strategy The set of input files is processed by a number of DICOMDatasetSorter objects which may do two sort of things: 1. split a list of input frames into multiple lists, based on DICOM tags such as "Rows", "Columns", which cannot be mixed within a single mitk::Image 2. sort the frames within the input lists, based on the values of DICOM tags such as "Image Position Patient" When the DICOMITKSeriesGDCMReader is configured with DICOMDatasetSorter%s, the list of input files is processed as follows: 1. build an initial set of output groups, simply by grouping all input files. 2. for each configured DICOMDatasetSorter, process: - for each output group: 1. set this group's files as input to the sorter 2. let the sorter sort (and split) 3. integrate the sorter's output groups with our own output groups \section DICOMITKSeriesGDCMReader_ForcedConfiguration Forced Configuration In all cases, the reader will add two DICOMDatasetSorter objects that are required to load mitk::Images properly via itk::ImageSeriesReader: 1. As a \b first step, the input files will be split into groups that are not compatible because they differ in essential aspects: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames 2. As are two forced \b last steps: 1. There will always be an instance of EquiDistantBlocksSorter, which ensures that there is an equal distance between all the frames of an Image. This is required to achieve correct geometrical positions in the mitk::Image, i.e. it is essential to be able to make measurements in images. - whether or not the distance is required to be orthogonal to the image planes is configured by SetFixTiltByShearing(). - during this check, we need to tolerate some minor errors in documented vs. calculated image origins. The amount of tolerance can be adjusted by SetToleratedOriginOffset() and SetToleratedOriginOffsetToAdaptive(). Please see EquiDistantBlocksSorter for more details. The default should be good for most cases. 2. There is always an instance of NormalDirectionConsistencySorter, which makes the order of images go along the image normals (see NormalDirectionConsistencySorter) \section DICOMITKSeriesGDCMReader_UserConfiguration User Configuration The user of this class can add more sorting steps (similar to the one described in above section) by calling AddSortingElement(). Usually, an application will add sorting by "Image Position Patient", by "Instance Number", and by other relevant tags here. \section DICOMITKSeriesGDCMReader_GantryTilt Gantry tilt handling When CT gantry tilt is used, the gantry plane (= X-Ray source and detector ring) and the vertical plane do not align anymore. This scanner feature is used for example to reduce metal artifacts (e.g. Lee C , Evaluation of Using CT Gantry Tilt Scan on Head and Neck Cancer Patients with Dental Structure: Scans Show Less Metal Artifacts. Presented at: Radiological Society of North America 2011 Scientific Assembly and Annual Meeting; November 27- December 2, 2011 Chicago IL.). The acquired planes of such CT series do not match the expectations of a orthogonal geometry in mitk::Image: if you stack the slices, they show a small shift along the Y axis: \verbatim without tilt with tilt |||||| ////// |||||| ////// -- |||||| --------- ////// -------- table orientation |||||| ////// |||||| ////// Stacked slices: without tilt with tilt -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- \endverbatim As such gemetries do not "work" in conjunction with mitk::Image, DICOMITKSeriesGDCMReader is able to perform a correction for such series. Whether or not such correction should be attempted is controlled by SetFixTiltByShearing(), the default being correction. For details, see "Internals" below. \section DICOMITKSeriesGDCMReader_Testing Testing A number of tests is implemented in module DICOMTesting, which is documented at \ref DICOMTesting. \section DICOMITKSeriesGDCMReader_Internals Class internals Internally, the class is based on GDCM and it depends heavily on the gdcm::Scanner class. Since the sorting elements (see DICOMDatasetSorter and DICOMSortCriterion) can access tags only via the DICOMDatasetAccess interface, BUT DICOMITKSeriesGDCMReader holds a list of more specific classes DICOMGDCMImageFrameInfo, we must convert between the two types sometimes. This explains the methods ToDICOMDatasetList(), FromDICOMDatasetList(). The intermediate result of all the sorting efforts is held in m_SortingResultInProgress, which is modified through InternalExecuteSortingStep(). \subsection DICOMITKSeriesGDCMReader_RelatedClasses Overview of related classes The following diagram gives an overview of the related classes: \image html implementeditkseriesgdcmreader.jpg \subsection DICOMITKSeriesGDCMReader_TiltInternals Details about the tilt correction The gantry tilt "correction" algorithm fixes two errors introduced by ITK's ImageSeriesReader: - the plane shift that is ignored by ITK's reader is recreated by applying a shearing transformation using itk::ResampleFilter. - the spacing is corrected (it is calculated by ITK's reader from the distance between two origins, which is NOT the slice distance in this special case) Both errors are introduced in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) For the correction, we examine two consecutive slices of a series, both described as a pair (origin/orientation): - we calculate if the first origin is on a line along the normal of the second slice - if this is not the case, the geometry will not fit a normal mitk::Image/mitk::%Geometry3D - we then project the second origin into the first slice's coordinate system to quantify the shift - both is done in class GantryTiltInformation with quite some comments. The geometry of image stacks with tilted geometries is illustrated below: - green: the DICOM images as described by their tags: origin as a point with the line indicating the orientation - red: the output of ITK ImageSeriesReader: wrong, larger spacing, no tilt - blue: how much a shear must correct \image html Modules/DICOM/doc/Doxygen/tilt-correction.jpg \subsection DICOMITKSeriesGDCMReader_Condensing Sub-classes can condense multiple blocks into a single larger block The sorting/splitting process described above is helpful for at least two more DICOM readers, which either try to load 3D+t images or which load diffusion data. In both cases, a single pixel of the mitk::Image is made up of multiple values, in one case values over time, in the other case multiple measurements of a single point. The specialized readers for these cases (e.g. ThreeDnTDICOMSeriesReader) can reuse most of the methods in DICOMITKSeriesGDCMReader, except that they need an extra step after the usual sorting, in which they can merge already grouped 3D blocks. What blocks are merged depends on the specialized reader's understanding of these images. To allow for such merging, a method Condense3DBlocks() is called as an absolute last step of AnalyzeInputFiles(). Given this, a sub-class could implement only LoadImages() and Condense3DBlocks() instead repeating most of AnalyzeInputFiles(). */ class MITKDICOM_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkFactorylessNewMacro( DICOMITKSeriesGDCMReader ); mitkNewMacro1Param( DICOMITKSeriesGDCMReader, unsigned int ); mitkNewMacro2Param( DICOMITKSeriesGDCMReader, unsigned int, bool ); /** \brief Runs the sorting / splitting process described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. Method required by DICOMFileReader. */ void AnalyzeInputFiles() override; // void AllocateOutputImages(); /** \brief Loads images using itk::ImageSeriesReader, potentially applies shearing to correct gantry tilt. */ bool LoadImages() override; // re-implemented from super-class bool CanHandleFile(const std::string& filename) override; /** \brief Add an element to the sorting procedure described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. */ virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); typedef const std::list ConstSorterList; ConstSorterList GetFreelyConfiguredSortingElements() const; /** \brief Controls whether to "fix" tilted acquisitions by shearing the output (see \ref DICOMITKSeriesGDCMReader_GantryTilt). */ void SetFixTiltByShearing(bool on); bool GetFixTiltByShearing() const; /** \brief Controls whether groups of only two images are accepted when ensuring consecutive slices via EquiDistantBlocksSorter. */ void SetAcceptTwoSlicesGroups(bool accept) const; bool GetAcceptTwoSlicesGroups() const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3) const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffset(double millimeters = 0.005) const; /** \brief Ignore all dicom tags that are non-essential for simple 3D volume import. */ void SetSimpleVolumeReading(bool read) { m_SimpleVolumeReading = read; }; /** \brief Ignore all dicom tags that are non-essential for simple 3D volume import. */ bool GetSimpleVolumeReading() { return m_SimpleVolumeReading; }; double GetToleratedOriginError() const; bool IsToleratedOriginOffsetAbsolute() const; double GetDecimalPlacesForOrientation() const; bool operator==(const DICOMFileReader& other) const override; DICOMTagPathList GetTagsOfInterest() const override; static int GetDefaultDecimalPlacesForOrientation() { return m_DefaultDecimalPlacesForOrientation; } static bool GetDefaultSimpleVolumeImport() { return m_DefaultSimpleVolumeImport; } static bool GetDefaultFixTiltByShearing() { return m_DefaultFixTiltByShearing; } protected: void InternalPrintConfiguration(std::ostream& os) const override; /// \brief Return active C locale - static std::string GetActiveLocale(); + static std::string GetActiveLocale(); /** \brief Remember current locale on stack, activate "C" locale. "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PushLocale() const; /** \brief Activate last remembered locale from locale stack "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PopLocale() const; const static int m_DefaultDecimalPlacesForOrientation = 5; const static bool m_DefaultSimpleVolumeImport = false; const static bool m_DefaultFixTiltByShearing = true; DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation = m_DefaultDecimalPlacesForOrientation, bool simpleVolumeImport = m_DefaultSimpleVolumeImport); ~DICOMITKSeriesGDCMReader() override; DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); - typedef std::vector SortingBlockList; + using SortingBlockListItemType = std::pair; + using SortingBlockList = std::vector ; /** \brief "Hook" for sub-classes, see \ref DICOMITKSeriesGDCMReader_Condensing \return REMAINING blocks */ virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); virtual DICOMTagCache::Pointer GetTagCache() const; void SetTagCache( const DICOMTagCache::Pointer& ) override; /// \brief Sorting step as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy - static SortingBlockList InternalExecuteSortingStep( + static SortingBlockList InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input); /// \brief Loads the mitk::Image by means of an itk::ImageSeriesReader virtual bool LoadMitkImageForOutput(unsigned int o); virtual bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const; /// \brief Describe this reader's confidence for given SOP class UID - static ReaderImplementationLevel GetReaderImplementationLevel(const std::string sopClassUID); + static ReaderImplementationLevel GetReaderImplementationLevel(const std::string sopClassUID); private: /// \brief Creates the required sorting steps described in \ref DICOMITKSeriesGDCMReader_ForcedConfiguration void EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation, bool simpleVolumeImport = false); protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time bool m_FixTiltByShearing; // could be removed by ITKDICOMSeriesReader NOT flagging tilt unless requested to fix it! bool m_SimpleVolumeReading; private: SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader and ClassicDICOMSeriesReader due to lack of time mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; mitk::NormalDirectionConsistencySorter::Pointer m_NormalDirectionConsistencySorter; private: static std::mutex s_LocaleMutex; mutable std::stack m_ReplacedCLocales; mutable std::stack m_ReplacedCinLocales; double m_DecimalPlacesForOrientation; DICOMTagCache::Pointer m_TagCache; bool m_ExternalCache; }; } #endif diff --git a/Modules/DICOM/include/mitkDICOMImageBlockDescriptor.h b/Modules/DICOM/include/mitkDICOMImageBlockDescriptor.h index db2a634911..1434cda24f 100644 --- a/Modules/DICOM/include/mitkDICOMImageBlockDescriptor.h +++ b/Modules/DICOM/include/mitkDICOMImageBlockDescriptor.h @@ -1,237 +1,247 @@ /*============================================================================ 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 mitkDICOMImageBlockDescriptor_h #define mitkDICOMImageBlockDescriptor_h #include "mitkDICOMEnums.h" #include "mitkDICOMImageFrameInfo.h" #include "mitkDICOMTag.h" #include "mitkDICOMTagCache.h" +#include "mitkIOVolumeSplitReason.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkWeakPointer.h" #include "mitkIPropertyProvider.h" #include "mitkGantryTiltInformation.h" #include namespace mitk { struct DICOMCachedValueInfo { unsigned int TimePoint; unsigned int SliceInTimePoint; std::string Value; }; class DICOMCachedValueLookupTable : public GenericLookupTable< DICOMCachedValueInfo > { public: typedef DICOMCachedValueLookupTable Self; typedef GenericLookupTable< DICOMCachedValueInfo > Superclass; const char *GetNameOfClass() const override { return "DICOMCachedValueLookupTable"; } DICOMCachedValueLookupTable() {} Superclass& operator=(const Superclass& other) override { return Superclass::operator=(other); } ~DICOMCachedValueLookupTable() override {} }; /** \ingroup DICOMModule \brief Output descriptor for DICOMFileReader. As a result of analysis by a mitk::DICOMFileReader, this class describes the properties of a single mitk::Images that could be loaded by the file reader. The descriptor contains the following information: - the mitk::Image itself. This will be nullptr after analysis and only be present after actual loading. - a list of frames (mostly: filenames) that went into composition of the mitk::Image. - an assessment of the reader's ability to load this set of files (ReaderImplementationLevel) - this can be used for reader selection when one reader is able to load an image with correct colors and the other is able to produce only gray values, for example - description of aspects of the image. Mostly a key-value list implemented by means of mitk::PropertyList. - for specific keys and possible values, see documentation of specific readers. \note an mitk::Image may both consist of multiple files (the "old" DICOM way) or a mitk::Image may be described by a single DICOM file or even only parts of a DICOM file (the newer multi-frame DICOM classes). To reflect this DICOMImageFrameList describes a list of frames from different or a single file. Described aspects of an image are: - whether pixel spacing is meant to be in-patient or on-detector (mitk::PixelSpacingInterpretation) - details about a possible gantry tilt (intended for use by file readers, may be hidden later) */ class MITKDICOM_EXPORT DICOMImageBlockDescriptor: public IPropertyProvider { public: DICOMImageBlockDescriptor(); ~DICOMImageBlockDescriptor() override; DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other); DICOMImageBlockDescriptor& operator=(const DICOMImageBlockDescriptor& other); static DICOMTagList GetTagsOfInterest(); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) void SetImageFrameList(const DICOMImageFrameList& framelist); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) const DICOMImageFrameList& GetImageFrameList() const; /// The 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList void SetMitkImage(Image::Pointer image); /// the 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList Image::Pointer GetMitkImage() const; /// Reader's capability to appropriately load this set of frames ReaderImplementationLevel GetReaderImplementationLevel() const; /// Reader's capability to appropriately load this set of frames void SetReaderImplementationLevel(const ReaderImplementationLevel& level); /// Key-value store describing aspects of the image to be loaded void SetProperty(const std::string& key, BaseProperty* value); /// Key-value store describing aspects of the image to be loaded BaseProperty* GetProperty(const std::string& key) const; /// Convenience function around GetProperty() std::string GetPropertyAsString(const std::string&) const; + /** Returns the pointer to the split reason of this block descriptor.*/ + IOVolumeSplitReason::ConstPointer GetSplitReason() const; + /** Returns the pointer to the split reason of this block descriptor.*/ + IOVolumeSplitReason::Pointer GetSplitReason(); + /** Sets the split reason for the block descriptor.*/ + void SetSplitReason(IOVolumeSplitReason::Pointer reason); + /// Convenience function around SetProperty() void SetFlag(const std::string& key, bool value); /// Convenience function around GetProperty() bool GetFlag(const std::string& key, bool defaultValue) const; /// Convenience function around SetProperty() void SetIntProperty(const std::string& key, int value); /// Convenience function around GetProperty() int GetIntProperty(const std::string& key, int defaultValue) const; BaseProperty::ConstPointer GetConstProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = true) const override; std::vector GetPropertyKeys(const std::string &contextName = "", bool includeDefaultContext = false) const override; std::vector GetPropertyContextNames() const override; private: // For future implementation: load slice-by-slice, mark this using these methods void SetSliceIsLoaded(unsigned int index, bool isLoaded); // For future implementation: load slice-by-slice, mark this using these methods bool IsSliceLoaded(unsigned int index) const; // For future implementation: load slice-by-slice, mark this using these methods bool AllSlicesAreLoaded() const; public: /// Describe how the mitk::Image's pixel spacing should be interpreted PixelSpacingInterpretation GetPixelSpacingInterpretation() const; /// Describe the correct x/y pixel spacing of the mitk::Image (which some readers might need to adjust after loading) void GetDesiredMITKImagePixelSpacing(ScalarType& spacingXinMM, ScalarType& spacingYinMM) const; /// Describe the gantry tilt of the acquisition void SetTiltInformation(const GantryTiltInformation& info); /// Describe the gantry tilt of the acquisition const GantryTiltInformation GetTiltInformation() const; /// SOP Class UID of this set of frames void SetSOPClassUID(const std::string& uid); /// SOP Class UID of this set of frames std::string GetSOPClassUID() const; /// SOP Class as human readable name (e.g. "CT Image Storage") std::string GetSOPClassUIDAsName() const; /**Convenience method that returns the property timesteps*/ int GetNumberOfTimeSteps() const; /**return the number of frames that constitute one timestep.*/ int GetNumberOfFramesPerTimeStep() const; void SetTagCache(DICOMTagCache* privateCache); /** Type specifies additional tags of interest. Key is the tag path of interest. * The value is an optional user defined name for the property that should be used to store the tag value(s). * Empty value is default and will imply to use the found DICOMTagPath as property name.*/ typedef std::map AdditionalTagsMapType; /** * \brief Set a list of DICOMTagPaths that specify all DICOM-Tags that will be copied into the property of the mitk::Image. * * This method can be used to specify a list of DICOM-tags that shall be available after the loading. * The value in the tagMap is an optional user defined name for the property key that should be used * when storing the property). Empty value is default and will imply to use the found DICOMTagPath * as property key. * By default the content of the DICOM tags will be stored in a StringLookupTable on the mitk::Image. * This behaviour can be changed by setting a different TagLookupTableToPropertyFunctor via * SetTagLookupTableToPropertyFunctor(). */ void SetAdditionalTagsOfInterest(const AdditionalTagsMapType& tagMap); typedef std::function TagLookupTableToPropertyFunctor; /** * \brief Set a functor that defines how the slice-specific tag-values are stored in a Property. * * This method sets a functor that is given a StringLookupTable that contains the values of one DICOM tag * mapped to the slice index. * The functor is supposed to store these values in an mitk Property. * * By default, the StringLookupTable is stored in a StringLookupTableProperty except if all values are * identical. In this case, the unique value is stored only once in a StringProperty. */ void SetTagLookupTableToPropertyFunctor(TagLookupTableToPropertyFunctor); /// Print information about this image block to given stream void Print(std::ostream& os, bool filenameDetails) const; private: // read values from tag cache std::string GetPixelSpacing() const; std::string GetImagerPixelSpacing() const; Image::Pointer FixupSpacing(Image* mitkImage); Image::Pointer DescribeImageWithProperties(Image* mitkImage); void UpdateImageDescribingProperties() const; static mitk::BaseProperty::Pointer GetPropertyForDICOMValues(const DICOMCachedValueLookupTable& cacheLookupTable); double stringtodouble(const std::string& str) const; DICOMImageFrameList m_ImageFrameList; Image::Pointer m_MitkImage; BoolList m_SliceIsLoaded; ReaderImplementationLevel m_ReaderImplementationLevel; GantryTiltInformation m_TiltInformation; PropertyList::Pointer m_PropertyList; - mitk::WeakPointer m_TagCache; + IOVolumeSplitReason::Pointer m_SplitReason; + + WeakPointer m_TagCache; mutable bool m_PropertiesOutOfDate; AdditionalTagsMapType m_AdditionalTagMap; std::set m_FoundAdditionalTags; TagLookupTableToPropertyFunctor m_PropertyFunctor; }; } #endif diff --git a/Modules/DICOM/include/mitkDICOMTagBasedSorter.h b/Modules/DICOM/include/mitkDICOMTagBasedSorter.h index b708bf53c1..8cc2b2be5d 100644 --- a/Modules/DICOM/include/mitkDICOMTagBasedSorter.h +++ b/Modules/DICOM/include/mitkDICOMTagBasedSorter.h @@ -1,202 +1,206 @@ /*============================================================================ 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 mitkDICOMTagBasedSorter_h #define mitkDICOMTagBasedSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" namespace mitk { /** \ingroup DICOMModule \brief Sort DICOM datasets based on configurable tags. This class implements sorting of input DICOM datasets into multiple outputs as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. The logic of sorting and splitting is most simple and most generic: 1. Datasets will be put into different groups, if they differ in their value of specific tags (defined by AddDistinguishingTag()) - there might be multiple distinguishing tags defined - tag values might be processed before comparison by means of TagValueProcessor (e.g. round to a number of decimal places) 2. Each of the groups will be sorted by comparing their tag values using multiple DICOMSortCriterion - DICOMSortCriterion might evaluate a single tag (e.g. Instance Number) or multiple values (as in SortByImagePositionPatient) - only a single DICOMSortCriterion is defined for DICOMTagBasedSorter, because each DICOMSortCriterion holds a "secondary sort criterion", i.e. an application can define multiple tags for sorting by chaining \link DICOMSortCriterion DICOMSortCriteria \endlink - applications should make sure that sorting is always defined (to avoid problems with standard containers), e.g. by adding a comparison of filenames or instance UIDs as a last sorting fallback. */ class MITKDICOM_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter { public: /** \brief Processes tag values before they are compared. These classes could do some kind of normalization such as rounding, lower case formatting, etc. */ class MITKDICOM_EXPORT TagValueProcessor { public: /// \brief Implements the "processing". virtual std::string operator()(const std::string&) const = 0; virtual TagValueProcessor* Clone() const = 0; virtual ~TagValueProcessor() {} }; /** \brief Cuts a number after configured number of decimal places. An instance of this class can be used to avoid errors when comparing minimally different image orientations. */ class MITKDICOM_EXPORT CutDecimalPlaces : public TagValueProcessor { public: CutDecimalPlaces(unsigned int precision); CutDecimalPlaces(const CutDecimalPlaces& other); unsigned int GetPrecision() const; std::string operator()(const std::string&) const override; TagValueProcessor* Clone() const override; private: unsigned int m_Precision; }; mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ); itkNewMacro( DICOMTagBasedSorter ); /** \brief Datasets that differ in given tag's value will be sorted into separate outputs. */ void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = nullptr ); DICOMTagList GetDistinguishingTags() const; const TagValueProcessor* GetTagValueProcessorForDistinguishingTag(const DICOMTag&) const; /** \brief Define the sorting criterion (which holds seconardy criteria) */ void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); DICOMSortCriterion::ConstPointer GetSortCriterion() const; /** \brief A list of all the tags needed for processing (facilitates scanning). */ DICOMTagList GetTagsOfInterest() override; /** \brief Whether or not groups should be checked for consecutive tag values. When this flag is set (default in constructor=off), the sorter will not only sort in a way that the values of a configured tag are ascending BUT in addition the sorter will enforce a constant numerical distance between values. Having this flag is useful for handling of series with missing slices, e.g. Instance Numbers 1 2 3 5 6 7 8. With the flag set to true, the sorter would split this group into two, because the initial distance of 1 is not kept between Instance Numbers 3 and 5. A special case of this behavior can be configured by SetExpectDistanceOne(). When this additional flag is set to true, the sorter will expect distance 1 exactly. This can help if the second slice is missing already. Without this additional flag, we would "learn" about a wrong distance of 2 (or similar) and then sort completely wrong. */ void SetStrictSorting(bool strict); bool GetStrictSorting() const; /** \brief Flag for a special case in "strict sorting". Please see documentation of SetStrictSorting(). \sa SetStrictSorting */ void SetExpectDistanceOne(bool strict); bool GetExpectDistanceOne() const; /** \brief Actually sort as described in the Detailed Description. */ void Sort() override; /** \brief Print configuration details into given stream. */ void PrintConfiguration(std::ostream& os, const std::string& indent = "") const override; bool operator==(const DICOMDatasetSorter& other) const override; static bool GetDefaultStrictSorting() { return m_DefaultStrictSorting; } static bool GetDefaultExpectDistanceOne() { return m_DefaultExpectDistanceOne; } protected: /** \brief Helper struct to feed into std::sort, configured via DICOMSortCriterion. */ struct ParameterizedDatasetSort { ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); DICOMSortCriterion::ConstPointer m_SortCriterion; }; DICOMTagBasedSorter(); ~DICOMTagBasedSorter() override; DICOMTagBasedSorter(const DICOMTagBasedSorter& other); DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); /** \brief Helper for SplitInputGroups(). */ std::string BuildGroupID( DICOMDatasetAccess* dataset ); - typedef std::map GroupIDToListType; + using GroupIDToListType = std::map; + + using SplitReasonListType = std::map; /** \brief Implements the "distiguishing tags". To sort datasets into different groups, a long string will be built for each dataset. The string concatenates all tags and their respective values. Datasets that match in all values will end up with the same string. + @param splitReasons Reference to the split reasons vector. It will be also updated by the method to reflect the reasons for the returned groups. */ - GroupIDToListType SplitInputGroups(); + GroupIDToListType SplitInputGroups(SplitReasonListType& splitReasons); /** \brief Implements the sorting step. Relatively simple implementation thanks to std::sort and a parameterization via DICOMSortCriterion. + @param splitReasons Reference to the split reasons vector. It will be also updated by the method to reflect the reasons for the returned groups. */ - GroupIDToListType& SortGroups(GroupIDToListType& groups); + GroupIDToListType& SortGroups(GroupIDToListType& groups, SplitReasonListType& splitReasons); DICOMTagList m_DistinguishingTags; typedef std::map TagValueProcessorMap; TagValueProcessorMap m_TagValueProcessor; DICOMSortCriterion::ConstPointer m_SortCriterion; bool m_StrictSorting; bool m_ExpectDistanceOne; const static bool m_DefaultStrictSorting = false; const static bool m_DefaultExpectDistanceOne = false; }; } #endif diff --git a/Modules/DICOM/include/mitkEquiDistantBlocksSorter.h b/Modules/DICOM/include/mitkEquiDistantBlocksSorter.h index 26e1b0da35..871d0e8341 100644 --- a/Modules/DICOM/include/mitkEquiDistantBlocksSorter.h +++ b/Modules/DICOM/include/mitkEquiDistantBlocksSorter.h @@ -1,211 +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. ============================================================================*/ #ifndef mitkEquiDistantBlocksSorter_h #define mitkEquiDistantBlocksSorter_h #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMSortCriterion.h" #include "mitkGantryTiltInformation.h" #include "mitkVector.h" namespace mitk { /** \ingroup DICOMModule \brief Split inputs into blocks of equidistant slices (for use in DICOMITKSeriesGDCMReader). Since inter-slice distance is not recorded in DICOM tags, we must ensure that blocks are made up of slices that have equal distances between neighboring slices. This is especially necessary because itk::ImageSeriesReader is later used for the actual loading, and this class expects (and does nocht verify) equal inter-slice distance (see \ref DICOMITKSeriesGDCMReader_ForcedConfiguration). To achieve such grouping, the inter-slice distance is calculated from the first two different slice positions of a block. Following slices are added to a block as long as they can be added by adding the calculated inter-slice distance to the last slice of the block. Slices that do not fit into the expected distance pattern, are set aside for further analysis. This grouping is done until each file has been assigned to a group. Slices that share a position in space are also sorted into separate blocks during this step. So the result of this step is a set of blocks that contain only slices with equal z spacing and unique slices at each position. During sorting, the origins (documented in tag image position patient) are compared against expected origins (from former origin plus moving direction). As there will be minor differences in numbers (from both calculations and imprecise tag values), we must be a bit tolerant here. The default behavior is to expect that an origin is not further away from the expected position than 30% of the inter-slice distance. To support a legacy behavior of a former loader (DicomSeriesReader), this default can be restricted to a constant number of millimeters by calling SetToleratedOriginOffset(mm). + REMARK: The EquiDistantBlocksSorter assumes that the order of the provided input + is sorted by image position like it is preferred by the reader and does not sort + it again. This assumption can lead to splittings even for complete volumes if + the input is not sorted by image position. The reason is that gaps are detected + because the next slice does not have the assumed distance and will be sorted out. + Detailed implementation in AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). */ class MITKDICOM_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter { public: mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter ); itkNewMacro( EquiDistantBlocksSorter ); DICOMTagList GetTagsOfInterest() override; /** \brief Delegates work to AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). AnalyzeFileForITKImageSeriesReaderSpacingAssumption() is called until it does not create multiple blocks anymore. */ void Sort() override; /** \brief Whether or not to accept images from a tilted acquisition in a single output group. */ void SetAcceptTilt(bool accept); bool GetAcceptTilt() const; /** \brief See class description and SetToleratedOriginOffset(). */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3); /** \brief See class description and SetToleratedOriginOffsetToAdaptive(). Default value of 0.005 is calculated so that we get a maximum of 1/10mm error when having a measurement crosses 20 slices in z direction (too strict? we don't know better..). */ void SetToleratedOriginOffset(double millimeters = 0.005); double GetToleratedOriginOffset() const; bool IsToleratedOriginOffsetAbsolute() const; void SetAcceptTwoSlicesGroups(bool accept); bool GetAcceptTwoSlicesGroups() const; void PrintConfiguration(std::ostream& os, const std::string& indent = "") const override; bool operator==(const DICOMDatasetSorter& other) const override; protected: /** \brief Return type of AnalyzeFileForITKImageSeriesReaderSpacingAssumption(). Class contains the grouping result of method AnalyzeFileForITKImageSeriesReaderSpacingAssumption(), which takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. The result contains of two blocks: a first one is the grouping result, all of those images can be loaded into one image block because they have an equal origin-to-origin distance without any gaps in-between. */ class SliceGroupingAnalysisResult { public: SliceGroupingAnalysisResult(); /** \brief Grouping result, all same origin-to-origin distance w/o gaps. */ - DICOMDatasetList GetBlockDatasets(); + const DICOMDatasetList& GetBlockDatasets() const; void SetFirstFilenameOfBlock(const std::string& filename); std::string GetFirstFilenameOfBlock() const; void SetLastFilenameOfBlock(const std::string& filename); std::string GetLastFilenameOfBlock() const; /** \brief Remaining files, which could not be grouped. */ - DICOMDatasetList GetUnsortedDatasets(); + const DICOMDatasetList& GetUnsortedDatasets() const; + + IOVolumeSplitReason::ConstPointer GetSplitReason() const; + IOVolumeSplitReason::Pointer GetSplitReason(); /** \brief Whether or not the grouped result contain a gantry tilt. */ bool ContainsGantryTilt(); /** \brief Detailed description of gantry tilt. */ const GantryTiltInformation& GetTiltInfo() const; /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToSortedBlock(DICOMDatasetAccess* dataset); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. */ void AddFileToUnsortedBlock(DICOMDatasetAccess* dataset); void AddFilesToUnsortedBlock(const DICOMDatasetList& datasets); /** \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. \todo Could make sense to enhance this with an instance of GantryTiltInformation to store the whole result! */ void FlagGantryTilt(const GantryTiltInformation& tiltInfo); /** \brief Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption. */ void UndoPrematureGrouping(); protected: DICOMDatasetList m_GroupedFiles; DICOMDatasetList m_UnsortedFiles; + IOVolumeSplitReason::Pointer m_SplitReason; + GantryTiltInformation m_TiltInfo; std::string m_FirstFilenameOfBlock; std::string m_LastFilenameOfBlock; }; /** \brief Ensure an equal z-spacing for a group of files. Takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. Internally used by GetSeries. Returns two lists: the first one contains slices of equal inter-slice spacing. The second list contains remaining files, which need to be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption again. Relevant code that is matched here is in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) */ - SliceGroupingAnalysisResult + std::shared_ptr AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const DICOMDatasetList& files, bool groupsOfSimilarImages); /** \brief Safely convert const char* to std::string. */ std::string ConstCharStarToString(const char* s); EquiDistantBlocksSorter(); ~EquiDistantBlocksSorter() override; EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other); EquiDistantBlocksSorter& operator=(const EquiDistantBlocksSorter& other); bool m_AcceptTilt; - typedef std::vector ResultsList; + typedef std::vector > ResultsList; ResultsList m_SliceGroupingResults; double m_ToleratedOriginOffset; bool m_ToleratedOriginOffsetIsAbsolute; bool m_AcceptTwoSlicesGroups; }; } #endif diff --git a/Modules/DICOM/src/mitkDICOMDatasetSorter.cpp b/Modules/DICOM/src/mitkDICOMDatasetSorter.cpp index d6af270fe5..957e913ed8 100644 --- a/Modules/DICOM/src/mitkDICOMDatasetSorter.cpp +++ b/Modules/DICOM/src/mitkDICOMDatasetSorter.cpp @@ -1,117 +1,134 @@ /*============================================================================ 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 "mitkDICOMDatasetSorter.h" mitk::DICOMDatasetSorter ::DICOMDatasetSorter() :itk::LightObject() { } mitk::DICOMDatasetSorter ::~DICOMDatasetSorter() { } mitk::DICOMDatasetSorter ::DICOMDatasetSorter(const DICOMDatasetSorter& other ) :itk::LightObject() ,m_Outputs( other.m_Outputs ) { } mitk::DICOMDatasetSorter& mitk::DICOMDatasetSorter ::operator=(const DICOMDatasetSorter& other) { if (this != &other) { m_Input = other.m_Input; m_Outputs = other.m_Outputs; } return *this; } void mitk::DICOMDatasetSorter ::SetInput(DICOMDatasetList datasets) { m_Input = datasets; } const mitk::DICOMDatasetList& mitk::DICOMDatasetSorter ::GetInput() const { return m_Input; } unsigned int mitk::DICOMDatasetSorter ::GetNumberOfOutputs() const { return m_Outputs.size(); } void mitk::DICOMDatasetSorter ::ClearOutputs() { m_Outputs.clear(); + m_SplitReasons.clear(); } void mitk::DICOMDatasetSorter ::SetNumberOfOutputs(unsigned int numberOfOutputs) { m_Outputs.resize(numberOfOutputs); + m_SplitReasons.resize(numberOfOutputs); } void mitk::DICOMDatasetSorter -::SetOutput(unsigned int index, const DICOMDatasetList& output) +::SetOutput(unsigned int index, const DICOMDatasetList& output, IOVolumeSplitReason::ConstPointer splitReason) { if (index < m_Outputs.size()) { m_Outputs[index] = output; + m_SplitReasons[index] = (nullptr == splitReason) ? IOVolumeSplitReason::New() : splitReason->Clone(); } else { std::stringstream ss; - ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + ss << "Cannot get output. Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } const mitk::DICOMDatasetList& mitk::DICOMDatasetSorter ::GetOutput(unsigned int index) const { return const_cast(this)->GetOutput(index); } mitk::DICOMDatasetList& mitk::DICOMDatasetSorter ::GetOutput(unsigned int index) { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; - ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + ss << "Cannot get output. Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } + +mitk::IOVolumeSplitReason::ConstPointer +mitk::DICOMDatasetSorter +::GetSplitReason(unsigned int index) const +{ + if (index >= m_Outputs.size()) + { + std::stringstream ss; + ss << "Cannot get split reason. Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument(ss.str()); + } + + return m_SplitReasons[index]; +} diff --git a/Modules/DICOM/src/mitkDICOMGDCMImageFrameInfo.cpp b/Modules/DICOM/src/mitkDICOMGDCMImageFrameInfo.cpp index 0d1e7bc67e..3eafed8c32 100644 --- a/Modules/DICOM/src/mitkDICOMGDCMImageFrameInfo.cpp +++ b/Modules/DICOM/src/mitkDICOMGDCMImageFrameInfo.cpp @@ -1,109 +1,89 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDICOMGDCMImageFrameInfo.h" mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const std::string& filename, unsigned int frameNo) :DICOMDatasetAccessingImageFrameInfo(filename, frameNo) ,m_TagForValue() { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo) :DICOMDatasetAccessingImageFrameInfo(frameinfo->Filename, frameinfo->FrameNo) ,m_TagForValue() { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping) :DICOMDatasetAccessingImageFrameInfo(frameinfo->Filename, frameinfo->FrameNo) ,m_TagForValue(tagToValueMapping) { } mitk::DICOMGDCMImageFrameInfo:: ~DICOMGDCMImageFrameInfo() { } mitk::DICOMDatasetFinding mitk::DICOMGDCMImageFrameInfo ::GetTagValueAsString(const DICOMTag& tag) const { const auto mappedValue = m_TagForValue.find( gdcm::Tag(tag.GetGroup(), tag.GetElement()) ); DICOMDatasetFinding result; if (mappedValue != m_TagForValue.cend()) { result.isValid = true; if (mappedValue->second != nullptr) { std::string s(mappedValue->second); try { result.value = s.erase(s.find_last_not_of(" \n\r\t")+1); } catch(...) { result.value = s; } } else { result.value = ""; } } - else - { - const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) - const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation - if (tag == tagImagePositionPatient) - { - result.isValid = true; - result.value = std::string("0\\0\\0"); - } - else if (tag == tagImageOrientation) - { - result.isValid = true; - result.value = std::string("1\\0\\0\\0\\1\\0"); - } - else - { - result.isValid = false; - result.value = ""; - } - } return result; } mitk::DICOMDatasetAccess::FindingsListType mitk::DICOMGDCMImageFrameInfo::GetTagValueAsString(const DICOMTagPath& path) const { FindingsListType result; if (path.Size() == 1 && path.IsExplicit()) { result.push_back(this->GetTagValueAsString(path.GetFirstNode().tag)); } return result; } std::string mitk::DICOMGDCMImageFrameInfo ::GetFilenameIfAvailable() const { return this->Filename; } diff --git a/Modules/DICOM/src/mitkDICOMIOMetaInformationPropertyConstants.cpp b/Modules/DICOM/src/mitkDICOMIOMetaInformationPropertyConstants.cpp index beecb531ed..b9fc0b6481 100644 --- a/Modules/DICOM/src/mitkDICOMIOMetaInformationPropertyConstants.cpp +++ b/Modules/DICOM/src/mitkDICOMIOMetaInformationPropertyConstants.cpp @@ -1,67 +1,68 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDICOMIOMetaInformationPropertyConstants.h" namespace mitk { PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_FILES() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "files" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION_STRING() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "PixelSpacingInterpretationString" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "PixelSpacingInterpretation" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL_STRING() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "ReaderImplementationLevelString" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "ReaderImplementationLevel" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_GANTRY_TILT_CORRECTED() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "GantyTiltCorrected" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_3D_plus_t() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "3D+t" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_GDCM() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "gdcm" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_DCMTK() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "dcmtk" }); } PropertyKeyPath DICOMIOMetaInformationPropertyConstants::READER_CONFIGURATION() { return PropertyKeyPath({ "MITK", "IO", "reader", "DICOM", "configuration" }); } + } diff --git a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp index a24947359c..98808873f0 100644 --- a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,628 +1,634 @@ /*============================================================================ 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 #define ENABLE_TIMING #include #include #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMGDCMTagScanner.h" std::mutex mitk::DICOMITKSeriesGDCMReader::s_LocaleMutex; mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( unsigned int decimalPlacesForOrientation, bool simpleVolumeImport ) : DICOMFileReader() , m_FixTiltByShearing(m_DefaultFixTiltByShearing) , m_SimpleVolumeReading( simpleVolumeImport ) , m_DecimalPlacesForOrientation( decimalPlacesForOrientation ) , m_ExternalCache(false) { this->EnsureMandatorySortersArePresent( decimalPlacesForOrientation, simpleVolumeImport ); } mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( const DICOMITKSeriesGDCMReader& other ) : DICOMFileReader( other ) , m_FixTiltByShearing( other.m_FixTiltByShearing) +, m_SimpleVolumeReading( other.m_SimpleVolumeReading) , m_SortingResultInProgress( other.m_SortingResultInProgress ) , m_Sorter( other.m_Sorter ) , m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) , m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) , m_ReplacedCLocales( other.m_ReplacedCLocales ) , m_ReplacedCinLocales( other.m_ReplacedCinLocales ) , m_DecimalPlacesForOrientation( other.m_DecimalPlacesForOrientation ) , m_TagCache( other.m_TagCache ) , m_ExternalCache(other.m_ExternalCache) { } mitk::DICOMITKSeriesGDCMReader::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader:: operator=( const DICOMITKSeriesGDCMReader& other ) { if ( this != &other ) { DICOMFileReader::operator =( other ); this->m_FixTiltByShearing = other.m_FixTiltByShearing; + this->m_SimpleVolumeReading = other.m_SimpleVolumeReading; this->m_SortingResultInProgress = other.m_SortingResultInProgress; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); this->m_ReplacedCLocales = other.m_ReplacedCLocales; this->m_ReplacedCinLocales = other.m_ReplacedCinLocales; this->m_DecimalPlacesForOrientation = other.m_DecimalPlacesForOrientation; this->m_TagCache = other.m_TagCache; } return *this; } bool mitk::DICOMITKSeriesGDCMReader::operator==( const DICOMFileReader& other ) const { if ( const auto* otherSelf = dynamic_cast( &other ) ) { if ( this->m_FixTiltByShearing == otherSelf->m_FixTiltByShearing && *( this->m_EquiDistantBlocksSorter ) == *( otherSelf->m_EquiDistantBlocksSorter ) && ( fabs( this->m_DecimalPlacesForOrientation - otherSelf->m_DecimalPlacesForOrientation ) < eps ) ) { // test sorters for equality if ( this->m_Sorter.size() != otherSelf->m_Sorter.size() ) return false; auto mySorterIter = this->m_Sorter.cbegin(); auto oSorterIter = otherSelf->m_Sorter.cbegin(); for ( ; mySorterIter != this->m_Sorter.cend() && oSorterIter != otherSelf->m_Sorter.cend(); ++mySorterIter, ++oSorterIter ) { if ( !( **mySorterIter == **oSorterIter ) ) return false; // this sorter differs } // nothing differs ==> all is equal return true; } else { return false; } } else { return false; } } void mitk::DICOMITKSeriesGDCMReader::SetFixTiltByShearing( bool on ) { this->Modified(); m_FixTiltByShearing = on; } bool mitk::DICOMITKSeriesGDCMReader::GetFixTiltByShearing() const { return m_FixTiltByShearing; } void mitk::DICOMITKSeriesGDCMReader::SetAcceptTwoSlicesGroups( bool accept ) const { this->Modified(); m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups( accept ); } bool mitk::DICOMITKSeriesGDCMReader::GetAcceptTwoSlicesGroups() const { return m_EquiDistantBlocksSorter->GetAcceptTwoSlicesGroups(); } void mitk::DICOMITKSeriesGDCMReader::InternalPrintConfiguration( std::ostream& os ) const { unsigned int sortIndex( 1 ); for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sortIndex, ++sorterIter ) { os << "Sorting step " << sortIndex << ":" << std::endl; ( *sorterIter )->PrintConfiguration( os, " " ); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration( os, " " ); } std::string mitk::DICOMITKSeriesGDCMReader::GetActiveLocale() { return setlocale( LC_NUMERIC, nullptr ); } void mitk::DICOMITKSeriesGDCMReader::PushLocale() const { s_LocaleMutex.lock(); std::string currentCLocale = setlocale( LC_NUMERIC, nullptr ); m_ReplacedCLocales.push( currentCLocale ); setlocale( LC_NUMERIC, "C" ); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue( l ); s_LocaleMutex.unlock(); } void mitk::DICOMITKSeriesGDCMReader::PopLocale() const { s_LocaleMutex.lock(); if ( !m_ReplacedCLocales.empty() ) { setlocale( LC_NUMERIC, m_ReplacedCLocales.top().c_str() ); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if ( !m_ReplacedCinLocales.empty() ) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } s_LocaleMutex.unlock(); } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::Condense3DBlocks( SortingBlockList& input ) { return input; // to be implemented differently by sub-classes } #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) #define timeStart( part ) timer.Start( part ); #define timeStop( part ) timer.Stop( part ); #else #define timeStart( part ) #define timeStop( part ) #endif void mitk::DICOMITKSeriesGDCMReader::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timeStart( "Reset" ); this->ClearOutputs(); timeStop( "Reset" ); // prepare initial sorting (== list of input files) const StringList inputFilenames = this->GetInputFiles(); timeStart( "Check input for DCM" ); if ( inputFilenames.empty() || !this->CanHandleFile( inputFilenames.front() ) // first || !this->CanHandleFile( inputFilenames.back() ) // last || !this->CanHandleFile( inputFilenames[inputFilenames.size() / 2] ) // roughly central file ) { // TODO a read-as-many-as-possible fallback could be implemented here MITK_DEBUG << "Reader unable to process files.."; return; } timeStop( "Check input for DCM" ); // scan files for sorting-relevant tags if ( m_TagCache.IsNull() || ( m_TagCache->GetMTime()GetMTime() && !m_ExternalCache )) { timeStart( "Tag scanning" ); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); filescanner->SetInputFiles( inputFilenames ); filescanner->AddTagPaths( this->GetTagsOfInterest() ); PushLocale(); filescanner->Scan(); PopLocale(); m_TagCache = filescanner->GetScanCache(); // keep alive and make accessible to sub-classes timeStop("Tag scanning"); } else { // ensure that the tag cache contains our required tags AND files and has scanned! } m_SortingResultInProgress.clear(); - m_SortingResultInProgress.push_back(m_TagCache->GetFrameInfoList()); + m_SortingResultInProgress.emplace_back(m_TagCache->GetFrameInfoList(), IOVolumeSplitReason::New()); // sort and split blocks as configured timeStart( "Sorting frames" ); unsigned int sorterIndex = 0; for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIndex, ++sorterIter ) { std::stringstream ss; ss << "Sorting step " << sorterIndex; timeStart( ss.str().c_str() ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex, *sorterIter, m_SortingResultInProgress ); timeStop( ss.str().c_str() ); } if ( !m_SimpleVolumeReading ) { // a last extra-sorting step: ensure equidistant slices timeStart( "EquiDistantBlocksSorter" ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress ); timeStop( "EquiDistantBlocksSorter" ); } timeStop( "Sorting frames" ); timeStart( "Condensing 3D blocks" ); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timeStop( "Condensing 3D blocks" ); // provide final result as output timeStart( "Output" ); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for ( auto blockIter = m_SortingResultInProgress.cbegin(); blockIter != m_SortingResultInProgress.cend(); ++o, ++blockIter ) { - const DICOMDatasetAccessingImageFrameList& gdcmFrameInfoList = *blockIter; + const auto& gdcmFrameInfoList = blockIter->first; + auto& splitReason = blockIter->second; + assert( !gdcmFrameInfoList.empty() ); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput( 0 ) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert( !frameList.empty() ); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because // SetImageFrameList will trigger reading of lots of interesting // tags! block.SetAdditionalTagsOfInterest( GetAdditionalTagsOfInterest() ); block.SetTagLookupTableToPropertyFunctor( GetTagLookupTableToPropertyFunctor() ); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); + block.SetSplitReason(splitReason); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel( block.GetSOPClassUID() ) ); this->SetOutput( o, block ); } timeStop( "Output" ); #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; #endif } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input ) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex << " '"; #if defined( MBILOG_ENABLE_DEBUG ) sorter->PrintConfiguration( ss ); #endif ss << "'"; nextStepSorting.clear(); MITK_DEBUG << "================================================================================"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << input.size() << " groups input"; #if defined( MBILOG_ENABLE_DEBUG ) unsigned int groupIndex = 0; #endif for ( auto blockIter = input.cbegin(); blockIter != input.cend(); #if defined( MBILOG_ENABLE_DEBUG ) ++groupIndex, #endif ++blockIter ) { - const DICOMDatasetAccessingImageFrameList& gdcmInfoFrameList = *blockIter; + const auto& [gdcmInfoFrameList, inputSplitReason] = *blockIter; const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmInfoFrameList ); #if defined( MBILOG_ENABLE_DEBUG ) MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for ( auto oi = datasetList.cbegin(); oi != datasetList.cend(); ++oi ) { MITK_DEBUG << " INPUT : " << ( *oi )->GetFilenameIfAvailable(); } #endif sorter->SetInput( datasetList ); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for ( unsigned int b = 0; b < numberOfResultingBlocks; ++b ) { const DICOMDatasetList blockResult = sorter->GetOutput( b ); for ( auto oi = blockResult.cbegin(); oi != blockResult.cend(); ++oi ) { MITK_DEBUG << " OUTPUT(" << b << ") :" << ( *oi )->GetFilenameIfAvailable(); } DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( blockResult ); - nextStepSorting.push_back( sortedGdcmInfoFrameList ); + + nextStepSorting.emplace_back(sortedGdcmInfoFrameList, inputSplitReason->ExtendReason(sorter->GetSplitReason(b)) ); } } return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader::GetReaderImplementationLevel( const std::string sopClassUID ) { if ( sopClassUID.empty() ) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSName gdcmType = static_cast((gdcm::UIDs::TSType)uidKnowledge); switch ( gdcmType ) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for ( unsigned int o = 0; o < numberOfOutputs; ++o ) { success &= this->LoadMitkImageForOutput( o ); } return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor( DICOMImageBlockDescriptor& block ) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); ITKDICOMSeriesReaderHelper::StringContainer filenames; filenames.reserve( frames.size() ); for ( auto frameIter = frames.cbegin(); frameIter != frames.cend(); ++frameIter ) { filenames.push_back( ( *frameIter )->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; bool success( true ); try { mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); } catch ( const std::exception& e ) { success = false; MITK_ERROR << "Exception during image loading: " << e.what(); } PopLocale(); return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForOutput( unsigned int o ) { DICOMImageBlockDescriptor& block = this->InternalGetOutput( o ); return this->LoadMitkImageForImageBlockDescriptor( block ); } bool mitk::DICOMITKSeriesGDCMReader::CanHandleFile( const std::string& filename ) { return ITKDICOMSeriesReaderHelper::CanHandleFile( filename ); } void mitk::DICOMITKSeriesGDCMReader::AddSortingElement( DICOMDatasetSorter* sorter, bool atFront ) { assert( sorter ); if ( atFront ) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } this->Modified(); } mitk::DICOMITKSeriesGDCMReader::ConstSorterList mitk::DICOMITKSeriesGDCMReader::GetFreelyConfiguredSortingElements() const { std::list result; unsigned int sortIndex( 0 ); for ( auto sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter ) { if ( sortIndex > 0 ) // ignore first element (see EnsureMandatorySortersArePresent) { result.push_back( ( *sorterIter ).GetPointer() ); } } return result; } void mitk::DICOMITKSeriesGDCMReader::EnsureMandatorySortersArePresent( unsigned int decimalPlacesForOrientation, bool simpleVolumeImport ) { DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(decimalPlacesForOrientation) ); // Image Orientation (Patient) splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness if ( simpleVolumeImport ) { MITK_DEBUG << "Simple volume reading: ignoring number of frames"; } else { splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames } this->AddSortingElement( splitter, true ); // true = at front if ( m_EquiDistantBlocksSorter.IsNull() ) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if ( m_NormalDirectionConsistencySorter.IsNull() ) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffsetToAdaptive( double fractionOfInterSliceDistance ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive( fractionOfInterSliceDistance ); this->Modified(); } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffset( double millimeters ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset( millimeters ); this->Modified(); } double mitk::DICOMITKSeriesGDCMReader::GetToleratedOriginError() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->GetToleratedOriginOffset(); } bool mitk::DICOMITKSeriesGDCMReader::IsToleratedOriginOffsetAbsolute() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->IsToleratedOriginOffsetAbsolute(); } double mitk::DICOMITKSeriesGDCMReader::GetDecimalPlacesForOrientation() const { return m_DecimalPlacesForOrientation; } mitk::DICOMTagCache::Pointer mitk::DICOMITKSeriesGDCMReader::GetTagCache() const { return m_TagCache; } void mitk::DICOMITKSeriesGDCMReader::SetTagCache( const DICOMTagCache::Pointer& tagCache ) { m_TagCache = tagCache; m_ExternalCache = tagCache.IsNotNull(); } mitk::DICOMTagPathList mitk::DICOMITKSeriesGDCMReader::GetTagsOfInterest() const { DICOMTagPathList completeList; // check all configured sorters for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIter ) { assert( sorterIter->IsNotNull() ); const DICOMTagList tags = ( *sorterIter )->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); } // check our own forced sorters DICOMTagList tags = m_EquiDistantBlocksSorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); tags = m_NormalDirectionConsistencySorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); // add the tags for DICOMImageBlockDescriptor tags = DICOMImageBlockDescriptor::GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); const AdditionalTagsMapType tagList = GetAdditionalTagsOfInterest(); for ( auto iter = tagList.cbegin(); iter != tagList.cend(); ++iter ) { completeList.push_back( iter->first ) ; } return completeList; } diff --git a/Modules/DICOM/src/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOM/src/mitkDICOMImageBlockDescriptor.cpp index 334d8da6fb..de4866f36c 100644 --- a/Modules/DICOM/src/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOM/src/mitkDICOMImageBlockDescriptor.cpp @@ -1,912 +1,940 @@ /*============================================================================ 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 "mitkDICOMImageBlockDescriptor.h" #include "mitkStringProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkPropertyKeyPath.h" #include "mitkDICOMIOMetaInformationPropertyConstants.h" +#include "mitkIOMetaInformationPropertyConstants.h" #include #include #include #include mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor() : m_ReaderImplementationLevel( SOPClassUnknown ) , m_PropertyList( PropertyList::New() ) +, m_SplitReason(IOVolumeSplitReason::New()) , m_TagCache( nullptr ) , m_PropertiesOutOfDate( true ) { m_PropertyFunctor = &mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues; } mitk::DICOMImageBlockDescriptor::~DICOMImageBlockDescriptor() { } mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor( const DICOMImageBlockDescriptor& other ) : m_ImageFrameList( other.m_ImageFrameList ) , m_MitkImage( other.m_MitkImage ) , m_SliceIsLoaded( other.m_SliceIsLoaded ) , m_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) , m_TiltInformation( other.m_TiltInformation ) , m_PropertyList( other.m_PropertyList->Clone() ) +, m_SplitReason( other.m_SplitReason->Clone() ) , m_TagCache( other.m_TagCache ) , m_PropertiesOutOfDate( other.m_PropertiesOutOfDate ) , m_AdditionalTagMap(other.m_AdditionalTagMap) , m_FoundAdditionalTags(other.m_FoundAdditionalTags) , m_PropertyFunctor(other.m_PropertyFunctor) { if ( m_MitkImage ) { m_MitkImage = m_MitkImage->Clone(); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMImageBlockDescriptor:: operator=( const DICOMImageBlockDescriptor& other ) { if ( this != &other ) { m_ImageFrameList = other.m_ImageFrameList; m_MitkImage = other.m_MitkImage; m_SliceIsLoaded = other.m_SliceIsLoaded; m_ReaderImplementationLevel = other.m_ReaderImplementationLevel; m_TiltInformation = other.m_TiltInformation; m_AdditionalTagMap = other.m_AdditionalTagMap; m_FoundAdditionalTags = other.m_FoundAdditionalTags; m_PropertyFunctor = other.m_PropertyFunctor; if ( other.m_PropertyList ) { m_PropertyList = other.m_PropertyList->Clone(); } + if (other.m_SplitReason) + { + m_SplitReason = other.m_SplitReason->Clone(); + } + if ( other.m_MitkImage ) { m_MitkImage = other.m_MitkImage->Clone(); } m_TagCache = other.m_TagCache; m_PropertiesOutOfDate = other.m_PropertiesOutOfDate; } return *this; } mitk::DICOMTagList mitk::DICOMImageBlockDescriptor::GetTagsOfInterest() { DICOMTagList completeList; completeList.push_back( DICOMTag( 0x0018, 0x1164 ) ); // pixel spacing completeList.push_back( DICOMTag( 0x0028, 0x0030 ) ); // imager pixel spacing completeList.push_back( DICOMTag( 0x0008, 0x0018 ) ); // sop instance UID completeList.push_back( DICOMTag( 0x0008, 0x0016 ) ); // sop class UID completeList.push_back( DICOMTag( 0x0020, 0x0011 ) ); // series number completeList.push_back( DICOMTag( 0x0008, 0x1030 ) ); // study description completeList.push_back( DICOMTag( 0x0008, 0x103e ) ); // series description completeList.push_back( DICOMTag( 0x0008, 0x0060 ) ); // modality completeList.push_back( DICOMTag( 0x0018, 0x0024 ) ); // sequence name completeList.push_back( DICOMTag( 0x0020, 0x0037 ) ); // image orientation completeList.push_back( DICOMTag( 0x0020, 0x1041 ) ); // slice location completeList.push_back( DICOMTag( 0x0020, 0x0012 ) ); // acquisition number completeList.push_back( DICOMTag( 0x0020, 0x0013 ) ); // instance number completeList.push_back( DICOMTag( 0x0020, 0x0032 ) ); // image position patient completeList.push_back( DICOMTag( 0x0028, 0x1050 ) ); // window center completeList.push_back( DICOMTag( 0x0028, 0x1051 ) ); // window width completeList.push_back( DICOMTag( 0x0008, 0x0008 ) ); // image type completeList.push_back( DICOMTag( 0x0028, 0x0004 ) ); // photometric interpretation return completeList; } void mitk::DICOMImageBlockDescriptor::SetAdditionalTagsOfInterest( const AdditionalTagsMapType& tagMap) { m_AdditionalTagMap = tagMap; } void mitk::DICOMImageBlockDescriptor::SetTiltInformation( const GantryTiltInformation& info ) { m_TiltInformation = info; } const mitk::GantryTiltInformation mitk::DICOMImageBlockDescriptor::GetTiltInformation() const { return m_TiltInformation; } void mitk::DICOMImageBlockDescriptor::SetImageFrameList( const DICOMImageFrameList& framelist ) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize( framelist.size() ); m_SliceIsLoaded.assign( framelist.size(), false ); m_PropertiesOutOfDate = true; } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor::SetMitkImage( Image::Pointer image ) { if ( m_MitkImage != image ) { if ( m_TagCache.IsExpired() ) { MITK_ERROR << "Unable to describe MITK image with properties without a tag-cache object!"; m_MitkImage = nullptr; return; } if ( m_ImageFrameList.empty() ) { MITK_ERROR << "Unable to describe MITK image with properties without a frame list!"; m_MitkImage = nullptr; return; } // Should verify that the image matches m_ImageFrameList and m_TagCache // however, this is hard to do without re-analyzing all // TODO we should at least make sure that the number of frames is identical (plus rows/columns, // orientation) // without gantry tilt correction, we can also check image origin m_MitkImage = this->DescribeImageWithProperties( this->FixupSpacing( image ) ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::GetMitkImage() const { return m_MitkImage; } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::FixupSpacing( Image* mitkImage ) { if ( mitkImage ) { Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); ScalarType desiredSpacingX = imageSpacing[0]; ScalarType desiredSpacingY = imageSpacing[1]; this->GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing if ( desiredSpacingX <= 0 || desiredSpacingY <= 0 ) { return mitkImage; } MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; mitkImage->GetGeometry()->SetSpacing( imageSpacing ); } return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetSliceIsLoaded( unsigned int index, bool isLoaded ) { if ( index < m_SliceIsLoaded.size() ) { m_SliceIsLoaded[index] = isLoaded; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::IsSliceLoaded( unsigned int index ) const { if ( index < m_SliceIsLoaded.size() ) { return m_SliceIsLoaded[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::AllSlicesAreLoaded() const { bool allLoaded = true; for ( auto iter = m_SliceIsLoaded.cbegin(); iter != m_SliceIsLoaded.cend(); ++iter ) { allLoaded &= *iter; } return allLoaded; } /* PS defined IPS defined PS==IPS 0 0 --> UNKNOWN spacing, loader will invent 0 1 --> spacing as at detector surface 1 0 --> spacing as in patient 1 1 0 --> detector surface spacing CORRECTED for geometrical magnifications: spacing as in patient 1 1 1 --> detector surface spacing NOT corrected for geometrical magnifications: spacing as at detector */ mitk::PixelSpacingInterpretation mitk::DICOMImageBlockDescriptor::GetPixelSpacingInterpretation() const { if ( m_ImageFrameList.empty() || m_TagCache.IsExpired() ) { MITK_ERROR << "Invalid call to GetPixelSpacingInterpretation. Need to have initialized tag-cache!"; return SpacingUnknown; } const std::string pixelSpacing = this->GetPixelSpacing(); const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); if ( pixelSpacing.empty() ) { if ( imagerPixelSpacing.empty() ) { return SpacingUnknown; } else { return SpacingAtDetector; } } else // Pixel Spacing defined { if ( imagerPixelSpacing.empty() ) { return SpacingInPatient; } else if ( pixelSpacing != imagerPixelSpacing ) { return SpacingInPatient; } else { return SpacingAtDetector; } } } std::string mitk::DICOMImageBlockDescriptor::GetPixelSpacing() const { auto tagCache = m_TagCache.Lock(); if ( m_ImageFrameList.empty() || tagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagPixelSpacing( 0x0028, 0x0030 ); return tagCache->GetTagValue( m_ImageFrameList.front(), tagPixelSpacing ).value; } std::string mitk::DICOMImageBlockDescriptor::GetImagerPixelSpacing() const { auto tagCache = m_TagCache.Lock(); if ( m_ImageFrameList.empty() || tagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetImagerPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagImagerPixelSpacing( 0x0018, 0x1164 ); return tagCache->GetTagValue( m_ImageFrameList.front(), tagImagerPixelSpacing ).value; } void mitk::DICOMImageBlockDescriptor::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY ) const { const std::string pixelSpacing = this->GetPixelSpacing(); // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( pixelSpacing, spacingX, spacingY ) ) { const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); // fallback to "on detector" spacing if ( !DICOMStringToSpacing( imagerPixelSpacing, spacingX, spacingY ) ) { // at this point we have no hints whether the spacing is correct // do a quick sanity check and either trust in the input or set both to 1 // We assume neither spacing to be negative, zero or unexpectedly large for // medical images if (spacingX < mitk::eps || spacingX > 1000 || spacingY < mitk::eps || spacingY > 1000) { spacingX = spacingY = 1.0; } } } } void mitk::DICOMImageBlockDescriptor::SetProperty( const std::string& key, BaseProperty* value ) { m_PropertyList->SetProperty( key, value ); } mitk::BaseProperty* mitk::DICOMImageBlockDescriptor::GetProperty( const std::string& key ) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetProperty( key ); } std::string mitk::DICOMImageBlockDescriptor::GetPropertyAsString( const std::string& key ) const { this->UpdateImageDescribingProperties(); const mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty( key ); if ( property.IsNotNull() ) { return property->GetValueAsString(); } else { return std::string( "" ); } } +mitk::IOVolumeSplitReason::ConstPointer mitk::DICOMImageBlockDescriptor::GetSplitReason() const +{ + return m_SplitReason; +} + +mitk::IOVolumeSplitReason::Pointer mitk::DICOMImageBlockDescriptor::GetSplitReason() +{ + return m_SplitReason; +} + +void mitk::DICOMImageBlockDescriptor::SetSplitReason(IOVolumeSplitReason::Pointer reason) +{ + m_SplitReason = reason; +} + void mitk::DICOMImageBlockDescriptor::SetFlag( const std::string& key, bool value ) { m_PropertyList->ReplaceProperty( key, BoolProperty::New( value ) ); } bool mitk::DICOMImageBlockDescriptor::GetFlag( const std::string& key, bool defaultValue ) const { this->UpdateImageDescribingProperties(); BoolProperty::ConstPointer boolProp = dynamic_cast( this->GetProperty( key ) ); if ( boolProp.IsNotNull() ) { return boolProp->GetValue(); } else { return defaultValue; } } void mitk::DICOMImageBlockDescriptor::SetIntProperty( const std::string& key, int value ) { m_PropertyList->ReplaceProperty( key, IntProperty::New( value ) ); } int mitk::DICOMImageBlockDescriptor::GetIntProperty( const std::string& key, int defaultValue ) const { this->UpdateImageDescribingProperties(); IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty( key ) ); if ( intProp.IsNotNull() ) { return intProp->GetValue(); } else { return defaultValue; } } double mitk::DICOMImageBlockDescriptor::stringtodouble( const std::string& str ) const { double d; std::string trimmedstring( str ); try { trimmedstring = trimmedstring.erase( trimmedstring.find_last_not_of( " \n\r\t" ) + 1 ); } catch ( ... ) { // no last not of } std::string firstcomponent( trimmedstring ); try { firstcomponent = trimmedstring.erase( trimmedstring.find_first_of( "\\" ) ); } catch ( ... ) { // no last not of } std::istringstream converter( firstcomponent ); if ( !firstcomponent.empty() && ( converter >> d ) && converter.eof() ) { return d; } else { throw std::invalid_argument( "Argument is not a convertible number" ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::DescribeImageWithProperties( Image* mitkImage ) { // TODO: this is a collection of properties that have been provided by the // legacy DicomSeriesReader. // We should at some point clean up this collection and name them in a more // consistent way! if ( !mitkImage ) return mitkImage; mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_FILES()), this->GetProperty("filenamesForSlices")); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION_STRING()), StringProperty::New(PixelSpacingInterpretationToString(this->GetPixelSpacingInterpretation()))); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION()), GenericProperty::New(this->GetPixelSpacingInterpretation())); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL_STRING()), StringProperty::New(ReaderImplementationLevelToString(m_ReaderImplementationLevel))); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL()), GenericProperty::New(m_ReaderImplementationLevel)); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_GANTRY_TILT_CORRECTED()), BoolProperty::New(this->GetTiltInformation().IsRegularGantryTilt())); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_3D_plus_t()), BoolProperty::New(this->GetFlag("3D+t", false))); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_GDCM()), StringProperty::New(gdcm::Version::GetVersion())); mitkImage->SetProperty(PropertyKeyPathToPropertyName(DICOMIOMetaInformationPropertyConstants::READER_DCMTK()), StringProperty::New(PACKAGE_VERSION)); + if (nullptr != m_SplitReason && m_SplitReason->HasReasons()) + { + mitkImage->SetProperty(PropertyKeyPathToPropertyName(IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON()), StringProperty::New(IOVolumeSplitReason::ToJSON(m_SplitReason).dump())); + } + // get all found additional tags of interest for (const auto &tag : m_FoundAdditionalTags) { BaseProperty* prop = this->GetProperty(tag); if (prop) { mitkImage->SetProperty(tag.c_str(), prop); } } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// //// Deprecated properties should be removed sooner then later (see above) ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // first part: add some tags that describe individual slices // these properties are defined at analysis time (see UpdateImageDescribingProperties()) const char* propertyKeySliceLocation = "dicom.image.0020.1041"; const char* propertyKeyInstanceNumber = "dicom.image.0020.0013"; const char* propertyKeySOPInstanceUID = "dicom.image.0008.0018"; mitkImage->SetProperty( propertyKeySliceLocation, this->GetProperty( "sliceLocationForSlices" ) ); mitkImage->SetProperty( propertyKeyInstanceNumber, this->GetProperty( "instanceNumberForSlices" ) ); mitkImage->SetProperty( propertyKeySOPInstanceUID, this->GetProperty( "SOPInstanceUIDForSlices" ) ); mitkImage->SetProperty( "files", this->GetProperty( "filenamesForSlices_deprecated" ) ); // second part: add properties that describe the whole image block mitkImage->SetProperty( "dicomseriesreader.SOPClassUID", StringProperty::New( this->GetSOPClassUID() ) ); mitkImage->SetProperty( "dicomseriesreader.SOPClass", StringProperty::New( this->GetSOPClassUIDAsName() ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New( this->GetPixelSpacingInterpretation() ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevelString", StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( m_ReaderImplementationLevel ) ); mitkImage->SetProperty( "dicomseriesreader.GantyTiltCorrected", BoolProperty::New( this->GetTiltInformation().IsRegularGantryTilt() ) ); mitkImage->SetProperty( "dicomseriesreader.3D+t", BoolProperty::New( this->GetFlag( "3D+t", false ) ) ); // level window const std::string windowCenter = this->GetPropertyAsString( "windowCenter" ); const std::string windowWidth = this->GetPropertyAsString( "windowWidth" ); try { const double level = stringtodouble( windowCenter ); const double window = stringtodouble( windowWidth ); mitkImage->SetProperty( "levelwindow", LevelWindowProperty::New( LevelWindow( level, window ) ) ); } catch ( ... ) { // nothing, no levelwindow to be predicted... } const std::string modality = this->GetPropertyAsString( "modality" ); mitkImage->SetProperty( "modality", StringProperty::New( modality ) ); mitkImage->SetProperty( "dicom.pixel.PhotometricInterpretation", this->GetProperty( "photometricInterpretation" ) ); mitkImage->SetProperty( "dicom.image.imagetype", this->GetProperty( "imagetype" ) ); mitkImage->SetProperty( "dicom.study.StudyDescription", this->GetProperty( "studyDescription" ) ); mitkImage->SetProperty( "dicom.series.SeriesDescription", this->GetProperty( "seriesDescription" ) ); mitkImage->SetProperty( "dicom.pixel.Rows", this->GetProperty( "rows" ) ); mitkImage->SetProperty( "dicom.pixel.Columns", this->GetProperty( "columns" ) ); // fourth part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetReaderImplementationLevel( const ReaderImplementationLevel& level ) { m_ReaderImplementationLevel = level; } mitk::ReaderImplementationLevel mitk::DICOMImageBlockDescriptor::GetReaderImplementationLevel() const { return m_ReaderImplementationLevel; } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUID() const { auto tagCache = m_TagCache.Lock(); if ( !m_ImageFrameList.empty() && tagCache.IsNotNull() ) { static const DICOMTag tagSOPClassUID( 0x0008, 0x0016 ); return tagCache->GetTagValue( m_ImageFrameList.front(), tagSOPClassUID ).value; } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUID(). Need to have initialized tag-cache!"; return std::string( "" ); } } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUIDAsName() const { if ( !m_ImageFrameList.empty() && !m_TagCache.IsExpired() ) { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( this->GetSOPClassUID().c_str() ); const char* name = uidKnowledge.GetName(); if ( name ) { return std::string( name ); } else { return std::string( "" ); } } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUIDAsName(). Need to have " "initialized tag-cache!"; return std::string( "" ); } } int mitk::DICOMImageBlockDescriptor::GetNumberOfTimeSteps() const { int result = 1; this->m_PropertyList->GetIntProperty("timesteps", result); return result; }; int mitk::DICOMImageBlockDescriptor::GetNumberOfFramesPerTimeStep() const { const int numberOfTimesteps = this->GetNumberOfTimeSteps(); int numberOfFramesPerTimestep = this->m_ImageFrameList.size() / numberOfTimesteps; assert(int(double((double)this->m_ImageFrameList.size() / (double)numberOfTimesteps)) == numberOfFramesPerTimestep); // this should hold return numberOfFramesPerTimestep; }; void mitk::DICOMImageBlockDescriptor::SetTagCache( DICOMTagCache* privateCache ) { // this must only be used during loading and never afterwards m_TagCache = privateCache; } #define printPropertyRange( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name "First" ); \ const std::string last = this->GetPropertyAsString( #property_name "Last" ); \ if ( !first.empty() || !last.empty() ) \ { \ if ( first == last ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ else \ { \ os << " " label ": '" << first << "' - '" << last << "'" << std::endl; \ } \ } \ \ } #define printProperty( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name ); \ if ( !first.empty() ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ \ } #define printBool( label, commands ) \ \ { \ os << " " label ": '" << ( commands ? "yes" : "no" ) << "'" << std::endl; \ \ } void mitk::DICOMImageBlockDescriptor::Print(std::ostream& os, bool filenameDetails) const { os << " Number of Frames: '" << m_ImageFrameList.size() << "'" << std::endl; os << " SOP class: '" << this->GetSOPClassUIDAsName() << "'" << std::endl; printProperty( "Series Number", seriesNumber ); printProperty( "Study Description", studyDescription ); printProperty( "Series Description", seriesDescription ); printProperty( "Modality", modality ); printProperty( "Sequence Name", sequenceName ); printPropertyRange( "Slice Location", sliceLocation ); printPropertyRange( "Acquisition Number", acquisitionNumber ); printPropertyRange( "Instance Number", instanceNumber ); printPropertyRange( "Image Position", imagePositionPatient ); printProperty( "Image Orientation", orientation ); os << " Pixel spacing interpretation: '" << PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) << "'" << std::endl; printBool( "Gantry Tilt", this->GetTiltInformation().IsRegularGantryTilt() ) // printBool("3D+t", this->GetFlag("3D+t",false)) // os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << // std::endl; if ( filenameDetails ) { os << " Files in this image block:" << std::endl; for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++frameIter ) { os << " " << ( *frameIter )->Filename; if ( ( *frameIter )->FrameNo > 0 ) { os << ", " << ( *frameIter )->FrameNo; } os << std::endl; } } } #define storeTagValueToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ const std::string tagValue = tagCache->GetTagValue( firstFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name, StringProperty::New( tagValue ) ); \ \ } #define storeTagValueRangeToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ const std::string tagValueFirst = tagCache->GetTagValue( firstFrame, t ).value; \ const std::string tagValueLast = tagCache->GetTagValue( lastFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name "First", StringProperty::New( tagValueFirst ) ); \ const_cast( this ) \ ->SetProperty( #tag_name "Last", StringProperty::New( tagValueLast ) ); \ \ } void mitk::DICOMImageBlockDescriptor::UpdateImageDescribingProperties() const { if ( !m_PropertiesOutOfDate ) return; if ( !m_ImageFrameList.empty() ) { auto tagCache = m_TagCache.Lock(); if (tagCache.IsNull()) { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::UpdateImageDescribingProperties(). Need to " "have initialized tag-cache!"; return; } const DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front(); const DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back(); // see macros above storeTagValueToProperty( seriesNumber, 0x0020, 0x0011 ); storeTagValueToProperty( studyDescription, 0x0008, 0x1030 ); storeTagValueToProperty( seriesDescription, 0x0008, 0x103e ); storeTagValueToProperty( modality, 0x0008, 0x0060 ); storeTagValueToProperty( sequenceName, 0x0018, 0x0024 ); storeTagValueToProperty( orientation, 0x0020, 0x0037 ); storeTagValueToProperty( rows, 0x0028, 0x0010 ); storeTagValueToProperty( columns, 0x0028, 0x0011 ); storeTagValueRangeToProperty( sliceLocation, 0x0020, 0x1041 ); storeTagValueRangeToProperty( acquisitionNumber, 0x0020, 0x0012 ); storeTagValueRangeToProperty( instanceNumber, 0x0020, 0x0013 ); storeTagValueRangeToProperty( imagePositionPatient, 0x0020, 0x0032 ); storeTagValueToProperty( windowCenter, 0x0028, 0x1050 ); storeTagValueToProperty( windowWidth, 0x0028, 0x1051 ); storeTagValueToProperty( imageType, 0x0008, 0x0008 ); storeTagValueToProperty( photometricInterpretation, 0x0028, 0x0004 ); // some per-image attributes // frames are just numbered starting from 0. timestep 1 (the second time-step) has frames starting at // (number-of-frames-per-timestep) // std::string propertyKeySliceLocation = "dicom.image.0020.1041"; // std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; // std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceUIDForSlices; StringLookupTable filenamesForSlices_deprecated; DICOMCachedValueLookupTable filenamesForSlices; const DICOMTag tagSliceLocation( 0x0020, 0x1041 ); const DICOMTag tagInstanceNumber( 0x0020, 0x0013 ); const DICOMTag tagSOPInstanceNumber( 0x0008, 0x0018 ); std::unordered_map additionalTagResultList; unsigned int slice(0); int timePoint(-1); const int framesPerTimeStep = this->GetNumberOfFramesPerTimeStep(); for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++slice, ++frameIter ) { unsigned int zSlice = slice%framesPerTimeStep; if ( zSlice == 0) { timePoint++; } const std::string sliceLocation = tagCache->GetTagValue( *frameIter, tagSliceLocation ).value; sliceLocationForSlices.SetTableValue( slice, sliceLocation ); const std::string instanceNumber = tagCache->GetTagValue( *frameIter, tagInstanceNumber ).value; instanceNumberForSlices.SetTableValue( slice, instanceNumber ); const std::string sopInstanceUID = tagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ).value; SOPInstanceUIDForSlices.SetTableValue( slice, sopInstanceUID ); const std::string filename = ( *frameIter )->Filename; filenamesForSlices_deprecated.SetTableValue( slice, filename ); filenamesForSlices.SetTableValue(slice, { static_cast(timePoint), zSlice, filename }); MITK_DEBUG << "Tag info for slice " << slice << ": SL '" << sliceLocation << "' IN '" << instanceNumber << "' SOP instance UID '" << sopInstanceUID << "'"; for (const auto& tag : m_AdditionalTagMap) { const DICOMTagCache::FindingsListType findings = tagCache->GetTagValue( *frameIter, tag.first ); for (const auto& finding : findings) { if (finding.isValid) { std::string propKey = (tag.second.empty()) ? DICOMTagPathToPropertyName(finding.path) : tag.second; DICOMCachedValueInfo info{ static_cast(timePoint), zSlice, finding.value }; additionalTagResultList[propKey].SetTableValue(slice, info); } } } } // add property or properties with proper names auto* thisInstance = const_cast( this ); thisInstance->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); thisInstance->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); thisInstance->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); thisInstance->SetProperty( "filenamesForSlices_deprecated", StringLookupTableProperty::New( filenamesForSlices_deprecated ) ); thisInstance->SetProperty("filenamesForSlices", m_PropertyFunctor(filenamesForSlices)); //add properties for additional tags of interest for ( auto iter = additionalTagResultList.cbegin(); iter != additionalTagResultList.cend(); ++iter ) { thisInstance->SetProperty( iter->first, m_PropertyFunctor( iter->second ) ); thisInstance->m_FoundAdditionalTags.insert(m_FoundAdditionalTags.cend(),iter->first); } m_PropertiesOutOfDate = false; } } mitk::BaseProperty::Pointer mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues(const DICOMCachedValueLookupTable& cacheLookupTable) { const auto& lookupTable = cacheLookupTable.GetLookupTable(); typedef std::pair PairType; if ( std::adjacent_find( lookupTable.cbegin(), lookupTable.cend(), []( const PairType& lhs, const PairType& rhs ) { return lhs.second.Value != rhs.second.Value; } ) == lookupTable.cend() ) { return static_cast( mitk::StringProperty::New(cacheLookupTable.GetTableValue(0).Value).GetPointer()); } StringLookupTable stringTable; for (const auto &element : lookupTable) { stringTable.SetTableValue(element.first, element.second.Value); } return static_cast( mitk::StringLookupTableProperty::New(stringTable).GetPointer()); } void mitk::DICOMImageBlockDescriptor::SetTagLookupTableToPropertyFunctor( TagLookupTableToPropertyFunctor functor ) { if ( functor != nullptr ) { m_PropertyFunctor = functor; } } mitk::BaseProperty::ConstPointer mitk::DICOMImageBlockDescriptor::GetConstProperty(const std::string &propertyKey, const std::string &/*contextName*/, bool /*fallBackOnDefaultContext*/) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetConstProperty(propertyKey); }; std::vector mitk::DICOMImageBlockDescriptor::GetPropertyKeys(const std::string &/*contextName*/, bool /*includeDefaultContext*/) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetPropertyKeys(); }; std::vector mitk::DICOMImageBlockDescriptor::GetPropertyContextNames() const { return std::vector(); }; diff --git a/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp index 1f93af9f62..af65bb55da 100644 --- a/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp @@ -1,601 +1,634 @@ /*============================================================================ 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 "mitkDICOMTagBasedSorter.h" #include #include mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(unsigned int precision) :m_Precision(precision) { } mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(const CutDecimalPlaces& other) :m_Precision(other.m_Precision) { } std::string mitk::DICOMTagBasedSorter::CutDecimalPlaces ::operator()(const std::string& input) const { // be a bit tolerant for tags such as image orientation orientation, let only the first few digits matter (https://phabricator.mitk.org/T12263) // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); std::stringstream ss(input); ss.str(input); ss.clear(); std::string item; double number(0); std::istringstream converter(item); while (std::getline(ss, item, '\\')) { converter.str(item); converter.clear(); if (converter >> number && converter.eof()) { // converted to double resultString << number; } else { // did not convert to double resultString << item; // just paste the unmodified string } if (!ss.eof()) { resultString << "\\"; } } return resultString.str(); } mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter::CutDecimalPlaces ::Clone() const { return new CutDecimalPlaces(*this); } unsigned int mitk::DICOMTagBasedSorter::CutDecimalPlaces ::GetPrecision() const { return m_Precision; } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() ,m_StrictSorting(m_DefaultStrictSorting) ,m_ExpectDistanceOne(m_DefaultExpectDistanceOne) { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { for(auto ti = m_TagValueProcessor.cbegin(); ti != m_TagValueProcessor.cend(); ++ti) { delete ti->second; } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) ,m_DistinguishingTags( other.m_DistinguishingTags ) ,m_SortCriterion( other.m_SortCriterion ) ,m_StrictSorting( other.m_StrictSorting ) ,m_ExpectDistanceOne( other.m_ExpectDistanceOne ) { for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); m_DistinguishingTags = other.m_DistinguishingTags; m_SortCriterion = other.m_SortCriterion; m_StrictSorting = other.m_StrictSorting; m_ExpectDistanceOne = other.m_ExpectDistanceOne; for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } return *this; } bool mitk::DICOMTagBasedSorter ::operator==(const DICOMDatasetSorter& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false; if (this->m_ExpectDistanceOne != otherSelf->m_ExpectDistanceOne) return false; bool allTagsPresentAndEqual(true); if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size()) return false; for (auto myTag = this->m_DistinguishingTags.cbegin(); myTag != this->m_DistinguishingTags.cend(); ++myTag) { allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.cbegin(), otherSelf->m_DistinguishingTags.cend(), *myTag ) != otherSelf->m_DistinguishingTags.cend()); // other contains this tags // since size is equal, we don't need to check the inverse } if (!allTagsPresentAndEqual) return false; if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull()) { return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion); } else { return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull(); } } else { return false; } } void mitk::DICOMTagBasedSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Tag based sorting " << "(strict=" << (m_StrictSorting?"true":"false") << ", expectDistanceOne=" << (m_ExpectDistanceOne?"true":"false") << "):" << std::endl; for (auto tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { os << indent << " Split on "; tagIter->Print(os); os << std::endl; } DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer(); while (crit.IsNotNull()) { os << indent << " Sort by "; crit->Print(os); os << std::endl; crit = crit->GetSecondaryCriterion(); } } void mitk::DICOMTagBasedSorter ::SetStrictSorting(bool strict) { m_StrictSorting = strict; } bool mitk::DICOMTagBasedSorter ::GetStrictSorting() const { return m_StrictSorting; } void mitk::DICOMTagBasedSorter ::SetExpectDistanceOne(bool strict) { m_ExpectDistanceOne = strict; } bool mitk::DICOMTagBasedSorter ::GetExpectDistanceOne() const { return m_ExpectDistanceOne; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; if (m_SortCriterion.IsNotNull()) { const DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.cbegin(), sortingRelevantTags.cend() ); // append } return allTags; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetDistinguishingTags() const { return m_DistinguishingTags; } const mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter ::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const { auto loc = m_TagValueProcessor.find(tag); if (loc != m_TagValueProcessor.cend()) { return loc->second; } else { return nullptr; } } void mitk::DICOMTagBasedSorter ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } mitk::DICOMSortCriterion::ConstPointer mitk::DICOMTagBasedSorter ::GetSortCriterion() const { return m_SortCriterion; } void mitk::DICOMTagBasedSorter ::Sort() { + SplitReasonListType splitReasons; + // 1. split - // 2. sort each group - GroupIDToListType groups = this->SplitInputGroups(); - GroupIDToListType& sortedGroups = this->SortGroups( groups ); + GroupIDToListType groups = this->SplitInputGroups(splitReasons); + + // 2. sort each group (can also lead to a split due to distance) + GroupIDToListType& sortedGroups = this->SortGroups( groups, splitReasons); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (auto groupIter = sortedGroups.cbegin(); groupIter != sortedGroups.cend(); ++outputIndex, ++groupIter) { - this->SetOutput(outputIndex, groupIter->second); + this->SetOutput(outputIndex, groupIter->second, splitReasons[groupIter->first]); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (auto tagIter = m_DistinguishingTags.cbegin(); tagIter != m_DistinguishingTags.cend(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags DICOMDatasetFinding rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != nullptr && rawTagValue.isValid) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue.value); } else { processedTagValue = rawTagValue.value; } - groupID << processedTagValue; + groupID << "#" << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter -::SplitInputGroups() +::SplitInputGroups(SplitReasonListType& splitReasons) { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (auto dsIter = input.cbegin(); dsIter != input.cend(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); - std::string groupID = this->BuildGroupID( dataset ); + const std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; + splitReasons.clear(); + if (listForGroupID.size() == 1) + { + //no split -> no reason + splitReasons[listForGroupID.begin()->first] = IOVolumeSplitReason::New(); + } + else + { + for (auto& [key, value] : listForGroupID) + { + (void)value; // Prevent unused variable error in older compilers + auto reason = IOVolumeSplitReason::New(); + reason->AddReason(IOVolumeSplitReason::ReasonType::ValueSplitDifference); + splitReasons[key] = reason; + } + } + return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter -::SortGroups(GroupIDToListType& groups) +::SortGroups(GroupIDToListType& groups, SplitReasonListType& splitReasons) { if (m_SortCriterion.IsNotNull()) { /* Three steps here: 1. sort within each group - this may result in orders such as 1 2 3 4 6 7 8 10 12 13 14 2. create new groups by enforcing consecutive order within each group - resorts above example like 1 2 3 4 ; 6 7 8 ; 10 ; 12 13 14 3. sort all of the groups (not WITHIN each group) by their first frame - if earlier "distinguish" steps created groups like 6 7 8 ; 1 2 3 4 ; 10, then this step would sort them like 1 2 3 4 ; 6 7 8 ; 10 */ // Step 1: sort within the groups // for each output // sort by all configured tags, use secondary tags when equal or empty // make configurable: // - sorting order (ascending, descending) // - sort numerically // - ... ? #ifdef MBILOG_ENABLE_DEBUG unsigned int groupIndex(0); #endif for (auto gIter = groups.begin(); gIter != groups.end(); #ifdef MBILOG_ENABLE_DEBUG ++groupIndex, #endif ++gIter) { DICOMDatasetList& dsList = gIter->second; #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; for (auto oi = dsList.begin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } #endif // #ifdef MBILOG_ENABLE_DEBUG std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for (auto oi = dsList.cbegin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; #endif // MBILOG_ENABLE_DEBUG } GroupIDToListType consecutiveGroups; + SplitReasonListType consecutiveReasons; if (m_StrictSorting) { // Step 2: create new groups by enforcing consecutive order within each group unsigned int groupIndex(0); for (auto gIter = groups.begin(); gIter != groups.end(); ++gIter) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupIndex++; + std::string groupKeyStr = groupKey.str(); DICOMDatasetList& dsList = gIter->second; + DICOMDatasetAccess* previousDS(nullptr); unsigned int dsIndex(0); double constantDistance(0.0); bool constantDistanceInitialized(false); for (auto dataset = dsList.cbegin(); dataset != dsList.cend(); ++dsIndex, ++dataset) { + bool splitted = false; if (dsIndex >0) // ignore the first dataset, we cannot check any distances yet.. { // for the second and every following dataset: // let the sorting criterion calculate a "distance" // if the distance is not 1, split off a new group! const double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset); if (constantDistanceInitialized) { if (fabs(currentDistance - constantDistance) < fabs(constantDistance * 0.01)) // ok, deviation of up to 1% of distance is tolerated { // nothing to do, just ok MITK_DEBUG << "Checking currentDistance==" << currentDistance << ": small enough"; } //else if (currentDistance < mitk::eps) // close enough to 0 else { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (current distance " << currentDistance << ", constant distance " << constantDistance << ")"; // split! this is done by simply creating a new group (key) groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; + groupKeyStr = groupKey.str(); + splitted = true; } } else { // second slice: learn about the expected distance! // heuristic: if distance is an integer, we check for a special case: // if the distance is integer and not 1/-1, then we assume // a missing slice right after the first slice // ==> split off slices // in all other cases: second dataset at this position, no need to split already, we are still learning about the images // addition to the above: when sorting by imagepositions, a distance other than 1 between the first two slices is // not unusual, actually expected... then we should not split if (m_ExpectDistanceOne) { if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0) // exact comparison. An integer should not be expressed as 1.000000000000000000000000001! { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (special case: expected distance 1 exactly)"; groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; + groupKeyStr = groupKey.str(); + splitted = true; } } MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance; constantDistance = currentDistance; constantDistanceInitialized = true; } } - consecutiveGroups[groupKey.str()].push_back(*dataset); + consecutiveGroups[groupKeyStr].push_back(*dataset); + + if (consecutiveReasons.find(groupKeyStr) == consecutiveReasons.end()) + { + auto dsReason = splitReasons[gIter->first]->Clone(); + if (splitted) + dsReason->AddReason(IOVolumeSplitReason::ReasonType::ValueSortDistance); + consecutiveReasons[groupKeyStr] = dsReason; + } + previousDS = *dataset; } } } else { consecutiveGroups = groups; } // Step 3: sort all of the groups (not WITHIN each group) by their first frame /* build a list-1 of datasets with the first dataset one of each group sort this list-1 build a new result list-2: - iterate list-1, for each dataset - find the group that contains this dataset - add this group as the next element to list-2 return list-2 as the sorted output */ DICOMDatasetList firstSlices; for (auto gIter = consecutiveGroups.cbegin(); gIter != consecutiveGroups.cend(); ++gIter) { assert(!gIter->second.empty()); firstSlices.push_back(gIter->second.front()); } std::sort( firstSlices.begin(), firstSlices.end(), ParameterizedDatasetSort( m_SortCriterion ) ); GroupIDToListType sortedResultBlocks; - unsigned int groupKeyValue(0); - for (auto firstSlice = firstSlices.cbegin(); - firstSlice != firstSlices.cend(); - ++firstSlice) + SplitReasonListType sortedResultsReasons; + + for (auto& [key, group] : consecutiveGroups) { - for (auto gIter = consecutiveGroups.cbegin(); - gIter != consecutiveGroups.cend(); - ++groupKeyValue, ++gIter) - { - if (gIter->second.front() == *firstSlice) - { - std::stringstream groupKey; - groupKey << std::setfill('0') << std::setw(6) << groupKeyValue; // try more than 999,999 groups and you are doomed (your application already is) - sortedResultBlocks[groupKey.str()] = gIter->second; - } - } + auto findSliceIterator = std::find(firstSlices.begin(), firstSlices.end(), group.front()); + + std::stringstream groupKey; + groupKey << std::setfill('0') << std::setw(6) << std::distance(firstSlices.begin(),findSliceIterator); // try more than 999,999 groups and you are doomed (your application already is) + const auto groupKeyStr = groupKey.str(); + sortedResultBlocks[groupKeyStr] = group; + sortedResultsReasons[groupKeyStr] = consecutiveReasons[key]; } groups = sortedResultBlocks; + splitReasons = sortedResultsReasons; } #ifdef MBILOG_ENABLE_DEBUG unsigned int groupIndex( 0 ); for ( auto gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter ) { DICOMDatasetList& dsList = gIter->second; MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for ( auto oi = dsList.begin(); oi != dsList.end(); ++oi ) { MITK_DEBUG << " OUTPUT : " << ( *oi )->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; } #endif // MBILOG_ENABLE_DEBUG return groups; } mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion) :m_SortCriterion(criterion) { } bool mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) { assert(left); assert(right); assert(m_SortCriterion.IsNotNull()); return m_SortCriterion->IsLeftBeforeRight(left, right); } diff --git a/Modules/DICOM/src/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOM/src/mitkEquiDistantBlocksSorter.cpp index 28f9ab9a7e..5b16bae486 100644 --- a/Modules/DICOM/src/mitkEquiDistantBlocksSorter.cpp +++ b/Modules/DICOM/src/mitkEquiDistantBlocksSorter.cpp @@ -1,570 +1,620 @@ /*============================================================================ 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 "mitkEquiDistantBlocksSorter.h" mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult -::SliceGroupingAnalysisResult() +::SliceGroupingAnalysisResult() : m_SplitReason(IOVolumeSplitReason::New()) { } -mitk::DICOMDatasetList +const mitk::DICOMDatasetList& mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult -::GetBlockDatasets() +::GetBlockDatasets() const { return m_GroupedFiles; } -mitk::DICOMDatasetList +const mitk::DICOMDatasetList& mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult -::GetUnsortedDatasets() +::GetUnsortedDatasets() const { return m_UnsortedFiles; } +mitk::IOVolumeSplitReason::ConstPointer +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult::GetSplitReason() const +{ + return m_SplitReason; +} + +mitk::IOVolumeSplitReason::Pointer +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult::GetSplitReason() +{ + return m_SplitReason; +} + bool mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::ContainsGantryTilt() { return m_TiltInfo.IsRegularGantryTilt(); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToSortedBlock(DICOMDatasetAccess* dataset) { m_GroupedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFileToUnsortedBlock(DICOMDatasetAccess* dataset) { m_UnsortedFiles.push_back( dataset ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::AddFilesToUnsortedBlock(const DICOMDatasetList& datasets) { m_UnsortedFiles.insert( m_UnsortedFiles.end(), datasets.begin(), datasets.end() ); } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SetFirstFilenameOfBlock(const std::string& filename) { m_FirstFilenameOfBlock = filename; } std::string mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetFirstFilenameOfBlock() const { return m_FirstFilenameOfBlock; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::SetLastFilenameOfBlock(const std::string& filename) { m_LastFilenameOfBlock = filename; } std::string mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetLastFilenameOfBlock() const { return m_LastFilenameOfBlock; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::FlagGantryTilt(const GantryTiltInformation& tiltInfo) { m_TiltInfo = tiltInfo; } const mitk::GantryTiltInformation& mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::GetTiltInfo() const { return m_TiltInfo; } void mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult ::UndoPrematureGrouping() { assert( !m_GroupedFiles.empty() ); m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() ); m_GroupedFiles.pop_back(); m_TiltInfo = GantryTiltInformation(); } // ------------------------ end helper class mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter() :DICOMDatasetSorter() ,m_AcceptTilt(false) ,m_ToleratedOriginOffset(0.3) ,m_ToleratedOriginOffsetIsAbsolute(false) ,m_AcceptTwoSlicesGroups(true) { } mitk::EquiDistantBlocksSorter ::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other ) :DICOMDatasetSorter(other) ,m_AcceptTilt(other.m_AcceptTilt) ,m_ToleratedOriginOffset(other.m_ToleratedOriginOffset) ,m_ToleratedOriginOffsetIsAbsolute(other.m_ToleratedOriginOffsetIsAbsolute) ,m_AcceptTwoSlicesGroups(other.m_AcceptTwoSlicesGroups) { } mitk::EquiDistantBlocksSorter ::~EquiDistantBlocksSorter() { } bool mitk::EquiDistantBlocksSorter ::operator==(const DICOMDatasetSorter& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { return this->m_AcceptTilt == otherSelf->m_AcceptTilt && this->m_ToleratedOriginOffsetIsAbsolute == otherSelf->m_ToleratedOriginOffsetIsAbsolute && this->m_AcceptTwoSlicesGroups == otherSelf->m_AcceptTwoSlicesGroups && (fabs(this->m_ToleratedOriginOffset - otherSelf->m_ToleratedOriginOffset) < eps); } else { return false; } } void mitk::EquiDistantBlocksSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { std::stringstream ts; if (!m_ToleratedOriginOffsetIsAbsolute) { ts << "adaptive"; } else { ts << m_ToleratedOriginOffset << "mm"; } os << indent << "Sort into blocks of equidistant, well-aligned (tolerance " << ts.str() << ") slices " << (m_AcceptTilt ? "(accepting a gantry tilt)" : "") << std::endl; } void mitk::EquiDistantBlocksSorter ::SetAcceptTilt(bool accept) { m_AcceptTilt = accept; } bool mitk::EquiDistantBlocksSorter ::GetAcceptTilt() const { return m_AcceptTilt; } void mitk::EquiDistantBlocksSorter ::SetAcceptTwoSlicesGroups(bool accept) { m_AcceptTwoSlicesGroups = accept; } bool mitk::EquiDistantBlocksSorter ::GetAcceptTwoSlicesGroups() const { return m_AcceptTwoSlicesGroups; } mitk::EquiDistantBlocksSorter& mitk::EquiDistantBlocksSorter ::operator=(const EquiDistantBlocksSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); m_AcceptTilt = other.m_AcceptTilt; m_ToleratedOriginOffset = other.m_ToleratedOriginOffset; m_ToleratedOriginOffsetIsAbsolute = other.m_ToleratedOriginOffsetIsAbsolute; m_AcceptTwoSlicesGroups = other.m_AcceptTwoSlicesGroups; } return *this; } mitk::DICOMTagList mitk::EquiDistantBlocksSorter ::GetTagsOfInterest() { DICOMTagList tags; tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient tags.push_back( DICOMTag(0x0018, 0x1120) ); // GantryDetectorTilt return tags; } void mitk::EquiDistantBlocksSorter ::Sort() { DICOMDatasetList remainingInput = GetInput(); // copy - typedef std::list OutputListType; - OutputListType outputs; - m_SliceGroupingResults.clear(); while (!remainingInput.empty()) // repeat until all files are grouped somehow { - SliceGroupingAnalysisResult regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, m_AcceptTilt ); + auto regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, m_AcceptTilt ); #ifdef MBILOG_ENABLE_DEBUG DICOMDatasetList inBlock = regularBlock.GetBlockDatasets(); DICOMDatasetList laterBlock = regularBlock.GetUnsortedDatasets(); MITK_DEBUG << "Result: sorted 3D group with " << inBlock.size() << " files"; for (DICOMDatasetList::const_iterator diter = inBlock.cbegin(); diter != inBlock.cend(); ++diter) MITK_DEBUG << " IN " << (*diter)->GetFilenameIfAvailable(); for (DICOMDatasetList::const_iterator diter = laterBlock.cbegin(); diter != laterBlock.cend(); ++diter) MITK_DEBUG << " OUT " << (*diter)->GetFilenameIfAvailable(); #endif // MBILOG_ENABLE_DEBUG - outputs.push_back( regularBlock.GetBlockDatasets() ); + remainingInput = regularBlock->GetUnsortedDatasets(); + + if (remainingInput.empty() && !m_SliceGroupingResults.empty() && m_SliceGroupingResults.back()->GetSplitReason()->HasReason(IOVolumeSplitReason::ReasonType::OverlappingSlices)) + { + //if all inputs are processed and there is already a preceding grouping result that has overlapping as split reason, add also overlapping as split reason for the current block + regularBlock->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::OverlappingSlices); + } + m_SliceGroupingResults.push_back( regularBlock ); - remainingInput = regularBlock.GetUnsortedDatasets(); } - unsigned int numberOfOutputs = outputs.size(); + unsigned int numberOfOutputs = m_SliceGroupingResults.size(); this->SetNumberOfOutputs(numberOfOutputs); unsigned int outputIndex(0); - for (auto oIter = outputs.cbegin(); - oIter != outputs.cend(); + for (auto oIter = m_SliceGroupingResults.cbegin(); + oIter != m_SliceGroupingResults.cend(); ++outputIndex, ++oIter) { - this->SetOutput(outputIndex, *oIter); + this->SetOutput(outputIndex, (*oIter)->GetBlockDatasets(), (*oIter)->GetSplitReason()); } } void mitk::EquiDistantBlocksSorter ::SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistance) { m_ToleratedOriginOffset = fractionOfInterSliceDistance; m_ToleratedOriginOffsetIsAbsolute = false; if (m_ToleratedOriginOffset < 0.0) { MITK_WARN << "Call SetToleratedOriginOffsetToAdaptive() only with positive numbers between 0.0 and 1.0, read documentation!"; } if (m_ToleratedOriginOffset > 0.5) { MITK_WARN << "EquiDistantBlocksSorter is now accepting large errors, take care of measurements, they could appear at imprecise locations!"; } } void mitk::EquiDistantBlocksSorter ::SetToleratedOriginOffset(double millimeters) { m_ToleratedOriginOffset = millimeters; m_ToleratedOriginOffsetIsAbsolute = true; if (m_ToleratedOriginOffset < 0.0) { MITK_WARN << "Negative tolerance set to SetToleratedOriginOffset()!"; } } double mitk::EquiDistantBlocksSorter ::GetToleratedOriginOffset() const { return m_ToleratedOriginOffset; } bool mitk::EquiDistantBlocksSorter ::IsToleratedOriginOffsetAbsolute() const { return m_ToleratedOriginOffsetIsAbsolute; } std::string mitk::EquiDistantBlocksSorter ::ConstCharStarToString(const char* s) { return s ? std::string(s) : std::string(); } -mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +std::shared_ptr mitk::EquiDistantBlocksSorter ::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const DICOMDatasetList& datasets, bool groupImagesWithGantryTilt) { - // result.first = files that fit ITK's assumption - // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again - SliceGroupingAnalysisResult result; + auto result = std::make_shared(); const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); + int missingSlicesCount = 0; MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing " << datasets.size() << " files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); double toleratedOriginError(0.005); // default: max. 1/10mm error when measurement crosses 20 slices in z direction (too strict? we don't know better) for (auto dsIter = datasets.cbegin(); dsIter != datasets.cend(); ++dsIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = (*dsIter)->GetTagValueAsString(tagImagePositionPatient).value; if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis (no position information)"; // we already have one occupying this position - if ( result.GetBlockDatasets().empty() ) // nothing WITH position information yet + if ( result->GetBlockDatasets().empty() ) // nothing WITH position information yet { // ==> this is a group of its own, stop processing, come back later - result.AddFileToSortedBlock( *dsIter ); + result->AddFileToSortedBlock( *dsIter ); DICOMDatasetList remainingFiles; remainingFiles.insert( remainingFiles.end(), dsIter+1, datasets.end() ); - result.AddFilesToUnsortedBlock( remainingFiles ); - + result->AddFilesToUnsortedBlock( remainingFiles ); + if (!remainingFiles.empty()) + //if there are remaining files add a split reason + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::ImagePostionMissing); fileFitsIntoPattern = false; break; // no files anymore } else { // ==> this does not match, consider later - result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis fileFitsIntoPattern = false; continue; // next file } } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); MITK_DEBUG << " " << fileIndex << " " << (*dsIter)->GetFilenameIfAvailable() << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) { MITK_DEBUG << " ==> Sort away " << *dsIter << " for separate time step"; // we already have one occupying this position - result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::OverlappingSlices); fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // classic mode without tolerance! if (!m_ToleratedOriginOffsetIsAbsolute) { MITK_DEBUG << "Distance of two slices: " << fromFirstToSecondOrigin.GetNorm() << "mm"; toleratedOriginError = fromFirstToSecondOrigin.GetNorm() * 0.3; // a third of the slice distance // (less than half, which would mean that a slice is displayed where another slice should actually be) } else { toleratedOriginError = m_ToleratedOriginOffset; } MITK_DEBUG << "Accepting errors in actual versus expected origin up to " << toleratedOriginError << "mm"; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. 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 std::string orientationValue = (*dsIter)->GetTagValueAsString( tagImageOrientation ).value; DICOMStringToOrientationVectors( orientationValue, right, up, ignoredConversionError ); GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); if ( tiltInfo.IsSheared() ) { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. - // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*dsIter) + // if NO, we need to split the already sorted part (result->first) and the currently analyzed file (*dsIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) { assert(!datasets.empty()); - result.FlagGantryTilt(tiltInfo); - result.AddFileToSortedBlock( *dsIter ); // this file is good for current block - result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); - result.SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() ); + result->FlagGantryTilt(tiltInfo); + result->AddFileToSortedBlock( *dsIter ); // this file is good for current block + result->SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); + result->SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() ); fileFitsIntoPattern = true; } else // caller does not want tilt compensation OR shearing is more complicated than tilt { - result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::GantryTiltDifference); fileFitsIntoPattern = false; } } else // not sheared { - result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + result->AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); if (norm > toleratedOriginError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedOriginError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ - result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + result->AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + + const auto fromLastToThisOriginDistance = (lastDifferentOrigin - thisOrigin).GetNorm(); + const auto fromFirstToSecondOriginDistance = fromFirstToSecondOrigin.GetNorm(); + + auto currentMissCount = static_cast(std::round((fromLastToThisOriginDistance / fromFirstToSecondOriginDistance)-1)); + if (missingSlicesCount == 0 || missingSlicesCount > currentMissCount) + { + if (missingSlicesCount != 0 && currentMissCount == 0) + { + result->GetSplitReason()->RemoveReason(IOVolumeSplitReason::ReasonType::MissingSlices); + } + + missingSlicesCount = currentMissCount; + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency, std::to_string(fromLastToThisOriginDistance)); + } + + if (missingSlicesCount < 0) + { + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::OverlappingSlices); + } + else if (missingSlicesCount > 0 && !result->GetSplitReason()->HasReason(IOVolumeSplitReason::ReasonType::OverlappingSlices)) + { + //If the missing slice count is positive, but no overlapping was flagged, add the missing slice reason. + //We only do it if overlapping was not flagged, to avoid false positives, that could be triggered by slices + //of the overlapping volume. + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::MissingSlices, std::to_string(missingSlicesCount)); + } + fileFitsIntoPattern = false; } else { - result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + result->AddFileToSortedBlock( *dsIter ); // this file is good for current block + result->GetSplitReason()->RemoveReason(IOVolumeSplitReason::ReasonType::SliceDistanceInconsistency); + result->GetSplitReason()->RemoveReason(IOVolumeSplitReason::ReasonType::MissingSlices); fileFitsIntoPattern = true; + missingSlicesCount = 0; } } else // this should be the very first slice { - result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + result->AddFileToSortedBlock( *dsIter ); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } - if ( result.ContainsGantryTilt() ) + if ( result->ContainsGantryTilt() ) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together // Above behavior can be configured via m_AcceptTwoSlicesGroups, the default being "do accept" - if ( result.GetBlockDatasets().size() == 2 && !m_AcceptTwoSlicesGroups ) + if ( result->GetBlockDatasets().size() == 2 && !m_AcceptTwoSlicesGroups ) { - result.UndoPrematureGrouping(); + result->UndoPrematureGrouping(); + result->GetSplitReason()->AddReason(IOVolumeSplitReason::ReasonType::GantryTiltDifference); } } // update tilt info to get maximum precision // earlier, tilt was only calculated from first and second slice. // now that we know the whole range, we can re-calculate using the very first and last slice - if ( result.ContainsGantryTilt() && result.GetBlockDatasets().size() > 1 ) + if ( result->ContainsGantryTilt() && result->GetBlockDatasets().size() > 1 ) { try { - DICOMDatasetList datasets = result.GetBlockDatasets(); + DICOMDatasetList datasets = result->GetBlockDatasets(); DICOMDatasetAccess* firstDataset = datasets.front(); DICOMDatasetAccess* lastDataset = datasets.back(); unsigned int numberOfSlicesApart = datasets.size() - 1; std::string orientationString = firstDataset->GetTagValueAsString( tagImageOrientation ).value; std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient ).value; std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient ).value; - result.FlagGantryTilt( GantryTiltInformation::MakeFromTagValues( firstOriginString, lastOriginString, orientationString, numberOfSlicesApart )); + result->FlagGantryTilt( GantryTiltInformation::MakeFromTagValues( firstOriginString, lastOriginString, orientationString, numberOfSlicesApart )); } catch (...) { // just do not flag anything, we are ok } } return result; } diff --git a/Modules/DICOM/src/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOM/src/mitkThreeDnTDICOMSeriesReader.cpp index a8245c16df..484d0f2442 100644 --- a/Modules/DICOM/src/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOM/src/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,264 +1,308 @@ /*============================================================================ 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 "mitkThreeDnTDICOMSeriesReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation) :DICOMITKSeriesGDCMReader(decimalPlacesForOrientation) ,m_Group3DandT(m_DefaultGroup3DandT), m_OnlyCondenseSameSeries(m_DefaultOnlyCondenseSameSeries) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) ,m_Group3DandT(m_DefaultGroup3DandT), m_OnlyCondenseSameSeries(m_DefaultOnlyCondenseSameSeries) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } bool mitk::ThreeDnTDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { return DICOMITKSeriesGDCMReader::operator==(other) && this->m_Group3DandT == otherSelf->m_Group3DandT; } else { return false; } } void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } bool mitk::ThreeDnTDICOMSeriesReader ::GetGroup3DandT() const { return m_Group3DandT; } +/** Helper function to make the code in mitk::ThreeDnTDICOMSeriesReader +::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) more readable.*/ +bool BlockShouldBeCondensed(bool onlyCondenseSameSeries, unsigned int currentBlockNumberOfSlices, unsigned int otherBlockNumberOfSlices, + const mitk::DICOMDatasetFinding& currentBlockFirstOrigin, const mitk::DICOMDatasetFinding& currentBlockLastOrigin, + const mitk::DICOMDatasetFinding& otherBlockFirstOrigin, const mitk::DICOMDatasetFinding& otherBlockLastOrigin, + const mitk::DICOMDatasetFinding& currentBlockSeriesInstanceUID, const mitk::DICOMDatasetFinding& otherBlockSeriesInstanceUID) +{ + if (otherBlockNumberOfSlices != currentBlockNumberOfSlices) + return false; //don't condense blocks that have unequal slice count + + if (!otherBlockFirstOrigin.isValid || !otherBlockLastOrigin.isValid) + return false; //don't condense blocks that have invalid origins + + if (!currentBlockFirstOrigin.isValid || !currentBlockLastOrigin.isValid) + return false; //don't condense blocks that have invalid origins + + const bool sameSeries = otherBlockSeriesInstanceUID.isValid + && currentBlockSeriesInstanceUID.isValid + && otherBlockSeriesInstanceUID.value == currentBlockSeriesInstanceUID.value; + + if (onlyCondenseSameSeries && !sameSeries) + return false; //don't condense blocks if it is only allowed to condense same series and series are not defined or not equal. + + if (otherBlockFirstOrigin.value != currentBlockFirstOrigin.value) + return false; //don't condense blocks that have unequal first origins + + if (otherBlockLastOrigin.value != currentBlockLastOrigin.value) + return false; //don't condense blocks that have unequal last origins + + return true; +} + mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::ThreeDnTDICOMSeriesReader ::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) { if (!m_Group3DandT) { return resultOf3DGrouping; // don't work if nobody asks us to } SortingBlockList remainingBlocks = resultOf3DGrouping; SortingBlockList non3DnTBlocks; SortingBlockList true3DnTBlocks; std::vector true3DnTBlocksTimeStepCount; // we should describe our need for this tag as needed via a function // (however, we currently know that the superclass will always need this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); const DICOMTag tagSeriesInstaceUID(0x0020, 0x000e); while (!remainingBlocks.empty()) { // new block to fill up - const DICOMDatasetAccessingImageFrameList& firstBlock = remainingBlocks.front(); + const DICOMDatasetAccessingImageFrameList& firstBlock = remainingBlocks.front().first; DICOMDatasetAccessingImageFrameList current3DnTBlock = firstBlock; + auto currentSplitReason = remainingBlocks.front().second; + int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block const unsigned int currentBlockNumberOfSlices = firstBlock.size(); - const std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; - const std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; - const auto currentBlockSeriesInstanceUID = firstBlock.back()->GetTagValueAsString(tagSeriesInstaceUID).value; + const auto currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ); + const auto currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ); + const auto currentBlockSeriesInstanceUID = firstBlock.back()->GetTagValueAsString(tagSeriesInstaceUID); remainingBlocks.erase( remainingBlocks.begin() ); // compare all other blocks against the first one for (auto otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.cend(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block - const DICOMDatasetAccessingImageFrameList otherBlock = *otherBlockIter; + const DICOMDatasetAccessingImageFrameList otherBlock = otherBlockIter->first; const unsigned int otherBlockNumberOfSlices = otherBlock.size(); - const std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; - const std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; - const auto otherBlockSeriesInstanceUID = otherBlock.back()->GetTagValueAsString(tagSeriesInstaceUID).value; + const auto otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ); + const auto otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ); + const auto otherBlockSeriesInstanceUID = otherBlock.back()->GetTagValueAsString(tagSeriesInstaceUID); // add matching blocks to current3DnTBlock // keep other blocks for later - if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices - && (!m_OnlyCondenseSameSeries || otherBlockSeriesInstanceUID == currentBlockSeriesInstanceUID) - && otherBlockFirstOrigin == currentBlockFirstOrigin - && otherBlockLastOrigin == currentBlockLastOrigin - ) + if ( BlockShouldBeCondensed(m_OnlyCondenseSameSeries, currentBlockNumberOfSlices, otherBlockNumberOfSlices, + currentBlockFirstOrigin, currentBlockLastOrigin, + otherBlockFirstOrigin, otherBlockLastOrigin, + currentBlockSeriesInstanceUID, otherBlockSeriesInstanceUID)) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append + //also merge split reasons + currentSplitReason = currentSplitReason->ExtendReason(otherBlockIter->second); + // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now know all about the first block of our list ... // ... and we either call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { - true3DnTBlocks.push_back(current3DnTBlock); + true3DnTBlocks.emplace_back(current3DnTBlock,currentSplitReason); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { - non3DnTBlocks.push_back(current3DnTBlock); + non3DnTBlocks.emplace_back(current3DnTBlock, currentSplitReason); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (auto blockIter = true3DnTBlocks.cbegin(); blockIter != true3DnTBlocks.cend(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way - DICOMDatasetAccessingImageFrameList gdcmFrameInfoList = *blockIter; + const auto& gdcmFrameInfoList = blockIter->first; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetAdditionalTagsOfInterest(GetAdditionalTagsOfInterest()); block.SetTagLookupTableToPropertyFunctor(GetTagLookupTableToPropertyFunctor()); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); + block.SetSplitReason(blockIter->second->Clone()); + + if (true3DnTBlocks.size() == 1 && non3DnTBlocks.empty()) + { + //if we have condensed everything into just on 3DnT block, we can remove the overlap reason, + //because no real overlap is existent any more. + block.GetSplitReason()->RemoveReason(IOVolumeSplitReason::ReasonType::OverlappingSlices); + } block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { const DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); const bool hasTilt = tiltInfo.IsRegularGantryTilt(); const int numberOfTimesteps = block.GetNumberOfTimeSteps(); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } const int numberOfFramesPerTimestep = block.GetNumberOfFramesPerTimeStep(); ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; } diff --git a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp index 62c86a2614..329ce3d39e 100644 --- a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp +++ b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp @@ -1,609 +1,616 @@ /*============================================================================ 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 "mitkTestDICOMLoading.h" #include "mitkDICOMIOMetaInformationPropertyConstants.h" +#include "mitkIOMetaInformationPropertyConstants.h" #include "mitkDICOMProperty.h" #include "mitkArbitraryTimeGeometry.h" #include #include #include #include "itksys/SystemTools.hxx" mitk::TestDICOMLoading::TestDICOMLoading() :m_PreviousCLocale(nullptr) { } void mitk::TestDICOMLoading::SetDefaultLocale() { // remember old locale only once if (m_PreviousCLocale == nullptr) { m_PreviousCLocale = setlocale(LC_NUMERIC, nullptr); // set to "C" setlocale(LC_NUMERIC, "C"); m_PreviousCppLocale = std::cin.getloc(); std::locale l( "C" ); std::cin.imbue(l); std::cout.imbue(l); } } void mitk::TestDICOMLoading::ResetUserLocale() { if (m_PreviousCLocale) { setlocale(LC_NUMERIC, m_PreviousCLocale); std::cin.imbue(m_PreviousCppLocale); std::cout.imbue(m_PreviousCppLocale); m_PreviousCLocale = nullptr; } } mitk::TestDICOMLoading::ImageList mitk::TestDICOMLoading ::LoadFiles( const StringList& files ) { for (auto iter = files.begin(); iter != files.end(); ++iter) { MITK_DEBUG << "File " << *iter; } ImageList result; ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader(); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles( files ); reader->AnalyzeInputFiles(); reader->PrintOutputs(std::cout,true); reader->LoadImages(); unsigned int numberOfImages = reader->GetNumberOfOutputs(); for (unsigned imageIndex = 0; imageIndex < numberOfImages; ++imageIndex) { const DICOMImageBlockDescriptor& block = reader->GetOutput(imageIndex); result.push_back( block.GetMitkImage() ); } return result; } mitk::ClassicDICOMSeriesReader::Pointer mitk::TestDICOMLoading ::BuildDICOMReader() { ClassicDICOMSeriesReader::Pointer reader = ClassicDICOMSeriesReader::New(); reader->SetFixTiltByShearing(true); return reader; } mitk::Image::Pointer mitk::TestDICOMLoading ::DecorateVerifyCachedImage( const StringList& files, mitk::DICOMTagCache* tagCache, mitk::Image::Pointer cachedImage ) { DICOMImageBlockDescriptor block; DICOMImageFrameList framelist; for (auto iter = files.begin(); iter != files.end(); ++iter) { framelist.push_back( DICOMImageFrameInfo::New(*iter) ); } block.SetImageFrameList( framelist ); block.SetTagCache( tagCache ); block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices return block.GetMitkImage(); } mitk::Image::Pointer mitk::TestDICOMLoading ::DecorateVerifyCachedImage( const StringList& files, mitk::Image::Pointer cachedImage ) { ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader(); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles( files ); reader->AnalyzeInputFiles(); // This just creates a "tag cache and a nice DICOMImageBlockDescriptor. // Both of these could also be produced in a different way. The only // important thing is, that the DICOMImageBlockDescriptor knows a // tag-cache object when PropertyDecorateCachedMitkImageForImageBlockDescriptor // is called. if ( reader->GetNumberOfOutputs() != 1 ) { MITK_ERROR << "Reader produce " << reader->GetNumberOfOutputs() << " images instead of 1 expected.."; return nullptr; } DICOMImageBlockDescriptor block = reader->GetOutput(0); // creates a block copy block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices return block.GetMitkImage(); } std::string mitk::TestDICOMLoading::ComponentTypeToString(itk::IOComponentEnum type) { if (type == itk::IOComponentEnum::UCHAR) return "UCHAR"; else if (type == itk::IOComponentEnum::CHAR) return "CHAR"; else if (type == itk::IOComponentEnum::USHORT) return "USHORT"; else if (type == itk::IOComponentEnum::SHORT) return "SHORT"; else if (type == itk::IOComponentEnum::UINT) return "UINT"; else if (type == itk::IOComponentEnum::INT) return "INT"; else if (type == itk::IOComponentEnum::ULONG) return "ULONG"; else if (type == itk::IOComponentEnum::LONG) return "LONG"; else if (type == itk::IOComponentEnum::FLOAT) return "FLOAT"; else if (type == itk::IOComponentEnum::DOUBLE) return "DOUBLE"; else return "UNKNOWN"; } // add a line to stringstream result (see DumpImageInformation #define DumpLine(field, data) DumpILine(0, field, data) // add an indented(!) line to stringstream result (see DumpImageInformation #define DumpILine(indent, field, data) \ { \ std::string DumpLine_INDENT; DumpLine_INDENT.resize(indent, ' ' ); \ result << DumpLine_INDENT << field << ": " << data << "\n"; \ } std::string mitk::TestDICOMLoading::DumpImageInformation( const Image* image ) { std::stringstream result; if (image == nullptr) return result.str(); SetDefaultLocale(); // basic image data DumpLine( "Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()) ); DumpLine( "BitsPerPixel", image->GetPixelType().GetBpe() ); DumpLine( "Dimension", image->GetDimension() ); result << "Dimensions: "; for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) result << image->GetDimension(dim) << " "; result << "\n"; // geometry data result << "Geometry: \n"; const TimeGeometry* timeGeometry = image->GetTimeGeometry(); BaseGeometry* geometry = timeGeometry->GetGeometryForTimeStep(0); if (geometry) { AffineTransform3D* transform = geometry->GetIndexToWorldTransform(); if (transform) { result << " " << "Matrix: "; const AffineTransform3D::MatrixType& matrix = transform->GetMatrix(); for (unsigned int i = 0; i < 3; ++i) for (unsigned int j = 0; j < 3; ++j) result << matrix[i][j] << " "; result << "\n"; result << " " << "Offset: "; const AffineTransform3D::OutputVectorType& offset = transform->GetOffset(); for (unsigned int i = 0; i < 3; ++i) result << offset[i] << " "; result << "\n"; result << " " << "Center: "; const AffineTransform3D::InputPointType& center = transform->GetCenter(); for (unsigned int i = 0; i < 3; ++i) result << center[i] << " "; result << "\n"; result << " " << "Translation: "; const AffineTransform3D::OutputVectorType& translation = transform->GetTranslation(); for (unsigned int i = 0; i < 3; ++i) result << translation[i] << " "; result << "\n"; result << " " << "Scale: "; const double* scale = transform->GetScale(); for (unsigned int i = 0; i < 3; ++i) result << scale[i] << " "; result << "\n"; result << " " << "Origin: "; const Point3D& origin = geometry->GetOrigin(); for (unsigned int i = 0; i < 3; ++i) result << origin[i] << " "; result << "\n"; result << " " << "Spacing: "; const Vector3D& spacing = geometry->GetSpacing(); for (unsigned int i = 0; i < 3; ++i) result << spacing[i] << " "; result << "\n"; result << " " << "TimeBounds: "; /////////////////////////////////////// // Workaround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. // This workaround should be removed as soon as T28262 is solved! TimeBounds timeBounds = timeGeometry->GetTimeBounds(); auto atg = dynamic_cast(timeGeometry); if (atg && atg->HasCollapsedFinalTimeStep()) { timeBounds[1] = timeBounds[1] - 1.; } //Original code: //const TimeBounds timeBounds = timeGeometry->GetTimeBounds(); // // End of workaround for T27883 ////////////////////////////////////// for (unsigned int i = 0; i < 2; ++i) result << timeBounds[i] << " "; result << "\n"; } } // io dicom meta information AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_CONFIGURATION(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_GANTRY_TILT_CORRECTED(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL_STRING(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION_STRING(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_3D_plus_t(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM(), image, result); + AddPropertyToDump(mitk::IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON(), image, result); ResetUserLocale(); return result.str(); } void mitk::TestDICOMLoading::AddPropertyToDump(const mitk::PropertyKeyPath& key, const mitk::Image* image, std::stringstream& result) { auto propKey = mitk::PropertyKeyPathToPropertyName(key); auto prop = image->GetProperty(propKey.c_str()); if (prop.IsNotNull()) { auto value = prop->GetValueAsString(); auto dicomProp = dynamic_cast< mitk::DICOMProperty*>(prop.GetPointer()); if (dicomProp != nullptr) { auto strippedProp = dicomProp->Clone(); if (key == mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES()) {//strip dicom file information from path to ensure generalized dump files auto timePoints = strippedProp->GetAvailableTimeSteps(); for (auto timePoint : timePoints) { auto slices = strippedProp->GetAvailableSlices(timePoint); for (auto slice : slices) { auto value = strippedProp->GetValue(timePoint, slice); value = itksys::SystemTools::GetFilenameName(value); strippedProp->SetValue(timePoint, slice, value); } } } value = mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(strippedProp); } result << propKey << ": " << value << "\n"; } } std::string mitk::TestDICOMLoading::trim(const std::string& pString, const std::string& pWhitespace) { const size_t beginStr = pString.find_first_not_of(pWhitespace); if (beginStr == std::string::npos) { // no content return ""; } const size_t endStr = pString.find_last_not_of(pWhitespace); const size_t range = endStr - beginStr + 1; return pString.substr(beginStr, range); } std::string mitk::TestDICOMLoading::reduce(const std::string& pString, const std::string& pFill, const std::string& pWhitespace) { // trim first std::string result(trim(pString, pWhitespace)); // replace sub ranges size_t beginSpace = result.find_first_of(pWhitespace); while (beginSpace != std::string::npos) { const size_t endSpace = result.find_first_not_of(pWhitespace, beginSpace); const size_t range = endSpace - beginSpace; result.replace(beginSpace, range, pFill); const size_t newStart = beginSpace + pFill.length(); beginSpace = result.find_first_of(pWhitespace, newStart); } return result; } bool mitk::TestDICOMLoading::CompareSpacedValueFields( const std::string& reference, const std::string& test, double /*eps*/ ) { bool result(true); // tokenize string, compare each token, if possible by float comparison std::stringstream referenceStream(reduce(reference)); std::stringstream testStream(reduce(test)); std::string refToken; std::string testToken; while ( std::getline( referenceStream, refToken, ' ' ) && std::getline ( testStream, testToken, ' ' ) ) { float refNumber; float testNumber; if ( this->StringToNumber(refToken, refNumber) ) { if ( this->StringToNumber(testToken, testNumber) ) { // print-out compared tokens if DEBUG output allowed MITK_DEBUG << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; bool old_result = result; result &= ( std::abs(refNumber - testNumber) < 0.0001f /*mitk::eps*/ ); // log the token/number which causes the test to fail if( old_result != result) { MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << std::abs(refNumber - testNumber) << " EPS: " << 0.0001f; //mitk::eps; } } else { MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'"; } } else { MITK_DEBUG << "Token '" << refToken << "'" << " handled as string"; result &= refToken == testToken; } } if ( std::getline( referenceStream, refToken, ' ' ) ) { MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } else if ( std::getline( testStream, testToken, ' ' ) ) { MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } return result; } bool mitk::TestDICOMLoading::CompareJSON(const std::string& reference, const std::string& test) { try { auto jReference = nlohmann::json::parse(reference); auto jTest = nlohmann::json::parse(test); return jReference == jTest; } catch (const nlohmann::json::exception& e) { MITK_ERROR << e.what(); return false; } } bool mitk::TestDICOMLoading::CompareImageInformationDumps( const std::string& referenceDump, const std::string& testDump ) { KeyValueMap reference = ParseDump(referenceDump); KeyValueMap test = ParseDump(testDump); bool testResult(true); // verify all expected values for (KeyValueMap::const_iterator refIter = reference.begin(); refIter != reference.end(); ++refIter) { const std::string& refKey = refIter->first; const std::string& refValue = refIter->second; if ( test.find(refKey) != test.end() ) { const std::string& testValue = test[refKey]; if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK())) { //check dcmtk version always against the current version of the system bool thisTestResult = testValue == std::string(" ") + PACKAGE_VERSION; testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << PACKAGE_VERSION << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } else if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM())) {//check gdcm version always against the current version of the system bool thisTestResult = testValue == std::string(" ") + gdcm::Version::GetVersion(); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << gdcm::Version::GetVersion() << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } else if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES())) { bool thisTestResult = CompareJSON(refValue, testValue); testResult &= thisTestResult; } + else if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::IOMetaInformationPropertyConstants::VOLUME_SPLIT_REASON())) + { + bool thisTestResult = CompareJSON(refValue, testValue); + testResult &= thisTestResult; + } else { bool thisTestResult = CompareSpacedValueFields(refValue, testValue); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } } else { MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "')." ; MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update your test data."; return false; } } // now check test dump does not contain any additional keys for (KeyValueMap::const_iterator testIter = test.begin(); testIter != test.end(); ++testIter) { const std::string& key = testIter->first; const std::string& value = testIter->second; if (key == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK())) {//check dcmtk version always against the current version of the system bool thisTestResult = value == std::string(" ")+PACKAGE_VERSION; testResult &= thisTestResult; MITK_DEBUG << key << ": '" << PACKAGE_VERSION << "' == '" << value << "' ? " << (thisTestResult ? "YES" : "NO"); } else if (key == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM())) {//check gdcm version always against the current version of the system bool thisTestResult = value == std::string(" ") + gdcm::Version::GetVersion(); testResult &= thisTestResult; MITK_DEBUG << key << ": '" << gdcm::Version::GetVersion() << "' == '" << value << "' ? " << (thisTestResult ? "YES" : "NO"); } else if ( reference.find(key) == reference.end() ) { MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "')." ; MITK_ERROR << "This key is not expected. Most probably you need to update your test data."; return false; } } return testResult; } mitk::TestDICOMLoading::KeyValueMap mitk::TestDICOMLoading::ParseDump( const std::string& dump ) { KeyValueMap parsedResult; std::string shredder(dump); std::stack surroundingKeys; std::stack expectedIndents; expectedIndents.push(0); while (true) { std::string::size_type newLinePos = shredder.find( '\n' ); if (newLinePos == std::string::npos || newLinePos == 0) break; std::string line = shredder.substr( 0, newLinePos ); shredder = shredder.erase( 0, newLinePos+1 ); std::string::size_type keyPosition = line.find_first_not_of( ' ' ); std::string::size_type colonPosition = line.find( ':' ); std::string key = line.substr(keyPosition, colonPosition - keyPosition); std::string::size_type firstSpacePosition = key.find_first_of(" "); if (firstSpacePosition != std::string::npos) { key.erase(firstSpacePosition); } if ( keyPosition > expectedIndents.top() ) { // more indent than before expectedIndents.push(keyPosition); } else { if (!surroundingKeys.empty()) { surroundingKeys.pop(); // last of same length } while (expectedIndents.top() != keyPosition) { expectedIndents.pop(); if (!surroundingKeys.empty()) { surroundingKeys.pop(); } }; // unwind until current indent is found } if (!surroundingKeys.empty()) { key = surroundingKeys.top() + "." + key; // construct current key name } surroundingKeys.push(key); // this is the new embracing key std::string value = line.substr(colonPosition+1); MITK_DEBUG << " Key: '" << key << "' value '" << value << "'" ; parsedResult[key] = value; // store parsing result } return parsedResult; } diff --git a/Modules/DICOMTesting/test/CMakeLists.txt b/Modules/DICOMTesting/test/CMakeLists.txt index 5912e42626..204d9c04a5 100644 --- a/Modules/DICOMTesting/test/CMakeLists.txt +++ b/Modules/DICOMTesting/test/CMakeLists.txt @@ -1,89 +1,101 @@ MITK_CREATE_MODULE_TESTS() - +############################################################### +# Test group 1 # verify minimum expectations: # files are recognized as DICOM # loading files results in a given number of images mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_NoFiles mitkDICOMTestingSanityTest 0) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_CTImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-ct.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_MRImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-mr.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_SCImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-sc.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_NoImagePositionPatient mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-sc-no2032.dcm) +############################################################### +# Test group 2 # verifies that the loader can also be used to just scan for tags and provide them in mitk::Properties (parameter preLoadedVolume) mitkAddCustomModuleTest(mitkDICOMPreloadedVolumeTest_Slice mitkDICOMPreloadedVolumeTest ${MITK_DATA_DIR}/spacing-ok-ct.dcm) +############################################################### +# Test group 3 +# verifies that the construction of the volumes (splitting, sorting, etc. of slices are done as expected) for different configurations set(VERIFY_DUMP_CMD ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/VerifyDICOMMitkImageDump) +# Alternative definition of VERIFY_DUMP_CMD +# Comment in to use the DumpDICOMMitkImage app to use the tests to produce new reference dumps +#set(VERIFY_DUMP_CMD ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DumpDICOMMitkImage) + set(CT_ABDOMEN_DIR ${MITK_DATA_DIR}/TinyCTAbdomen_DICOMReader) set(MR_HEART_DIR ${MITK_DATA_DIR}/3D+t-Heart) set(CT_TILT_HEAD_DIR ${MITK_DATA_DIR}/TiltHead) set(CT_TILT_DIR ${MITK_DATA_DIR}/TiltedData) set(SOP_CLASSES_DIR ${MITK_DATA_DIR}/DICOMReader) # these variables could be passed as parameters to a generic test creation function set(TESTS_DIR Tests) set(INPUT_LISTNAME input) set(EXPECTED_DUMP expected.dump) -function(AddDicomTestsFromDataRepository CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) +function(AddDicomTestsFromDataRepository CURRENT_DATASET_NAME CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) # find all test input lists file(GLOB_RECURSE allInputs ${CURRENT_DATASET_DIR}/${TESTS_DIR}/*/${INPUT_LISTNAME}) function(expectFileExists filename) if(NOT EXISTS ${filename}) message(SEND_ERROR "Test case expected file ${filename} which does not exist! Please fix your CMake code or file layout.") endif(NOT EXISTS ${filename}) endfunction(expectFileExists) foreach(input ${allInputs}) # extract only the name of the directory of this very test case string(REGEX REPLACE ".*${TESTS_DIR}/([^/]+)/.*" "\\1" input ${input}) set(inputfilelist "${CURRENT_DATASET_DIR}/${TESTS_DIR}/${input}/${INPUT_LISTNAME}") set(expecteddump "${CURRENT_DATASET_DIR}/${TESTS_DIR}/${input}/${EXPECTED_DUMP}") - set(test_name "DICOM_Load_${input}") + set(test_name "DICOM_Load_${CURRENT_DATASET_NAME}_${input}") #message(STATUS "DICOM loading test case '${input}'") expectFileExists(${inputfilelist}) expectFileExists(${expecteddump}) # TODO provide error messages if input not valid set(dicomFiles) # clear list # read list of file names from file "input" file(STRINGS ${inputfilelist} rawDicomFiles) foreach(raw ${rawDicomFiles}) # prepend each file with an abosolute path set(fileWithFullPath ${CURRENT_DATASET_DIR}/${raw}) list(APPEND dicomFiles ${fileWithFullPath}) endforeach(raw ${rawDicomFiles}) #message(STATUS " Load ${dicomFiles}") add_test(${test_name} ${VERIFY_DUMP_CMD} ${expecteddump} ${dicomFiles}) mitkFunctionGetLibrarySearchPaths(MITK_RUNTIME_PATH_RELEASE release RELEASE) mitkFunctionGetLibrarySearchPaths(MITK_RUNTIME_PATH_DEBUG debug DEBUG) set(test_env_path ${MITK_RUNTIME_PATH_RELEASE} ${MITK_RUNTIME_PATH_DEBUG} $ENV{PATH}) list(REMOVE_DUPLICATES test_env_path) string (REGEX REPLACE "\;" "\\\;" test_env_path "${test_env_path}") set_property(TEST ${test_name} PROPERTY ENVIRONMENT "PATH=${test_env_path}" APPEND) set_property(TEST ${test_name} PROPERTY SKIP_RETURN_CODE 77) endforeach(input allInputs) endfunction(AddDicomTestsFromDataRepository CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) -AddDicomTestsFromDataRepository(${CT_ABDOMEN_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) -AddDicomTestsFromDataRepository(${CT_TILT_HEAD_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) -AddDicomTestsFromDataRepository(${CT_TILT_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) -AddDicomTestsFromDataRepository(${MR_HEART_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDicomTestsFromDataRepository(TinyCTAbdomen_DICOMReader ${CT_ABDOMEN_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDicomTestsFromDataRepository(TiltHead ${CT_TILT_HEAD_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDicomTestsFromDataRepository(TiltedData ${CT_TILT_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDicomTestsFromDataRepository(3D+t-Heart ${MR_HEART_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) -AddDicomTestsFromDataRepository(${SOP_CLASSES_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDicomTestsFromDataRepository(DICOMReader ${SOP_CLASSES_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +############################################################### +# Test group 4 # use one more realistic series for testing property lists file(GLOB_RECURSE abdomenImages ${CT_ABDOMEN_DIR}/14?) # this is just one small volume mitkAddCustomModuleTest(mitkDICOMPreloadedVolumeTest_Abdomen mitkDICOMPreloadedVolumeTest ${abdomenImages}) diff --git a/Modules/DICOMweb/CMakeLists.txt b/Modules/DICOMweb/CMakeLists.txt deleted file mode 100644 index 75c9941b05..0000000000 --- a/Modules/DICOMweb/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -mitk_create_module(DEPENDS MitkCore - MitkREST) - diff --git a/Modules/DICOMweb/files.cmake b/Modules/DICOMweb/files.cmake deleted file mode 100644 index adba99da21..0000000000 --- a/Modules/DICOMweb/files.cmake +++ /dev/null @@ -1,3 +0,0 @@ -set(CPP_FILES - mitkDICOMweb.cpp -) diff --git a/Modules/DICOMweb/include/mitkDICOMweb.h b/Modules/DICOMweb/include/mitkDICOMweb.h deleted file mode 100644 index 2b3d02f30c..0000000000 --- a/Modules/DICOMweb/include/mitkDICOMweb.h +++ /dev/null @@ -1,137 +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 mitkDICOMweb_h -#define mitkDICOMweb_h - -#include - -#include "cpprest/asyncrt_utils.h" -#include "cpprest/http_client.h" -#include -#include -#include -#include - -#include -#include - -/** - * @brief This class represents the implementation of the RESTful DICOMweb standard - * (https://dicom.nema.org/medical/dicom/current/output/html/part18.html). It is used to communicate DICOM data over HTTP - * to a DICOMweb enabled PACS server. - * - * @author Tobias Stein - */ - -namespace mitk -{ - - class MITKDICOMWEB_EXPORT DICOMweb - { - public: - typedef web::http::uri_builder MitkUriBuilder; - typedef web::http::http_request MitkRequest; - typedef web::http::http_response MitkResponse; - typedef web::http::methods MitkRESTMethods; - - DICOMweb(); - - /** - * @brief Creates a DICOMweb service instance which allow basic DICOM operations on the given base URI. - * - * @param baseURI the uri for the PACS server: for example a dcm4chee - * https:///dcm4chee-arc/aets/DCM4CHEE/ - */ - DICOMweb(utility::string_t baseURI); - - /** - * @brief Sends a STOW request with the file in the given path to the study given bei its UID. - * - * @param filePath the path to a valid DICOM file which should be send - * @param studyUID the DICOM study uid - * @return the task to wait for - */ - pplx::task SendSTOW(utility::string_t filePath, utility::string_t studyUID); - - /** - * @brief Sends a WADO request for an DICOM object instance matching the given uid parameters and stores it at the - * given file path. - * - * @param filePath the path at which the retrieved DICOM object instance will be stored - * @param studyUID the DICOM study uid - * @param seriesUID the DICOM series uid - * @param instanceUID the DICOM instance uid - * @return the task to wait for, which unfolds no value when finished - */ - pplx::task SendWADO(utility::string_t filePath, - utility::string_t studyUID, - utility::string_t seriesUID, - utility::string_t instanceUID); - - /** - * @brief Sends a WADO request for an DICOM object series matching the given uid parameters and stores all the - * containing instances at the given folder path. - * - * @param folderPath the path at which the retrieved DICOM object instances of the retrieved series will be stored - * @param studyUID the DICOM study uid - * @param seriesUID the DICOM series uid - * @return the task to wait for, which unfolds the name of the first DICOM object file within the folder path - */ - pplx::task SendWADO(utility::string_t folderPath, - utility::string_t studyUID, - utility::string_t seriesUID); - - /** - * @brief Sends a QIDO request containing the given parameters to filter the query. - * - * Example Map: - * - * mitk::RESTUtil::ParamMap seriesInstancesParams; - * seriesInstancesParams.insert(mitk::RESTUtil::ParamMap::value_type(U("limit"), U("1"))); - * - * - * @param map the map of parameters to filter the query - * @return the task to wait for, which unfolds the result JSON response for the request when finished - */ - pplx::task SendQIDO(mitk::RESTUtil::ParamMap map); - - private: - /** - * @brief Creates a QIDO request URI with the given parameter map - */ - utility::string_t CreateQIDOUri(mitk::RESTUtil::ParamMap map); - - /** - * @brief Creates a WADO request URI with the given parameter - */ - utility::string_t CreateWADOUri(utility::string_t studyUID, - utility::string_t seriesUID, - utility::string_t instanceUID); - - /** - * @brief Creates a STOW request URI with the study uid - */ - utility::string_t CreateSTOWUri(utility::string_t studyUID); - - /** - * @brief Initializes the rest manager for this service instance. Should be called in constructor to make sure the - * public API can work properly. - */ - void InitializeRESTManager(); - - utility::string_t m_BaseURI; - mitk::IRESTManager *m_RESTManager; - }; -} - -#endif diff --git a/Modules/DICOMweb/src/mitkDICOMweb.cpp b/Modules/DICOMweb/src/mitkDICOMweb.cpp deleted file mode 100644 index d58a294b0c..0000000000 --- a/Modules/DICOMweb/src/mitkDICOMweb.cpp +++ /dev/null @@ -1,217 +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. - -============================================================================*/ - -#include "mitkDICOMweb.h" - -mitk::DICOMweb::DICOMweb() {} - -mitk::DICOMweb::DICOMweb(utility::string_t baseURI) : m_BaseURI(baseURI) -{ - MITK_INFO << "base uri: " << mitk::RESTUtil::convertToUtf8(m_BaseURI); - InitializeRESTManager(); -} - -utility::string_t mitk::DICOMweb::CreateQIDOUri(mitk::RESTUtil::ParamMap map) -{ - MitkUriBuilder queryBuilder(m_BaseURI + U("rs/instances")); - - for (auto const &element : map) - { - queryBuilder.append_query(element.first, element.second); - } - - return queryBuilder.to_string(); -} - -utility::string_t mitk::DICOMweb::CreateWADOUri(utility::string_t studyUID, - utility::string_t seriesUID, - utility::string_t instanceUID) -{ - MitkUriBuilder builder(m_BaseURI + U("wado")); - builder.append_query(U("requestType"), U("WADO")); - builder.append_query(U("studyUID"), studyUID); - builder.append_query(U("seriesUID"), seriesUID); - builder.append_query(U("objectUID"), instanceUID); - builder.append_query(U("contentType"), U("application/dicom")); - - return builder.to_string(); -} - -utility::string_t mitk::DICOMweb::CreateSTOWUri(utility::string_t studyUID) -{ - MitkUriBuilder builder(m_BaseURI + U("rs/studies")); - builder.append_path(studyUID); - return builder.to_string(); -} - -pplx::task mitk::DICOMweb::SendSTOW(utility::string_t filePath, utility::string_t studyUID) -{ - auto uri = CreateSTOWUri(studyUID); - - // this is the working stow-rs request which supports just one dicom file packed into a multipart message - std::ifstream input(filePath, std::ios::binary); - if (!input) - { - MITK_WARN << "could not read file to POST"; - return pplx::task(); - } - - std::vector result; - std::vector buffer; - - // Stop eating new lines in binary mode!!! - input.unsetf(std::ios::skipws); - - input.seekg(0, std::ios::end); - const std::streampos fileSize = input.tellg(); - input.seekg(0, std::ios::beg); - - MITK_INFO << fileSize << " bytes will be sent."; - buffer.reserve(fileSize); // file size - std::copy( - std::istream_iterator(input), std::istream_iterator(), std::back_inserter(buffer)); - - // in future more than one file should also be supported.. - std::string head = ""; - head += "\r\n--boundary"; - head += "\r\nContent-Type: " + mitk::RESTUtil::convertToUtf8(U("application/dicom")) + "\r\n\r\n"; - - std::vector bodyVector(head.begin(), head.end()); - - std::string tail = ""; - tail += "\r\n--boundary--"; - - result.insert(result.end(), bodyVector.begin(), bodyVector.end()); - result.insert(result.end(), buffer.begin(), buffer.end()); - result.insert(result.end(), tail.begin(), tail.end()); - - mitk::RESTUtil::ParamMap headers; - headers.insert(mitk::RESTUtil::ParamMap::value_type( - U("Content-Type"), U("multipart/related; type=\"application/dicom\"; boundary=boundary"))); - - try - { - return m_RESTManager->SendBinaryRequest(uri, mitk::IRESTManager::RequestType::Post, &result, headers) - .then([=](web::json::value result) { - MITK_INFO << "after send"; - MITK_INFO << mitk::RESTUtil::convertToUtf8(result.serialize()); - result.is_null(); - }); - } - catch (std::exception &e) - { - MITK_WARN << e.what(); - } - - return pplx::task(); -} - -pplx::task mitk::DICOMweb::SendWADO(utility::string_t filePath, - utility::string_t studyUID, - utility::string_t seriesUID, - utility::string_t instanceUID) -{ - auto uri = CreateWADOUri(studyUID, seriesUID, instanceUID); - - // don't want return something - try - { - return m_RESTManager->SendJSONRequest(uri, mitk::IRESTManager::RequestType::Get, nullptr, {}, filePath) - .then([=](web::json::value result) { result.is_null(); }); - } - catch (const mitk::Exception &e) - { - mitkThrow() << e.what(); - } -} - -pplx::task mitk::DICOMweb::SendWADO(utility::string_t folderPath, - utility::string_t studyUID, - utility::string_t seriesUID) -{ - mitk::RESTUtil::ParamMap seriesInstances; - seriesInstances.insert(mitk::RESTUtil::ParamMap::value_type(U("StudyInstanceUID"), studyUID)); - seriesInstances.insert(mitk::RESTUtil::ParamMap::value_type(U("SeriesInstanceUID"), seriesUID)); - - return SendQIDO(seriesInstances).then([=](web::json::value jsonResult) -> pplx::task { - auto jsonListResult = jsonResult; - auto resultArray = jsonListResult.as_array(); - - auto firstFileName = std::string(); - - std::vector> tasks; - - for (unsigned short i = 0; i < resultArray.size(); i++) - { - try - { - auto firstResult = resultArray[i]; - auto sopInstanceUIDKey = firstResult.at(U("00080018")); - auto sopInstanceObject = sopInstanceUIDKey.as_object(); - auto valueKey = sopInstanceObject.at(U("Value")); - auto valueArray = valueKey.as_array(); - auto sopInstanceUID = valueArray[0].as_string(); - - auto fileName = utility::string_t(sopInstanceUID).append(U(".dcm")); - - // save first file name as result to load series - if (i == 0) - { - firstFileName = utility::conversions::to_utf8string(fileName); - } - - auto filePath = utility::string_t(folderPath).append(fileName); - auto task = SendWADO(filePath, studyUID, seriesUID, sopInstanceUID); - tasks.push_back(task); - } - catch (const web::json::json_exception &e) - { - MITK_ERROR << e.what(); - mitkThrow() << e.what(); - } - } - - auto joinTask = pplx::when_all(begin(tasks), end(tasks)); - - auto returnTask = joinTask.then([=](void) -> std::string { - auto folderPathUtf8 = utility::conversions::to_utf8string(folderPath); - auto result = folderPathUtf8 + firstFileName; - - return result; - }); - - return returnTask; - }); -} - -pplx::task mitk::DICOMweb::SendQIDO(mitk::RESTUtil::ParamMap map) -{ - auto uri = CreateQIDOUri(map); - - mitk::RESTUtil::ParamMap headers; - headers.insert(mitk::RESTUtil::ParamMap::value_type(U("Accept"), U("application/json"))); - return m_RESTManager->SendJSONRequest(uri, mitk::IRESTManager::RequestType::Get, nullptr, headers); -} - -void mitk::DICOMweb::InitializeRESTManager() -{ - auto *context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - m_RESTManager = managerService; - } - } -} diff --git a/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp index 7e32fa9b6a..9bf126b717 100644 --- a/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp +++ b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp @@ -1,482 +1,482 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCommandLineParser.h" #include #include #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct Settings { std::string movingFileName = ""; std::string targetFileName = ""; std::string outFileName = ""; std::string algFileName = ""; std::string parameters = ""; }; void SetupParser(mitkCommandLineParser& parser) { parser.setTitle("Match Image"); parser.setCategory("Registration Tools"); parser.setDescription(""); parser.setContributor("MIC, German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); // Add command line argument names parser.beginGroup("Required I/O parameters"); parser.addArgument( "moving", "m", mitkCommandLineParser::File, "Moving image files", "Path to the data that should be registered into the target space.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "target", "t", mitkCommandLineParser::File, "Tareget image files", "Path to the data that should be the target data on which the moving data should be registered.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "algorithm", "a", mitkCommandLineParser::File, "Registration algorithm", "Path to the registration algorithm that should be used for registration.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file path", "Path to the generated registration.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); parser.addArgument( "parameters", "p", mitkCommandLineParser::String, "Parameters", "Json string containing a json object that contains the parameters that should be passed to the algorithm as key value pairs."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); } bool ConfigureApplicationSettings(std::map parsedArgs, Settings& settings) { try { if (parsedArgs.size() == 0) return false; settings.movingFileName = us::any_cast(parsedArgs["moving"]); settings.targetFileName = us::any_cast(parsedArgs["target"]); settings.outFileName = us::any_cast(parsedArgs["output"]); settings.algFileName = us::any_cast(parsedArgs["algorithm"]); if (parsedArgs.count("parameters") > 0) { settings.parameters = us::any_cast(parsedArgs["parameters"]); } } catch (...) { return false; } return true; } map::deployment::RegistrationAlgorithmBasePointer loadAlgorithm(const Settings& settings) { map::deployment::RegistrationAlgorithmBasePointer spAlgorithmBase = nullptr; std::cout << std::endl << "Load registration algorithm..." << std::endl; map::deployment::DLLHandle::Pointer spHandle = nullptr; spHandle = map::deployment::openDeploymentDLL(settings.algFileName); if (spHandle.IsNull()) { mapDefaultExceptionStaticMacro(<< "Cannot open deployed registration algorithm file."); } std::cout << "... library opened..." << std::endl; std::cout << "Algorithm information: " << std::endl; spHandle->getAlgorithmUID().Print(std::cout, 2); std::cout << std::endl; //Now load the algorithm from DLL spAlgorithmBase = map::deployment::getRegistrationAlgorithm(spHandle); if (spAlgorithmBase.IsNotNull()) { std::cout << "... done" << std::endl << std::endl; } else { mapDefaultExceptionStaticMacro(<< "Cannot create algorithm instance"); } return spAlgorithmBase; }; mitk::Image::Pointer ExtractFirstFrame(const mitk::Image* dynamicImage) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(dynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } template map::core::MetaPropertyBase::Pointer CheckCastAndSetProp(const nlohmann::json& value) { map::core::MetaPropertyBase::Pointer prop; try { const auto castedValue = value.get(); prop = map::core::MetaProperty::New(castedValue).GetPointer(); } catch (const std::exception& e) { MITK_ERROR << "Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name() << ". Details: " << e.what(); } catch (...) { - MITK_ERROR << "Unkown error. Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name(); + MITK_ERROR << "Unknown error. Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name(); } return prop; }; template map::core::MetaPropertyBase::Pointer CheckCastAndSetItkArrayProp(const nlohmann::json& valueSequence) { using ArrayType = ::itk::Array; ArrayType castedValue; map::core::MetaPropertyBase::Pointer prop; try { castedValue.SetSize(valueSequence.size()); typename ::itk::Array::SizeValueType index = 0; for (const auto& element : valueSequence) { const auto castedElement = element.template get(); castedValue[index] = castedElement; } prop = map::core::MetaProperty<::itk::Array>::New(castedValue).GetPointer(); } catch (const std::exception& e) { MITK_ERROR << "Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name() << ". Details: " << e.what(); } catch (...) { - MITK_ERROR << "Unkown error. Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name(); + MITK_ERROR << "Unknown error. Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name(); } return prop; }; ::map::core::MetaPropertyBase::Pointer WrapIntoMetaProperty(const ::map::algorithm::MetaPropertyInfo* pInfo, const nlohmann::json& value) { map::core::MetaPropertyBase::Pointer metaProp; if (pInfo == nullptr) { return metaProp; } if (pInfo->getTypeInfo() == typeid(int)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(unsigned int)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(long)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(unsigned long)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(float)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(double)) { metaProp = CheckCastAndSetProp(value); } else if (pInfo->getTypeInfo() == typeid(::itk::Array)) { metaProp = CheckCastAndSetItkArrayProp< double >(value); } else if (pInfo->getTypeInfo() == typeid(bool)) { metaProp = CheckCastAndSetProp< bool >(value); } else if (pInfo->getTypeInfo() == typeid(::map::core::String)) { metaProp = map::core::MetaProperty::New(value).GetPointer(); } return metaProp; }; void OnMapAlgorithmEvent(::itk::Object*, const itk::EventObject& event, void*) { const map::events::AlgorithmEvent* pAlgEvent = dynamic_cast(&event); const map::events::AlgorithmWrapperEvent* pWrapEvent = dynamic_cast(&event); const map::events::InitializingAlgorithmEvent* pInitEvent = dynamic_cast(&event); const map::events::StartingAlgorithmEvent* pStartEvent = dynamic_cast(&event); const map::events::StoppingAlgorithmEvent* pStoppingEvent = dynamic_cast(&event); const map::events::StoppedAlgorithmEvent* pStoppedEvent = dynamic_cast(&event); const map::events::FinalizingAlgorithmEvent* pFinalizingEvent = dynamic_cast(&event); const map::events::FinalizedAlgorithmEvent* pFinalizedEvent = dynamic_cast(&event); if (pInitEvent) { std::cout <<"Initializing algorithm ..." << std::endl; } else if (pStartEvent) { std::cout <<"Starting algorithm ..." << std::endl; } else if (pStoppingEvent) { std::cout <<"Stopping algorithm ..." << std::endl; } else if (pStoppedEvent) { std::cout <<"Stopped algorithm ..." << std::endl; if (!pStoppedEvent->getComment().empty()) { std::cout <<"Stopping condition: " << pStoppedEvent->getComment() << std::endl; } } else if (pFinalizingEvent) { std::cout <<"Finalizing algorithm and results ..." << std::endl; } else if (pFinalizedEvent) { std::cout <<"Finalized algorithm ..." << std::endl; } else if (pAlgEvent && !pWrapEvent) { std::cout << pAlgEvent->getComment() << std::endl; } } int main(int argc, char* argv[]) { std::cout << "MitkMatchImage - Generic light weight image registration tool based on MatchPoint." << std::endl; Settings settings; mitkCommandLineParser parser; SetupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!ConfigureApplicationSettings(parsedArgs, settings)) { MITK_ERROR << "Command line arguments are invalid. To see the correct usage please call with -h or --help to show the help information."; return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } std::cout << std::endl << "*******************************************" << std::endl; std::cout << "Moving file: " << settings.movingFileName << std::endl; std::cout << "Target file: " << settings.targetFileName << std::endl; std::cout << "Output file: " << settings.outFileName << std::endl; std::cout << "Algorithm location: " << settings.algFileName << std::endl; try { auto algorithm = loadAlgorithm(settings); auto command = ::itk::CStyleCommand::New(); command->SetCallback(OnMapAlgorithmEvent); algorithm->AddObserver(::map::events::AlgorithmEvent(), command); auto metaPropInterface = dynamic_cast(algorithm.GetPointer()); if (!settings.parameters.empty()) { if (nullptr == metaPropInterface) { MITK_WARN << "loaded algorithm does not support custom parameterization. Passed user parameters are ignored."; } else { nlohmann::json paramMap; std::string parseError = ""; try { paramMap = nlohmann::json::parse(settings.parameters); } catch (const std::exception& e) { parseError = e.what(); } if (!parseError.empty()) { mitkThrow() << "Cannot parametrize algorithm. Passed JSON parameter string seems to be invalid. Passed string: \"" << settings.parameters << "\". Error details: " << parseError; } std::cout << "Configuring algorithm with user specified parameters ..." << std::endl; for (const auto& [key, val] : paramMap.items()) { const auto info = metaPropInterface->getPropertyInfo(key); if (info.IsNotNull()) { if (info->isWritable()) { std::cout << "Set meta property: " << key << " = " << val << std::endl; ::map::core::MetaPropertyBase::Pointer prop = WrapIntoMetaProperty(info, val); if (prop.IsNull()) { mitkThrow() << "Error. Cannot set specified meta property. Type conversion is not supported or value cannot be converted into type. Property name: " << info->getName() << "; property type: " << info->getTypeName(); } else { metaPropInterface->setProperty(key, prop); } } else { mitkThrow() << "Cannot parametrize algorithm. A passed parameter is not writable for the algorithm. Violating parameter: \"" << key << "\"."; } } else { auto knownProps = metaPropInterface->getPropertyInfos(); std::ostringstream knownPropsNameString; for (const auto& knownProp : knownProps) { knownPropsNameString << knownProp->getName() << "; "; } - mitkThrow() << "Cannot parametrize algorithm. A parameter is unkown to algorithm. Unkown passed parameter: \"" << key << "\". Known parameters: " << knownPropsNameString.str(); + mitkThrow() << "Cannot parametrize algorithm. A parameter is unkown to algorithm. Unknown passed parameter: \"" << key << "\". Known parameters: " << knownPropsNameString.str(); } } } } std::cout << "Load moving data..." << std::endl; auto movingImage = mitk::IOUtil::Load(settings.movingFileName); if (movingImage.IsNull()) { MITK_ERROR << "Cannot load moving image."; return EXIT_FAILURE; } if (movingImage->GetTimeSteps() > 1) { movingImage = mitk::SelectImageByTimeStep(movingImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Moving image has multiple time steps. Use first time step for registration." << std::endl; } std::cout << "Load target data..." << std::endl; auto targetImage = mitk::IOUtil::Load(settings.targetFileName); if (targetImage.IsNull()) { MITK_ERROR << "Cannot load target image."; return EXIT_FAILURE; } if (targetImage->GetTimeSteps() > 1) { targetImage = mitk::SelectImageByTimeStep(targetImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Target image has multiple time steps. Use first time step for registration." << std::endl; } std::cout << "Start registration...." << std::endl; mitk::MAPAlgorithmHelper helper(algorithm); helper.SetData(movingImage, targetImage); ::itk::StdStreamLogOutput::Pointer spStreamLogOutput = ::itk::StdStreamLogOutput::New(); spStreamLogOutput->SetStream(std::cout); map::core::Logbook::addAdditionalLogOutput(spStreamLogOutput); auto registration = helper.GetRegistration(); // wrap the registration in a data node if (registration.IsNull()) { MITK_ERROR << "No valid registration generated"; return EXIT_FAILURE; } auto regWrapper = mitk::MAPRegistrationWrapper::New(registration); std::cout << "Store registration...." << std::endl; mitk::IOUtil::Save(regWrapper, settings.outFileName); } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } return EXIT_SUCCESS; } diff --git a/Modules/ModelFit/cmdapps/CMakeLists.txt b/Modules/ModelFit/cmdapps/CMakeLists.txt index 9ba9628919..bbed8c8fdc 100644 --- a/Modules/ModelFit/cmdapps/CMakeLists.txt +++ b/Modules/ModelFit/cmdapps/CMakeLists.txt @@ -1,34 +1,33 @@ option(BUILD_ModelFitMiniApps "Build commandline tools for the ModelFit module" OFF) if(BUILD_ModelFitMiniApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of miniapps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( miniapps - GenericFittingMiniApp^^ - PixelDumpMiniApp^^ - Fuse3Dto4DImageMiniApp^^ + GenericFitting^^ + PixelDump^^ ) foreach(miniapp ${miniapps}) # extract mini app name and dependencies string(REPLACE "^^" "\\;" miniapp_info ${miniapp}) set(miniapp_info_list ${miniapp_info}) list(GET miniapp_info_list 0 appname) list(GET miniapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkModelFit ${dependencies_list} ) endforeach() endif(BUILD_ModelFitMiniApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp b/Modules/ModelFit/cmdapps/GenericFitting.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/GenericFittingMiniApp.cpp rename to Modules/ModelFit/cmdapps/GenericFitting.cpp diff --git a/Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp b/Modules/ModelFit/cmdapps/PixelDump.cpp similarity index 100% rename from Modules/ModelFit/cmdapps/PixelDumpMiniApp.cpp rename to Modules/ModelFit/cmdapps/PixelDump.cpp diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 117970cb92..54ad857bcd 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,62 +1,59 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Log Core CommandLine CoreCmdApps AppUtil LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOM DICOMQI DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel Chart ImageStatistics ContourModel SurfaceInterpolation BoundingShape Segmentation QtWidgets QtWidgetsExt ImageStatisticsUI SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification QtOverlays DICOMUI Remeshing Python QtPython Persistence RT RTUI IOExt XNAT RenderWindowManagerUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI DICOMPM - REST - RESTService - DICOMweb ROI ) diff --git a/Modules/REST/CMakeLists.txt b/Modules/REST/CMakeLists.txt deleted file mode 100644 index 61b6633c19..0000000000 --- a/Modules/REST/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -set(boost_depends "Boost|date_time+regex+system") - -if(UNIX) - set(boost_depends "${boost_depends}+atomic+chrono+filesystem+random+thread") -endif() - -mitk_create_module( - DEPENDS MitkCore - PACKAGE_DEPENDS PUBLIC cpprestsdk OpenSSL|SSL ${boost_depends} -) - -if(TARGET ${MODULE_TARGET}) - if(MSVC) - #[[ Compiler warnings/errors because of C++ REST SDK's http_msg.h on Visual Studio 2022 version 17.8: - - 'stdext::checked_array_iterator': warning STL4043: stdext::checked_array_iterator, - stdext::unchecked_array_iterator, and related factory functions are non-Standard extensions - and will be removed in the future. std::span (since C++20) and gsl::span can be used instead. - You can define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING or _SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS - to suppress this warning. - ]] - target_compile_definitions(${MODULE_TARGET} PUBLIC _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) - endif() - - add_subdirectory(test) -endif() diff --git a/Modules/REST/documentation/REST.dox b/Modules/REST/documentation/REST.dox deleted file mode 100644 index 9f49f002a1..0000000000 --- a/Modules/REST/documentation/REST.dox +++ /dev/null @@ -1,194 +0,0 @@ -/** - -\page RESTModule REST Module - -\tableofcontents - -\section REST_brief Description -The MITK REST Module is able to manage REST requests. The main class is the RESTManager. -It is a MicroServices which can be accessed via -\code{.cpp} -auto *context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - //call the function you need from the service - } - } -\endcode - -\subsection REST_Technical Technical background - - The module uses the Microsoft C++ REST SDK for REST mechanisms as well as JSON conversion and asynchronic programming. - -\section Use_REST How to use the REST Module - -You can use the REST module from two different perspectives in MITK: - -
    -
  1. The Server view (receive requests from clients) -
  2. The Client view (send requests to servers) -
- -The following sections will give you an introduction on how to use which of those roles: - -\subsection Server_Use Use from a Server perspective -To act as a server, you need to implement the IRESTObserver, which has a Notify() method that has to be implemented. -In this Notify() method you specify how you want to react to incoming requests and with which data you want to respond to the requests. - -You can then start listening for requests from clients as shown below: - -\code{.cpp} -auto *context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - managerService->ReceiveRequests(uri /*specify your uri which you want to receive requests for*/, this); - } - } -\endcode - -If a client sends a request, the Notify method is called and a response is sent. By now, only GET-requests from clients are supported. - -If you want to stop listening for requests you can do this by calling - -\code{.cpp} -auto *context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - managerService->HandleDeleteObserver(this, uri); - } - } -\endcode - -You don't have to specify a uri in the HandleDeleteObserver method, if you only call managerService->HandleDeleteObserver(this);, all uris you receive requests for are deleted and you aren't listening to any requests anymore. - -\subsection Client_Use Use from a Client perspective - -The following example shows how to send requests from a client perspective: - -\code{.cpp} - //Get the microservice - auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - //Call the send request method which starts the actual request - managerService - ->SendRequest(U("https://jsonplaceholder.typicode.com/posts/1")) - .then([=](pplx::task resultTask)/*It is important to use task-based continuation*/ { - try - { - //Get the result of the request - //This will throw an exception if the ascendent task threw an exception (e.g. invalid URI) - web::json::value result = resultTask.get(); - //Do something with the result (e.g. convert it to a QString to update an UI element) - utility::string_t stringT = result.to_string(); - std::string stringStd(stringT.begin(), stringT.end()); - QString stringQ = QString::fromStdString(stringStd); - //Note: if you want to update your UI, do this by using signals and slots. - //The UI can't be updated from a Thread different to the Qt main thread - emit UpdateLabel(stringQ); - } - catch (const mitk::Exception &exception) - { - //Exceptions from ascendent tasks are catched here - MITK_ERROR << exception.what(); - return; - } - }); - } - } -\endcode - -The steps you need to make are the following: -
    -
  1. Get the microservice. You can get the microservice via the module context. If you want to use the microservice within a plug-in, you need to get the module context from the us::ModuleRegistry. -
  2. Call the SendRequest method. This will start the request itself and is performed asynchronously. As soon as the response is sent by the server, the .then(...) block is executed. -
  3. Choose parameters for .then(...) block. For exception handling, it is important to choose pplx::task . This is a task-based continuation. - For more information, visit https://docs.microsoft.com/en-us/cpp/parallel/concrt/exception-handling-in-the-concurrency-runtime?view=vs-2017. -
  4. Get the result of the request. You can get the JSON-value of the result by callint .get(). At this point, an exception is thrown if something in the previous tasks threw an exception. -
  5. Do something with the result. - \note If you want to modify GUI elements within the .then(...) block, you need to do this by using signals and slots because GUI elements can only be modified by th Qt Main Thread. - For more information, visit https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread -
  6. Exception handling. Here you can define the behaviour if an exception is thrown, exceptions from ascendent tasks are also catched here. -
- -Code, which is followed by this codeblock shown above will be performed asynchronously while waiting for the result. -Besides Get-Requests, you can also perform Put or Post requests by specifying a RequestType in the SendRequest method. - -The following example shows, how you can perform multiple tasks, encapsulated to one joined task. The steps are based on the example for one request and only the specific steps for encapsulation are described. - -\code{.cpp} - //Get the microservice - //Get microservice - auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto managerService = context->GetService(managerRef); - if (managerService) - { - //Create multiple tasks e.g. as shown below - std::vector> tasks; - for (int i = 0; i < 20; i++) - { - pplx::task singleTask = managerService->SendRequest(L"https://jsonplaceholder.typicode.com/posts/1") - .then([=](pplx::task resultTask) { - //Do something when a single task is done - try - { - resultTask.get(); - emit UpdateProgressBar(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); - tasks.emplace_back(singleTask); - } - //Create a joinTask which includes all tasks you've created - auto joinTask = pplx::when_all(begin(tasks), end(tasks)); - //Run asynchonously - joinTask.then([=](pplx::task resultTask) { - //Do something when all tasks are finished - try - { - resultTask.get(); - emit UpdateLabel("All tasks finished"); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); - } -\endcode - -The steps you need to make are the following: -
    -
  1. Get the microservice. See example above. -
  2. Create multiple tasks. In this example, 20 identical tasks are created and are saved into a vector. In general, it is possible to place any tasks in that vector. -
  3. Do something when a single task is done. Here, an action is performed if a single tasks is finished. In this example, a progress bar is loaded by a specific number of percent. -
  4. Create a joinTask. Here, all small tasks are encapsulated in one big task. -
  5. Run joinTask asynchonously. The then(...) of the joinTask is performed when all single tasks are finished. -
  6. Do something when all tasks are finished. The handling of the end of a joinTask is equivalent to the end of a single tasks. -
- -*/ diff --git a/Modules/REST/files.cmake b/Modules/REST/files.cmake deleted file mode 100644 index cdb6da57a4..0000000000 --- a/Modules/REST/files.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(CPP_FILES - mitkRESTClient.cpp - mitkRESTServer.cpp - mitkIRESTManager.cpp - mitkIRESTObserver.cpp -) diff --git a/Modules/REST/include/mitkIRESTManager.h b/Modules/REST/include/mitkIRESTManager.h deleted file mode 100644 index 609779776e..0000000000 --- a/Modules/REST/include/mitkIRESTManager.h +++ /dev/null @@ -1,133 +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 mitkIRESTManager_h -#define mitkIRESTManager_h - -#include - -#include -#include - -#include -#include -#include - -namespace mitk -{ - class IRESTObserver; - class RESTServer; - - /** - * @class IRESTManager - * @brief This is a microservice interface for managing REST requests. - */ - class MITKREST_EXPORT IRESTManager - { - public: - virtual ~IRESTManager(); - - /** - * @brief request type for client requests by calling SendRequest - */ - enum class RequestType - { - Get, - Post, - Put - }; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @throw mitk::Exception if RequestType is not supported - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param headers the headers for the request (optional) - * @return task to wait for - */ - virtual pplx::task SendRequest( - const web::uri &uri, - const RequestType &type = RequestType::Get, - const std::map headers = {}) = 0; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param body the body for the request (optional) - * @param headers the headers for the request (optional) - * @param filePath the file path to store the request to (optional) - * @return task to wait for - */ - virtual pplx::task SendJSONRequest( - const web::uri &uri, - const RequestType &type = RequestType::Get, - const web::json::value *body = nullptr, - const std::map headers = {}, - const utility::string_t &filePath = {} - ) = 0; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param body the body for the request (optional) - * @param headers the headers for the request (optional) - * @return task to wait for - */ - virtual pplx::task SendBinaryRequest(const web::uri &uri, - const RequestType &type = RequestType::Get, - const std::vector *body = {}, - const std::map headers = {}) = 0; - - /** - * @brief starts listening for requests if there isn't another observer listening and the port is free - * - * @param uri defines the URI for which incoming requests should be send to the observer - * @param observer the observer which handles the incoming requests - */ - virtual void ReceiveRequest(const web::uri &uri, IRESTObserver *observer) = 0; - - /** - * @brief Handles incoming requests by notifying the observer which should receive it - * - * @param uri defines the URI of the request - * @param body the body of the request - * @param method the http method of the request - * @param headers the http headers of the request - * @return the response - */ - virtual web::http::http_response Handle(const web::uri &uri, - const web::json::value &body, - const web::http::method &method, - const mitk::RESTUtil::ParamMap &headers) = 0; - - /** - * @brief Handles the deletion of an observer for all or a specific uri - * - * @param observer the observer which shouldn't receive requests anymore - * @param uri the uri for which the observer doesn't handle requests anymore (optional) - */ - virtual void HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri = {}) = 0; - - virtual const std::map& GetServerMap() = 0; - virtual const std::map, IRESTObserver *>& GetObservers() = 0; - - }; -} - -MITK_DECLARE_SERVICE_INTERFACE(mitk::IRESTManager, "org.mitk.IRESTManager") - -#endif diff --git a/Modules/REST/include/mitkIRESTObserver.h b/Modules/REST/include/mitkIRESTObserver.h deleted file mode 100644 index db3330d055..0000000000 --- a/Modules/REST/include/mitkIRESTObserver.h +++ /dev/null @@ -1,53 +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 mitkIRESTObserver_h -#define mitkIRESTObserver_h - -#include -#include -#include -#include -#include - -namespace mitk -{ - class MITKREST_EXPORT IRESTObserver - { - public: - /** - * @brief Deletes an observer and calls HandleDeleteObserver() in RESTManager class - * - * @see HandleDeleteObserver() - */ - virtual ~IRESTObserver(); - - /** - * @brief Called if there's an incoming request for the observer, observer implements how to handle request - * - * @param uri - * @param data the data of the incoming request - * @param method the http method of the incoming request - * @param headers - * @return the modified data - */ - virtual web::http::http_response Notify(const web::uri &uri, - const web::json::value &data, - const web::http::method &method, - const mitk::RESTUtil::ParamMap &headers) = 0; - - - private: - }; -} - -#endif diff --git a/Modules/REST/include/mitkRESTClient.h b/Modules/REST/include/mitkRESTClient.h deleted file mode 100644 index 075e826924..0000000000 --- a/Modules/REST/include/mitkRESTClient.h +++ /dev/null @@ -1,105 +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 mitkRESTClient_h -#define mitkRESTClient_h - -#include -#include - -namespace mitk -{ - class MITKREST_EXPORT RESTClient - { - public: - using http_request = web::http::http_request; - RESTClient(); - ~RESTClient(); - - /** - * @brief Executes a HTTP GET request with the given uri and returns a task waiting for a json object - * - * @throw mitk::Exception if request went wrong - * @param uri the URI resulting the target of the HTTP request - * @param headers the additional headers to be set to the HTTP request - * @return task to wait for with resulting json object - */ - pplx::task Get(const web::uri &uri, const std::map headers); - - /** - * @brief Executes a HTTP GET request with the given uri and and stores the byte stream in a file given by the - * filePath - * - * @throw mitk::Exception if request went wrong - * @param uri the URI resulting the target of the HTTP request - * @param filePath - * @param headers the additional headers to be set to the HTTP request - * @return task to wait for returning an empty json object - */ - pplx::task Get(const web::uri &uri, - const utility::string_t &filePath, - const std::map headers); - - /** - * @brief Executes a HTTP PUT request with given uri and the content given as json - * - * @throw mitk::Exception if request went wrong - * @param uri defines the URI resulting the target of the HTTP request - * @param content the content as json value which should be the body of the request and thus the content of the - * created resources - * @return task to wait for with resulting json object - */ - pplx::task Put(const web::uri &uri, const web::json::value *content); - - /** - * @brief Executes a HTTP POST request with given uri and the content given as json - * - * @throw mitk::Exception if request went wrong - * @param uri defines the URI resulting the target of the HTTP request - * @param content the content as json value which should be the body of the request and thus the content of the - * created resource - * @param headers the additional headers to be set to the HTTP request - * @return task to wait for with resulting json object - */ - pplx::task Post(const web::uri &uri, - const web::json::value *content, - const std::map headers); - - /** - * @brief Executes a HTTP POST request with given uri and the content given as json - * - * @throw mitk::Exception if request went wrong - * @param uri defines the URI resulting the target of the HTTP request - * @param content the content as json value which should be the body of the request and thus the content of the - * created resource - * @param headers the additional headers to be set to the HTTP request - * @return task to wait for with resulting json object - */ - pplx::task Post(const web::uri &uri, - const std::vector *content, - const std::map headers); - - private: - /** - * @brief Use this to create and init a new request with the given headers. If needed, set the body on the resulting - * request object to avoid an automatic change of the content type header when setting the body first. - */ - http_request InitRequest(const std::map headers); - - void CheckResponseContentType(web::http::http_response &response); - - pplx::task ExecutePost(const web::uri &uri, http_request request); - web::http::client::http_client_config m_ClientConfig; - }; -} // namespace mitk - -#endif diff --git a/Modules/REST/include/mitkRESTServer.h b/Modules/REST/include/mitkRESTServer.h deleted file mode 100644 index 429f0b687d..0000000000 --- a/Modules/REST/include/mitkRESTServer.h +++ /dev/null @@ -1,54 +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 mitkRESTServer_h -#define mitkRESTServer_h - -#include -#include - -namespace mitk -{ - class MITKREST_EXPORT RESTServer - { - public: - /** - * @brief Creates an server listening to the given URI - * - * @param uri the URI at which the server is listening for requests - */ - RESTServer(const web::uri &uri); - ~RESTServer(); - - web::uri GetUri(); - - /** - * @brief Opens the listener and starts the listening process - */ - void OpenListener(); - - /** - * @brief Closes the listener and stops the listening process - */ - void CloseListener(); - private: - /** - * @brief Handle for incoming GET requests - * - * @param MitkRequest incoming request object - */ - class Impl; - std::unique_ptr m_Impl; - }; -} - -#endif diff --git a/Modules/REST/include/mitkRESTUtil.h b/Modules/REST/include/mitkRESTUtil.h deleted file mode 100644 index 22b0fed593..0000000000 --- a/Modules/REST/include/mitkRESTUtil.h +++ /dev/null @@ -1,47 +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 mitkRESTUtil_h -#define mitkRESTUtil_h - -#include -#include -#include - - -namespace mitk -{ - class MITKREST_EXPORT RESTUtil - { - public: - - typedef std::map ParamMap; - - /** - * @brief Converts the given std::wstring into a std::string representation - */ - static std::string convertToUtf8(const utility::string_t &string) - { - return utility::conversions::to_utf8string(string); - } - - /** - * @brief Converts the given std::string into a std::wstring representation - */ - static utility::string_t convertToTString(const std::string &string) - { - return utility::conversions::to_string_t(string); - } - }; -} - -#endif diff --git a/Modules/REST/src/mitkIRESTManager.cpp b/Modules/REST/src/mitkIRESTManager.cpp deleted file mode 100644 index b26b7dc1b0..0000000000 --- a/Modules/REST/src/mitkIRESTManager.cpp +++ /dev/null @@ -1,17 +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. - -============================================================================*/ - -#include - -mitk::IRESTManager::~IRESTManager() -{ -} diff --git a/Modules/REST/src/mitkIRESTObserver.cpp b/Modules/REST/src/mitkIRESTObserver.cpp deleted file mode 100644 index c60342304f..0000000000 --- a/Modules/REST/src/mitkIRESTObserver.cpp +++ /dev/null @@ -1,29 +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. - -============================================================================*/ - -#include -#include - -#include -#include - -mitk::IRESTObserver::~IRESTObserver() -{ - auto context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - if (managerRef) - { - auto manager = context->GetService(managerRef); - if (manager) - manager->HandleDeleteObserver(this); - } -} diff --git a/Modules/REST/src/mitkRESTClient.cpp b/Modules/REST/src/mitkRESTClient.cpp deleted file mode 100644 index 11c66a519f..0000000000 --- a/Modules/REST/src/mitkRESTClient.cpp +++ /dev/null @@ -1,203 +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. - -============================================================================*/ - -#include -#include -#include - -#include -#include - -using http_client = web::http::client::http_client; -using http_request = web::http::http_request; -using http_response = web::http::http_response; -using methods = web::http::methods; -using status_codes = web::http::status_codes; -using file_buffer = concurrency::streams::file_buffer; -using streambuf = concurrency::streams::streambuf; - -mitk::RESTClient::RESTClient() -{ - m_ClientConfig.set_validate_certificates(false); -} - -mitk::RESTClient::~RESTClient() {} - -void mitk::RESTClient::CheckResponseContentType(web::http::http_response &response) -{ - auto status = response.status_code(); - - if (status_codes::OK != status) - { - MITK_WARN << "Status: " << status; - MITK_WARN << "Response: " << mitk::RESTUtil::convertToUtf8(response.to_string()); - mitkThrow() << mitk::RESTUtil::convertToUtf8(response.to_string()); - } - - auto requestContentType = response.headers().content_type(); - MITK_DEBUG << "Content Type: " << mitk::RESTUtil::convertToUtf8(requestContentType); - MITK_DEBUG << "Body: " << mitk::RESTUtil::convertToUtf8(response.to_string()); - if (requestContentType.find(U("json")) != std::wstring::npos) - { - MITK_DEBUG << "Caution! The given response content type was '" << mitk::RESTUtil::convertToUtf8(requestContentType) - << "' but contains 'json'. So we awesome the answer actually contains a JSON message."; - response.headers().set_content_type(U("application/json")); - } -} - -pplx::task mitk::RESTClient::Get(const web::uri &uri, - const std::map headers) -{ - auto client = new http_client(uri, m_ClientConfig); - http_request request; - - for (auto param : headers) - { - request.headers().add(param.first, param.second); - } - - return client->request(request).then([=](pplx::task responseTask) { - try - { - auto response = responseTask.get(); - - CheckResponseContentType(response); - - return response.extract_json().get(); - } - catch (const std::exception &e) - { - MITK_INFO << e.what(); - mitkThrow() << "Getting response went wrong: " << e.what(); - } - }); -} - -pplx::task mitk::RESTClient::Get(const web::uri &uri, - const utility::string_t &filePath, - const std::map headers) -{ - auto client = new http_client(uri, m_ClientConfig); - auto fileBuffer = std::make_shared>(); - http_request request; - - for (auto param : headers) - { - request.headers().add(param.first, param.second); - } - - // Open file stream for the specified file path - return file_buffer::open(filePath, std::ios::out) - .then([=](streambuf outFile) -> pplx::task { - *fileBuffer = outFile; - return client->request(methods::GET); - }) - // Write the response body into the file buffer - .then([=](http_response response) -> pplx::task { - auto status = response.status_code(); - - if (status_codes::OK != status) - { - MITK_INFO << status; - MITK_INFO << mitk::RESTUtil::convertToUtf8(response.to_string()); - mitkThrow() << mitk::RESTUtil::convertToUtf8(response.to_string()); - } - - return response.body().read_to_end(*fileBuffer); - }) - // Close the file buffer - .then([=](size_t) { return fileBuffer->close(); }) - // Return empty JSON object - .then([=]() { return web::json::value(); }); -} - -pplx::task mitk::RESTClient::Put(const web::uri &uri, const web::json::value *content) -{ - auto client = new http_client(uri, m_ClientConfig); - http_request request(methods::PUT); - - if (nullptr != content) - request.set_body(*content); - - return client->request(request).then([=](pplx::task responseTask) { - try - { - auto response = responseTask.get(); - - CheckResponseContentType(response); - - return response.extract_json().get(); - } - catch (std::exception &e) - { - MITK_INFO << e.what(); - mitkThrow() << "Getting response went wrong"; - } - }); -} - -pplx::task mitk::RESTClient::Post(const web::uri &uri, - const std::vector *content, - const std::map headers) -{ - auto request = InitRequest(headers); - request.set_method(methods::POST); - - if (nullptr != content) - request.set_body(*content); - - return ExecutePost(uri, request); -} - -pplx::task mitk::RESTClient::Post(const web::uri &uri, - const web::json::value *content, - const std::map headers) -{ - auto request = InitRequest(headers); - request.set_method(methods::POST); - - if (nullptr != content) - request.set_body(*content); - - return ExecutePost(uri, request); -} - -http_request mitk::RESTClient::InitRequest(const std::map headers) -{ - http_request request; - - for (auto param : headers) - { - request.headers().add(param.first, param.second); - } - return request; -} - -pplx::task mitk::RESTClient::ExecutePost(const web::uri &uri, http_request request) -{ - auto client = new http_client(uri, m_ClientConfig); - return client->request(request).then([=](pplx::task responseTask) { - try - { - auto response = responseTask.get(); - - CheckResponseContentType(response); - - return response.extract_json().get(); - } - catch (std::exception &e) - { - MITK_INFO << e.what(); - mitkThrow() << "Getting response went wrong"; - } - }); -} diff --git a/Modules/REST/src/mitkRESTServer.cpp b/Modules/REST/src/mitkRESTServer.cpp deleted file mode 100644 index 64e94fdb89..0000000000 --- a/Modules/REST/src/mitkRESTServer.cpp +++ /dev/null @@ -1,109 +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. - -============================================================================*/ - -#include -#include - -#include -#include - -#include - -using namespace std::placeholders; - -using http_listener = web::http::experimental::listener::http_listener; -using http_request = web::http::http_request; -using http_response = web::http::http_response; -using methods = web::http::methods; -using status_codes = web::http::status_codes; - -namespace mitk -{ - class RESTServer::Impl - { - public: - Impl(const web::uri &uri); - ~Impl(); - - void HandleRequest(const http_request &request); - - web::http::experimental::listener::http_listener listener; - web::uri uri; - }; - - RESTServer::Impl::Impl(const web::uri &uri) : uri{uri} {} - - RESTServer::Impl::~Impl() {} - - void RESTServer::Impl::HandleRequest(const http_request &request) - { - web::uri_builder builder(this->listener.uri()); - builder.append(request.absolute_uri()); - - auto uriString = builder.to_uri().to_string(); - - http_response response(status_codes::InternalError); - response.set_body(U("There went something wrong after receiving the request.")); - - auto context = us::GetModuleContext(); - auto managerRef = context->GetServiceReference(); - - if (managerRef) - { - auto manager = context->GetService(managerRef); - if (manager) - { - // not every request contains JSON data - web::json::value data = {}; - if (request.headers().content_type() == U("application/json")) - { - data = request.extract_json().get(); - } - - mitk::RESTUtil::ParamMap headers; - auto begin = request.headers().begin(); - auto end = request.headers().end(); - - for (; begin != end; ++begin) - { - headers.insert(mitk::RESTUtil::ParamMap::value_type(begin->first, begin->second)); - } - - response = manager->Handle(builder.to_uri(), data, request.method(), headers); - } - } - - request.reply(response); - } -} // namespace mitk - -mitk::RESTServer::RESTServer(const web::uri &uri) : m_Impl{std::make_unique(uri)} {} - -mitk::RESTServer::~RESTServer() {} - -void mitk::RESTServer::OpenListener() -{ - m_Impl->listener = http_listener(m_Impl->uri); - m_Impl->listener.support(std::bind(&Impl::HandleRequest, m_Impl.get(), _1)); - m_Impl->listener.support(methods::OPTIONS, std::bind(&Impl::HandleRequest, m_Impl.get(), _1)); - m_Impl->listener.open().wait(); -} - -void mitk::RESTServer::CloseListener() -{ - m_Impl->listener.close().wait(); -} - -web::uri mitk::RESTServer::GetUri() -{ - return m_Impl->uri; -} diff --git a/Modules/REST/test/CMakeLists.txt b/Modules/REST/test/CMakeLists.txt deleted file mode 100644 index 2e7b24b2f7..0000000000 --- a/Modules/REST/test/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -mitk_create_module_tests() -set_tests_properties(mitkRESTClientTest mitkRESTServerTest mitkRESTServerHttpLibTest PROPERTIES RUN_SERIAL TRUE) diff --git a/Modules/REST/test/files.cmake b/Modules/REST/test/files.cmake deleted file mode 100644 index 57628aaee3..0000000000 --- a/Modules/REST/test/files.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(MODULE_TESTS - mitkRESTServerHttpLibTest.cpp - mitkRESTClientTest.cpp - mitkRESTServerTest.cpp -) \ No newline at end of file diff --git a/Modules/REST/test/mitkRESTClientTest.cpp b/Modules/REST/test/mitkRESTClientTest.cpp deleted file mode 100644 index 7acdbc44b3..0000000000 --- a/Modules/REST/test/mitkRESTClientTest.cpp +++ /dev/null @@ -1,264 +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. - -============================================================================*/ - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -class mitkRESTClientTestSuite : public mitk::TestFixture, mitk::IRESTObserver -{ - CPPUNIT_TEST_SUITE(mitkRESTClientTestSuite); - // MITK_TEST(GetRequestValidURI_ReturnsExpectedJSON); GET requests do not support content yet? - MITK_TEST(MultipleGetRequestValidURI_AllTasksFinish); - // MITK_TEST(PutRequestValidURI_ReturnsExpectedJSON); Does not work reliably on dart clients - // MITK_TEST(PostRequestValidURI_ReturnsExpectedJSON); -- " -- - MITK_TEST(GetRequestInvalidURI_ThrowsException); - MITK_TEST(PutRequestInvalidURI_ThrowsException); - MITK_TEST(PostRequestInvalidURI_ThrowsException); - CPPUNIT_TEST_SUITE_END(); - -public: - mitk::IRESTManager *m_Service; - web::json::value m_Data; - - web::http::http_response Notify(const web::uri &, - const web::json::value &, - const web::http::method &, - const mitk::RESTUtil::ParamMap &) override - { - auto response = web::http::http_response(); - response.set_body(m_Data); - response.set_status_code(web::http::status_codes::OK); - - return response; - } - - /** - * @brief Setup Always call this method before each Test-case to ensure correct and new initialization of the used - * members for a new test case. (If the members are not used in a test, the method does not need to be called). - */ - void setUp() override - { - m_Data = web::json::value(); - m_Data[U("userId")] = web::json::value(1); - m_Data[U("id")] = web::json::value(1); - m_Data[U("title")] = web::json::value(U("this is a title")); - m_Data[U("body")] = web::json::value(U("this is a body")); - - us::ServiceReference serviceRef = - us::GetModuleContext()->GetServiceReference(); - if (serviceRef) - { - m_Service = us::GetModuleContext()->GetService(serviceRef); - } - - if (!m_Service) - { - CPPUNIT_FAIL("Getting Service in setUp() failed"); - } - - m_Service->ReceiveRequest(U("http://localhost:8080/clienttest"), this); - } - - void tearDown() override { m_Service->HandleDeleteObserver(this); } - - void GetRequestValidURI_ReturnsExpectedJSON() - { - web::json::value result; - - m_Service->SendRequest(U("http://localhost:8080/clienttest")) - .then([&](pplx::task resultTask) { - try - { - result = resultTask.get(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }) - .wait(); - - CPPUNIT_ASSERT_MESSAGE("Result is the expected JSON value", result == m_Data); - } - - void MultipleGetRequestValidURI_AllTasksFinish() - { - std::atomic count {0}; - - // Create multiple tasks e.g. as shown below - std::vector> tasks; - for (int i = 0; i < 20; ++i) - { - pplx::task singleTask = m_Service->SendRequest(U("http://localhost:8080/clienttest")) - .then([&](pplx::task resultTask) { - // Do something when a single task is done - try - { - resultTask.get(); - count += 1; - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); - tasks.emplace_back(singleTask); - } - // Create a joinTask which includes all tasks you've created - auto joinTask = pplx::when_all(begin(tasks), end(tasks)); - // Run asynchonously - joinTask - .then([&](pplx::task resultTask) { - // Do something when all tasks are finished - try - { - resultTask.get(); - count += 1; - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }) - .wait(); - - CPPUNIT_ASSERT_MESSAGE("Multiple Requests", 21 == count); - } - - void PutRequestValidURI_ReturnsExpectedJSON() - { - // optional: link might get invalid or content is changed - web::json::value result; - - m_Service - ->SendJSONRequest( - U("https://jsonplaceholder.typicode.com/posts/1"), mitk::IRESTManager::RequestType::Put) - .then([&](pplx::task resultTask) { - try - { - result = resultTask.get(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }) - .wait(); - - CPPUNIT_ASSERT_MESSAGE( - "Result is the expected JSON value, check if the link is still valid since this is an optional test", - result == m_Data); - } - - void PostRequestValidURI_ReturnsExpectedJSON() - { - // optional: link might get invalid or content is changed - web::json::value result; - web::json::value data; - - data[U("userId")] = m_Data[U("userId")]; - data[U("title")] = m_Data[U("title")]; - data[U("body")] = m_Data[U("body")]; - - m_Service - ->SendJSONRequest(U("https://jsonplaceholder.typicode.com/posts"), mitk::IRESTManager::RequestType::Post, &data) - .then([&](pplx::task resultTask) { - try - { - result = resultTask.get(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }) - .wait(); - - data[U("id")] = web::json::value(101); - CPPUNIT_ASSERT_MESSAGE( - "Result is the expected JSON value, check if the link is still valid since this is an optional test", - result == data); - } - - void PostRequestHeaders_Success() - { - mitk::RESTUtil::ParamMap headers; - headers.insert(mitk::RESTUtil::ParamMap::value_type( - U("Content-Type"), U("multipart/related; type=\"application/dicom\"; boundary=boundary"))); - - m_Service->SendRequest(U("http://localhost:8080/clienttest")).then([&](pplx::task resultTask) { - // Do something when a single task is done - try - { - resultTask.get(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); - } - - void GetException() - { - // Method which makes a get request to an invalid uri - web::json::value result; - - m_Service->SendRequest(U("http://localhost:1234/invalid")) - .then([&](pplx::task resultTask) { result = resultTask.get(); }) - .wait(); - } - void GetRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(GetException(), mitk::Exception); } - - void PutException() - { - // Method which makes a put request to an invalid uri - web::json::value result; - - m_Service->SendJSONRequest(U("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Put, &m_Data) - .then([&](pplx::task resultTask) { result = resultTask.get(); }) - .wait(); - } - void PutRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(PutException(), mitk::Exception); } - - void PostException() - { - // Method which makes a post request to an invalid uri - web::json::value result; - - m_Service->SendJSONRequest(U("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Post, &m_Data) - .then([&](pplx::task resultTask) { result = resultTask.get(); }) - .wait(); - } - void PostRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(PostException(), mitk::Exception); } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkRESTClient) diff --git a/Modules/REST/test/mitkRESTServerHttpLibTest.cpp b/Modules/REST/test/mitkRESTServerHttpLibTest.cpp deleted file mode 100644 index 8d0f63f3e2..0000000000 --- a/Modules/REST/test/mitkRESTServerHttpLibTest.cpp +++ /dev/null @@ -1,106 +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. - -============================================================================*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -class mitkRESTServerHttpLibTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkRESTServerHttpLibTestSuite); - MITK_TEST(OpenListener_Succeed); - MITK_TEST(OpenListenerGetRequestSamePath_ReturnExpectedJSON); - CPPUNIT_TEST_SUITE_END(); - std::vector> m_ft; - -public: - void OpenListener_Succeed() - { - httplib::Server svr; - svr.Get("/get", - [](const httplib::Request &, httplib::Response &res) - { res.set_content("Hello World from MITK!", "text/plain"); }); - std::future ft = std::async(std::launch::async, [&]() { svr.listen("localhost", 8080); }); - while (!svr.is_running()) - { - std::this_thread::sleep_for(100ms); - } - CPPUNIT_ASSERT_MESSAGE("Server is running", svr.is_running()); - svr.stop(); - } - - void OpenListenerGetRequestSamePath_ReturnExpectedJSON() - { - httplib::Server svr; - std::map msgdb; - msgdb[0] = "hello_MITK"; - - svr.Get(R"(/msg/(\d+))", - [&](const httplib::Request &req, httplib::Response &res) - { - auto n = req.matches[1]; - nlohmann::json jRes; - try - { - jRes["id"] = std::stoi(n); - jRes["msg"] = msgdb[std::stoi(n)]; - - res.set_content(jRes.dump(), "application/json"); - } - catch (const std::exception &) - { - res.set_content("Cannot find the requested message.", "text/plain"); - } - }); - std::future ft = std::async(std::launch::async, [&]() { svr.listen("localhost", 8080); }); - while (!svr.is_running()) - { - std::this_thread::sleep_for(100ms); - } - httplib::Client cli("http://localhost:8080"); - try - { - if (auto response = cli.Get("/msg/0")) - { - if (response->status == 200) - { - auto js = nlohmann::json::parse(response->body); - std::string msg = js["msg"]; - CPPUNIT_ASSERT_MESSAGE("Result is the expected JSON value", msg == msgdb[0]); - } - else - { - CPPUNIT_ASSERT_MESSAGE("A connection cannot be established", false); - } - } - else - { - CPPUNIT_ASSERT_MESSAGE("A connection cannot be established", false); - } - } - catch (const std::exception &e) - { - svr.stop(); - CPPUNIT_ASSERT_MESSAGE(e.what(), false); - } - svr.stop(); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkRESTServerHttpLib) diff --git a/Modules/REST/test/mitkRESTServerTest.cpp b/Modules/REST/test/mitkRESTServerTest.cpp deleted file mode 100644 index 22f1fe65cf..0000000000 --- a/Modules/REST/test/mitkRESTServerTest.cpp +++ /dev/null @@ -1,252 +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. - -============================================================================*/ - -#ifdef _WIN32 -#include -#endif - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - -class mitkRESTServerTestSuite : public mitk::TestFixture, mitk::IRESTObserver -{ - CPPUNIT_TEST_SUITE(mitkRESTServerTestSuite); - MITK_TEST(OpenListener_Succeed); - MITK_TEST(TwoListenerSameHostSamePort_OnlyOneOpened); - MITK_TEST(CloseListener_Succeed); - MITK_TEST(OpenMultipleListenerCloseOne_Succeed); - MITK_TEST(OpenMultipleListenerCloseAll_Succeed); - // MITK_TEST(OpenListenerGetRequestSamePath_ReturnExpectedJSON); GET requests do not support content yet? - MITK_TEST(CloseListener_NoRequestPossible); - MITK_TEST(OpenListenerGetRequestDifferentPath_ReturnNotFound); - MITK_TEST(OpenListenerCloseAndReopen_Succeed); - MITK_TEST(HandleHeader_Succeed); - CPPUNIT_TEST_SUITE_END(); - -public: - mitk::IRESTManager *m_Service; - web::json::value m_Data; - - web::http::http_response Notify(const web::uri &, - const web::json::value &, - const web::http::method &, - const mitk::RESTUtil::ParamMap &headers) override - { - auto response = web::http::http_response(); - response.set_body(m_Data); - mitk::RESTUtil::ParamMap::const_iterator contentTypePos = headers.find(U("Content-Type")); - if (contentTypePos != headers.end() && contentTypePos->second == U("awesome/type")) - { - m_Data[U("result")] = web::json::value(U("awesome/type")); - } - - return response; - } - - /** - * @brief Setup Always call this method before each Test-case to ensure correct and new initialization of the used - * members for a new test case. (If the members are not used in a test, the method does not need to be called). - */ - void setUp() override - { - m_Data = web::json::value(); - m_Data[U("userId")] = web::json::value(1); - m_Data[U("id")] = web::json::value(1); - m_Data[U("title")] = web::json::value(U("this is a title")); - m_Data[U("body")] = web::json::value(U("this is a body")); - - auto serviceRef = us::GetModuleContext()->GetServiceReference(); - - if (serviceRef) - m_Service = us::GetModuleContext()->GetService(serviceRef); - - if (!m_Service) - CPPUNIT_FAIL("Getting Service in setUp() failed"); - } - - void tearDown() override { m_Service->HandleDeleteObserver(this); } - - void OpenListener_Succeed() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); - } - - void TwoListenerSameHostSamePort_OnlyOneOpened() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(U("http://localhost:8080/serverexample"), this); - - CPPUNIT_ASSERT_MESSAGE("Open two listener with a different path,same host, same port, observer map size is two", - 2 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open two listener with a different path,same host, same port, server map size is one", - 1 == m_Service->GetServerMap().size()); - } - - void CloseListener_Succeed() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); - - m_Service->HandleDeleteObserver(this); - - CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); - } - - void OpenMultipleListenerCloseOne_Succeed() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(U("http://localhost:8090/serverexample"), this); - - CPPUNIT_ASSERT_MESSAGE("Open two listener, observer map size is two", 2 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open two listener, server map size is two", 2 == m_Service->GetServerMap().size()); - - m_Service->HandleDeleteObserver(this, U("http://localhost:8080/servertest")); - - CPPUNIT_ASSERT_MESSAGE("Closed one of two listeners, observer map is size is one", - 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed one of two listener, server map size is one", 1 == m_Service->GetServerMap().size()); - } - - void OpenMultipleListenerCloseAll_Succeed() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(U("http://localhost:8090/serverexample"), this); - - CPPUNIT_ASSERT_MESSAGE("Open two listener, observer map size is two", 2 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open two listener, server map size is two", 2 == m_Service->GetServerMap().size()); - - m_Service->HandleDeleteObserver(this); - - CPPUNIT_ASSERT_MESSAGE("Closed all listeners, observer map is empty", 0 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed all listeners, server map is empty", 0 == m_Service->GetServerMap().size()); - } - - void OpenListenerGetRequestSamePath_ReturnExpectedJSON() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - web::json::value result; - auto body = web::json::value(); - m_Service->SendRequest(U("http://localhost:8080/servertest")) - .then([&](pplx::task resultTask) { - try - { - result = resultTask.get(); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }) - .wait(); - - CPPUNIT_ASSERT_MESSAGE("Opened listener and send request to same uri, returned expected JSON", result == m_Data); - } - - void RequestToClosedListener() - { - web::json::value result; - - m_Service->SendRequest(U("http://localhost:8080/servertest")) - .then([&](pplx::task resultTask) { result = resultTask.get(); }) - .wait(); - } - - void CloseListener_NoRequestPossible() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); - - m_Service->HandleDeleteObserver(this); - - CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); - - CPPUNIT_ASSERT_THROW(RequestToClosedListener(), mitk::Exception); - } - - void RequestToDifferentPathNotFound() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - web::json::value result; - - m_Service->SendRequest(U("http://localhost:8080/serverexample")) - .then([&](pplx::task resultTask) { result = resultTask.get(); }) - .wait(); - } - - void OpenListenerGetRequestDifferentPath_ReturnNotFound() - { - CPPUNIT_ASSERT_THROW(RequestToDifferentPathNotFound(), mitk::Exception); - } - - void OpenListenerCloseAndReopen_Succeed() - { - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); - - m_Service->HandleDeleteObserver(this); - - CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); - - m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); - - CPPUNIT_ASSERT_MESSAGE("Reopened listener, observer map size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Reopened listener, server map size is one", 1 == m_Service->GetServerMap().size()); - } - - void HandleHeader_Succeed() - { - mitk::RESTUtil::ParamMap headers; - headers.insert(mitk::RESTUtil::ParamMap::value_type(U("Content-Type"), U("awesome/type"))); - - m_Service->SendRequest(U("http://localhost:8080/clienttest")).then([&](pplx::task resultTask) { - // Do something when a single task is done - try - { - auto result = resultTask.get(); - CPPUNIT_ASSERT_MESSAGE("Sent Header is not successful transferred to server", - result[U("result")].as_string() == U("awesome/type")); - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkRESTServer) diff --git a/Modules/RESTService/CMakeLists.txt b/Modules/RESTService/CMakeLists.txt deleted file mode 100644 index 7a43698da8..0000000000 --- a/Modules/RESTService/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -if(MITK_USE_cpprestsdk) - - mitk_create_module( - DEPENDS MitkREST - AUTOLOAD_WITH MitkCore - ) - -endif() diff --git a/Modules/RESTService/files.cmake b/Modules/RESTService/files.cmake deleted file mode 100644 index df7a46d0bc..0000000000 --- a/Modules/RESTService/files.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(CPP_FILES - mitkRESTServiceActivator.cpp - mitkRESTManager.cpp -) diff --git a/Modules/RESTService/include/mitkRESTManager.h b/Modules/RESTService/include/mitkRESTManager.h deleted file mode 100644 index 2e62c12ea7..0000000000 --- a/Modules/RESTService/include/mitkRESTManager.h +++ /dev/null @@ -1,153 +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 mitkRESTManager_h -#define mitkRESTManager_h - -#include -#include -#include - -namespace mitk -{ - /** - * @class RESTManager - * @brief this is a microservice for managing REST-requests, used for non-qt applications. - * - * RESTManagerQt in the CppRestSdkQt module inherits from this class and is the equivalent microservice - * used for Qt applications. - */ - class MITKRESTSERVICE_EXPORT RESTManager : public IRESTManager - { - public: - RESTManager(); - ~RESTManager() override; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @throw mitk::Exception if RequestType is not supported - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param headers the headers for the request (optional) - * @return task to wait for - */ - pplx::task SendRequest( - const web::uri &uri, - const RequestType &type = RequestType::Get, - const std::map headers = {}) override; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @throw mitk::Exception if RequestType is not supported - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param body the body for the request (optional) - * @param headers the headers for the request (optional) - * @param filePath the file path to store the request to (optional) - * @return task to wait for - */ - pplx::task SendJSONRequest(const web::uri &uri, - const RequestType &type = RequestType::Get, - const web::json::value *body = nullptr, - const std::map headers = {}, - const utility::string_t &filePath = {}) override; - - /** - * @brief Executes a HTTP request in the mitkRESTClient class - * - * @throw mitk::Exception if RequestType is not supported - * @param uri defines the URI the request is send to - * @param type the RequestType of the HTTP request (optional) - * @param body the body for the request (optional) - * @param headers the headers for the request (optional) - * @return task to wait for - */ - pplx::task SendBinaryRequest( - const web::uri &uri, - const RequestType &type = RequestType::Get, - const std::vector *body = {}, - const std::map headers = {}) override; - - /** - * @brief starts listening for requests if there isn't another observer listening and the port is free - * - * @param uri defines the URI for which incoming requests should be send to the observer - * @param observer the observer which handles the incoming requests - */ - void ReceiveRequest(const web::uri &uri, IRESTObserver *observer) override; - - /** - * @brief Handles incoming requests by notifying the observer which should receive it - * - * @param uri defines the URI of the request - * @param body the body of the request - * @param method the http method of the request - * @param headers the http headers of the request - * @return the response - */ - - web::http::http_response Handle(const web::uri &uri, - const web::json::value &body, - const web::http::method &method, - const mitk::RESTUtil::ParamMap &headers) override; - - /** - * @brief Handles the deletion of an observer for all or a specific uri - * - * @param observer the observer which shouldn't receive requests anymore - * @param uri the uri for which the observer doesn't handle requests anymore (optional) - */ - void HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri = {}) override; - - /** - * @brief internal use only - */ - const std::map &GetServerMap() override; - std::map, IRESTObserver *> &GetObservers() override; - - private: - /** - * @brief adds an observer if a port is free, called by ReceiveRequest method - * - * @param uri the uri which builds the key for the observer map - * @param observer the observer which is added - */ - void AddObserver(const web::uri &uri, IRESTObserver *observer); - - /** - * @brief handles server management if there is already a server under a port, called by ReceiveRequest method - * - * @param uri the uri which which is requested to be added - * @param observer the observer which proceeds the request - */ - void RequestForATakenPort(const web::uri &uri, IRESTObserver *observer); - - /** - * @brief deletes an observer, called by HandleDeleteObserver method - * - * @param it the iterator comparing the observers in HandleDeleteObserver method - * @return bool if there is another observer under the port - */ - bool DeleteObserver(std::map, IRESTObserver *>::iterator &it); - - void SetServerMap(const int port, RESTServer *server); - void DeleteFromServerMap(const int port); - void SetObservers(const std::pair key, IRESTObserver *observer); - - std::map m_ServerMap; // Map with port server pairs - std::map, IRESTObserver *> m_Observers; // Map with all observers - }; -} // namespace mitk - -#endif diff --git a/Modules/RESTService/src/mitkRESTManager.cpp b/Modules/RESTService/src/mitkRESTManager.cpp deleted file mode 100644 index d619170369..0000000000 --- a/Modules/RESTService/src/mitkRESTManager.cpp +++ /dev/null @@ -1,258 +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. - -============================================================================*/ - -#include -#include -#include -#include - -#include -#include - -mitk::RESTManager::RESTManager() {} - -mitk::RESTManager::~RESTManager() {} - -pplx::task mitk::RESTManager::SendRequest( - const web::uri &uri, const RequestType &type, const std::map headers) -{ - pplx::task answer; - auto client = new RESTClient; - - switch (type) - { - case RequestType::Get: - answer = client->Get(uri, headers); - break; - - default: - mitkThrow() << "Request Type not supported"; - } - - return answer; -} - -pplx::task mitk::RESTManager::SendBinaryRequest( - const web::uri &uri, - const RequestType &type, - const std::vector *content, - const std::map headers) -{ - pplx::task answer; - auto client = new RESTClient; - - switch (type) - { - case RequestType::Post: - if (nullptr == content) - MITK_WARN << "Content for post is empty, this will create an empty resource"; - - answer = client->Post(uri, content, headers); - break; - - default: - mitkThrow() << "Request Type not supported for binary data"; - } - - return answer; -} - -pplx::task mitk::RESTManager::SendJSONRequest( - const web::uri &uri, - const RequestType &type, - const web::json::value *content, - const std::map headers, - const utility::string_t &filePath) -{ - pplx::task answer; - auto client = new RESTClient; - - switch (type) - { - case RequestType::Get: - answer = !filePath.empty() ? client->Get(uri, filePath, headers) : client->Get(uri, headers); - break; - - case RequestType::Post: - if (nullptr == content) - MITK_WARN << "Content for post is empty, this will create an empty resource"; - - answer = client->Post(uri, content, headers); - break; - - case RequestType::Put: - - if (nullptr == content) - MITK_WARN << "Content for put is empty, this will empty the resource"; - - answer = client->Put(uri, content); - break; - - default: - mitkThrow() << "Request Type not supported"; - } - - return answer; -} - -void mitk::RESTManager::ReceiveRequest(const web::uri &uri, mitk::IRESTObserver *observer) -{ - // New instance of RESTServer in m_ServerMap, key is port of the request - auto port = uri.port(); - - // Checking if port is free to add a new Server - if (0 == m_ServerMap.count(port)) - { - this->AddObserver(uri, observer); - // creating server instance - auto server = new RESTServer(uri.authority()); - // add reference to server instance to map - m_ServerMap[port] = server; - // start Server - server->OpenListener(); - } - // If there is already a server under this port - else - { - this->RequestForATakenPort(uri, observer); - } -} - -web::http::http_response mitk::RESTManager::Handle(const web::uri &uri, - const web::json::value &body, - const web::http::method &method, - const mitk::RESTUtil::ParamMap &headers) -{ - // Checking if there is an observer for the port and path - auto key = std::make_pair(uri.port(), uri.path()); - if (0 != m_Observers.count(key)) - { - return m_Observers[key]->Notify(uri, body, method, headers); - } - // No observer under this port, return null which results in status code 404 (s. RESTServer) - else - { - MITK_WARN << "No Observer can handle the data"; - web::http::http_response response(web::http::status_codes::BadGateway); - response.set_body(U("No one can handle the request under the given port.")); - return response; - } -} - -void mitk::RESTManager::HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri) -{ - for (auto it = m_Observers.begin(); it != m_Observers.end();) - { - mitk::IRESTObserver *obsMap = it->second; - // Check whether observer is at this place in map - if (observer == obsMap) - { - // Check whether it is the right uri to be deleted - if (uri.is_empty() || uri.path() == it->first.second) - { - int port = it->first.first; - bool noObserverForPort = this->DeleteObserver(it); - if (noObserverForPort) - { - // there isn't an observer at this port, delete m_ServerMap entry for this port - // close listener - m_ServerMap[port]->CloseListener(); - delete m_ServerMap[port]; - // delete server from map - m_ServerMap.erase(port); - } - } - else - { - ++it; - } - } - else - { - ++it; - } - } -} - -const std::map &mitk::RESTManager::GetServerMap() -{ - return m_ServerMap; -} - -std::map, mitk::IRESTObserver *> &mitk::RESTManager::GetObservers() -{ - return m_Observers; -} - -void mitk::RESTManager::AddObserver(const web::uri &uri, IRESTObserver *observer) -{ - // new observer has to be added - std::pair key(uri.port(), uri.path()); - m_Observers[key] = observer; -} - -void mitk::RESTManager::RequestForATakenPort(const web::uri &uri, IRESTObserver *observer) -{ - // Same host, means new observer but not a new server instance - if (uri.authority() == m_ServerMap[uri.port()]->GetUri()) - { - // new observer has to be added - std::pair key(uri.port(), uri.path()); - // only add a new observer if there isn't already an observer for this uri - if (0 == m_Observers.count(key)) - { - this->AddObserver(uri, observer); - } - else - { - MITK_ERROR << "There is already an observer handling this data"; - } - } - // Error, since another server can't be added under this port - else - { - MITK_ERROR << "There is already another server listening under this port"; - } -} - -bool mitk::RESTManager::DeleteObserver(std::map, IRESTObserver *>::iterator &it) -{ - int port = it->first.first; - - it = m_Observers.erase(it); - - for (auto observer : m_Observers) - { - if (port == observer.first.first) - { - // there still exists an observer for this port - return false; - } - } - - return true; -} - -void mitk::RESTManager::SetServerMap(const int port, RESTServer *server) -{ - m_ServerMap[port] = server; -} - -void mitk::RESTManager::DeleteFromServerMap(const int port) -{ - m_ServerMap.erase(port); -} - -void mitk::RESTManager::SetObservers(const std::pair key, IRESTObserver *observer) -{ - m_Observers[key] = observer; -} diff --git a/Modules/RESTService/src/mitkRESTServiceActivator.cpp b/Modules/RESTService/src/mitkRESTServiceActivator.cpp deleted file mode 100644 index 5128dd40a1..0000000000 --- a/Modules/RESTService/src/mitkRESTServiceActivator.cpp +++ /dev/null @@ -1,26 +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. - -============================================================================*/ - -#include "mitkRESTServiceActivator.h" -#include - -void MitkRESTServiceActivator::Load(us::ModuleContext *context) -{ - m_RESTManager.reset(new mitk::RESTManager); - context->RegisterService(m_RESTManager.get()); -} - -void MitkRESTServiceActivator::Unload(us::ModuleContext *) -{ -} - -US_EXPORT_MODULE_ACTIVATOR(MitkRESTServiceActivator) diff --git a/Modules/RESTService/src/mitkRESTServiceActivator.h b/Modules/RESTService/src/mitkRESTServiceActivator.h deleted file mode 100644 index 2029536e64..0000000000 --- a/Modules/RESTService/src/mitkRESTServiceActivator.h +++ /dev/null @@ -1,29 +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 mitkRESTServiceActivator_h -#define mitkRESTServiceActivator_h - -#include -#include - -class MitkRESTServiceActivator : public us::ModuleActivator -{ -public: - void Load(us::ModuleContext *context) override; - void Unload(us::ModuleContext *context) override; - -private: - std::unique_ptr m_RESTManager; -}; - -#endif diff --git a/Modules/Segmentation/Controllers/mitkToolManager.cpp b/Modules/Segmentation/Controllers/mitkToolManager.cpp index d1fab073f6..802c8cf234 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.cpp +++ b/Modules/Segmentation/Controllers/mitkToolManager.cpp @@ -1,592 +1,604 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include "mitkCoreObjectFactory.h" #include #include #include #include "mitkInteractionEventObserver.h" #include "mitkSegTool2D.h" #include "mitkRenderingManager.h" #include "mitkTimeNavigationController.h" #include "usGetModuleContext.h" #include "usModuleContext.h" mitk::ToolManager::ToolManager(DataStorage *storage) : m_ActiveTool(nullptr), m_ActiveToolID(-1), m_RegisteredClients(0), m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools // are registered) - bug 1029 this->InitializeTools(); } void mitk::ToolManager::EnsureTimeObservation() { auto* timeController = RenderingManager::GetInstance()->GetTimeNavigationController(); m_LastTimePoint = timeController->GetSelectedTimePoint(); auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (timeController != currentTimeController) { if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); } itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnTimeChanged); command->SetCallbackFunction(this, &ToolManager::OnTimeChangedConst); m_CurrentTimeNavigationController = timeController; m_TimePointObserverTag = timeController->AddObserver(TimeNavigationController::TimeEvent(0), command); } } void mitk::ToolManager::StopTimeObservation() { auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); m_CurrentTimeNavigationController = nullptr; m_TimePointObserverTag = 0; } } mitk::ToolManager::~ToolManager() { for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) (*dataIter)->RemoveObserver(m_WorkingDataObserverTags[(*dataIter)]); if (this->GetDataStorage() != nullptr) this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for (auto observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter) { observerTagMapIter->first->RemoveObserver(observerTagMapIter->second); } this->StopTimeObservation(); } void mitk::ToolManager::InitializeTools() { // clear all previous tool pointers (tools may be still activated from another recently used plugin) if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } m_Tools.clear(); // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for (auto iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter) { if (auto *tool = dynamic_cast(iter->GetPointer())) { tool->InitializeStateMachine(); tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1(this, &ToolManager::OnToolErrorMessage); tool->GeneralMessage += MessageDelegate1(this, &ToolManager::OnGeneralToolMessage); m_Tools.push_back(tool); } } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter) { resultList.push_back(iter->GetPointer()); } return resultList; } mitk::Tool *mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch (const std::exception &) { return nullptr; } } bool mitk::ToolManager::ActivateTool(int id) { const auto workingDataNode = this->GetWorkingData(0); const mitk::BaseData* workingData = nullptr; if (nullptr != workingDataNode) { workingData = workingDataNode->GetData(); } const auto referenceDataNode = this->GetReferenceData(0); const mitk::BaseData* referenceData = nullptr; if (nullptr != referenceDataNode) { referenceData = referenceDataNode->GetData(); } if (id != -1 && !this->GetToolById(id)->CanHandle(referenceData, workingData)) return false; if (this->GetDataStorage()) { this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); } if (GetToolById(id) == m_ActiveTool) return true; // no change needed static int nextTool = -1; nextTool = id; static bool inActivateTool = false; if (inActivateTool) { return true; } inActivateTool = true; while (nextTool != m_ActiveToolID) { // Deactivate all other active tools to ensure a globally single active tool for (const auto& toolManager : ToolManagerProvider::GetInstance()->GetToolManagers()) { if (nullptr != toolManager.second->m_ActiveTool) { toolManager.second->m_ActiveTool->Deactivated(); - toolManager.second->m_ActiveToolRegistration.Unregister(); + + try + { + toolManager.second->m_ActiveToolRegistration.Unregister(); + } + catch (const std::logic_error&) + { + // In rare conditions it may happen that this code is called twice for the same tool leading to an attempt to + // unregister an already unregistered tool. We just want to make sure that the tool is unregistered, hence we + // can safely ignore the attempt/exception as the outcome is the same. + + MITK_DEBUG << "Detected an attempt to unregister an already unregistered segmentation tool."; + } // The active tool of *this* ToolManager is handled below this loop if (this != toolManager.second) { toolManager.second->m_ActiveTool = nullptr; toolManager.second->m_ActiveToolID = -1; toolManager.second->ActiveToolChanged.Send(); } } } m_ActiveTool = GetToolById(nextTool); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { this->EnsureTimeObservation(); if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } } inActivateTool = false; return (m_ActiveTool != nullptr); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { auto searchIter = m_ReferenceDataObserverTags.find(*dataIter); if (searchIter != m_ReferenceDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeletedConst); m_ReferenceDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheReferenceDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_ReferenceDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetReferenceData(v); } void mitk::ToolManager::SetReferenceData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if (data != m_WorkingData) { // remove observers from old nodes for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { auto searchIter = m_WorkingDataObserverTags.find(*dataIter); if (searchIter != m_WorkingDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_WorkingData = data; // TODO tell active tool? // Quick workaround for bug #16598 if (m_WorkingData.empty()) this->ActivateTool(-1); // workaround end // attach new observers m_WorkingDataObserverTags.clear(); for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeletedConst); m_WorkingDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheWorkingDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_WorkingDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetWorkingData(v); } void mitk::ToolManager::SetWorkingData(DataNode *data) { DataVectorType v; if (data) // don't allow for nullptr nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { auto searchIter = m_RoiDataObserverTags.find(*dataIter); if (searchIter != m_RoiDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeletedConst); m_RoiDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheRoiDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_RoiDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetRoiData(v); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode *mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode *mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::DataStorage::Pointer mitk::ToolManager::GetDataStorage() const { return m_DataStorage.Lock(); } void mitk::ToolManager::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } mitk::DataNode *mitk::ToolManager::GetWorkingData(unsigned int idx) { if (m_WorkingData.empty()) return nullptr; if (m_WorkingData.size() > idx) return m_WorkingData[idx]; return nullptr; } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool *mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if (m_RegisteredClients < 1) return; --m_RegisteredClients; if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } } } int mitk::ToolManager::GetToolID(const Tool *tool) { int id(0); for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (tool == iter->GetPointer()) { return id; } } return -1; } void mitk::ToolManager::OnNodeRemoved(const mitk::DataNode *node) { // check all storage vectors OnOneOfTheReferenceDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheRoiDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheWorkingDataDeleted(const_cast(node), itk::DeleteEvent()); } void mitk::ToolManager::OnTimeChanged(itk::Object* caller, const itk::EventObject& e) { this->OnTimeChangedConst(caller, e); } void mitk::ToolManager::OnTimeChangedConst(const itk::Object* caller, const itk::EventObject& /*e*/) { const auto currentController = m_CurrentTimeNavigationController.Lock(); if (caller == currentController) { const TimePointType currentTimePoint = currentController->GetSelectedTimePoint(); if (currentTimePoint != m_LastTimePoint) { m_LastTimePoint = currentTimePoint; SelectedTimePointChanged.Send(); } } } mitk::TimePointType mitk::ToolManager::GetCurrentTimePoint() const { return m_LastTimePoint; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h index 4beb2a2e45..966801623b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h @@ -1,183 +1,190 @@ /*============================================================================ 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 QmitkTotalSegmentatorToolGUI_h_Included #define QmitkTotalSegmentatorToolGUI_h_Included #include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" #include "QmitkSetupVirtualEnvUtil.h" #include "QmitknnUNetGPU.h" #include "ui_QmitkTotalSegmentatorGUIControls.h" #include #include #include #include #include /** * @brief Installer class for TotalSegmentator Tool. * Class specifies the virtual environment name, install version, packages required to pip install * and implements SetupVirtualEnv method. * */ class QmitkTotalSegmentatorToolInstaller : public QmitkSetupVirtualEnvUtil { public: const QString VENV_NAME = ".totalsegmentator_v2"; const QString TOTALSEGMENTATOR_VERSION = "2.2.1"; const std::vector PACKAGES = {QString("Totalsegmentator==") + TOTALSEGMENTATOR_VERSION, + QString("numpy<2"), + QString("SimpleITK<=2.3.1"), + QString("p-tqdm<=1.4.0"), + QString("xvfbwrapper<=0.2.9"), + QString("rt_utils<=1.2.7"), + QString("dicom2nifti<=2.4.11"), + QString("pyarrow<=16.1.0"), QString("setuptools")}; /* just in case */ const QString STORAGE_DIR; inline QmitkTotalSegmentatorToolInstaller( const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + qApp->organizationName() + QDir::separator()) : QmitkSetupVirtualEnvUtil(baseDir), STORAGE_DIR(baseDir){}; bool SetupVirtualEnv(const QString &) override; QString GetVirtualEnvPath() override; }; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::TotalSegmentatorTool. \sa mitk:: */ class MITKSEGMENTATIONUI_EXPORT QmitkTotalSegmentatorToolGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkTotalSegmentatorToolGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots: /** * @brief Qt Slot */ void OnPreviewBtnClicked(); /** * @brief Qt Slot */ void OnPythonPathChanged(const QString &); /** * @brief Qt Slot */ std::pair OnSystemPythonChanged(const QString &); /** * @brief Qt Slot */ void OnInstallBtnClicked(); /** * @brief Qt Slot */ void OnOverrideChecked(int); /** * @brief Qt Slot */ void OnClearInstall(); protected: QmitkTotalSegmentatorToolGUI(); ~QmitkTotalSegmentatorToolGUI() = default; void ConnectNewTool(mitk::SegWithPreviewTool *newTool) override; void InitializeUI(QBoxLayout *mainLayout) override; /** * @brief Enable (or Disable) GUI elements. */ void EnableAll(bool); /** * @brief Searches and parses paths of python virtual environments * from predefined lookout locations */ void AutoParsePythonPaths(); /** * @brief Checks if TotalSegmentator command is valid in the selected python virtual environment. * * @return bool */ bool IsTotalSegmentatorInstalled(const QString &); /** * @brief Creates a QMessage object and shows on screen. */ void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); /** * @brief Writes any message in white on the tool pane. */ void WriteStatusMessage(const QString &); /** * @brief Writes any message in red on the tool pane. */ void WriteErrorMessage(const QString &); /** * @brief Adds GPU information to the gpu combo box. * In case, there aren't any GPUs available, the combo box will be * rendered editable. */ void SetGPUInfo(); /** * @brief Returns GPU id of the selected GPU from the Combo box. * * @return unsigned int */ unsigned int FetchSelectedGPUFromUI() const; /** * @brief Get the virtual env path from UI combobox removing any * extra special characters. * * @return QString */ QString GetPythonPathFromUI(const QString &) const; /** * @brief For storing values like Python path across sessions. */ QSettings m_Settings; QString m_PythonPath; QmitkGPULoader m_GpuLoader; Ui_QmitkTotalSegmentatorToolGUIControls m_Controls; bool m_FirstPreviewComputation = true; bool m_IsInstalled = false; EnableConfirmSegBtnFunctionType m_SuperclassEnableConfirmSegBtnFnc; const std::string WARNING_TOTALSEG_NOT_FOUND = "TotalSegmentator is not detected in the selected python environment.Please select a valid " "python environment or install TotalSegmentator."; const QStringList VALID_TASKS = { "total", "total_mr", "cerebral_bleed", "hip_implant", "coronary_arteries", "body", "lung_vessels", "pleural_pericard_effusion" }; QmitkTotalSegmentatorToolInstaller m_Installer; }; #endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidgetControls.ui index 814b27ebe1..470d1c3542 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidgetControls.ui @@ -1,157 +1,157 @@ QmitkConvertToMultiLabelSegmentationWidgetControls 0 0 408 316 - Inputs; + Inputs: Output: Convert to new segmentation true 20 If activated, the conversion will generate separate multi label segmentations for each input instead of joining all inputs in one multi label segmentation. Convert inputs separately Add to existing segmentation Grouping: 4 4 4 4 4 Merge all inputs in new group true New group per input Convert Qt::Vertical 20 40 label inputNodesSelector groupGrouping btnConvert groupOutput QmitkMultiNodeSelectionWidget QWidget
QmitkMultiNodeSelectionWidget.h
1
QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidgetControls.ui index 3635ba4792..b49fbd6f09 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidgetControls.ui @@ -1,129 +1,129 @@ QmitkExtractFromMultiLabelSegmentationWidgetControls 0 0 313 316 - Segmentation; + Segmentation: Extract only selected label(s) Outputs: false 4 4 4 4 4 <html><head/><body><p>If checked, a binary mask image is generated for each selected label.</p></body></html> Label instance mask(s) <html><head/><body><p>If checked, an image is generated for each selected group containing all selected label instances of that group.</p></body></html> Label instance map(s) <html><head/><body><p>If checked, an image is generated for each selected group containing all classes of selected label instances of that group.</p><p>So basically all label instances with the same label name will get the same pixel value in the map.</p></body></html> Label class map(s) Extract Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
QmitkMultiLabelInspector QWidget
QmitkMultiLabelInspector.h
1
diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp index e07b4f07f4..cbb96092e7 100644 --- a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp +++ b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp @@ -1,754 +1,756 @@ /*============================================================================ 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 "GenericDataFittingView.h" #include "mitkWorkbenchUtil.h" #include #include #include #include #include #include #include #include #include #include "mitkTwoStepLinearModelFactory.h" #include "mitkTwoStepLinearModelParameterizer.h" #include "mitkThreeStepLinearModelFactory.h" #include "mitkThreeStepLinearModelParameterizer.h" #include #include #include #include #include #include #include #include "mitkNodePredicateFunction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include #include const std::string GenericDataFittingView::VIEW_ID = "org.mitk.views.fit.genericfitting"; void GenericDataFittingView::SetFocus() { m_Controls.btnModelling->setFocus(); } void GenericDataFittingView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); this->InitModelComboBox(); m_Controls.labelMaskInfo->hide(); m_Controls.timeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.timeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.timeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.timeSeriesNodeSelector->SetInvalidInfo("Please select time series."); - m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); m_Controls.maskNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); m_Controls.maskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.maskNodeSelector->SetSelectionIsOptional(true); m_Controls.maskNodeSelector->SetEmptyInfo("Please select (optional) mask."); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.timeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &GenericDataFittingView::OnImageNodeSelectionChanged); connect(m_Controls.maskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &GenericDataFittingView::OnMaskNodeSelectionChanged); //Generic setting m_Controls.groupGeneric->hide(); m_Controls.labelFormulaInfo->hide(); connect(m_Controls.editFormula, SIGNAL(textChanged(const QString&)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkFormulaInfo, SIGNAL(toggled(bool)), m_Controls.labelFormulaInfo, SLOT(setVisible(bool))); connect(m_Controls.nrOfParams, SIGNAL(valueChanged(int)), this, SLOT(OnNrOfParamsChanged())); connect(m_Controls.checkMaskInfo, SIGNAL(toggled(bool)), m_Controls.labelMaskInfo, SLOT(setVisible(bool))); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); + // Should be done last, if everything else is configured because it triggers the autoselection of data. + m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); + UpdateGUIControls(); } void GenericDataFittingView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool isGenericFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupGeneric->setVisible(isGenericFactory); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupGeneric->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); } std::string GenericDataFittingView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string GenericDataFittingView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void GenericDataFittingView::OnNrOfParamsChanged() { PrepareFitConfiguration(); UpdateGUIControls(); } void GenericDataFittingView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } UpdateGUIControls(); } bool GenericDataFittingView::IsGenericParamFactorySelected() const { return dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; } void GenericDataFittingView::PrepareFitConfiguration() { if (m_selectedModelFactory) { mitk::ModelBase::ParameterNamesType paramNames = m_selectedModelFactory->GetParameterNames(); unsigned int nrOfPools = this->m_Controls.nrOfParams->value(); //init values if (this->IsGenericParamFactorySelected()) { mitk::modelFit::ModelFitInfo::Pointer fitInfo = mitk::modelFit::ModelFitInfo::New(); fitInfo->staticParamMap.Add(mitk::GenericParamModel::NAME_STATIC_PARAMETER_number, { static_cast(nrOfPools) }); auto parameterizer = m_selectedModelFactory->CreateParameterizer(fitInfo); paramNames = parameterizer->GetParameterNames(); m_Controls.initialValuesManager->setInitialValues(paramNames, parameterizer->GetDefaultInitialParameterization()); } else { m_Controls.initialValuesManager->setInitialValues(paramNames, this->m_selectedModelFactory->GetDefaultInitialParameterization()); } //constraints this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, paramNames); } }; void GenericDataFittingView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isGenericFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExponentialDecayFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTwoStepLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isThreeStepLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExponentialSaturationFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isGenericFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isExponentialDecayFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isTwoStepLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isThreeStepLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isExponentialSaturationFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; UpdateGUIControls(); DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void GenericDataFittingView::OnImageNodeSelectionChanged(QList/*nodes*/) { if (m_Controls.timeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.timeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); if (m_selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(m_selectedImage->GetGeometry()); m_Controls.maskNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(m_selectedImage->GetGeometry())); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); m_Controls.maskNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(nullptr)); } } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } UpdateGUIControls(); } void GenericDataFittingView::OnMaskNodeSelectionChanged(QList/*nodes*/) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; if (m_Controls.maskNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedMaskNode = m_Controls.maskNodeSelector->GetSelectedNode(); auto selectedLabelSetMask = dynamic_cast(m_selectedMaskNode->GetData()); if (selectedLabelSetMask != nullptr) { if (selectedLabelSetMask->GetAllLabelValues().size() > 1) { MITK_INFO << "Selected mask has multiple labels. Only use first used to mask the model fit."; } this->m_selectedMask = mitk::CreateLabelMask(selectedLabelSetMask, selectedLabelSetMask->GetAllLabelValues().front(), true); } if (this->m_selectedMask.IsNotNull() && this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_Controls.maskNodeSelector->GetSelectedNode()->GetName(); this->m_selectedMask = SelectImageByTimeStep(m_selectedMask, 0); } } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } UpdateGUIControls(); } bool GenericDataFittingView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isGenericFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; if (isGenericFactory) { ok = !m_Controls.editFormula->text().isEmpty(); } } else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter has an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; return ok; } void GenericDataFittingView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::ValueBasedParameterizationDelegate::Pointer paramDelegate = mitk::ValueBasedParameterizationDelegate::New(); paramDelegate->SetInitialParameterization(m_Controls.initialValuesManager->getInitialValues()); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } mitk::GenericParamModelParameterizer* genericParameterizer = dynamic_cast(parameterizer); if (genericParameterizer) { genericParameterizer->SetFunctionString(m_Controls.editFormula->text().toStdString()); } } template void GenericDataFittingView::GenerateModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); auto genericParameterizer = dynamic_cast(modelParameterizer.GetPointer()); if (genericParameterizer) { genericParameterizer->SetNumberOfParameters(this->m_Controls.nrOfParams->value()); } this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } template void GenericDataFittingView::GenerateModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); auto genericParameterizer = dynamic_cast(modelParameterizer.GetPointer()); if (genericParameterizer) { genericParameterizer->SetNumberOfParameters(this->m_Controls.nrOfParams->value()); } this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } void GenericDataFittingView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { QString message = "Fitting Data Set . . ."; m_Controls.infoBox->append(message); ///////////////////////// //create job and put it into the thread pool ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } GenericDataFittingView::GenericDataFittingView() : m_FittingInProgress(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::LinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::GenericParamModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExponentialDecayModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExponentialSaturationModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoStepLinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ThreeStepLinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); auto isDynamicData = mitk::NodePredicateFunction::New([](const mitk::DataNode* node) { return (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); auto isNoMask = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); this->m_isValidTimeSeriesImagePredicate = mitk::NodePredicateAnd::New(isDynamicData, isImage, isNoMask); } void GenericDataFittingView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished")); this->m_FittingInProgress = false; this->UpdateGUIControls(); }; void GenericDataFittingView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void GenericDataFittingView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); }; void GenericDataFittingView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void GenericDataFittingView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void GenericDataFittingView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::ModelFitFunctorBase::Pointer GenericDataFittingView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(m_Controls.checkDebug->isChecked()); return fitFunctor.GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp index 38992f91f4..6bc91e20bd 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp @@ -1,925 +1,937 @@ /*============================================================================ 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 "ModelFitInspectorView.h" // Blueberry #include #include #include // mitk #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string ModelFitInspectorView::VIEW_ID = "org.mitk.views.fit.inspector"; const unsigned int ModelFitInspectorView::INTERPOLATION_STEPS = 10; const std::string DEFAULT_X_AXIS = "Time [s]"; ModelFitInspectorView::ModelFitInspectorView() : m_renderWindowPart(nullptr), m_internalUpdateFlag(false), m_currentFit(nullptr), m_currentModelParameterizer(nullptr), m_currentModelProviderService(nullptr), m_currentSelectedTimeStep(0), m_currentSelectedNode(nullptr) { m_currentSelectedPosition.Fill(0.0); m_modelfitList.clear(); } ModelFitInspectorView::~ModelFitInspectorView() { } void ModelFitInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_renderWindowPart != renderWindowPart) { m_renderWindowPart = renderWindowPart; } this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void ModelFitInspectorView::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { m_renderWindowPart = nullptr; this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void ModelFitInspectorView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_SelectionServiceConnector = std::make_unique(); m_SelectionServiceConnector->AddPostSelectionListener(this->GetSite()->GetWorkbenchWindow()->GetSelectionService()); m_Controls.inputNodeSelector->SetDataStorage(GetDataStorage()); m_Controls.inputNodeSelector->SetEmptyInfo(QString("Please select input data to be viewed.")); m_Controls.inputNodeSelector->SetInvalidInfo(QString("No input data is selected")); m_Controls.inputNodeSelector->SetPopUpTitel(QString("Choose 3D+t input data that should be viewed!")); m_Controls.inputNodeSelector->SetSelectionIsOptional(false); m_Controls.inputNodeSelector->SetSelectOnlyVisibleNodes(true); m_Controls.groupSettings->setVisible(false); auto predicate = mitk::NodePredicateFunction::New([](const mitk::DataNode *node) { bool isModelFitNode = node->GetData() && node->GetData()->GetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str()).IsNotNull(); return isModelFitNode || (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); m_Controls.inputNodeSelector->SetNodePredicate(predicate); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.inputNodeSelector, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); connect(m_Controls.inputNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ModelFitInspectorView::OnInputChanged); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); connect(m_Controls.cmbFit, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFitSelectionChanged(int))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.btnScaleToData, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedYChecked(bool))); connect(m_Controls.btnScaleToData, SIGNAL(clicked()), this, SLOT(OnScaleToDataYClicked())); connect(m_Controls.sbFixMax, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.sbFixMin, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.btnScaleToData_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedXChecked(bool))); connect(m_Controls.btnScaleToData_x, SIGNAL(clicked()), this, SLOT(OnScaleToDataXClicked())); connect(m_Controls.sbFixMax_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.sbFixMin_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.btnFullPlot, SIGNAL(clicked(bool)), this, SLOT(OnFullPlotClicked(bool))); this->EnsureBookmarkPointSet(); m_Controls.inspectionPositionWidget->SetPositionBookmarkNode(m_PositionBookmarksNode); connect(m_Controls.inspectionPositionWidget, SIGNAL(PositionBookmarksChanged()), this, SLOT(OnPositionBookmarksChanged())); // For some reason this needs to be called to set the plot widget's minimum width to an // acceptable level (since Qwt 6). // Otherwise it tries to keep both axes equal in length, resulting in a minimum width of // 400-500px which is way too much. m_Controls.widgetPlot->GetPlot()->updateAxes(); m_Controls.cmbFit->clear(); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } void ModelFitInspectorView::SetFocus() { } void ModelFitInspectorView::NodeRemoved(const mitk::DataNode* node) { if (node == this->m_currentSelectedNode) { QmitkSingleNodeSelectionWidget::NodeList emptylist; this->m_Controls.inputNodeSelector->SetCurrentSelection(emptylist); } } void ModelFitInspectorView::OnScaleFixedYChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::yLeft, !checked); if (checked) { OnScaleToDataYClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnScaleFixedXChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::xBottom, !checked); if (checked) { OnScaleToDataXClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnScaleToDataYClicked() { auto minmax = this->m_PlotCurves.GetYMinMax(); auto min = minmax.first - std::abs(minmax.first) * 0.01; auto max = minmax.second + std::abs(minmax.second) * 0.01; m_Controls.sbFixMin->setValue(min); m_Controls.sbFixMax->setValue(max); }; void ModelFitInspectorView::OnScaleToDataXClicked() { auto minmax = this->m_PlotCurves.GetXMinMax(); auto min = minmax.first - std::abs(minmax.first) * 0.01; auto max = minmax.second + std::abs(minmax.second) * 0.01; m_Controls.sbFixMin_x->setValue(min); m_Controls.sbFixMax_x->setValue(max); }; void ModelFitInspectorView::OnFixedScalingYChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::yLeft, m_Controls.sbFixMin->value(), m_Controls.sbFixMax->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnFixedScalingXChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::xBottom, m_Controls.sbFixMin_x->value(), m_Controls.sbFixMax_x->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnFullPlotClicked(bool checked) { m_Controls.tabWidget->setVisible(!checked); }; int ModelFitInspectorView::ActualizeFitSelectionWidget() { mitk::modelFit::ModelFitInfo::UIDType selectedFitUD = ""; bool isModelFitNode = false; if (this->m_Controls.inputNodeSelector->GetSelectedNode().IsNotNull()) { isModelFitNode = this->m_Controls.inputNodeSelector->GetSelectedNode()->GetData()->GetPropertyList()->GetStringProperty( mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), selectedFitUD); } mitk::DataStorage::Pointer storage = this->GetDataStorage(); mitk::modelFit::NodeUIDSetType fitUIDs = mitk::modelFit::GetFitUIDsOfNode( this->m_currentSelectedNode, storage); this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); for (const auto & fitUID : fitUIDs) { mitk::modelFit::ModelFitInfo::ConstPointer info = mitk::modelFit::CreateFitInfoFromNode(fitUID, storage).GetPointer(); if (info.IsNotNull()) { this->m_modelfitList.insert(std::make_pair(info->uid, info)); std::ostringstream nameStrm; if (info->fitName.empty()) { nameStrm << info->uid; } else { nameStrm << info->fitName; } nameStrm << " (" << info->modelName << ")"; QVariant data(info->uid.c_str()); m_Controls.cmbFit->addItem(QString::fromStdString(nameStrm.str()), data); } else { MITK_ERROR << "Was not able to extract model fit information from storage. Node properties in storage may be invalid. Failed fit UID:" << fitUID; } } int cmbIndex = 0; if (m_modelfitList.empty()) { cmbIndex = -1; }; if (isModelFitNode) { //model was selected, thus select this one in combobox QVariant data(selectedFitUD.c_str()); cmbIndex = m_Controls.cmbFit->findData(data); if (cmbIndex == -1) { MITK_WARN << "Model fit Inspector in invalid state. Selected fit seems to be not available in plugin selection. Failed fit UID:" << selectedFitUD; } }; m_Controls.cmbFit->setCurrentIndex(cmbIndex); return cmbIndex; } void ModelFitInspectorView::OnInputChanged(const QList& nodes) { if (nodes.size() > 0) { if (nodes.front() != this->m_currentSelectedNode) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nodes.front(); mitk::modelFit::ModelFitInfo::UIDType selectedFitUD = ""; bool isModelFitNode = this->m_currentSelectedNode->GetData()->GetPropertyList()->GetStringProperty( mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), selectedFitUD); if (isModelFitNode) { this->m_currentSelectedNode = this->GetInputNode(this->m_currentSelectedNode); if (this->m_currentSelectedNode.IsNull()) { MITK_WARN << "Model fit Inspector in invalid state. Input image for selected fit cannot be found in data storage. Failed fit UID:" << selectedFitUD; } } auto cmbIndex = ActualizeFitSelectionWidget(); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); if (cmbIndex == -1) { //only raw 4D data selected. Just update plots for current position m_currentFit = nullptr; m_currentFitTime.Modified(); OnSliceChanged(); m_Controls.plotDataWidget->SetXName(DEFAULT_X_AXIS); } else { //refresh fit selection (and implicitly update plots) OnFitSelectionChanged(cmbIndex); } } } else { if (this->m_currentSelectedNode.IsNotNull()) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nullptr; this->m_currentFit = nullptr; this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); OnFitSelectionChanged(0); RefreshPlotData(); m_Controls.plotDataWidget->SetPlotData(&(this->m_PlotCurves)); m_Controls.fitParametersWidget->setFits(QmitkFitParameterModel::FitVectorType()); RenderPlot(); } } } mitk::DataNode::ConstPointer ModelFitInspectorView::GetInputNode(mitk::DataNode::ConstPointer node) { if (node.IsNotNull()) { std::string selectedFitUD = ""; auto rule = mitk::ModelFitResultRelationRule::New(); auto predicate = rule->GetDestinationsDetector(node); mitk::DataStorage::SetOfObjects::ConstPointer parentNodeList = GetDataStorage()->GetSubset(predicate); if (parentNodeList->size() > 0) { return parentNodeList->front().GetPointer(); } } return mitk::DataNode::ConstPointer(); } void ModelFitInspectorView::ValidateAndSetCurrentPosition() { const mitk::Point3D currentSelectedPosition = GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetSelectedPosition(nullptr); const unsigned int currentSelectedTimestep = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimeStep(); if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimeStep != currentSelectedTimestep || m_selectedNodeTime > m_currentPositionTime) { //the current position has been changed or the selected node has been changed since the last position validation -> check position m_currentSelectedPosition = currentSelectedPosition; m_currentSelectedTimeStep = currentSelectedTimestep; m_currentPositionTime.Modified(); m_validSelectedPosition = false; auto inputImage = this->GetCurrentInputImage(); if (inputImage.IsNull()) { return; } mitk::BaseGeometry::ConstPointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep( m_currentSelectedTimeStep).GetPointer(); // check for invalid time step if (geometry.IsNull()) { geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_validSelectedPosition = geometry->IsInside(m_currentSelectedPosition); } } mitk::Image::ConstPointer ModelFitInspectorView::GetCurrentInputImage() const { mitk::Image::ConstPointer result = nullptr; if (this->m_currentFit.IsNotNull()) { result = m_currentFit->inputImage; } else if (this->m_currentSelectedNode.IsNotNull()) { result = dynamic_cast(this->m_currentSelectedNode->GetData()); if (result.IsNotNull() && result->GetTimeSteps() <= 1) { //if the image is not dynamic, we can't use it. result = nullptr; } } return result; }; const mitk::ModelBase::TimeGridType ModelFitInspectorView::GetCurrentTimeGrid() const { if (m_currentModelProviderService && m_currentFit.IsNotNull()) { return m_currentModelProviderService->GetVariableGrid(m_currentFit); } else { //fall back if there is no model provider we assume to use the normal time grid. return ExtractTimeGrid(GetCurrentInputImage()); } }; void ModelFitInspectorView::OnSliceChanged() { ValidateAndSetCurrentPosition(); m_Controls.widgetPlot->setEnabled(m_validSelectedPosition); if (m_currentSelectedNode.IsNotNull()) { m_Controls.inspectionPositionWidget->SetCurrentPosition(m_currentSelectedPosition); if (RefreshPlotData()) { RenderPlot(); m_Controls.plotDataWidget->SetPlotData(&m_PlotCurves); RenderFitInfo(); } } } void ModelFitInspectorView::OnPositionBookmarksChanged() { m_PositionBookmarks = dynamic_cast(m_PositionBookmarksNode->GetData()); m_PositionBookmarks->Modified(); if (RefreshPlotData()) { RenderPlot(); m_Controls.plotDataWidget->SetPlotData(&m_PlotCurves); RenderFitInfo(); } } void ModelFitInspectorView::OnFitSelectionChanged(int index) { if (!m_internalUpdateFlag) { MITK_DEBUG << "selected fit index: " << index; std::string uid = ""; if (m_Controls.cmbFit->count() > index) { uid = m_Controls.cmbFit->itemData(index).toString().toStdString(); } mitk::modelFit::ModelFitInfo::ConstPointer newFit = nullptr; ModelFitInfoListType::iterator finding = m_modelfitList.find(uid); if (finding != m_modelfitList.end()) { newFit = finding->second; } if (m_currentFit != newFit) { m_currentModelParameterizer = nullptr; m_currentModelProviderService = nullptr; if (newFit.IsNotNull()) { m_currentModelParameterizer = mitk::ModelGenerator::GenerateModelParameterizer(*newFit); m_currentModelProviderService = mitk::ModelGenerator::GetProviderService(newFit->functionClassID); } m_currentFit = newFit; m_currentFitTime.Modified(); auto name = m_currentFit->xAxisName; if (!m_currentFit->xAxisUnit.empty()) { name += " [" + m_currentFit->xAxisUnit + "]"; } m_Controls.plotDataWidget->SetXName(name); OnSliceChanged(); } } } mitk::PlotDataCurveCollection::Pointer ModelFitInspectorView::RefreshPlotDataCurveCollection(const mitk::Point3D& position, const mitk::Image* input, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid, mitk::ModelParameterizerBase* parameterizer) { mitk::PlotDataCurveCollection::Pointer result = mitk::PlotDataCurveCollection::New(); //sample curve if (input) { result->InsertElement(mitk::MODEL_FIT_PLOT_SAMPLE_NAME(), GenerateImageSamplePlotData(position, input, timeGrid)); } //model signal curve if (fitInfo) { // Interpolate time grid (x values) so the curve looks smooth const mitk::ModelBase::TimeGridType interpolatedTimeGrid = mitk::GenerateSupersampledTimeGrid(timeGrid, INTERPOLATION_STEPS); auto hires_curve = mitk::GenerateModelSignalPlotData(position, fitInfo, interpolatedTimeGrid, parameterizer); result->InsertElement(mitk::MODEL_FIT_PLOT_INTERPOLATED_SIGNAL_NAME(), hires_curve); auto curve = mitk::GenerateModelSignalPlotData(position, fitInfo, timeGrid, parameterizer); result->InsertElement(mitk::MODEL_FIT_PLOT_SIGNAL_NAME(), curve); } return result; }; bool ModelFitInspectorView::RefreshPlotData() { bool changed = false; if (m_currentSelectedNode.IsNull()) { this->m_PlotCurves = mitk::ModelFitPlotData(); changed = m_selectedNodeTime > m_lastRefreshTime; m_lastRefreshTime.Modified(); } else { assert(GetRenderWindowPart() != NULL); const mitk::Image* input = GetCurrentInputImage(); const mitk::ModelBase::TimeGridType timeGrid = GetCurrentTimeGrid(); if (m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { if (m_validSelectedPosition) { m_PlotCurves.currentPositionPlots = RefreshPlotDataCurveCollection(m_currentSelectedPosition, input, m_currentFit, timeGrid, m_currentModelParameterizer); } else { m_PlotCurves.currentPositionPlots = mitk::PlotDataCurveCollection::New(); } changed = true; } auto bookmarks = m_PositionBookmarks; if (m_currentFitTime > m_lastRefreshTime || bookmarks->GetMTime() > m_lastRefreshTime) { m_PlotCurves.positionalPlots.clear(); if (!bookmarks->IsEmpty()) { auto endIter = bookmarks->End(); for (auto iter = bookmarks->Begin(); iter != endIter; iter++) { auto collection = RefreshPlotDataCurveCollection(iter.Value(), input, m_currentFit, timeGrid, m_currentModelParameterizer); m_PlotCurves.positionalPlots.emplace(iter.Index(), std::make_pair(iter.Value(), collection)); } } else { m_PlotCurves.positionalPlots.clear(); } changed = true; } // input data curve if (m_currentFitTime > m_lastRefreshTime) { m_PlotCurves.staticPlots->clear(); if (m_currentFit.IsNotNull()) { m_PlotCurves.staticPlots = GenerateAdditionalModelFitPlotData(m_currentSelectedPosition, m_currentFit, timeGrid); } changed = true; } m_lastRefreshTime.Modified(); } return changed; } void ModelFitInspectorView::RenderFitInfo() { assert(m_renderWindowPart != nullptr); // configure fit information if (m_currentFit.IsNull()) { m_Controls.lFitType->setText(""); m_Controls.lFitUID->setText(""); m_Controls.lModelName->setText(""); m_Controls.lModelType->setText(""); } else { m_Controls.lFitType->setText(QString::fromStdString(m_currentFit->fitType)); m_Controls.lFitUID->setText(QString::fromStdString(m_currentFit->uid)); m_Controls.lModelName->setText(QString::fromStdString(m_currentFit->modelName)); m_Controls.lModelType->setText(QString::fromStdString(m_currentFit->modelType)); } // print results std::stringstream infoOutput; m_Controls.fitParametersWidget->setVisible(false); if (m_currentFit.IsNull()) { infoOutput << "No fit selected. Only raw image data is plotted."; } else if (!m_validSelectedPosition) { infoOutput << "Current position is outside of the input image of the selected fit.\nInspector is deactivated."; } else { m_Controls.fitParametersWidget->setVisible(true); m_Controls.fitParametersWidget->setFits({ m_currentFit }); m_Controls.fitParametersWidget->setPositionBookmarks(m_PositionBookmarks); m_Controls.fitParametersWidget->setCurrentPosition(m_currentSelectedPosition); } // configure data table m_Controls.tableInputData->clearContents(); if (m_currentFit.IsNotNull()) { m_Controls.groupSettings->setVisible(false); m_Controls.tableInputData->setRowCount(m_PlotCurves.staticPlots->size()); unsigned int rowIndex = 0; for (mitk::PlotDataCurveCollection::const_iterator pos = m_PlotCurves.staticPlots->begin(); pos != m_PlotCurves.staticPlots->end(); ++pos, ++rowIndex) { QColor dataColor; if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing free iso lines. dataColor.setHsv(((rowIndex + 1) * 85) % 360, 255, 150); } QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(pos->first)); m_Controls.tableInputData->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(); newItem->setBackground(dataColor); - m_Controls.tableInputData->setItem(rowIndex, 1, newItem); } } m_Controls.lInfo->setText(QString::fromStdString(infoOutput.str())); } -void ModelFitInspectorView::RenderPlotCurve(const mitk::PlotDataCurveCollection* curveCollection, const QColor& sampleColor, const QColor& signalColor, const std::string& posString) +void ModelFitInspectorView::RenderPlotCurve(const mitk::PlotDataCurveCollection* curveCollection, const QColor& sampleColor, const QColor& signalColor, const std::string& posString, const QColor&legendTextColor) { + auto sampleCurve = mitk::ModelFitPlotData::GetSamplePlot(curveCollection); if (sampleCurve) { std::string name = mitk::MODEL_FIT_PLOT_SAMPLE_NAME() + posString; unsigned int curveId = m_Controls.widgetPlot->InsertCurve(name.c_str()); m_Controls.widgetPlot->SetCurveData(curveId, sampleCurve->GetValues()); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* dataSymbol = new QwtSymbol(QwtSymbol::Diamond, sampleColor, sampleColor, QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, dataSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); + + QwtText legendText = measurementCurve->title(); + legendText.setColor(legendTextColor); + measurementCurve->setTitle(legendText); + } //draw model curve auto signalCurve = mitk::ModelFitPlotData::GetInterpolatedSignalPlot(curveCollection); if (signalCurve) { std::string name = mitk::MODEL_FIT_PLOT_SIGNAL_NAME() + posString; QPen pen; pen.setColor(signalColor); pen.setWidth(2); unsigned int curveId = m_Controls.widgetPlot->InsertCurve(name.c_str()); m_Controls.widgetPlot->SetCurveData(curveId, signalCurve->GetValues()); m_Controls.widgetPlot->SetCurvePen(curveId, pen); // Manually set the legend attribute to use the symbol as the legend icon and alter its // size. Otherwise it would revert to default which is drawing a square which is the color // of the curve's pen, so in this case none which defaults to black. // Unfortunately, QmitkPlotWidget offers no way to set the legend attribute and icon size so // this looks a bit hacky. QwtPlotCurve* fitCurve = dynamic_cast(m_Controls.widgetPlot->GetPlot()-> itemList(QwtPlotItem::Rtti_PlotCurve).back()); fitCurve->setLegendAttribute(QwtPlotCurve::LegendShowLine); + QwtText legendText = fitCurve->title(); + legendText.setColor(legendTextColor); + fitCurve->setTitle(legendText); + } } void ModelFitInspectorView::RenderPlot() { m_Controls.widgetPlot->Clear(); std::string xAxis = DEFAULT_X_AXIS; std::string yAxis = "Intensity"; std::string plotTitle = "Raw data plot: no data"; + QColor legendTextColor = Qt::gray; if (m_currentSelectedNode.IsNotNull()) { plotTitle = "Raw data plot: " + m_currentSelectedNode->GetName(); } if (m_currentFit.IsNotNull()) { plotTitle = m_currentFit->modelName.c_str(); xAxis = m_currentFit->xAxisName; if (!m_currentFit->xAxisUnit.empty()) { xAxis += " [" + m_currentFit->xAxisUnit + "]"; } yAxis = m_currentFit->yAxisName; if (!m_currentFit->yAxisUnit.empty()) { yAxis += " [" + m_currentFit->yAxisUnit + "]"; } } m_Controls.widgetPlot->SetAxisTitle(QwtPlot::xBottom, xAxis.c_str()); m_Controls.widgetPlot->SetAxisTitle(QwtPlot::yLeft, yAxis.c_str()); m_Controls.widgetPlot->SetPlotTitle(plotTitle.c_str()); // Draw static curves unsigned int colorIndex = 0; for (mitk::PlotDataCurveCollection::const_iterator pos = m_PlotCurves.staticPlots->begin(); pos != m_PlotCurves.staticPlots->end(); ++pos) { QColor dataColor; - unsigned int curveId = m_Controls.widgetPlot->InsertCurve(pos->first.c_str()); m_Controls.widgetPlot->SetCurveData(curveId, pos->second->GetValues()); if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); QPen pen; pen.setColor(dataColor); pen.setStyle(Qt::SolidLine); m_Controls.widgetPlot->SetCurvePen(curveId, pen); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing curves. dataColor.setHsv((++colorIndex * 85) % 360, 255, 150); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); } // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* dataSymbol = new QwtSymbol(QwtSymbol::Triangle, dataColor, dataColor, QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, dataSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); + QwtText legendText = measurementCurve->title(); + legendText.setColor(legendTextColor); + measurementCurve->setTitle(legendText); } // Draw positional curves for (const auto& posIter : this->m_PlotCurves.positionalPlots) { QColor dataColor; dataColor.setHsv((++colorIndex * 85) % 360, 255, 150); - this->RenderPlotCurve(posIter.second.second, dataColor, dataColor, " @ "+mitk::ModelFitPlotData::GetPositionalCollectionName(posIter)); + this->RenderPlotCurve(posIter.second.second, dataColor, dataColor, " @ "+mitk::ModelFitPlotData::GetPositionalCollectionName(posIter), legendTextColor); } // Draw current pos curve - this->RenderPlotCurve(m_PlotCurves.currentPositionPlots, QColor(Qt::red), QColor(Qt::black), ""); - + this->RenderPlotCurve(m_PlotCurves.currentPositionPlots, QColor(Qt::red), QColor(Qt::gray), "", legendTextColor); QwtLegend* legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); m_Controls.widgetPlot->SetLegend(legend, QwtPlot::BottomLegend); + m_Controls.widgetPlot->Replot(); } void ModelFitInspectorView::EnsureBookmarkPointSet() { if (m_PositionBookmarks.IsNull()|| m_PositionBookmarksNode.IsNull()) { const char* nodeName = "org.mitk.gui.qt.fit.inspector.positions"; mitk::DataNode::Pointer node = this->GetDataStorage()->GetNamedNode(nodeName); if (!node) { node = mitk::DataNode::New(); node->SetName(nodeName); node->SetBoolProperty("helper object", true); this->GetDataStorage()->Add(node); } m_PositionBookmarksNode = node; mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); if (pointSet.IsNull()) { pointSet = mitk::PointSet::New(); node->SetData(pointSet); } m_PositionBookmarks = pointSet; } } diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h index 54be3a6ef2..c2aa1eaa52 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.h @@ -1,202 +1,202 @@ /*============================================================================ 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 ModelFitInspectorView_h #define ModelFitInspectorView_h // Blueberry //#include #include // mitk #include #include #include "QmitkSliceNavigationListener.h" #include "mitkModelFitStaticParameterMap.h" #include "mitkModelParameterizerBase.h" #include "mitkModelFitInfo.h" #include "mitkIModelFitProvider.h" #include "mitkModelFitPlotDataHelper.h" #include "QmitkSelectionServiceConnector.h" #include "QmitkFitParameterModel.h" // Qt #include "ui_ModelFitInspectorViewControls.h" /** * @brief View class defining the UI part of the ModelFitInspector plug-in. */ class ModelFitInspectorView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // 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: ModelFitInspectorView(); ~ModelFitInspectorView() override; static const std::string VIEW_ID; protected slots: void OnSliceChanged(); /** * @brief Triggered when the selection of the "Modelfit" combo box changes. * Sets the selected fit as the current one. * @param index The index (in the combo box) of the selected item. */ void OnFitSelectionChanged(int index); void OnInputChanged(const QList& nodes); void OnPositionBookmarksChanged(); /** Triggered when the selection of "fixed" y axis scaling changes*/ void OnScaleFixedYChecked(bool checked); void OnScaleToDataYClicked(); void OnFixedScalingYChanged(double value); /** Triggered when the selection of "fixed" x axis scaling changes*/ void OnScaleFixedXChecked(bool checked); void OnScaleToDataXClicked(); void OnFixedScalingXChanged(double value); void OnFullPlotClicked(bool checked); protected: void CreateQtPartControl(QWidget* parent) override; void SetFocus() override; void NodeRemoved(const mitk::DataNode* node) override; /** Helper that actualizes the fit selection widget and returns the index of the currently selected * fit.*/ int ActualizeFitSelectionWidget(); void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; /** * @brief Calculates the curve data using the current fit's model and parameterizer. * @param position Indicating the point in the input image for which the model curve should be calculated. * @return A vector of points for the curve data. */ QmitkPlotWidget::XYDataVector CalcCurveFromModel(const mitk::Point3D& position); /** * @brief Calculates the curve data using the current fit's function string. * @param timeGrid The time grid containing the (interpolated) x values for the curve data. * @return A vector of points for the curve data. */ QmitkPlotWidget::XYDataVector CalcCurveFromFunction(const mitk::Point3D& position, const mitk::ModelBase::TimeGridType& timeGrid); /** * @brief Returns the input node that was used to generate the passed model fit node if it exists. * @param node Node that is a model fit result. * @return The input node that was used to generate the passed node. If the input node cannot * be found in the storage or the passed node is not a model fit result node, a nullptr will be returned. */ mitk::DataNode::ConstPointer GetInputNode(mitk::DataNode::ConstPointer node); /** Sets m_currentSelectedPosition to the current selection and validates if this position is valid * for the input image of the currently selected fit. If it is valid, m_validSelectedPosition is set to true. * If the fit, his input image or geometry is not specified, it will also handled as invalid.*/ void ValidateAndSetCurrentPosition(); /** Returns the current input image. If a current fit is set it will be its input image. * Otherwise it will be the image stored in the currently selected node. If the node is not set, contains no image * or the image is not 4D, NULL will be returned.*/ mitk::Image::ConstPointer GetCurrentInputImage() const; /** Returns the variable/time grid of the GetCurrentInputImage(). If a model fit is selected its provider will be used to get the correct grid, otherwise just a simple time grid will be extracted.*/ const mitk::ModelBase::TimeGridType GetCurrentTimeGrid() const; Ui::ModelFitInspectorViewControls m_Controls; mitk::IRenderWindowPart* m_renderWindowPart; /** @brief Is a visualization currently running? */ bool m_internalUpdateFlag; /** @brief List of modelfits currently in the data manager */ typedef std::map ModelFitInfoListType; ModelFitInfoListType m_modelfitList; /** @brief The currently selected modelfit */ mitk::modelFit::ModelFitInfo::ConstPointer m_currentFit; /** @brief Pointer to the instance of the model parameterizer for the current fit */ mitk::ModelParameterizerBase::Pointer m_currentModelParameterizer; mitk::IModelFitProvider* m_currentModelProviderService; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; /** @brief indicates if the currently selected position is valid for the currently selected fit. * This it is within the input image */ bool m_validSelectedPosition; /** @brief currently selected time step of the selected node for the visualization logic*/ unsigned int m_currentSelectedTimeStep; /** @brief currently selected node for the visualization logic*/ mitk::DataNode::ConstPointer m_currentSelectedNode; mitk::DataNode::Pointer m_PositionBookmarksNode; mitk::PointSet::Pointer m_PositionBookmarks; /** @brief Number of interpolation steps between two x values */ static const unsigned int INTERPOLATION_STEPS; /*************************************/ /* Members for visualizing the model */ itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentFitTime; itk::TimeStamp m_currentPositionTime; itk::TimeStamp m_lastRefreshTime; mitk::ModelFitPlotData m_PlotCurves; std::unique_ptr m_SelectionServiceConnector; QmitkFitParameterModel* m_FitParameterModel; QmitkSliceNavigationListener m_SliceChangeListener; /** Check and updates the plot data if needed. * @return indicates if something was refreshed (true)*/ bool RefreshPlotData(); void RenderPlot(); - void RenderPlotCurve(const mitk::PlotDataCurveCollection* curveCollection, const QColor& sampleColor, const QColor& signalColor, const std::string& posString); + void RenderPlotCurve(const mitk::PlotDataCurveCollection* curveCollection, const QColor& sampleColor, const QColor& signalColor, const std::string& posString, const QColor& legendTextColor); void RenderFitInfo(); void EnsureBookmarkPointSet(); static mitk::PlotDataCurveCollection::Pointer RefreshPlotDataCurveCollection(const mitk::Point3D& position, const mitk::Image* input, const mitk::modelFit::ModelFitInfo* fitInfo, const mitk::ModelBase::TimeGridType& timeGrid, mitk::ModelParameterizerBase* parameterizer); }; #endif // ModelFitInspectorView_h diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp index 0f8a1a3022..bfd871fb1b 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp @@ -1,1366 +1,1368 @@ /*============================================================================ 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 "MRPerfusionView.h" #include "boost/tokenizer.hpp" #include "boost/math/constants/constants.hpp" #include #include "mitkWorkbenchUtil.h" #include "mitkAterialInputFunctionGenerator.h" #include "mitkConcentrationCurveGenerator.h" #include #include #include #include #include #include #include #include "mitkTwoCompartmentExchangeModelFactory.h" #include "mitkTwoCompartmentExchangeModelParameterizer.h" #include #include #include #include #include #include #include #include "mitkNodePredicateFunction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include const std::string MRPerfusionView::VIEW_ID = "org.mitk.views.pharmacokinetics.mri"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); stepStream.imbue(std::locale("C")); double value = 0.0; if (!(stepStream >> value) || !(stepStream.eof())) { mitkThrow() << "Cannot convert string to double. String: " << data; } return value; } void MRPerfusionView::SetFocus() { m_Controls.btnModelling->setFocus(); } void MRPerfusionView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); this->InitModelComboBox(); m_Controls.labelMaskInfo->hide(); m_Controls.timeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.timeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.timeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.timeSeriesNodeSelector->SetInvalidInfo("Please select time series."); - m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); m_Controls.maskNodeSelector->SetNodePredicate(this->m_IsMaskPredicate); m_Controls.maskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.maskNodeSelector->SetSelectionIsOptional(true); m_Controls.maskNodeSelector->SetEmptyInfo("Please select (optional) mask."); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkMaskInfo, SIGNAL(toggled(bool)), m_Controls.labelMaskInfo, SLOT(setVisible(bool))); connect(m_Controls.timeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnImageNodeSelectionChanged); connect(m_Controls.maskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnMaskNodeSelectionChanged); connect(m_Controls.AIFMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); connect(m_Controls.AIFImageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); //AIF setting m_Controls.groupAIF->hide(); m_Controls.btnAIFFile->setEnabled(false); m_Controls.btnAIFFile->setVisible(false); m_Controls.aifFilePath->setEnabled(false); m_Controls.aifFilePath->setVisible(false); m_Controls.radioAIFImage->setChecked(true); m_Controls.AIFMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFMaskNodeSelector->SetNodePredicate(m_IsMaskPredicate); m_Controls.AIFMaskNodeSelector->setVisible(true); m_Controls.AIFMaskNodeSelector->setEnabled(true); - m_Controls.AIFMaskNodeSelector->SetAutoSelectNewNodes(true); m_Controls.AIFImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFImageNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.AIFImageNodeSelector->setEnabled(false); m_Controls.AIFImageNodeSelector->setVisible(false); m_Controls.checkDedicatedAIFImage->setEnabled(true); m_Controls.HCLSpinBox->setValue(mitk::AterialInputFunctionGenerator::DEFAULT_HEMATOCRIT_LEVEL); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.labelAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setVisible(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setVisible(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setVisible(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.btnAIFFile, SIGNAL(clicked()), this, SLOT(LoadAIFfromFile())); //Brix setting m_Controls.groupDescBrix->hide(); connect(m_Controls.injectiontime, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); //Concentration m_Controls.groupConcentration->hide(); m_Controls.groupBoxEnhancement->hide(); m_Controls.radioButtonNoConversion->setChecked(true); m_Controls.groupBox_T1MapviaVFA->hide(); m_Controls.spinBox_baselineStartTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setMinimum(0); m_Controls.spinBox_baselineStartTimeStep->setMinimum(0); m_Controls.groupBox_baselineRangeSelection->hide(); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.factorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineStartTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineEndTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_T1MapviaVFA, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.FlipangleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.RelaxivitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.TRSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); m_Controls.PDWImageNodeSelector->SetNodePredicate(m_isValidPDWImagePredicate); m_Controls.PDWImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.PDWImageNodeSelector->SetInvalidInfo("Please select PDW Image."); m_Controls.PDWImageNodeSelector->setEnabled(false); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.PDWImageNodeSelector, SLOT(setEnabled(bool))); + // Should be done last, if everything else is configured because it triggers the autoselection of data. + m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); + m_Controls.AIFMaskNodeSelector->SetAutoSelectNewNodes(true); + UpdateGUIControls(); } void MRPerfusionView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr || dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupAIF->setVisible(isToftsFactory || is2CXMFactory); m_Controls.groupDescBrix->setVisible(isDescBrixFactory); if (isDescBrixFactory) { m_Controls.toolboxConfiguration->setItemEnabled(2, false); } else { m_Controls.toolboxConfiguration->setItemEnabled(2, true); } m_Controls.groupConcentration->setVisible(isToftsFactory || is2CXMFactory ); m_Controls.AIFImageNodeSelector->setVisible(!m_Controls.radioAIFFile->isChecked()); m_Controls.AIFImageNodeSelector->setVisible(m_Controls.radioAIFImage->isChecked() && m_Controls.checkDedicatedAIFImage->isChecked()); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupAIF->setEnabled(!m_FittingInProgress); m_Controls.groupDescBrix->setEnabled(!m_FittingInProgress); m_Controls.groupConcentration->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); m_Controls.spinBox_baselineStartTimeStep->setEnabled( m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); m_Controls.spinBox_baselineEndTimeStep->setEnabled(m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); } void MRPerfusionView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } UpdateGUIControls(); } std::string MRPerfusionView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string MRPerfusionView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void MRPerfusionView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { m_HasGeneratedNewInput = false; m_HasGeneratedNewInputAIF = false; mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExtToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isStanToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateDescriptiveBrixModel_PixelBased(fitSession, generator); } else { GenerateDescriptiveBrixModel_ROIBased(fitSession, generator); } } else if (isStanToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (isExtToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (is2CXMFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; UpdateGUIControls(); DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void MRPerfusionView::OnImageNodeSelectionChanged(QList/*nodes*/) { if (m_Controls.timeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.timeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); if (m_selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(m_selectedImage->GetGeometry()); m_Controls.maskNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(m_selectedImage->GetGeometry())); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); m_Controls.maskNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(nullptr)); } } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } if (this->m_selectedImage.IsNotNull()) { m_Controls.spinBox_baselineStartTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); m_Controls.spinBox_baselineEndTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); } UpdateGUIControls(); } void MRPerfusionView::OnMaskNodeSelectionChanged(QList/*nodes*/) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; if (m_Controls.maskNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedMaskNode = m_Controls.maskNodeSelector->GetSelectedNode(); auto selectedLabelSetMask = dynamic_cast(m_selectedMaskNode->GetData()); if (selectedLabelSetMask != nullptr) { if (selectedLabelSetMask->GetAllLabelValues().size() > 1) { MITK_INFO << "Selected mask has multiple labels. Only use first used to mask the model fit."; } this->m_selectedMask = mitk::CreateLabelMask(selectedLabelSetMask, selectedLabelSetMask->GetAllLabelValues().front(), true); } if (this->m_selectedMask.IsNotNull() && this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_Controls.maskNodeSelector->GetSelectedNode()->GetName(); this->m_selectedMask = SelectImageByTimeStep(m_selectedMask, 0); } } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } UpdateGUIControls(); } bool MRPerfusionView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr|| dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { //if all static parameters for this model are set, exit with true, Otherwise exit with false ok = m_Controls.injectiontime->value() > 0; } else if (isToftsFactory || is2CXMFactory) { if (this->m_Controls.radioAIFImage->isChecked()) { ok = ok && m_Controls.AIFMaskNodeSelector->GetSelectedNode().IsNotNull(); if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { ok = ok && m_Controls.AIFImageNodeSelector->GetSelectedNode().IsNotNull(); } } else if (this->m_Controls.radioAIFFile->isChecked()) { ok = ok && (this->AIFinputGrid.size() != 0) && (this->AIFinputFunction.size() != 0); } else { ok = false; } if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() || this->m_Controls.radioButton_relativeEnchancement->isChecked() ) { ok = ok && (m_Controls.factorSpinBox->value() > 0); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked() ) { ok = ok && (m_Controls.FlipangleSpinBox->value() > 0); ok = ok && (m_Controls.TRSpinBox->value() > 0); ok = ok && (m_Controls.RelaxivitySpinBox->value() > 0); ok = ok && (m_Controls.PDWImageNodeSelector->GetSelectedNode().IsNotNull()); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonNoConversion->isChecked()) { ok = ok && true; } else { ok = false; } } //add other models as else if and check whether all needed static parameters are set else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter as an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; } else { ok = false; } return ok; } bool MRPerfusionView::CheckBaselineSelectionSettings() const { return m_Controls.spinBox_baselineStartTimeStep->value() <= m_Controls.spinBox_baselineEndTimeStep->value(); } void MRPerfusionView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::InitialParameterizationDelegateBase::Pointer paramDelegate = m_Controls.initialValuesManager->getInitialParametrizationDelegate(); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } } void MRPerfusionView::GenerateDescriptiveBrixModel_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelParameterizer::New(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(this->m_selectedImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::DescriptivePharmacokineticBrixModelParameterizer::BaseImageType::Pointer baseImage; mitk::CastToItkImage(imageTimeSelector->GetOutput(), baseImage); modelParameterizer->SetBaseImage(baseImage); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } void MRPerfusionView::GenerateDescriptiveBrixModel_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); modelParameterizer->SetBaseValue(roiSignal[0]); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateLinearModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } template void MRPerfusionView::GenerateLinearModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_inputImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(this->m_inputImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(this->m_inputImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void MRPerfusionView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { this->m_Controls.infoBox->append(QString("" + QString("Fitting Data Set . . .") + QString (""))); ///////////////////////// //create job and put it into the thread pool mitk::modelFit::ModelFitResultNodeVectorType additionalNodes; if (m_HasGeneratedNewInput) { additionalNodes.push_back(m_inputNode); } if (m_HasGeneratedNewInputAIF) { additionalNodes.push_back(m_inputAIFNode); } ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode, additionalNodes); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } MRPerfusionView::MRPerfusionView() : m_FittingInProgress(false), m_HasGeneratedNewInput(false), m_HasGeneratedNewInputAIF(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::DescriptivePharmacokineticBrixModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::StandardToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExtendedToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoCompartmentExchangeModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateDimension::Pointer is3D = mitk::NodePredicateDimension::New(3); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); mitk::NodePredicateAnd::Pointer is3DImage = mitk::NodePredicateAnd::New(isImage, is3D, isNoMask); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); auto isDynamicData = mitk::NodePredicateFunction::New([](const mitk::DataNode* node) { return (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); auto modelFitResultRelationRule = mitk::ModelFitResultRelationRule::New(); auto isNoModelFitNodePredicate = mitk::NodePredicateNot::New(modelFitResultRelationRule->GetConnectedSourcesDetector()); this->m_isValidPDWImagePredicate = mitk::NodePredicateAnd::New(is3DImage, isNoModelFitNodePredicate); this->m_isValidTimeSeriesImagePredicate = mitk::NodePredicateAnd::New(isDynamicData, isImage, isNoMask); } void MRPerfusionView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished.")); this->m_FittingInProgress = false; this->UpdateGUIControls(); }; void MRPerfusionView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void MRPerfusionView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); //this stores the concentration image and AIF concentration image, if generated for this fit in the storage. //if not generated for this fit, relevant nodes are empty. mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), pJob->GetAdditionalRelevantNodes(), pJob->GetParentNode()); }; void MRPerfusionView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void MRPerfusionView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void MRPerfusionView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::DataNode::Pointer MRPerfusionView::GenerateConcentrationNode(mitk::Image* image, const std::string& nodeName) const { if (!image) { mitkThrow() << "Cannot generate concentration node. Passed image is null. parameter name: "; } mitk::DataNode::Pointer result = mitk::DataNode::New(); result->SetData(image); result->SetName(nodeName); result->SetVisibility(true); return result; }; mitk::Image::Pointer MRPerfusionView::ConvertConcentrationImage(bool AIFMode) { //Compute Concentration image mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); if (m_Controls.checkDedicatedAIFImage->isChecked() && AIFMode) { concentrationGen->SetDynamicImage(this->m_selectedAIFImage); } else { concentrationGen->SetDynamicImage(this->m_selectedImage); } concentrationGen->SetAbsoluteSignalEnhancement(m_Controls.radioButton_absoluteEnhancement->isChecked()); concentrationGen->SetRelativeSignalEnhancement(m_Controls.radioButton_relativeEnchancement->isChecked()); concentrationGen->SetUsingT1Map(m_Controls.radioButtonUsingT1viaVFA->isChecked()); if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) { concentrationGen->SetRepetitionTime(m_Controls.TRSpinBox->value()); concentrationGen->SetRelaxivity(m_Controls.RelaxivitySpinBox->value()); concentrationGen->SetPDWImage(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); //Convert Flipangle from degree to radiant double alpha = m_Controls.FlipangleSpinBox->value()/360*2* boost::math::constants::pi(); concentrationGen->SetFlipAngle(alpha); double alphaPDW = m_Controls.FlipanglePDWSpinBox->value() / 360 * 2 * boost::math::constants::pi(); concentrationGen->SetFlipAnglePDW(alphaPDW); } else { concentrationGen->SetFactor(m_Controls.factorSpinBox->value()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); return concentrationImage; } void MRPerfusionView::GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (this->m_Controls.radioAIFFile->isChecked()) { aif.clear(); aifTimeGrid.clear(); aif.SetSize(AIFinputFunction.size()); aifTimeGrid.SetSize(AIFinputGrid.size()); aif.fill(0.0); aifTimeGrid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for (std::vector::const_iterator pos = AIFinputFunction.begin(); pos != AIFinputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = aifTimeGrid.begin(); for (std::vector::const_iterator pos = AIFinputGrid.begin(); pos != AIFinputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } } else if (this->m_Controls.radioAIFImage->isChecked()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(this->m_Controls.HCLSpinBox->value()); //mask settings this->m_selectedAIFMaskNode = m_Controls.AIFMaskNodeSelector->GetSelectedNode(); this->m_selectedAIFMask = dynamic_cast(this->m_selectedAIFMaskNode->GetData()); if (this->m_selectedAIFMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit. AIF Mask name: " << m_selectedAIFMaskNode->GetName() ; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedAIFMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedAIFMask = maskedImageTimeSelector->GetOutput(); } if (this->m_selectedAIFMask.IsNotNull()) { aifGenerator->SetMask(this->m_selectedAIFMask); } //image settings if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { this->m_selectedAIFImageNode = m_Controls.AIFImageNodeSelector->GetSelectedNode(); this->m_selectedAIFImage = dynamic_cast(this->m_selectedAIFImageNode->GetData()); } else { this->m_selectedAIFImageNode = m_selectedNode; this->m_selectedAIFImage = m_selectedImage; } this->PrepareAIFConcentrationImage(); aifGenerator->SetDynamicImage(this->m_inputAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. View is in a invalid state. No AIF mode selected."; } } void MRPerfusionView::LoadAIFfromFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.aifFilePath->setText(fileName); std::string m_aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::ifstream in1(m_aifFilePath.c_str()); if (!in1.is_open()) { this->m_Controls.infoBox->append(QString("Could not open AIF File!")); } std::vector< std::string > vec1; std::string line1; while (getline(in1, line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(), tok.end()); this->AIFinputGrid.push_back(convertToDouble(vec1[0])); this->AIFinputFunction.push_back(convertToDouble(vec1[1])); } } void MRPerfusionView::PrepareConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInput = false; if (!this->m_Controls.radioButtonNoConversion->isChecked()) { concentrationImage = this->ConvertConcentrationImage(false); concentrationNode = GenerateConcentrationNode(concentrationImage, "Concentration"); m_HasGeneratedNewInput = true; } m_inputImage = concentrationImage; m_inputNode = concentrationNode; } void MRPerfusionView::PrepareAIFConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInputAIF = false; if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { concentrationImage = this->m_selectedAIFImage; concentrationNode = this->m_selectedAIFImageNode; } if (!this->m_Controls.radioButtonNoConversion->isChecked()) { if (!this->m_Controls.checkDedicatedAIFImage->isChecked()) { if (m_inputImage.IsNull()) { mitkThrow() << "Cannot get AIF concentration image. Invalid view state. Input image is not defined yet, but should be."; } //we can directly use the concentration input image/node (generated by GetConcentrationImage) also for the AIF concentrationImage = this->m_inputImage; concentrationNode = this->m_inputNode; } else { concentrationImage = this->ConvertConcentrationImage(true); concentrationNode = GenerateConcentrationNode(concentrationImage, "AIF Concentration"); m_HasGeneratedNewInputAIF = true; } } m_inputAIFImage = concentrationImage; m_inputAIFNode = concentrationNode; } mitk::ModelFitFunctorBase::Pointer MRPerfusionView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(m_Controls.checkDebug->isChecked()); return fitFunctor.GetPointer(); } diff --git a/README.md b/README.md index 2204c8ade9..f2c8c8469b 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,110 @@ ![MITK Logo][logo] The [Medical Imaging Interaction Toolkit][mitk] (MITK) is a free open-source software system for development of interactive medical image processing software. MITK combines the [Insight Toolkit][itk] (ITK) and the [Visualization Toolkit][vtk] (VTK) with an application framework. The links below provide high-level and reference documentation targeting different usage scenarios: - Get a [high-level overview][mitk-overview] about MITK with pointers to further documentation - End-users looking for help with MITK applications should read the [MITK User Manual][mitk-usermanual] - Developers contributing to or using MITK, please see the [MITK Developer Manual][mitk-devmanual] as well as the [MITK API Reference][mitk-apiref] See the [MITK homepage][mitk] for details. Supported platforms ------------------- MITK is a cross-platform C++ toolkit and officially supports: - Windows - Linux - macOS For details, please read the [Supported Platforms][platforms] page. ### Build status of develop branch [![Windows][windows-build-status]][cdash] [![Ubuntu 20.04][ubuntu-20.04-build-status]][cdash] [![Ubuntu 22.04][ubuntu-22.04-build-status]][cdash] [![macOS 10.15 Catalina][macos-10.15-build-status]][cdash] [![macOS 11 Big Sur][macos-11-build-status]][cdash] We highly recommend to use the stable **master** branch instead. It is updated 1-2 times per month accompanied by curated [changelogs][changelog] and [snapshot installers][snapshot-installers]. License ------- Copyright (c) [German Cancer Research Center (DKFZ)][dkfz]. All rights reserved. MITK is available as free open-source software under a [3-clause BSD license][license]. Download -------- The MITK source code and binaries for the *MitkWorkbench* application are released regularly according to the [MITK release cycle][release-cycle]. See the [Download][download] page for a list of releases. The official MITK source code is available in the [MITK Git repository][phab_repo]. The Git clone command is git clone https://phabricator.mitk.org/source/mitk.git MITK Active development takes place in the MITK develop branch and its usage is advised for advanced users only. How to contribute ----------------- Contributions of all kind are happily accepted. However, to make the contribution process as smooth as possible, please read the [How to contribute to MITK][contribute] page if you plan to contribute to MITK. Build instructions ------------------ MITK uses [CMake][cmake] to configure a build tree. The following is a crash course about cloning, configuring, and building MITK on a Linux/Unix system: git clone https://phabricator.mitk.org/source/mitk.git MITK mkdir MITK-build cd MITK-build cmake ../MITK make -j4 Read the comprehensive [build instructions][build] page for details. Useful links ------------ - [Homepage][mitk] - [Download][download] - [Mailing list][mailinglist] - [Issue tracker][bugs] [logo]: https://github.com/MITK/MITK/raw/master/mitk.png [mitk]: https://www.mitk.org [itk]: https://itk.org [vtk]: https://vtk.org -[mitk-overview]: https://docs.mitk.org/2024.06/ -[mitk-usermanual]: https://docs.mitk.org/2024.06/UserManualPortal.html -[mitk-devmanual]: https://docs.mitk.org/2024.06/DeveloperManualPortal.html -[mitk-apiref]: https://docs.mitk.org/2024.06/usergroup0.html -[platforms]: https://docs.mitk.org/2024.06/SupportedPlatformsPage.html +[mitk-overview]: https://docs.mitk.org/nightly/ +[mitk-usermanual]: https://docs.mitk.org/nightly/UserManualPortal.html +[mitk-devmanual]: https://docs.mitk.org/nightly/DeveloperManualPortal.html +[mitk-apiref]: https://docs.mitk.org/nightly/usergroup0.html +[platforms]: https://docs.mitk.org/nightly/SupportedPlatformsPage.html [dkfz]: https://www.dkfz.de [license]: https://github.com/MITK/MITK/blob/master/LICENSE [release-cycle]: https://www.mitk.org/MitkReleaseCycle [download]: https://www.mitk.org/Download [phab_repo]: https://phabricator.mitk.org/source/mitk/ [contribute]: https://www.mitk.org/How_to_contribute [cmake]: https://www.cmake.org -[build]: https://docs.mitk.org/2024.06/BuildInstructionsPage.html +[build]: https://docs.mitk.org/nightly/BuildInstructionsPage.html [mailinglist]: https://www.mitk.org/Mailinglist [bugs]: https://phabricator.mitk.org/maniphest/ [cdash]: https://cdash.mitk.org/index.php?project=MITK [changelog]: https://phabricator.mitk.org/w/mitk/changelog/ [snapshot-installers]: https://www.mitk.org/download/ci/snapshots/ [windows-build-status]: https://ci.mitk.org/buildStatus/icon?job=MITK%2FContinuous%2FWindows&subject=Windows [ubuntu-22.04-build-status]: https://ci.mitk.org/buildStatus/icon?job=MITK%2FContinuous%2FUbuntu+22.04&subject=Ubuntu+22.04 [ubuntu-20.04-build-status]: https://ci.mitk.org/buildStatus/icon?job=MITK%2FContinuous%2FUbuntu+20.04&subject=Ubuntu+20.04 [macOS-10.15-build-status]: https://ci.mitk.org/buildStatus/icon?job=MITK%2FContinuous%2FmacOS+Catalina&subject=macOS+10.15+Catalina [macOS-11-build-status]: https://ci.mitk.org/buildStatus/icon?job=MITK%2FContinuous%2FmacOS+Big+Sur&subject=macOS+11+Big+Sur