diff --git a/CITATION.cff b/CITATION.cff index e8016a7d89..0ef4ccfa96 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,37 +1,37 @@ cff-version: 1.2.0 title: MITK message: >- If you use this software, please cite it using the metadata from this file. type: software authors: - given-names: MITK Team affiliation: German Cancer Research Center (DKFZ) repository-code: 'https://github.com/MITK/MITK' url: 'https://www.mitk.org/' abstract: >- The Medical Imaging Interaction Toolkit (MITK) is a free open-source software for the development of interactive medical image processing software. Based on MITK, we provide the MITK Workbench, a powerful and free application to view, process, and segment medical images, as well as a set of useful commandline applications for typical image processing tasks like image registration, conversion, stitching. keywords: - dicom - medical-imaging - dicom-browser - medical-image-computing - cpp-library - dicom-rt - mitk - image-segmentation - annotation - image-registration - perfusion - visualization - application-framework license: BSD-3-Clause -version: v2023.04 -date-released: '2023-05-17' +version: v2023.12 +date-released: '2023-11-30' diff --git a/CMake/BuildConfigurations/Default.cmake b/CMake/BuildConfigurations/Default.cmake index 08e48e4bfd..7ec36d8732 100644 --- a/CMake/BuildConfigurations/Default.cmake +++ b/CMake/BuildConfigurations/Default.cmake @@ -1,25 +1,24 @@ set(MITK_CONFIG_PACKAGES ACVD Qt5 BLUEBERRY ) set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.mitkworkbench.intro org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.mxnmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization - org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper org.mitk.gui.qt.pixelvalue ) diff --git a/CMake/BuildConfigurations/WorkbenchRelease.cmake b/CMake/BuildConfigurations/WorkbenchRelease.cmake index 0163c6a085..7f02a7654c 100644 --- a/CMake/BuildConfigurations/WorkbenchRelease.cmake +++ b/CMake/BuildConfigurations/WorkbenchRelease.cmake @@ -1,29 +1,36 @@ include(${CMAKE_CURRENT_LIST_DIR}/Default.cmake) set(MITK_CONFIG_PACKAGES ${MITK_CONFIG_PACKAGES} MatchPoint ) set(MITK_CONFIG_PLUGINS ${MITK_CONFIG_PLUGINS} org.mitk.matchpoint.core.helper org.mitk.gui.qt.matchpoint.algorithm.browser org.mitk.gui.qt.matchpoint.algorithm.control org.mitk.gui.qt.matchpoint.mapper org.mitk.gui.qt.matchpoint.framereg org.mitk.gui.qt.matchpoint.visualizer org.mitk.gui.qt.matchpoint.evaluator org.mitk.gui.qt.matchpoint.manipulator + org.mitk.gui.qt.dicominspector + org.mitk.gui.qt.fit.genericfitting + org.mitk.gui.qt.fit.inspector + org.mitk.gui.qt.pharmacokinetics.mri + org.mitk.gui.qt.pharmacokinetics.concentration.mri + org.mitk.gui.qt.pharmacokinetics.curvedescriptor ) if(NOT MITK_USE_SUPERBUILD) set(BUILD_CoreCmdApps ON CACHE BOOL "" FORCE) set(BUILD_MatchPointCmdApps ON CACHE BOOL "" FORCE) set(BUILD_SegmentationCmdApps ON CACHE BOOL "" FORCE) + set(BUILD_DICOMCmdApps ON CACHE BOOL "" FORCE) endif() set(MITK_VTK_DEBUG_LEAKS OFF CACHE BOOL "Enable VTK Debug Leaks" FORCE) find_package(Doxygen REQUIRED) # Ensure that the in-application help can be build set(BLUEBERRY_QT_HELP_REQUIRED ON CACHE BOOL "Required Qt help documentation in plug-ins" FORCE) diff --git a/CMake/BuildConfigurations/mitkNavigationModules.cmake b/CMake/BuildConfigurations/mitkNavigationModules.cmake index d6881034ab..a5eb033617 100644 --- a/CMake/BuildConfigurations/mitkNavigationModules.cmake +++ b/CMake/BuildConfigurations/mitkNavigationModules.cmake @@ -1,35 +1,34 @@ message(STATUS "Configuring MITK Navigation Modules Build") set(MITK_CONFIG_PACKAGES ACVD Qt5 BLUEBERRY ) # Enable open cv and open igt link, which is a necessary configuration set(MITK_USE_OpenCV ON CACHE BOOL "MITK Use OpenCV Library" FORCE) set(MITK_USE_OpenIGTLink ON CACHE BOOL "MITK Use OpenIGTLink Library" FORCE) # Enable default plugins and the navigation modules set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization - org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.registration org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper org.mitk.gui.qt.igttracking org.mitk.gui.qt.openigtlink org.mitk.gui.qt.ultrasound org.mitk.gui.qt.tofutil ) diff --git a/CMake/Whitelists/FlowBenchSegmentation.cmake b/CMake/Whitelists/FlowBenchSegmentation.cmake index c0eedb2dfb..7c7a9ea489 100644 --- a/CMake/Whitelists/FlowBenchSegmentation.cmake +++ b/CMake/Whitelists/FlowBenchSegmentation.cmake @@ -1,54 +1,53 @@ set(enabled_modules Core CppMicroServices DICOM DICOMPM DataTypesExt AlgorithmsExt DICOMQI Multilabel SceneSerializationBase DICOMPMIO DICOMImageIO ContourModel DICOMSegIO LegacyGL MapperExt SceneSerialization LegacyIO IOExt MultilabelIO AppUtil QtWidgets QtWidgetsExt Segmentation SegmentationUI PlanarFigure Annotation SurfaceInterpolation GraphAlgorithms ImageExtraction ImageStatistics ) set(enabled_plugins org.blueberry.core.commands org.blueberry.core.expressions org.blueberry.core.runtime org.blueberry.ui.qt org.blueberry.ui.qt.help org.blueberry.ui.qt.log org.mitk.core.services org.mitk.gui.common org.mitk.gui.qt.application org.mitk.gui.qt.common org.mitk.gui.qt.datamanager org.mitk.gui.qt.ext org.mitk.gui.qt.flow.segmentation org.mitk.gui.qt.flowapplication org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.stdmultiwidgeteditor -org.mitk.planarfigure ) diff --git a/CMakeExternals/CTK.cmake b/CMakeExternals/CTK.cmake index a14e888731..93716a6ca6 100644 --- a/CMakeExternals/CTK.cmake +++ b/CMakeExternals/CTK.cmake @@ -1,102 +1,78 @@ #----------------------------------------------------------------------------- # CTK #----------------------------------------------------------------------------- if(MITK_USE_CTK) # Sanity checks if(DEFINED CTK_DIR AND NOT EXISTS ${CTK_DIR}) message(FATAL_ERROR "CTK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj CTK) set(proj_DEPENDENCIES DCMTK) set(CTK_DEPENDS ${proj}) if(NOT DEFINED CTK_DIR) set(ctk_optional_cache_args ) - if(MITK_USE_Python3) - list(APPEND ctk_optional_cache_args - -DCTK_LIB_Scripting/Python/Widgets:BOOL=ON - -DCTK_ENABLE_Python_Wrapping:BOOL=OFF - -DCTK_APP_ctkSimplePythonShell:BOOL=OFF - "-DPYTHON_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIRS}" - "-DPYTHON_LIBRARY:FILEPATH=${Python3_LIBRARY_RELEASE}" - ) - else() - list(APPEND ctk_optional_cache_args - -DCTK_LIB_Scripting/Python/Widgets:BOOL=OFF - -DCTK_ENABLE_Python_Wrapping:BOOL=OFF - -DCTK_APP_ctkSimplePythonShell:BOOL=OFF - -DDCMTK_CMAKE_DEBUG_POSTFIX:STRING=d - ) - endif() if(CTEST_USE_LAUNCHERS) list(APPEND ctk_optional_cache_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() FOREACH(type RUNTIME ARCHIVE LIBRARY) IF(DEFINED CTK_PLUGIN_${type}_OUTPUT_DIRECTORY) LIST(APPEND mitk_optional_cache_args -DCTK_PLUGIN_${type}_OUTPUT_DIRECTORY:PATH=${CTK_PLUGIN_${type}_OUTPUT_DIRECTORY}) ENDIF() ENDFOREACH() mitk_query_custom_ep_vars() - set(pythonqt_location_args - "-DPythonQt_GIT_REPOSITORY:STRING=https://github.com/MITK/PythonQt.git" - -DPythonQt_REVISION_TAG:STRING=patched-10-patched - ) - ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} GIT_REPOSITORY https://github.com/MITK/CTK.git - GIT_TAG ec816cbb-patched + GIT_TAG 37aff99226ed936b930b1ef07df046877771ea03 # branch: qt-6 UPDATE_COMMAND "" INSTALL_COMMAND "" CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${ctk_optional_cache_args} # The CTK PluginFramework cannot cope with # a non-empty CMAKE_DEBUG_POSTFIX for the plugin # libraries yet. -DCMAKE_DEBUG_POSTFIX:STRING= -DCTK_QT_VERSION:STRING=5 - -DQt5_DIR=${Qt5_DIR} - -DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE} - -DCTK_BUILD_QTDESIGNER_PLUGINS:BOOL=ON - -DCTK_LIB_CommandLineModules/Backend/LocalProcess:BOOL=ON - -DCTK_LIB_CommandLineModules/Frontend/QtGui:BOOL=ON + "-DQt5_DIR=${Qt5_DIR}" + "-DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE}" -DCTK_LIB_PluginFramework:BOOL=ON -DCTK_LIB_DICOM/Widgets:BOOL=ON -DCTK_LIB_XNAT/Core:BOOL=ON -DCTK_PLUGIN_org.commontk.eventadmin:BOOL=ON -DCTK_PLUGIN_org.commontk.configadmin:BOOL=ON - -DDCMTK_DIR:PATH=${DCMTK_DIR} - ${pythonqt_location_args} + -DCTK_USE_SYSTEM_DCMTK:BOOL=ON + "-DDCMTK_ROOT:PATH=${ep_prefix}" ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) ExternalProject_Get_Property(${proj} binary_dir) set(CTK_DIR ${binary_dir}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/Eigen.cmake b/CMakeExternals/Eigen.cmake index a39df68097..eb734a01b6 100644 --- a/CMakeExternals/Eigen.cmake +++ b/CMakeExternals/Eigen.cmake @@ -1,43 +1,45 @@ #----------------------------------------------------------------------------- # Eigen #----------------------------------------------------------------------------- if(MITK_USE_Eigen) # Sanity checks if(DEFINED Eigen_DIR AND NOT EXISTS ${Eigen_DIR}) message(FATAL_ERROR "Eigen_DIR variable is defined but corresponds to non-existing directory") endif() set(proj Eigen) - set(proj_DEPENDENCIES ) + set(proj_DEPENDENCIES Boost) set(Eigen_DEPENDS ${proj}) if(NOT DEFINED Eigen_DIR) ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git GIT_TAG 3.4.0 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} + "-DBoost_DIR:PATH=${Boost_DIR}" -DBUILD_TESTING:BOOL=ON -DEIGEN_BUILD_PKGCONFIG:BOOL=OFF CMAKE_CACHE_ARGS ${ep_common_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} + DEPENDS ${proj_DEPENDENCIES} ) set(Eigen_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index 02f6fe2280..c968205bf8 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,33 +1,33 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME OpenIGTLink OFF) mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) -mitkFunctionAddExternalProject(NAME Eigen ON ADVANCED DOC "Use the Eigen library") +mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) +mitkFunctionAddExternalProject(NAME Eigen ON DEPENDS Boost ADVANCED DOC "Use the Eigen library") 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 OpenCV OFF) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) -mitkFunctionAddExternalProject(NAME Boost 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 Qt5 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_Qt5) mitkFunctionAddExternalProject(NAME Qwt ON ADVANCED DEPENDS Qt5) 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/ITK.cmake b/CMakeExternals/ITK.cmake index 59b448cc79..4ea3b132db 100644 --- a/CMakeExternals/ITK.cmake +++ b/CMakeExternals/ITK.cmake @@ -1,90 +1,90 @@ #----------------------------------------------------------------------------- # ITK #----------------------------------------------------------------------------- # Sanity checks if(DEFINED ITK_DIR AND NOT EXISTS ${ITK_DIR}) message(FATAL_ERROR "ITK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj ITK) set(proj_DEPENDENCIES GDCM) if(MITK_USE_OpenCV) list(APPEND proj_DEPENDENCIES OpenCV) endif() if(MITK_USE_HDF5) list(APPEND proj_DEPENDENCIES HDF5) endif() set(ITK_DEPENDS ${proj}) if(NOT DEFINED ITK_DIR) set(additional_cmake_args -DUSE_WRAP_ITK:BOOL=OFF) list(APPEND additional_cmake_args -DITKV4_COMPATIBILITY:BOOL=OFF -DITK_LEGACY_REMOVE:BOOL=ON ) if(MITK_USE_OpenCV) list(APPEND additional_cmake_args -DModule_ITKVideoBridgeOpenCV:BOOL=ON -DOpenCV_DIR:PATH=${OpenCV_DIR} "-DCMAKE_CONFIGURATION_TYPES:STRING=Debug$Release" ) endif() # Keep the behaviour of ITK 4.3 which by default turned on ITK Review # see MITK bug #17338 list(APPEND additional_cmake_args -DModule_ITKReview:BOOL=ON -DModule_ITKOpenJPEG:BOOL=ON # for 4.7, the OpenJPEG is needed by review but the variable must be set -DModule_IsotropicWavelets:BOOL=ON ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_cmake_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} UPDATE_COMMAND "" - GIT_REPOSITORY https://github.com/MITK/ITK.git - GIT_TAG v5.3.0-patched + GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITK.git + GIT_TAG 80446b1c18fde0aaca3eecab6e1e7a30d015ac2c # tag: v5.4rc02 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_cmake_args} -DITK_SKIP_PATH_LENGTH_CHECKS:BOOL=ON -DBUILD_EXAMPLES:BOOL=OFF -DITK_USE_SYSTEM_GDCM:BOOL=ON -DGDCM_DIR:PATH=${GDCM_DIR} -DITK_USE_SYSTEM_HDF5:BOOL=ON -DHDF5_DIR:PATH=${HDF5_DIR} -DModule_GrowCut:BOOL=ON ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(ITK_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() diff --git a/CMakeExternals/cpprestsdk.cmake b/CMakeExternals/cpprestsdk.cmake index ea888903a9..3c172eab55 100644 --- a/CMakeExternals/cpprestsdk.cmake +++ b/CMakeExternals/cpprestsdk.cmake @@ -1,41 +1,41 @@ 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/microsoft/cpprestsdk.git - GIT_TAG v2.10.18 + 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 704c4e583e..4a3085768e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1422 +1,1424 @@ #[[ 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 2023.04.99) + project(MITK VERSION 2023.12.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_OpenCL "Use OpenCL GPU-Computing library" OFF) option(MITK_USE_OpenMP "Use OpenMP" OFF) option(MITK_USE_Python3 "Use Python 3" OFF) #----------------------------------------------------------------------------- # Build configurations #----------------------------------------------------------------------------- set(_buildConfigs "Custom") file(GLOB _buildConfigFiles CMake/BuildConfigurations/*.cmake) foreach(_buildConfigFile ${_buildConfigFiles}) get_filename_component(_buildConfigFile ${_buildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_buildConfigFile}) endforeach() foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) file(GLOB _extBuildConfigFiles "${MITK_EXTENSION_DIR}/CMake/BuildConfigurations/*.cmake") foreach(_extBuildConfigFile ${_extBuildConfigFiles}) get_filename_component(_extBuildConfigFile "${_extBuildConfigFile}" NAME_WE) list(APPEND _buildConfigs "${_extBuildConfigFile}") endforeach() list(REMOVE_DUPLICATES _buildConfigs) endforeach() set(MITK_BUILD_CONFIGURATION "Custom" CACHE STRING "Use pre-defined MITK configurations") set_property(CACHE MITK_BUILD_CONFIGURATION PROPERTY STRINGS ${_buildConfigs}) mitkFunctionEnableBuildConfiguration() mitkFunctionCreateWhitelistPaths(MITK) mitkFunctionFindWhitelists(MITK) # ----------------------------------------- # Qt version related variables option(MITK_USE_Qt5 "Use Qt 5 library" ON) if(MITK_USE_Qt5) if(WIN32) set(MITK_QT5_MINIMUM_VERSION 5.12.9) else() set(MITK_QT5_MINIMUM_VERSION 5.12) endif() set(MITK_QT5_COMPONENTS Concurrent OpenGL PrintSupport Script Sql Svg Widgets Xml XmlPatterns WebEngineWidgets UiTools Help LinguistTools) if(APPLE) list(APPEND MITK_QT5_COMPONENTS DBus) elseif(UNIX) list(APPEND MITK_QT5_COMPONENTS X11Extras) endif() # Hint at default install locations of Qt if(NOT Qt5_DIR) if(MSVC) set(_dir_candidates "C:/Qt") if(CMAKE_GENERATOR MATCHES "^Visual Studio [0-9]+ ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") elseif(CMAKE_GENERATOR MATCHES "Ninja") include(mitkFunctionGetMSVCVersion) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_PRODUCT_NAME MATCHES "^Visual Studio ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") endif() endif() if(_compilers MATCHES "[0-9]+") if (CMAKE_MATCH_0 EQUAL 2022) list(APPEND _compilers "msvc2019" "msvc2017") # Binary compatible elseif (CMAKE_MATCH_0 EQUAL 2019) list(APPEND _compilers "msvc2017") # Binary compatible endif() endif() else() set(_dir_candidates ~/Qt) if(APPLE) set(_compilers clang) else() list(APPEND _dir_candidates /opt/Qt) set(_compilers gcc) endif() endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) foreach(_compiler ${_compilers}) list(APPEND _compilers64 "${_compiler}_64") endforeach() set(_compilers ${_compilers64}) endif() foreach(_dir_candidate ${_dir_candidates}) get_filename_component(_dir_candidate ${_dir_candidate} REALPATH) foreach(_compiler ${_compilers}) set(_glob_expression "${_dir_candidate}/5.*/${_compiler}") file(GLOB _hints ${_glob_expression}) list(SORT _hints) list(APPEND MITK_QT5_HINTS ${_hints}) endforeach() endforeach() endif() find_package(Qt5 ${MITK_QT5_MINIMUM_VERSION} COMPONENTS ${MITK_QT5_COMPONENTS} REQUIRED HINTS ${MITK_QT5_HINTS}) endif() # ----------------------------------------- # Custom dependency logic if(WIN32 AND Qt5_DIR) set(_dir_candidate "${Qt5_DIR}/../../../../../Tools/OpenSSL/Win_x64") get_filename_component(_dir_candidate ${_dir_candidate} ABSOLUTE) if(EXISTS "${_dir_candidate}") set(OPENSSL_ROOT_DIR "${_dir_candidate}") endif() endif() find_package(OpenSSL) option(MITK_USE_SYSTEM_Boost "Use the system Boost" OFF) set(MITK_USE_Boost_LIBRARIES "" CACHE STRING "A semi-colon separated list of required Boost libraries") if(MITK_USE_cpprestsdk OR MITK_USE_httplib) if(NOT OpenSSL_FOUND) set(openssl_message "Could not find OpenSSL (dependency of C++ REST SDK and 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() list(APPEND MITK_USE_Boost_LIBRARIES date_time regex system) if(UNIX) list(APPEND MITK_USE_Boost_LIBRARIES atomic chrono filesystem random thread) endif() list(REMOVE_DUPLICATES MITK_USE_Boost_LIBRARIES) set(MITK_USE_Boost_LIBRARIES ${MITK_USE_Boost_LIBRARIES} CACHE STRING "A semi-colon separated list of required Boost libraries" FORCE) endif() if(MITK_USE_Python3) set(MITK_USE_ZLIB ON CACHE BOOL "" FORCE) - if(APPLE AND CMAKE_FRAMEWORK_PATH AND CMAKE_FRAMEWORK_PATH MATCHES "python3\\.?([0-9]+)") - find_package(Python3 3.${CMAKE_MATCH_1} EXACT REQUIRED COMPONENTS Interpreter Development NumPy) + if(APPLE) + set(python3_mininum_version 3.11) else() - find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy) + 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) # Deprecated include(mitkMacroCreateCTKPlugin) #----------------------------------------------------------------------------- # 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 "externaly set dir of the package ${_package}" FORCE) endif(DEFINED _temp_EP_${_package}_dir) find_package(${_package} REQUIRED) endif() endif() endif() endforeach() # Ensure that the MITK CMake module path comes first set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR} ${CMAKE_MODULE_PATH} ) if(MITK_USE_DCMTK) if(${_dcmtk_dir_orig} MATCHES "${MITK_EXTERNAL_PROJECT_PREFIX}.*") # Help our FindDCMTK.cmake script find our super-build DCMTK set(DCMTK_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) else() # Use the original value set(DCMTK_DIR ${_dcmtk_dir_orig}) endif() endif() if(MITK_USE_DCMQI) # Due to the preferred CONFIG mode in find_package calls above, # the DCMQIConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMQI. # Help our FindDCMQI.cmake script find our super-build DCMQI set(DCMQI_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) find_package(DCMQI REQUIRED) endif() if(MITK_USE_OpenIGTLink) link_directories(${OpenIGTLink_LIBRARY_DIRS}) endif() if(MITK_USE_OpenCL) find_package(OpenCL REQUIRED) endif() if(MITK_USE_OpenMP) find_package(OpenMP REQUIRED COMPONENTS CXX) else() find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) elseif(APPLE AND OpenMP_libomp_LIBRARY AND NOT OpenMP_CXX_LIB_NAMES) set(OpenMP_CXX_LIB_NAMES libomp CACHE STRING "" FORCE) get_filename_component(openmp_lib_dir "${OpenMP_libomp_LIBRARY}" DIRECTORY) set(openmp_include_dir "${openmp_lib_dir}/../include") if(EXISTS "${openmp_include_dir}") get_filename_component(openmp_include_dir "${openmp_include_dir}" REALPATH) set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${openmp_include_dir}" CACHE STRING "" FORCE) find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) endif() endif() endif() endif() # Qt support if(MITK_USE_Qt5) find_package(Qt5Core ${MITK_QT5_MINIMUM_VERSION} REQUIRED) # at least Core required get_target_property(_qmake_exec Qt5::qmake LOCATION) execute_process(COMMAND ${_qmake_exec} -query QT_INSTALL_BINS RESULT_VARIABLE _result OUTPUT_VARIABLE QT_BINARY_DIR ERROR_VARIABLE _error ) string(STRIP "${QT_BINARY_DIR}" QT_BINARY_DIR) if(_result OR NOT EXISTS "${QT_BINARY_DIR}") message(FATAL_ERROR "Could not determine Qt binary directory: ${_result} ${QT_BINARY_DIR} ${_error}") endif() find_program(QT_HELPGENERATOR_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5 qhelpgenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_COLLECTIONGENERATOR_EXECUTABLE NAMES qcollectiongenerator qcollectiongenerator-qt5 qcollectiongenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_ASSISTANT_EXECUTABLE NAMES assistant assistant-qt5 assistant5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_XMLPATTERNS_EXECUTABLE NAMES xmlpatterns PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) mark_as_advanced(QT_HELPGENERATOR_EXECUTABLE QT_COLLECTIONGENERATOR_EXECUTABLE QT_ASSISTANT_EXECUTABLE QT_XMLPATTERNS_EXECUTABLE ) if(MITK_USE_BLUEBERRY) option(BLUEBERRY_USE_QT_HELP "Enable support for integrating plugin documentation into Qt Help" ${DOXYGEN_FOUND}) mark_as_advanced(BLUEBERRY_USE_QT_HELP) # Sanity checks for in-application BlueBerry plug-in help generation if(BLUEBERRY_USE_QT_HELP) set(_force_blueberry_use_qt_help_to_off 0) if(NOT DOXYGEN_FOUND) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen was not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(DOXYGEN_FOUND AND DOXYGEN_VERSION VERSION_LESS 1.8.7) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen version 1.8.7 or newer not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_HELPGENERATOR_EXECUTABLE) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because QT_HELPGENERATOR_EXECUTABLE is empty.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT MITK_USE_Qt5) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because MITK_USE_Qt5 is OFF.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_XMLPATTERNS_EXECUTABLE) message("You have enabled Qt Help support, but QT_XMLPATTERNS_EXECUTABLE is empty") set(_force_blueberry_use_qt_help_to_off 1) endif() if(_force_blueberry_use_qt_help_to_off) set(BLUEBERRY_USE_QT_HELP OFF CACHE BOOL "Enable support for integrating plugin documentation into Qt Help" FORCE) endif() endif() if(BLUEBERRY_QT_HELP_REQUIRED AND NOT BLUEBERRY_USE_QT_HELP) message(FATAL_ERROR "BLUEBERRY_USE_QT_HELP is required to be set to ON") endif() endif() endif() #----------------------------------------------------------------------------- # Testing #----------------------------------------------------------------------------- if(BUILD_TESTING) # 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) 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}\"") endforeach() if(DOXYGEN_FOUND) add_subdirectory(Documentation) endif() #----------------------------------------------------------------------------- # Installation #----------------------------------------------------------------------------- # set MITK cpack variables # These are the default variables, which can be overwritten ( see below ) include(mitkSetupCPack) set(use_default_config ON) set(ALL_MITK_APPS "") set(activated_apps_no 0) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") set(MITK_APPS "") include("${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") foreach(mitk_app ${MITK_APPS}) string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) list(APPEND ALL_MITK_APPS "${MITK_EXTENSION_DIR}/Applications/${directory_name}^^${option_name}^^${executable_name}") if(${option_name} OR MITK_BUILD_ALL_APPS) MATH(EXPR activated_apps_no "${activated_apps_no} + 1") endif() endforeach() endif() endforeach() list(LENGTH ALL_MITK_APPS app_count) if(app_count EQUAL 1 AND (activated_apps_no EQUAL 1 OR MITK_BUILD_ALL_APPS)) # Corner case if there is only one app in total set(use_project_cpack ON) elseif(activated_apps_no EQUAL 1 AND NOT MITK_BUILD_ALL_APPS) # Only one app is enabled (no "build all" flag set) set(use_project_cpack ON) else() # Less or more then one app is enabled set(use_project_cpack OFF) endif() foreach(mitk_app ${ALL_MITK_APPS}) # extract target_dir and option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 target_dir) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) # check whether application specific configuration files will be used if(use_project_cpack) # use files if they exist if(EXISTS "${target_dir}/CPackOptions.cmake") include("${target_dir}/CPackOptions.cmake") endif() if(EXISTS "${target_dir}/CPackConfig.cmake.in") set(CPACK_PROJECT_CONFIG_FILE "${target_dir}/CPackConfig.cmake") configure_file(${target_dir}/CPackConfig.cmake.in ${CPACK_PROJECT_CONFIG_FILE} @ONLY) set(use_default_config OFF) endif() endif() # add link to the list list(APPEND CPACK_CREATE_DESKTOP_LINKS "${executable_name}") endif() endforeach() # if no application specific configuration file was used, use default if(use_default_config) configure_file(${MITK_SOURCE_DIR}/MITKCPackOptions.cmake.in ${MITK_BINARY_DIR}/MITKCPackOptions.cmake @ONLY) set(CPACK_PROJECT_CONFIG_FILE "${MITK_BINARY_DIR}/MITKCPackOptions.cmake") endif() # include CPack model once all variables are set include(CPack) # Additional installation rules include(mitkInstallRules) #----------------------------------------------------------------------------- # Last configuration steps #----------------------------------------------------------------------------- # ---------------- Export targets ----------------- set(MITK_EXPORTS_FILE "${MITK_BINARY_DIR}/MitkExports.cmake") file(REMOVE ${MITK_EXPORTS_FILE}) set(targets_to_export) get_property(module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) if(module_targets) list(APPEND targets_to_export ${module_targets}) endif() if(MITK_USE_BLUEBERRY) if(MITK_PLUGIN_LIBRARIES) list(APPEND targets_to_export ${MITK_PLUGIN_LIBRARIES}) endif() endif() export(TARGETS ${targets_to_export} APPEND FILE ${MITK_EXPORTS_FILE}) set(MITK_EXPORTED_TARGET_PROPERTIES ) foreach(target_to_export ${targets_to_export}) get_target_property(autoload_targets ${target_to_export} MITK_AUTOLOAD_TARGETS) if(autoload_targets) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_TARGETS \"${autoload_targets}\")") endif() get_target_property(autoload_dir ${target_to_export} MITK_AUTOLOAD_DIRECTORY) if(autoload_dir) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_DIRECTORY \"${autoload_dir}\")") endif() get_target_property(deprecated_module ${target_to_export} MITK_MODULE_DEPRECATED_SINCE) if(deprecated_module) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_MODULE_DEPRECATED_SINCE \"${deprecated_module}\")") endif() endforeach() # ---------------- External projects ----------------- get_property(MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS_CONFIG GLOBAL PROPERTY MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS) set(MITK_CONFIG_EXTERNAL_PROJECTS ) #string(REPLACE "^^" ";" _mitk_external_projects ${MITK_EXTERNAL_PROJECTS}) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} set(MITK_USE_${ep} ${MITK_USE_${ep}}) set(MITK_${ep}_DIR \"${${ep}_DIR}\") set(MITK_${ep}_COMPONENTS ${_components}) ") endforeach() foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(_components) set(_components_arg COMPONENTS \${_components}) else() set(_components_arg) endif() if(_package) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} if(MITK_USE_${ep}) set(${ep}_DIR \${MITK_${ep}_DIR}) if(MITK_${ep}_COMPONENTS) mitkMacroFindDependency(${_package} COMPONENTS \${MITK_${ep}_COMPONENTS}) else() mitkMacroFindDependency(${_package}) endif() endif()") endif() endforeach() # ---------------- Tools ----------------- configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactory.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactoryLoader.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactoryLoader.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolGUIExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolGUIExtensionITKFactory.cpp.in COPYONLY) # ---------------- Configure files ----------------- configure_file(mitkVersion.h.in ${MITK_BINARY_DIR}/mitkVersion.h) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) set(IPFUNC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ipFunc) set(UTILITIES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) configure_file(MITKConfig.cmake.in ${MITK_BINARY_DIR}/MITKConfig.cmake @ONLY) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake VERSION ${MITK_VERSION_STRING} COMPATIBILITY AnyNewerVersion) #----------------------------------------------------------------------------- # MITK Applications #----------------------------------------------------------------------------- # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Applications) if(MSVC AND TARGET MitkWorkbench) set_directory_properties(PROPERTIES VS_STARTUP_PROJECT MitkWorkbench) endif() foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/CMakeLists.txt") add_subdirectory("${MITK_APPLICATIONS_EXTENSION_DIR}" "Applications") endif() endforeach() #----------------------------------------------------------------------------- # MITK Examples #----------------------------------------------------------------------------- if(MITK_BUILD_EXAMPLES) # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Examples) endif() #----------------------------------------------------------------------------- # Print configuration summary #----------------------------------------------------------------------------- message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL ) diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox index 8304a11c44..215faaecb1 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox @@ -1,74 +1,73 @@ /** \page PluginListPage MITK Plugin Manuals The plugins and bundles provide much of the extended functionality of MITK. Each encapsulates a solution to a problem and associated features. This way one can easily assemble the necessary capabilites for a workflow without adding a lot of bloat, by combining plugins as needed.
  • \subpage org_mitk_views_basicimageprocessing
  • \subpage org_mitk_views_datamanager
  • \subpage org_mitk_editors_dicombrowser
  • \subpage org_mitk_views_dicominspector
  • \subpage org_mitk_views_imagecropper
  • \subpage org_mitk_views_imagenavigator
  • \subpage org_mitk_views_pixelvalue
  • \subpage org_blueberry_views_logview
  • \subpage org_mitk_views_matchpoint_algorithm_browser
  • \subpage org_mitk_views_matchpoint_algorithm_control
  • \subpage org_mitk_views_matchpoint_evaluator
  • \subpage org_mitk_views_matchpoint_framereg
  • \subpage org_mitk_views_matchpoint_manipulator
  • \subpage org_mitk_views_matchpoint_mapper
  • \subpage org_mitk_views_matchpoint_visualizer
  • \subpage org_mitk_gui_qt_measurementtoolbox
    • \subpage org_mitk_views_measurement
    • \subpage org_mitk_views_imagestatistics
  • \subpage org_mitk_views_moviemaker
  • \subpage org_mitk_views_pointsetinteraction
  • \subpage org_mitk_views_python
  • \subpage org_mitk_views_remeshing
  • \subpage org_mitk_views_screenshotmaker
  • Segmentation
    • \subpage org_mitk_views_segmentation
    • \subpage org_mitk_views_segmentationutilities
    • \subpage org_mitk_views_segmentationtasklist
  • \subpage org_mitk_editors_stdmultiwidget
  • \subpage org_mitk_editors_mxnmultiwidget
  • \subpage org_mitk_views_deformableclippingplane
  • \subpage org_mitk_views_viewnavigator
  • \subpage org_mitk_views_volumevisualization
  • \subpage org_mitk_views_properties
  • \subpage org_mitk_gui_qt_flowapplication
  • \subpage org_mitk_gui_qt_flow_segmentation
  • \subpage org_mitk_gui_qt_aicpregistration
  • \subpage org_mitk_gui_qt_cest
  • -
  • \subpage org_mitk_views_cmdlinemodules
  • \subpage org_mitk_views_pharmacokinetics_concentration_mri
  • \subpage org_mitk_views_pharmacokinetics_mri
  • \subpage org_mitk_views_pharmacokinetics_pet
  • \subpage org_mitk_gui_qt_examples
  • \subpage org_mitk_gui_qt_geometrytools
  • \subpage org_mitk_gui_qt_igtexample
  • \subpage org_mitk_gui_qt_igttracking
  • \subpage org_mitk_views_igttrackingsemiautomaticmeasurement
  • \subpage org_mitk_views_fit_demo
  • \subpage org_mitk_views_fit_genericfitting
  • \subpage org_mitk_views_fit_inspector
  • \subpage org_mitkexamplesopencv
  • \subpage org_mitk_gui_qt_overlaymanager
  • \subpage org_mitk_gui_qt_preprocessing_resampling
  • \subpage org_mitk_views_pharmacokinetics_curvedescriptor
  • \subpage org_mitk_views_pharmacokinetics_simulation
  • \subpage org_surfacematerialeditor
  • \subpage org_toftutorial
  • \subpage org_blueberry_ui_qt_objectinspector
  • \subpage org_mitk_gui_qt_ultrasound
  • \subpage org_mitk_gui_qt_xnat
*/ diff --git a/Documentation/Doxygen/3-DeveloperManual/Concepts/Concepts.dox b/Documentation/Doxygen/3-DeveloperManual/Concepts/Concepts.dox index ef6f9c5bfa..c66a8971dc 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Concepts/Concepts.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Concepts/Concepts.dox @@ -1,31 +1,32 @@ /** \page Concepts MITK Concepts The following items describe some issues about MITK on a more abstract level. -# \subpage CodingPage "Coding Concepts" -# \ref CodingPageGeneral -# \ref CodingPageStyle -# \ref CodingPageMITKMacros -# \subpage MicroServices_Overview -# Data Concepts -# \subpage BasicDataTypesPage -# \subpage DataManagementPage -# \subpage ReaderWriterPage -# \subpage MitkImagePage -# \subpage MITKSegmentationTaskListsPage + -# \subpage MITKROIPage -# \subpage PropertiesPage -# \subpage GeometryOverviewPage -# \subpage PipelineingConceptPage -# \subpage AnnotationPage -# \subpage PersistenceConceptPage -# \subpage SelectionConceptPage -# \subpage QVTKRendering -# Interaction -# \subpage DataInteractionPage -# \subpage InteractionPage -# \subpage LoggingPage -# \subpage ExceptionPage -# \subpage ModularizationPage "Modularization Concept" -# \ref ModularizationPageOverview */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKROIs.md b/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKROIs.md new file mode 100644 index 0000000000..2dac2c9742 --- /dev/null +++ b/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKROIs.md @@ -0,0 +1,154 @@ +# MITK ROI {#MITKROIPage} + +[TOC] + +## Disclaimer + +Until the MITK ROI file format is going to be officially announced in a 2024 release of MITK, the file format must be considered experimental and is prone to change without any prior warning. + +## Overview + +MITK ROI is a JSON-based file format defining a collection of region of interests (ROIs). + +ROIs must have an ID (unsigned integer) and their shape is currently considered to be an axis-aligned bounding box. +Its bounds are defined by minimum and maximum index coordinates, typically relative to an image. +Custom properties of various known types can be optionally attached to a ROI. +A few of these properties are used by MITK to define the appearance of a rendered ROI, for example: + + - "color" (mitk::ColorProperty): Color/RGB triplet of the rendered ROI (default: white \[1.0, 1.0, 1.0\]) + - "opacity" (mitk::FloatProperty): Opacity of the rendered ROI (default: 100% \[1.0\]) + - "lineWidth" (mitk::FloatProperty): Line width of the egdes of the rendered ROI (default: 1px \[1.0\]) + +ROIs can be optionally time-resolved and define both coordinates and properties per time step, allowing for a dynamic appearance, position, and size over time. + +ROIs also display a caption at their bottom-left corner (supporting multiple lines), that can be set once per MITK ROI file for all contained ROIs. +Placeholders enclosed by braces in the caption are substituted by their corresponding ROI property values at runtime. +The default caption is "{name} ({ID})", where ``{ID}`` is a special placeholder for the ID of a ROI (technically not a ROI property), and ``{name}`` refers to the ROI property "name" (typically an mitk::StringProperty). + +Last but not least the reference (image) geometry of the ROIs in an MITK ROI file must be specified to be able to map all index coordinates to actual world coordinates. +A geometry is defined by an origin, the pixel/voxel spacing, a size, and optionally the number of time steps in case of a time-resolved MITK ROI file. + +## File format + +As all features are explained in the overview above, the JSON-based file format is defined here by two examples with minimal additional notes: one example for a static MITK ROI file and one example for a time-resolved MITK ROI file. + +### Static MITK ROI file + +This example contains two ROIs for detected tumors in an image with certain confidence. +Names and confidence values will be displayed in separate lines for each ROI. + +~~~{.json} +{ + "FileFormat": "MITK ROI", + "Version": 1, + "Name": "Static example", + "Caption": "{name}\nConfidence: {confidence}", + "Geometry": { + "Origin": [0, 0, 0], + "Spacing": [1, 1, 3], + "Size": [256, 256, 49] + }, + "ROIs": [ + { + "ID": 0, + "Min": [4, 4, 1], + "Max": [124, 124, 31], + "Properties": { + "StringProperty": { + "name": "tumor", + "comment": "Detected a tumor with 95% confidence.", + "note": "Properties are grouped by their type to reduce verbosity." + }, + "ColorProperty": { + "color": [0, 1, 0] + }, + "FloatProperty": { + "confidence": 0.95 + } + } + }, + { + "ID": 1, + "Min": [132, 4, 1], + "Max": [252, 60, 15], + "Properties": { + "StringProperty": { + "name": "Another tumor", + "comment": "Maybe another tumor (confidence only 25%)." + }, + "ColorProperty": { + "color": [1, 0, 0] + }, + "FloatProperty": { + "confidence": 0.25 + } + } + } + ] +} + +~~~ + +Further hints: + + - "FileFormat" ("MITK ROI"), "Version" (1), and "Geometry" are mandatory. + - "Name" is optional. If not set, the file name is used by MITK instead. + - ROIs are defined by JSON objects in the "ROIs" JSON array. + - See the derived classes of mitk::BaseProperty for an overview of known property types. + +### Time-resolved MITK ROI file + +This example only contains a single ROI but it is defined for several time steps. +Fallbacks of time step properties to default properties are demonstrated as well. + +~~~{.json} +{ + "FileFormat": "MITK ROI", + "Version": 1, + "Name": "Time-resolved example", + "Geometry": { + "Origin": [0, 0, 0], + "Spacing": [1, 1, 3], + "Size": [256, 256, 49], + "TimeSteps": 3 + }, + "ROIs": [ + { + "ID": 0, + "Properties": { + "ColorProperty": { + "color": [1, 0, 0] + }, + "StringProperty": { + "name": "Color-changing ROI" + } + }, + "TimeSteps": [ + { + "t": 0, + "Min": [4, 4, 1], + "Max": [124, 124, 31] + }, + { + "t": 2, + "Min": [14, 14, 11], + "Max": [121, 121, 28], + "Properties": { + "ColorProperty": { + "color": [0, 1, 0] + } + } + } + ] + } + ] +} +~~~ + +Further hints: + + - The geometry defines 3 time steps. + - The "Properties" directly in the ROI function as fallbacks, if they are not defined for a certain time step. + - Time time step indices "t" are mandatory. The ROI is only present at time steps 0 and 2. + - The ROI is red (fallback) at time step 0 and green at time step 2. + - Its extents are larger at time step 0 than at time step 2. diff --git a/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKSegmentationTaskLists.md b/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKSegmentationTaskLists.md index ddbb5b926b..b070e52c16 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKSegmentationTaskLists.md +++ b/Documentation/Doxygen/3-DeveloperManual/Concepts/MITKSegmentationTaskLists.md @@ -1,145 +1,144 @@ # MITK Segmentation Task Lists {#MITKSegmentationTaskListsPage} [TOC] ## Overview MITK Segmentation Task Lists are a JSON-based file format defining a list of segmentation tasks. Segmentation tasks consist at least of a path to a reference image and a unique result path. The result path specifies where the final segmentation of the task is expected to be located once it is done. Optional properties of a segmentation task include a task name and description as well as various degrees of start conditions for the segmentation like a label name, a list of suggested names and colors for new labels, a label set preset, or even a pre-segmentation to begin with. The complete set of properties is specified further below in the file format specification. MITK Segmentation Task Lists must be considered experimental at the moment and are prone to change without any prior warning. -They are currently supported only in the MITK FlowBench application where a dedicated widget for task navigation and management appears if an MITK Segmentation Task List has been opened. ## File format MITK Segmentation Task Lists are JSON files containing a JSON object as root, which must contain the two mandatory properties `FileFormat` and `Version`: ~~~{.json} { "FileFormat": "MITK Segmentation Task List", "Version": 1 } ~~~ We also recommend to specify an optional `Name` that is used in the application if present instead of the plain filename of the JSON file: ~~~{.json} { "FileFormat": "MITK Segmentation Task List", "Version": 1, "Name": "My First Task List" } ~~~ ### Tasks The root object must also contain a mandatory `Tasks` array containing JSON objects that specify the individual tasks of the task list. A minimum task object must consist of `Image` and `Result` file paths. `Image` is a path to the reference image for the task and `Result` is a path where the final segmentation is expected to be stored once it is done. Paths can be absolute or relative to the MITK Segmentation Task List file. ~~~{.json} { "FileFormat": "MITK Segmentation Task List", "Version": 1, "Tasks": [ { "Image": "images/input.nrrd", "Result": "results/output.nrrd" } ] } ~~~ In addition, tasks can define various optional properties that mainly specify the start conditions for a segmentation: - `Name` (*string*): A name for the task. - `Description` (*string*): A short description/definition of the task. - `LabelName` (*string*): The name of the first label in a new segmentation that is created for the task on the fly. - `LabelNameSuggestions` (*file path*): A Label Suggestions JSON file specifying names and optional colors that are suggested to the user for new labels in the segmentation. - `Preset` (*file path*): A Label Set Preset XML file in MITK's .lsetp file format. The preset is applied to a new segmentation that is created for the task on the fly. We recommend to use the Segmentation plugin to create such label set preset files as described in its [user guide](@ref org_mitk_views_segmentationlabelpresets). - `Segmentation` (*file path*): A pre-segmentation that a user can start with or should refine. - `Dynamic` (*boolean*): In case `Image` refers to a dynamic (3d+t) image, specifies whether the segmentation should be static (*false*), i.e. equal for all time steps, or dynamic (*true*), i.e. individual for each time step. ### Task defaults / common properties If a task list contains multiple tasks with common properties, they do not have to be specified for each and every task again and again. Instead, the root object may contain an optional `Defaults` object that is identical in format to the tasks specified above. There is one exception, though: A `Defaults` object must not contain a `Result` file path, since result files of tasks must be distinct by definition. As the name indicates, default properties can still be overridden by individual tasks if they are specified explicitly. ### Example The following example is a complete showcase of the properties and features listed above. It defines four tasks, three of which refer to the same reference image so it is specified as default. Remember that the only task property required to be distinct is `Result` so you are pretty free in your task design. For simplicity, we chose to define tasks around organs for this example and named the tasks accordingly: ~~~{.json} { "FileFormat": "MITK Segmentation Task List", "Version": 1, "Name": "Example Segmentation Task List", "Defaults": { "Image": "images/Pic3D.nrrd" }, "Tasks": [ { "Name": "Liver", "LabelName": "Liver", "LabelNameSuggestions": "suggestions/label_suggestions.json", "Description": "This task provides an image and label name suggestions for new labels. The segmentation will start with an empty label named Liver.", "Result": "results/liver.nrrd" }, { "Name": "Kidneys", "Description": "This task provides an image and a label set preset that is applied to the new segmentation.", "Preset": "presets/kidneys.lsetp", "Result": "results/kidneys.nrrd" }, { "Name": "Spleen", "Description": "This task provides an image and an initial (pre-)segmentation.", "Segmentation": "segmentations/spleen.nrrd", "Result": "results/spleen.nrrd" }, { "Name": "Surprise", "Description": "And now for something completely different. This task overrides the default Image and starts with an empty static segmentation for a dynamic image.", "Image": "images/US4DCyl.nrrd", "Result": "results/US4DCyl.nrrd", "Dynamic": false } ] } ~~~ ## Label name suggestions The `LabelNameSuggestions` property of segmentation tasks is supposed to reference a JSON file that consists of an array of objects with a mandatory `name` property and an optional `color` property. For example: ~~~{.json} [ { "name": "Abdomen", "color": "red" }, { "name": "Lung", "color": "#00ff00" }, { "name": "Heart" }, { "name": "Aortic Valve", "color": "CornflowerBlue" } ] ~~~ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox index e3111bbf82..58661d1811 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox @@ -1,232 +1,232 @@ /** \page BuildInstructionsPage Build Instructions \tableofcontents \section BuildInstructions_Introduction Introduction The CMake-based build system of MITK supports a "superbuild" process, meaning that it will download, configure, and build all required third-party libraries (except Qt) automatically. These instructions will show you how to use the MITK superbuild. \note This page explains explicitly how to build MITK itself. If you want to create your own project based on MITK, the process described below is completely automated. Please see \ref HowToNewProject. For more advanced users, the last sections explains how to inject custom build libraries into the superbuild process. \section BuildInstructions_Prerequisites Prerequisites You need: -# Git (there are also numerous third-party graphical clients available). We recommend using Git, but see below for a way how to get the current source code without using it. -# CMake (version \minimumCMakeVersion or higher) -# Qt \minimumQt5Version if you plan to develop Qt-based applications -# If you are using macOS you need an XCode installation and the Command Line Tools as it provides the neccessary compilers and SDKs To build MITK on Linux, install the following packages, e. g. with APT: \code{.unparsed} sudo apt install build-essential doxygen git graphviz libfreetype6-dev libglu1-mesa-dev libssl-dev libtiff5-dev libxcomposite1 libxcursor1 libxdamage-dev libxi-dev libxkbcommon-x11-0 libxt-dev mesa-common-dev \endcode For the optional and experimental (!) Python integration, install NumPy and SimpleITK v1.x, e. g.: \code{.unparsed} sudo apt install python3-numpy python3-pip pip3 install SimpleITK \endcode \section BuildInstructions_Qt A note about Qt As we do not provide Qt in the MITK superbuild you need to install Qt manually. The Qt Company provides online installers for all supported platforms. We highly recommend to install Qt to the default location of the installer as it will allow MITK to automatically find Qt without any further action needed. Make sure to also select the following required components: - QtWebEngine - QtScript On Windows, the Qt installer offers a welcome and straight forward way to install OpenSSL. You find it under the Tools node. \section BuildInstructions_Get_Source Get a source tree Since MITK is under active development we recommend to use Git to check out the latest stable release from the homepage. If you decide to use the most current nightly release, make sure to get a stable tree: Check the MITK dashboard before checking out. If the build tree is not clean, you can specify an older revision for the checkout or get a stable tar ball from www.mitk.org. To clone MITK's current Git repository do: \code git clone https://phabricator.mitk.org/source/mitk.git MITK \endcode \section BuildInstructions_Build_With_CMake Build MITK with CMake Create a new directory for the superbuild binary tree, change to it and call CMake: In the shell (assuming your current directory is the same as the one where you issued the git clone command): \code mkdir MITK-superbuild cd MITK-superbuild ccmake ../MITK \endcode If you use Windows or prefer to use the CMake GUI, start the CMake GUI and enter the location of the source tree and binary tree, choose a suitable generator and configure the project. CMake will present you a couple of options, these are the most important ones: - CMAKE_PREFIX_PATH The path to your Qt installation, e.g., C:/Qt/5.12.9/msvc2017_64 or /home/user/Qt/5.12.9/gcc_64 - MITK_USE_BLUEBERRY Build the BlueBerry application framework - MITK_USE_Boost_LIBRARIES If you need binary Boost libraries, specify them here. - MITK_USE_OpenCV Build MITK code which depends on OpenCV (this will download and build OpenCV 2.4) - MITK_USE_Python3 Enables Python wrapping in MITK. This will also configure ITK, VTK, and OpenCV (if enabled) to build Python wrappers. - MITK_USE_Qt5 Build MITK code which depends on Qt 5 If you are satisfied with the configuration of your MITK superbuild, generate the project files with CMake by pressing "Generate". Linux and macOS users usually just enter "make" (optionally supplying the number threads to be used for a parallel build): \code make -j6 \endcode Windows users using Visual Studio can open the generated MITK-superbuild.sln solution file in the MITK-superbuild directory and start the build by building the BUILD_ALL project. \section BuildInstructions_Customize Customize your MITK superbuild The MITK superbuild configures MITK as well as all external libraries. The build directories of these libraries, and of MITK itself are located inside the MITK-superbuild directory. For example, the directory layout may look like: \code MITK-superbuild |- ep "external projects" |-bin |-lib |-include |-src |- MITK-build \endcode To change the configuration of the MITK build itself, choose the MITK-build directory as the binary directory in the CMake GUI (not the MITK-superbuild directory). After generating the project files, build the MITK project by either issuing "make" in the MITK-build directory (Linux, macOS), or by opening MITK-build/MITK.sln (Windows). You may also change the configuration of any project configured via the superbuild process. Make sure to also build the changed project and also the projects which depend on it. \section BuildInstructions_Running Running Applications On Linux, just execute the application you want to run. MITK executables are located in MITK-superbuild/MITK-build/bin On Windows, the PATH environment variable must contain the directories containing the third-party libraries. This is automatically done from Visual Studio. For running the applications directly use the generated batch files in the MITK-superbuild/MITK-build/bin. \section BuildInstructions_Documentation Documentation If you have the Doxygen documentation tool installed, you get a new project (Visual Studio) or "make" target named "doc". You can build this to generate the HTML documentation of MITK in the Documentation/Doxygen directory of your MITK-build binary tree or in the MITK_DOXYGEN_OUTPUT_DIR CMake variable (if specified). \section BuildInstructions_As_Toolkit Use MITK in your own project (as a toolkit) To use MITK in your external project, add the CMake command find_package(MITK REQUIRED) to your CMakeLists.txt and make use of the CMake macros mitk_create_module() and mitk_create_executable() provided by MITK. Here is a very basic example CMakeLists.txt including MITK as a project: \code cmake_minimum_required(VERSION 3.18 FATAL_ERROR) project(MyProject) -find_package(MITK 2023.04 REQUIRED) +find_package(MITK 2023.12 REQUIRED) add_executable(MyApp main.cpp) target_link_libraries(MyApp MitkCore) \endcode with the main.cpp being \code #include #include int main() { MITK_INFO << "Hello world!"; return 0; } \endcode \section BuildInstructions_Advanced_Customization Superbuild customization You can inject pre-build third-party libraries into the MITK superbuild by setting certain CMake variables before the first configure step. MITK will then use these third-party libraries instead of downloading and building them by itself. Note that you must take care of configuring those libraries with all options MITK requires. The variables listed below are provided for injecting third-party libraries. Their occurrence in the CMake GUI or in ccmake may depend on specific MITK_USE_* options set to ON. You may also use the variable names below without the EXTERNAL_ prefix, for example when providing their values on a command line call to CMake. - EXTERNAL_BOOST_ROOT Set this variable to your custom Boost installation - EXTERNAL_CTK_DIR Set this variable to your CTK binary tree (the directory containing the CTKConfig.cmake file) - EXTERNAL_CableSwig_DIR Set this variable to your CableSwig binary tree for Python wrapping (the directory containing the CableSwigConfig.cmake file) - EXTERNAL_DCMTK_DIR Set this variable to your DCMTK binary tree (the directory containing the DCMTKConfig.cmake file) - EXTERNAL_GDCM_DIR Set this variable to your GDCM binary tree (the directory containing the GDCMConfig.cmake file) - EXTERNAL_ITK_DIR Set this variable to your ITK binary tree (the directory containing the ITKConfig.cmake file) - EXTERNAL_OpenCV_DIR Set this variable to your OpenCV binary tree (the directory containing the OpenCVConfig.cmake file) - EXTERNAL_VTK_DIR Set this variable to your VTK binary tree (the directory containing the VTKConfig.cmake file) To set CMake options before the first configure step is invoked, supply them on the command line, i.e. \code ccmake -DITK_DIR:PATH=/opt/ITK-release ../MITK \endcode */ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md index 693917cc85..3d03b00901 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md @@ -1,55 +1,56 @@ Supported Platforms {#SupportedPlatformsPage} =================== MITK is a cross-platform framework that is available for the following platforms: - Windows - Linux - macOS Supported Platforms Details --------------------------- The MITK team provides support for the most frequently used platforms and continuously runs testing procedures to ensure compatibility. Due to the large amount of possible combinations of operating systems and compiler versions, we divide platform support into two test categories: Tier 1 and Tier 2. Although MITK may be built on a broader range of platform-compiler combinations, only a subset of these are actively supported by the MITK development team. In general, only 64-bit builds are supported. Tier 1 Platforms ---------------- All Tier 1 platforms are continuously tested by our unit test suite and other internal testing procedures. Errors or bugs discovered in these platforms are prioritized and corrected as soon as possible. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- -| Microsoft Windows 10 | Visual Studio 2019 (latest update) +| Microsoft Windows 10 | Visual Studio 2022 | Linux Ubuntu 22.04 | Default GCC version | Linux Ubuntu 20.04 | Default GCC version Tier 2 Platforms ---------------- Tier 2 platforms may or may not be tested on a regular basis. Some Tier 2 platforms are used by individual members of the MITK development team on a daily basis and some only receive occasional testing. While we strive to support these platforms, MITK users should note that errors may be present in released versions as well as in the current master branch. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- -| Microsoft Windows 10 | Visual Studio 2022 (latest update) -| Apple macOS 11 "Big Sur" | Default Apple Clang version -| Apple macOS 10.15 "Catalina" | Default Apple Clang version +| Microsoft Windows 11 | Visual Studio 2022 +| Microsoft Windows 10 | Visual Studio 2019 +| Apple macOS 13 Ventura | Default Apple Clang version +| Apple macOS 12 Monterey | Default Apple Clang version All platforms not listed above are not officially supported by the MITK team. However, we will happily accept contributions to improve support for other platforms as long as we have the hardware and capacity for maintenance. CI Build Clients ---------------- To get an overview of currently tested platforms, see the build reports on our CDash site. diff --git a/Documentation/Doxygen/4-API/Pages.dox b/Documentation/Doxygen/4-API/Pages.dox index 6a94d8dc3f..8de59a8245 100644 --- a/Documentation/Doxygen/4-API/Pages.dox +++ b/Documentation/Doxygen/4-API/Pages.dox @@ -1,51 +1,54 @@ /** \defgroup MITKDeprecatedAPI Deprecated \page deprecatedSince2012_09 Deprecated as of 2012.09 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_03 Deprecated as of 2013.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_06 Deprecated as of 2013.06 \ingroup MITKDeprecatedAPI \page deprecatedSince2013_09 Deprecated as of 2013.09 \ingroup MITKDeprecatedAPI \page deprecatedSince2014_03 Deprecated as of 2014.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2014_10 Deprecated as of 2014.10 \ingroup MITKDeprecatedAPI \page deprecatedSince2015_05 Deprecated as of 2015.05 \ingroup MITKDeprecatedAPI \page deprecatedSince2016_03 Deprecated as of 2016.03 \ingroup MITKDeprecatedAPI \page deprecatedSince2016_11 Deprecated as of 2016.11 \ingroup MITKDeprecatedAPI \page deprecatedSince2018_04 Deprecated as of 2018.04 \ingroup MITKDeprecatedAPI \page deprecatedSince2021_02 Deprecated as of 2021.02 \ingroup MITKDeprecatedAPI \page deprecatedSince2021_10 Deprecated as of 2021.10 \ingroup MITKDeprecatedAPI \page deprecatedSince2022_04 Deprecated as of 2022.04 \ingroup MITKDeprecatedAPI \page deprecatedSince2022_10 Deprecated as of 2022.10 \ingroup MITKDeprecatedAPI \page deprecatedSince2023_04 Deprecated as of 2023.04 \ingroup MITKDeprecatedAPI +\page deprecatedSince2023_12 Deprecated as of 2023.12 +\ingroup MITKDeprecatedAPI + */ diff --git a/Modules/AppUtil/src/mitkBaseApplication.cpp b/Modules/AppUtil/src/mitkBaseApplication.cpp index 68948a6607..6f9e3a72d2 100644 --- a/Modules/AppUtil/src/mitkBaseApplication.cpp +++ b/Modules/AppUtil/src/mitkBaseApplication.cpp @@ -1,902 +1,927 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include namespace { - void outputQtMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) + void outputImportantQtMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) + { + auto message = msg.toStdString(); + + switch (type) + { + case QtWarningMsg: + MITK_WARN << message; + break; + + case QtCriticalMsg: + MITK_ERROR << message; + break; + + case QtFatalMsg: + MITK_ERROR << message; + abort(); + + default: + break; + } + } + + void outputQtMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg) { auto message = msg.toStdString(); switch (type) { case QtDebugMsg: MITK_DEBUG << message; break; case QtInfoMsg: MITK_INFO << message; break; case QtWarningMsg: - MITK_WARN << message; - break; + [[fallthrough]]; case QtCriticalMsg: - MITK_ERROR << message; - break; + [[fallthrough]]; case QtFatalMsg: - MITK_ERROR << message; - abort(); + outputImportantQtMessage(type, context, msg); + break; default: MITK_INFO << message; break; } } } namespace mitk { const QString BaseApplication::ARG_APPLICATION = "BlueBerry.application"; const QString BaseApplication::ARG_CLEAN = "BlueBerry.clean"; const QString BaseApplication::ARG_CONSOLELOG = "BlueBerry.consoleLog"; const QString BaseApplication::ARG_DEBUG = "BlueBerry.debug"; const QString BaseApplication::ARG_FORCE_PLUGIN_INSTALL = "BlueBerry.forcePlugins"; const QString BaseApplication::ARG_HOME = "BlueBerry.home"; const QString BaseApplication::ARG_NEWINSTANCE = "BlueBerry.newInstance"; const QString BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING = "BlueBerry.noLazyRegistryCacheLoading"; const QString BaseApplication::ARG_NO_REGISTRY_CACHE = "BlueBerry.noRegistryCache"; const QString BaseApplication::ARG_PLUGIN_CACHE = "BlueBerry.plugin_cache_dir"; const QString BaseApplication::ARG_PLUGIN_DIRS = "BlueBerry.plugin_dirs"; const QString BaseApplication::ARG_PRELOAD_LIBRARY = "BlueBerry.preloadLibrary"; const QString BaseApplication::ARG_PRODUCT = "BlueBerry.product"; const QString BaseApplication::ARG_PROVISIONING = "BlueBerry.provisioning"; const QString BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE = "BlueBerry.registryMultiLanguage"; const QString BaseApplication::ARG_SPLASH_IMAGE = "BlueBerry.splashscreen"; const QString BaseApplication::ARG_STORAGE_DIR = "BlueBerry.storageDir"; const QString BaseApplication::ARG_XARGS = "xargs"; const QString BaseApplication::ARG_LOG_QT_MESSAGES = "Qt.logMessages"; const QString BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET = "Segmentation.labelSetPreset"; const QString BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS = "Segmentation.labelSuggestions"; const QString BaseApplication::PROP_APPLICATION = "blueberry.application"; const QString BaseApplication::PROP_FORCE_PLUGIN_INSTALL = BaseApplication::ARG_FORCE_PLUGIN_INSTALL; const QString BaseApplication::PROP_NEWINSTANCE = BaseApplication::ARG_NEWINSTANCE; const QString BaseApplication::PROP_NO_LAZY_REGISTRY_CACHE_LOADING = BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING; const QString BaseApplication::PROP_NO_REGISTRY_CACHE = BaseApplication::ARG_NO_REGISTRY_CACHE; const QString BaseApplication::PROP_PRODUCT = "blueberry.product"; const QString BaseApplication::PROP_REGISTRY_MULTI_LANGUAGE = BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE; class SplashCloserCallback : public QRunnable { public: SplashCloserCallback(QSplashScreen* splashscreen) : m_Splashscreen(splashscreen) { } void run() override { this->m_Splashscreen->close(); } private: QSplashScreen *m_Splashscreen; // Owned by BaseApplication::Impl }; struct BaseApplication::Impl { ctkProperties m_FWProps; QCoreApplication *m_QApp; int m_Argc; char **m_Argv; #ifdef Q_OS_MAC std::vector m_Argv_macOS; #endif QString m_AppName; QString m_OrgaName; QString m_OrgaDomain; bool m_SingleMode; bool m_SafeMode; QSplashScreen *m_Splashscreen; SplashCloserCallback *m_SplashscreenClosingCallback; bool m_LogQtMessages; QStringList m_PreloadLibs; QString m_ProvFile; Impl(int argc, char **argv) : m_Argc(argc), m_Argv(argv), #ifdef Q_OS_MAC m_Argv_macOS(), #endif m_SingleMode(false), m_SafeMode(true), m_Splashscreen(nullptr), m_SplashscreenClosingCallback(nullptr), m_LogQtMessages(false) { #ifdef Q_OS_MAC /* On macOS the process serial number is passed as an command line argument (-psn_) in certain circumstances. This option causes a Poco exception. We remove it, if present. */ m_Argv_macOS.reserve(argc + 1); const char psn[] = "-psn"; for (int i = 0; i < argc; ++i) { if (0 == strncmp(argv[i], psn, sizeof(psn) - 1)) continue; m_Argv_macOS.push_back(argv[i]); } m_Argv_macOS.push_back(nullptr); m_Argc = static_cast(m_Argv_macOS.size() - 1); m_Argv = m_Argv_macOS.data(); #endif } ~Impl() { delete m_SplashscreenClosingCallback; delete m_Splashscreen; delete m_QApp; } QVariant getProperty(const QString &property) const { auto iter = m_FWProps.find(property); return m_FWProps.end() != iter ? iter.value() : QVariant(); } void handleBooleanOption(const std::string &name, const std::string &) { if (ARG_LOG_QT_MESSAGES.toStdString() == name) { m_LogQtMessages = true; return; } auto fwKey = QString::fromStdString(name); // Translate some keys to proper framework properties if (ARG_CONSOLELOG == fwKey) fwKey = ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG; // For all other options we use the command line option name as the // framework property key. m_FWProps[fwKey] = true; } void handlePreloadLibraryOption(const std::string &, const std::string &value) { m_PreloadLibs.push_back(QString::fromStdString(value)); } void handleClean(const std::string &, const std::string &) { m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT; } void initializeCTKPluginFrameworkProperties(Poco::Util::LayeredConfiguration &configuration) { // Add all configuration key/value pairs as framework properties Poco::Util::LayeredConfiguration::Keys keys; Poco::Util::LayeredConfiguration::Keys keyStack; configuration.keys(keyStack); std::vector keyChain; while (!keyStack.empty()) { const auto currSubKey = keyStack.back(); if (!keyChain.empty() && keyChain.back() == currSubKey) { keyChain.pop_back(); keyStack.pop_back(); continue; } Poco::Util::LayeredConfiguration::Keys subKeys; configuration.keys(currSubKey, subKeys); if (subKeys.empty()) { std::string finalKey; keyStack.pop_back(); for (const auto& key : keyChain) finalKey += key + '.'; finalKey += currSubKey; keys.push_back(finalKey); } else { keyChain.push_back(currSubKey); for (const auto& key : subKeys) keyStack.push_back(key); } } for (const auto& key : keys) { if (configuration.hasProperty(key)) { // .ini and command line options overwrite already inserted keys auto qKey = QString::fromStdString(key); m_FWProps[qKey] = QString::fromStdString(configuration.getString(key)); } } } void parseProvisioningFile(const QString &filePath) { // Skip parsing if the file path is empty if (filePath.isEmpty()) return; auto consoleLog = this->getProperty(ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG).toBool(); // Read initial plugins from a provisioning file QFileInfo provFile(filePath); QStringList pluginsToStart; if (provFile.exists()) { MITK_INFO(consoleLog) << "Using provisioning file: " << qPrintable(provFile.absoluteFilePath()); ProvisioningInfo provInfo(provFile.absoluteFilePath()); // It can still happen that the encoding is not compatible with the fromUtf8 function (i.e. when // manipulating the LANG variable). The QStringList in provInfo is empty then. if (provInfo.getPluginDirs().empty()) { MITK_ERROR << "Cannot search for provisioning file, the retrieved directory list is empty.\n" << "This can happen if there are some special non-ASCII characters in the install path."; } else { for(const auto& pluginPath : provInfo.getPluginDirs()) ctkPluginFrameworkLauncher::addSearchPath(pluginPath); auto pluginUrlsToStart = provInfo.getPluginsToStart(); for (const auto& url : qAsConst(pluginUrlsToStart)) pluginsToStart.push_back(url.toString()); } } else { MITK_INFO(consoleLog) << "Provisionig file does not exist."; } if (!pluginsToStart.isEmpty()) { m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS] = pluginsToStart; // Use transient start with declared activation policy (this helps when the provisioning file // changes and some plug-ins should not be installed in the application any more). ctkPlugin::StartOptions startOptions(ctkPlugin::START_TRANSIENT | ctkPlugin::START_ACTIVATION_POLICY); m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS_START_OPTIONS] = static_cast(startOptions); } } }; BaseApplication::BaseApplication(int argc, char **argv) : Application(), d(new Impl(argc, argv)) { } BaseApplication::~BaseApplication() { delete d; } void BaseApplication::printHelp(const std::string &, const std::string &) { Poco::Util::HelpFormatter help(this->options()); help.setAutoIndent(); help.setCommand(this->commandName()); help.format(std::cout); exit(EXIT_OK); } void BaseApplication::setApplicationName(const QString &name) { if (nullptr != qApp) qApp->setApplicationName(name); d->m_AppName = name; } QString BaseApplication::getApplicationName() const { return nullptr != qApp ? qApp->applicationName() : d->m_AppName; } void BaseApplication::setOrganizationName(const QString &name) { if (nullptr != qApp) qApp->setOrganizationName(name); d->m_OrgaName = name; } QString BaseApplication::getOrganizationName() const { return nullptr != qApp ? qApp->organizationName() : d->m_OrgaName; } void BaseApplication::setOrganizationDomain(const QString &domain) { if (nullptr != qApp) qApp->setOrganizationDomain(domain); d->m_OrgaDomain = domain; } QString BaseApplication::getOrganizationDomain() const { return nullptr != qApp ? qApp->organizationDomain() : d->m_OrgaDomain; } void BaseApplication::setSingleMode(bool singleMode) { if (nullptr != qApp) return; d->m_SingleMode = singleMode; } bool BaseApplication::getSingleMode() const { return d->m_SingleMode; } void BaseApplication::setSafeMode(bool safeMode) { if (nullptr != qApp && nullptr == d->m_QApp) return; d->m_SafeMode = safeMode; nullptr == d->m_QApp && getSingleMode() ? static_cast(d->m_QApp)->setSafeMode(safeMode) : static_cast(d->m_QApp)->setSafeMode(safeMode); } bool BaseApplication::getSafeMode() const { return d->m_SafeMode; } void BaseApplication::setPreloadLibraries(const QStringList &libraryBaseNames) { d->m_PreloadLibs = libraryBaseNames; } QStringList BaseApplication::getPreloadLibraries() const { return d->m_PreloadLibs; } void BaseApplication::setProvisioningFilePath(const QString &filePath) { d->m_ProvFile = filePath; } QString BaseApplication::getProvisioningFilePath() const { auto provFilePath = d->m_ProvFile; // A null QString means look up a default provisioning file if (provFilePath.isNull() && nullptr != qApp) { QFileInfo appFilePath(QCoreApplication::applicationFilePath()); QDir basePath(QCoreApplication::applicationDirPath()); auto provFileName = appFilePath.baseName() + ".provisioning"; QFileInfo provFile(basePath.absoluteFilePath(provFileName)); #ifdef Q_OS_MAC /* * On macOS, if started from the build directory, the .provisioning file is located at: * * The executable path is: * * In this case we have to cdUp threetimes. * * During packaging the MitkWorkbench.provisioning file is placed at the same * level like the executable. Nothing has to be done. */ if (!provFile.exists()) { basePath.cdUp(); basePath.cdUp(); basePath.cdUp(); provFile = basePath.absoluteFilePath(provFileName); } #endif if (provFile.exists()) { provFilePath = provFile.absoluteFilePath(); } #ifdef CMAKE_INTDIR else { basePath.cdUp(); provFile.setFile(basePath.absoluteFilePath(provFileName)); if (provFile.exists()) provFilePath = provFile.absoluteFilePath(); } #endif } return provFilePath; } void BaseApplication::initializeQt() { if (nullptr != qApp) return; #ifdef Q_OS_LINUX qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--single-process"); // See T29332 #endif // If parameters have been set before, we have to store them to hand them // through to the application auto appName = this->getApplicationName(); auto orgName = this->getOrganizationName(); auto orgDomain = this->getOrganizationDomain(); // Create a QCoreApplication instance this->getQApplication(); // Provide parameters to QCoreApplication this->setApplicationName(appName); this->setOrganizationName(orgName); this->setOrganizationDomain(orgDomain); - if (d->m_LogQtMessages) - qInstallMessageHandler(outputQtMessage); + qInstallMessageHandler(!d->m_LogQtMessages + ? outputImportantQtMessage + : outputQtMessage); QWebEngineUrlScheme qtHelpScheme("qthelp"); qtHelpScheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed); QWebEngineUrlScheme::registerScheme(qtHelpScheme); } void BaseApplication::initialize(Poco::Util::Application &self) { // 1. Call the super-class method Poco::Util::Application::initialize(self); // 2. Initialize the Qt framework (by creating a QCoreApplication) this->initializeQt(); // 3. Seed the random number generator, once at startup. QTime time = QTime::currentTime(); qsrand((uint)time.msec()); // 4. Load the "default" configuration, which involves parsing // an optional .ini file and parsing any // command line arguments this->loadConfiguration(); // 5. Add configuration data from the command line and the // optional .ini file as CTK plugin // framework properties. d->initializeCTKPluginFrameworkProperties(this->config()); // 6. Initialize splash screen if an image path is provided // in the .ini file this->initializeSplashScreen(qApp); // 7. Set the custom CTK Plugin Framework storage directory QString storageDir = this->getCTKFrameworkStorageDir(); if (!storageDir.isEmpty()) { d->m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storageDir; // Initialize core service preferences at the exact same location as their predecessor BlueBerry preferences mitk::CoreServicePointer preferencesService(mitk::CoreServices::GetPreferencesService()); preferencesService->InitializeStorage(storageDir.toStdString() + "/data/3/prefs.xml"); } // 8. Set the library search paths and the pre-load library property this->initializeLibraryPaths(); auto preloadLibs = this->getPreloadLibraries(); if (!preloadLibs.isEmpty()) d->m_FWProps[ctkPluginConstants::FRAMEWORK_PRELOAD_LIBRARIES] = preloadLibs; // 9. Initialize the CppMicroServices library. // The initializeCppMicroServices() method reuses the // FRAMEWORK_STORAGE property, so we call it after the // getCTKFrameworkStorageDir method. this->initializeCppMicroServices(); // 10. Parse the (optional) provisioning file and set the // correct framework properties. d->parseProvisioningFile(this->getProvisioningFilePath()); // 11. Set the CTK Plugin Framework properties ctkPluginFrameworkLauncher::setFrameworkProperties(d->m_FWProps); } void BaseApplication::uninitialize() { auto pfw = this->getFramework(); if (pfw) { pfw->stop(); // Wait for up to 10 seconds for the CTK plugin framework to stop pfw->waitForStop(10000); } Poco::Util::Application::uninitialize(); } int BaseApplication::getArgc() const { return d->m_Argc; } char **BaseApplication::getArgv() const { return d->m_Argv; } QString BaseApplication::getCTKFrameworkStorageDir() const { QString storageDir; if (this->getSingleMode()) { // This function checks if an instance is already running and either sends a message to // it containing the command line arguments or checks if a new instance was forced by // providing the BlueBerry.newInstance command line argument. In the latter case, a path // to a temporary directory for the new application's storage directory is returned. storageDir = handleNewAppInstance(static_cast(d->m_QApp), d->m_Argc, d->m_Argv, ARG_NEWINSTANCE); } if (storageDir.isEmpty()) { // This is a new instance and no other instance is already running. We specify the // storage directory here (this is the same code as in berryInternalPlatform.cpp) // so that we can re-use the location for the persistent data location of the // the CppMicroServices library. // Append a hash value of the absolute path of the executable to the data location. // This allows to start the same application from different build or install trees. storageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + this->getOrganizationName() + "/" + this->getApplicationName() + '_'; storageDir += QString::number(qHash(QCoreApplication::applicationDirPath())) + "/"; } return storageDir; } void BaseApplication::initializeCppMicroServices() { auto storageDir = this->getProperty(ctkPluginConstants::FRAMEWORK_STORAGE).toString(); if (!storageDir.isEmpty()) us::ModuleSettings::SetStoragePath((storageDir + "us" + QDir::separator()).toStdString()); } QCoreApplication *BaseApplication::getQApplication() const { if (nullptr == qApp) { + vtkLogger::SetStderrVerbosity(vtkLogger::VERBOSITY_WARNING); + vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0); auto defaultFormat = QVTKOpenGLNativeWidget::defaultFormat(); defaultFormat.setSamples(0); QSurfaceFormat::setDefaultFormat(defaultFormat); #ifdef Q_OS_OSX QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); d->m_QApp = this->getSingleMode() ? static_cast(new QmitkSingleApplication(d->m_Argc, d->m_Argv, this->getSafeMode())) : static_cast(new QmitkSafeApplication(d->m_Argc, d->m_Argv, this->getSafeMode())); } return qApp; } void BaseApplication::initializeLibraryPaths() { QStringList suffixes; suffixes << "plugins"; #ifdef Q_OS_WINDOWS suffixes << "bin/plugins"; #ifdef CMAKE_INTDIR suffixes << "bin/" CMAKE_INTDIR "/plugins"; #endif #else suffixes << "lib/plugins"; #ifdef CMAKE_INTDIR suffixes << "lib/" CMAKE_INTDIR "/plugins"; #endif #endif #ifdef Q_OS_MAC suffixes << "../../plugins"; #endif // We add a couple of standard library search paths for plug-ins QDir appDir(QCoreApplication::applicationDirPath()); // Walk one directory up and add bin and lib sub-dirs; this might be redundant appDir.cdUp(); for (const auto& suffix : qAsConst(suffixes)) ctkPluginFrameworkLauncher::addSearchPath(appDir.absoluteFilePath(suffix)); } int BaseApplication::main(const std::vector &args) { // Start the plugin framework and all installed plug-ins according to their auto-start setting QStringList arguments; for (auto const &arg : args) arguments.push_back(QString::fromStdString(arg)); if (nullptr != d->m_Splashscreen) { // A splash screen is displayed. Create the closing callback. d->m_SplashscreenClosingCallback = new SplashCloserCallback(d->m_Splashscreen); } return ctkPluginFrameworkLauncher::run(d->m_SplashscreenClosingCallback, QVariant::fromValue(arguments)).toInt(); } void BaseApplication::defineOptions(Poco::Util::OptionSet &options) { Poco::Util::Option helpOption("help", "h", "print this help text"); helpOption.callback(Poco::Util::OptionCallback(this, &BaseApplication::printHelp)); options.addOption(helpOption); Poco::Util::Option newInstanceOption(ARG_NEWINSTANCE.toStdString(), "", "forces a new instance of this application"); newInstanceOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(newInstanceOption); Poco::Util::Option cleanOption(ARG_CLEAN.toStdString(), "", "cleans the plugin cache"); cleanOption.callback(Poco::Util::OptionCallback(d, &Impl::handleClean)); options.addOption(cleanOption); Poco::Util::Option productOption(ARG_PRODUCT.toStdString(), "", "the id of the product to be launched"); productOption.argument("").binding(PROP_PRODUCT.toStdString()); options.addOption(productOption); Poco::Util::Option appOption(ARG_APPLICATION.toStdString(), "", "the id of the application extension to be executed"); appOption.argument("").binding(PROP_APPLICATION.toStdString()); options.addOption(appOption); Poco::Util::Option provOption(ARG_PROVISIONING.toStdString(), "", "the location of a provisioning file"); provOption.argument("").binding(ARG_PROVISIONING.toStdString()); options.addOption(provOption); Poco::Util::Option storageDirOption(ARG_STORAGE_DIR.toStdString(), "", "the location for storing persistent application data"); storageDirOption.argument("").binding(ctkPluginConstants::FRAMEWORK_STORAGE.toStdString()); options.addOption(storageDirOption); Poco::Util::Option consoleLogOption(ARG_CONSOLELOG.toStdString(), "", "log messages to the console"); consoleLogOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(consoleLogOption); Poco::Util::Option debugOption(ARG_DEBUG.toStdString(), "", "enable debug mode"); debugOption.argument("", false).binding(ctkPluginFrameworkLauncher::PROP_DEBUG.toStdString()); options.addOption(debugOption); Poco::Util::Option forcePluginOption(ARG_FORCE_PLUGIN_INSTALL.toStdString(), "", "force installing plug-ins with same symbolic name"); forcePluginOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(forcePluginOption); Poco::Util::Option preloadLibsOption(ARG_PRELOAD_LIBRARY.toStdString(), "", "preload a library"); preloadLibsOption.argument("") .repeatable(true) .callback(Poco::Util::OptionCallback(d, &Impl::handlePreloadLibraryOption)); options.addOption(preloadLibsOption); Poco::Util::Option noRegistryCacheOption(ARG_NO_REGISTRY_CACHE.toStdString(), "", "do not use a cache for the registry"); noRegistryCacheOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noRegistryCacheOption); Poco::Util::Option noLazyRegistryCacheLoadingOption(ARG_NO_LAZY_REGISTRY_CACHE_LOADING.toStdString(), "", "do not use lazy cache loading for the registry"); noLazyRegistryCacheLoadingOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noLazyRegistryCacheLoadingOption); Poco::Util::Option registryMultiLanguageOption(ARG_REGISTRY_MULTI_LANGUAGE.toStdString(), "", "enable multi-language support for the registry"); registryMultiLanguageOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(registryMultiLanguageOption); Poco::Util::Option splashScreenOption(ARG_SPLASH_IMAGE.toStdString(), "", "optional picture to use as a splash screen"); splashScreenOption.argument("").binding(ARG_SPLASH_IMAGE.toStdString()); options.addOption(splashScreenOption); Poco::Util::Option xargsOption(ARG_XARGS.toStdString(), "", "Extended argument list"); xargsOption.argument("").binding(ARG_XARGS.toStdString()); options.addOption(xargsOption); Poco::Util::Option logQtMessagesOption(ARG_LOG_QT_MESSAGES.toStdString(), "", "log Qt messages"); logQtMessagesOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(logQtMessagesOption); Poco::Util::Option labelSetPresetOption(ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), "", "use this label set preset for new segmentations"); labelSetPresetOption.argument("").binding(ARG_SEGMENTATION_LABELSET_PRESET.toStdString()); options.addOption(labelSetPresetOption); Poco::Util::Option labelSuggestionsOption(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), "", "use this list of predefined suggestions for segmentation labels"); labelSuggestionsOption.argument("").binding(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString()); options.addOption(labelSuggestionsOption); Poco::Util::Application::defineOptions(options); } QSharedPointer BaseApplication::getFramework() const { return ctkPluginFrameworkLauncher::getPluginFramework(); } ctkPluginContext *BaseApplication::getFrameworkContext() const { auto framework = getFramework(); return framework ? framework->getPluginContext() : nullptr; } void BaseApplication::initializeSplashScreen(QCoreApplication * application) const { auto pixmapFileNameProp = d->getProperty(ARG_SPLASH_IMAGE); if (!pixmapFileNameProp.isNull()) { auto pixmapFileName = pixmapFileNameProp.toString(); QFileInfo checkFile(pixmapFileName); if (checkFile.exists() && checkFile.isFile()) { QPixmap pixmap(checkFile.absoluteFilePath()); d->m_Splashscreen = new QSplashScreen(pixmap, Qt::WindowStaysOnTopHint); d->m_Splashscreen->show(); application->processEvents(); } } } QHash BaseApplication::getFrameworkProperties() const { return d->m_FWProps; } int BaseApplication::run() { this->init(d->m_Argc, d->m_Argv); return Application::run(); } void BaseApplication::setProperty(const QString &property, const QVariant &value) { d->m_FWProps[property] = value; } QVariant BaseApplication::getProperty(const QString &property) const { return d->getProperty(property); } void BaseApplication::installTranslator(QTranslator* translator) { this->getQApplication()->installTranslator(translator); } bool BaseApplication::isRunning() { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->isRunning(); mitkThrow() << "Method not implemented."; } void BaseApplication::sendMessage(const QByteArray msg) { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->sendMessage(msg); mitkThrow() << "Method not implemented."; } } diff --git a/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper2D.h b/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper2D.h index f3eebac5e3..1c67809032 100644 --- a/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper2D.h +++ b/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper2D.h @@ -1,83 +1,83 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBoundingShapeVtkMapper2D_h #define mitkBoundingShapeVtkMapper2D_h #include #include #include #include #include #include #include #include -#include +#include namespace mitk { class MITKBOUNDINGSHAPE_EXPORT BoundingShapeVtkMapper2D final : public VtkMapper { class LocalStorage : public Mapper::BaseLocalStorage { public: LocalStorage(); ~LocalStorage() override; bool IsUpdateRequired(mitk::BaseRenderer *renderer, mitk::Mapper *mapper, mitk::DataNode *dataNode); vtkSmartPointer m_Actor; vtkSmartPointer m_HandleActor; vtkSmartPointer m_SelectedHandleActor; vtkSmartPointer m_Mapper; vtkSmartPointer m_HandleMapper; vtkSmartPointer m_SelectedHandleMapper; vtkSmartPointer m_Cutter; vtkSmartPointer m_CuttingPlane; unsigned int m_LastSliceNumber; - std::vector> m_Handles; + std::vector> m_Handles; vtkSmartPointer m_PropAssembly; double m_ZoomFactor; private: LocalStorage(const LocalStorage &); LocalStorage &operator=(const LocalStorage &); }; public: static void SetDefaultProperties(DataNode *node, BaseRenderer *renderer = nullptr, bool overwrite = false); mitkClassMacro(BoundingShapeVtkMapper2D, VtkMapper); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void ApplyColorAndOpacityProperties(BaseRenderer *, vtkActor *) override; vtkProp *GetVtkProp(BaseRenderer *renderer) override; private: BoundingShapeVtkMapper2D(); ~BoundingShapeVtkMapper2D() override; BoundingShapeVtkMapper2D(const Self &); Self &operator=(const Self &); void GenerateDataForRenderer(BaseRenderer *renderer) override; void Update(mitk::BaseRenderer *renderer) override; class Impl; Impl *m_Impl; }; } #endif diff --git a/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper3D.h b/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper3D.h index 4e800576b0..3af3bdfe70 100644 --- a/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper3D.h +++ b/Modules/BoundingShape/include/mitkBoundingShapeVtkMapper3D.h @@ -1,56 +1,51 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBoundingShapeVtkMapper3D_h #define mitkBoundingShapeVtkMapper3D_h #include - #include -#include -#include -#include - namespace mitk { class MITKBOUNDINGSHAPE_EXPORT BoundingShapeVtkMapper3D : public VtkMapper { public: static void SetDefaultProperties(DataNode *node, BaseRenderer *renderer = nullptr, bool overwrite = false); mitkClassMacro(BoundingShapeVtkMapper3D, VtkMapper); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void ApplyColorAndOpacityProperties(BaseRenderer *, vtkActor *) override; void ApplyBoundingShapeProperties(BaseRenderer *renderer, vtkActor *); vtkProp *GetVtkProp(BaseRenderer *renderer) override; // virtual void UpdateVtkTransform(mitk::BaseRenderer* renderer) override; protected: void GenerateDataForRenderer(BaseRenderer *renderer) override; private: BoundingShapeVtkMapper3D(); ~BoundingShapeVtkMapper3D() override; BoundingShapeVtkMapper3D(const Self &); Self &operator=(const Self &); class Impl; Impl *m_Impl; }; } #endif diff --git a/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp b/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp index 9c61cf79ae..27a7210bb2 100644 --- a/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp +++ b/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp @@ -1,605 +1,605 @@ /*============================================================================ 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 "../DataManagement/mitkBoundingShapeUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usGetModuleContext.h" #include "usModuleRegistry.h" // Properties to allow the user to interact with the base data const char *selectedColorPropertyName = "Bounding Shape.Selected Color"; const char *deselectedColorPropertyName = "Bounding Shape.Deselected Color"; const char *activeHandleIdPropertyName = "Bounding Shape.Active Handle ID"; const char *boundingShapePropertyName = "Bounding Shape"; namespace mitk { itkEventMacroDefinition(BoundingShapeInteractionEvent, itk::AnyEvent); class BoundingShapeInteractor::Impl { public: Impl() : OriginalInteractionEnabled(false), RotationEnabled(false) { Point3D initialPoint; initialPoint.Fill(0.0); for (int i = 0; i < 6; ++i) Handles.push_back(Handle(initialPoint, i, GetHandleIndices(i))); } ~Impl() {} bool OriginalInteractionEnabled; Point3D InitialPickedWorldPoint; Point3D LastPickedWorldPoint; Point2D InitialPickedDisplayPoint; std::vector Handles; Handle ActiveHandle; Geometry3D::Pointer OriginalGeometry; bool RotationEnabled; std::map DisplayInteractionConfigs; }; } mitk::BoundingShapeInteractor::BoundingShapeInteractor() : m_Impl(new Impl) { } mitk::BoundingShapeInteractor::~BoundingShapeInteractor() { this->RestoreNodeProperties(); delete m_Impl; } void mitk::BoundingShapeInteractor::ConnectActionsAndFunctions() { // **Conditions** that can be used in the state machine, to ensure that certain conditions are met, before actually // executing an action CONNECT_CONDITION("isHoveringOverObject", CheckOverObject); CONNECT_CONDITION("isHoveringOverHandles", CheckOverHandles); // **Function** in the statemachine patterns also referred to as **Actions** CONNECT_FUNCTION("selectObject", SelectObject); CONNECT_FUNCTION("deselectObject", DeselectObject); CONNECT_FUNCTION("deselectHandles", DeselectHandles); CONNECT_FUNCTION("initInteraction", InitInteraction); CONNECT_FUNCTION("translateObject", TranslateObject); CONNECT_FUNCTION("selectHandle", SelectHandle); CONNECT_FUNCTION("scaleObject", ScaleObject); // CONNECT_FUNCTION("rotateObject",RotateObject); } // RotateObject(StateMachineAction*, InteractionEvent* interactionEvent) // void mitk::BoundingShapeInteractor::RotateGeometry(mitk::ScalarType angle, int rotationaxis, mitk::BaseGeometry* // geometry) //{ // mitk::Vector3D rotationAxis = geometry->GetAxisVector(rotationaxis); // float pointX = 0.0f; // float pointY = 0.0f; // float pointZ = 0.0f; // mitk::Point3D pointOfRotation; // pointOfRotation.Fill(0.0); // this->GetDataNode()->GetFloatProperty(anchorPointX, pointX); // this->GetDataNode()->GetFloatProperty(anchorPointY, pointY); // this->GetDataNode()->GetFloatProperty(anchorPointZ, pointZ); // pointOfRotation[0] = pointX; // pointOfRotation[1] = pointY; // pointOfRotation[2] = pointZ; // // mitk::RotationOperation* doOp = new mitk::RotationOperation(OpROTATE, pointOfRotation, rotationAxis, angle); // // geometry->ExecuteOperation(doOp); // delete doOp; //} void mitk::BoundingShapeInteractor::SetRotationEnabled(bool rotationEnabled) { m_Impl->RotationEnabled = rotationEnabled; } void mitk::BoundingShapeInteractor::DataNodeChanged() { mitk::DataNode::Pointer newInputNode = this->GetDataNode(); if (newInputNode == nullptr) return; // add color properties mitk::ColorProperty::Pointer selectedColor = dynamic_cast(newInputNode->GetProperty(selectedColorPropertyName)); mitk::ColorProperty::Pointer deselectedColor = dynamic_cast(newInputNode->GetProperty(deselectedColorPropertyName)); if (selectedColor.IsNull()) newInputNode->AddProperty(selectedColorPropertyName, mitk::ColorProperty::New(0.0, 1.0, 0.0)); if (deselectedColor.IsNull()) - newInputNode->AddProperty(deselectedColorPropertyName, mitk::ColorProperty::New(1.0, 1.0, 1.0)); + newInputNode->AddProperty(deselectedColorPropertyName, mitk::ColorProperty::New(1.0, 0.0, 0.0)); newInputNode->SetProperty(boundingShapePropertyName, mitk::BoolProperty::New(true)); newInputNode->AddProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); newInputNode->SetProperty("layer", mitk::IntProperty::New(101)); newInputNode->SetBoolProperty("fixedLayer", mitk::BoolProperty::New(true)); newInputNode->SetBoolProperty("pickable", true); mitk::ColorProperty::Pointer initialColor = dynamic_cast(newInputNode->GetProperty(deselectedColorPropertyName)); if (initialColor.IsNotNull()) { newInputNode->SetColor(initialColor->GetColor()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::BoundingShapeInteractor::HandlePositionChanged(const InteractionEvent *interactionEvent, Point3D ¢er) { GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); std::vector cornerPoints = GetCornerPoints(geometry, true); if (m_Impl->Handles.size() == 6) { // set handle positions Point3D pointLeft = CalcAvgPoint(cornerPoints[5], cornerPoints[6]); Point3D pointRight = CalcAvgPoint(cornerPoints[1], cornerPoints[2]); Point3D pointTop = CalcAvgPoint(cornerPoints[0], cornerPoints[6]); Point3D pointBottom = CalcAvgPoint(cornerPoints[7], cornerPoints[1]); Point3D pointFront = CalcAvgPoint(cornerPoints[2], cornerPoints[7]); Point3D pointBack = CalcAvgPoint(cornerPoints[4], cornerPoints[1]); m_Impl->Handles[0].SetPosition(pointLeft); m_Impl->Handles[1].SetPosition(pointRight); m_Impl->Handles[2].SetPosition(pointTop); m_Impl->Handles[3].SetPosition(pointBottom); m_Impl->Handles[4].SetPosition(pointFront); m_Impl->Handles[5].SetPosition(pointBack); // calculate center based on half way of the distance between two opposing cornerpoints center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); } } void mitk::BoundingShapeInteractor::SetDataNode(DataNode *node) { this->RestoreNodeProperties(); // if there is another node set, restore it's color if (node == nullptr) return; DataInteractor::SetDataNode(node); // calls DataNodeChanged internally this->DataNodeChanged(); } bool mitk::BoundingShapeInteractor::CheckOverObject(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); // calculates translation based on offset+extent not on the transformation matrix (because the cube is located in the // center not in the origin) vtkSmartPointer imageTransform = geometry->GetVtkTransform()->GetMatrix(); Point3D center = geometry->GetCenter(); auto translation = vtkSmartPointer::New(); auto transform = vtkSmartPointer::New(); translation->Translate(center[0] - imageTransform->GetElement(0, 3), center[1] - imageTransform->GetElement(1, 3), center[2] - imageTransform->GetElement(2, 3)); transform->SetMatrix(imageTransform); transform->PostMultiply(); transform->Concatenate(translation); transform->Update(); mitk::Vector3D extent; for (unsigned int i = 0; i < 3; ++i) extent[i] = (geometry->GetExtent(i)); Point3D currentWorldPosition; Point2D currentDisplayPosition = positionEvent->GetPointerPositionOnScreen(); interactionEvent->GetSender()->DisplayToWorld(currentDisplayPosition, currentWorldPosition); ScalarType transformedPosition[4]; transformedPosition[0] = currentWorldPosition[0]; transformedPosition[1] = currentWorldPosition[1]; transformedPosition[2] = currentWorldPosition[2]; transformedPosition[3] = 1; // transform point from world to object coordinates transform->GetInverse()->TransformPoint(transformedPosition, transformedPosition); // check if the world point is within bounds bool isInside = (transformedPosition[0] >= (-extent[0] / 2.0)) && (transformedPosition[0] <= (extent[0] / 2.0)) && (transformedPosition[1] >= (-extent[1] / 2.0)) && (transformedPosition[1] <= (extent[1] / 2.0)) && (transformedPosition[2] >= (-extent[2] / 2.0)) && (transformedPosition[2] <= (extent[2] / 2.0)); return isInside; } bool mitk::BoundingShapeInteractor::CheckOverHandles(const InteractionEvent *interactionEvent) { Point3D boundingBoxCenter; HandlePositionChanged(interactionEvent, boundingBoxCenter); const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; Point2D displayCenterPoint; // to do: change to actual time step (currently not necessary because geometry remains the same for each timestep int timeStep = 0; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); BaseGeometry::Pointer geometry = geometryData->GetUpdatedTimeGeometry()->GetGeometryForTimeStep(timeStep); std::vector cornerPoints = GetCornerPoints(geometry, true); interactionEvent->GetSender()->WorldToDisplay(boundingBoxCenter, displayCenterPoint); double scale = interactionEvent->GetSender()->GetScaleFactorMMPerDisplayUnit(); // GetDisplaySizeInMM mitk::DoubleProperty::Pointer handleSizeProperty = dynamic_cast(this->GetDataNode()->GetProperty("Bounding Shape.Handle Size Factor")); ScalarType initialHandleSize; if (handleSizeProperty != nullptr) initialHandleSize = handleSizeProperty->GetValue(); else initialHandleSize = 1.0 / 40.0; mitk::Point2D displaysize = interactionEvent->GetSender()->GetDisplaySizeInMM(); ScalarType handlesize = ((displaysize[0] + displaysize[1]) / 2.0) * initialHandleSize; unsigned int handleNum = 0; for (auto &handle : m_Impl->Handles) { Point2D centerpoint; interactionEvent->GetSender()->WorldToDisplay(handle.GetPosition(), centerpoint); Point2D currentDisplayPosition = positionEvent->GetPointerPositionOnScreen(); if ((currentDisplayPosition.EuclideanDistanceTo(centerpoint) < (handlesize / scale)) && (currentDisplayPosition.EuclideanDistanceTo(displayCenterPoint) > (handlesize / scale))) // check if mouse is hovering over center point { handle.SetActive(true); m_Impl->ActiveHandle = handle; this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(handleNum++)); this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return true; } else { handleNum++; handle.SetActive(false); } this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); } return false; } void mitk::BoundingShapeInteractor::SelectHandle(StateMachineAction *, InteractionEvent *) { this->DisableOriginalInteraction(); DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer selectedColor = dynamic_cast(node->GetProperty(deselectedColorPropertyName)); if (selectedColor.IsNotNull()) { this->GetDataNode()->GetPropertyList()->SetProperty("color", selectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::DeselectHandles(StateMachineAction *, InteractionEvent *) { this->DisableOriginalInteraction(); DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::SelectObject(StateMachineAction *, InteractionEvent *) { this->DisableOriginalInteraction(); // disable crosshair interaction and scolling if user is hovering over the object DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer selectedColor = dynamic_cast(node->GetProperty(selectedColorPropertyName)); if (selectedColor.IsNotNull()) { node->GetPropertyList()->SetProperty("color", selectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::DeselectObject(StateMachineAction *, InteractionEvent *) { this->EnableOriginalInteraction(); // enable crosshair interaction and scolling if user is hovering over the object DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer deselectedColor = dynamic_cast(node->GetProperty(deselectedColorPropertyName)); if (deselectedColor.IsNotNull()) { node->GetPropertyList()->SetProperty("color", deselectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::InitInteraction(StateMachineAction *, InteractionEvent *interactionEvent) { InitMembers(interactionEvent); } bool mitk::BoundingShapeInteractor::InitMembers(InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; // get initial position coordinates m_Impl->InitialPickedDisplayPoint = positionEvent->GetPointerPositionOnScreen(); m_Impl->InitialPickedWorldPoint = positionEvent->GetPositionInWorld(); m_Impl->LastPickedWorldPoint = positionEvent->GetPositionInWorld(); return true; } void mitk::BoundingShapeInteractor::TranslateObject(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = this->GetDataNode()->GetData()->GetUpdatedTimeGeometry()->GetGeometryForTimeStep(timeStep); Vector3D spacing = geometry->GetSpacing(); Point3D currentPickedPoint; interactionEvent->GetSender()->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), currentPickedPoint); Vector3D interactionMove; // pixel aligned shifting of the bounding box interactionMove[0] = std::round((currentPickedPoint[0] - m_Impl->LastPickedWorldPoint[0]) / spacing[0]) * spacing[0]; interactionMove[1] = std::round((currentPickedPoint[1] - m_Impl->LastPickedWorldPoint[1]) / spacing[1]) * spacing[1]; interactionMove[2] = std::round((currentPickedPoint[2] - m_Impl->LastPickedWorldPoint[2]) / spacing[2]) * spacing[2]; if ((interactionMove[0] + interactionMove[1] + interactionMove[2]) != 0.0) // only update current position if a movement occured { m_Impl->LastPickedWorldPoint = currentPickedPoint; geometry->SetOrigin(geometry->GetOrigin() + interactionMove); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } return; } void mitk::BoundingShapeInteractor::ScaleObject(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); Point3D handlePickedPoint = m_Impl->ActiveHandle.GetPosition(); Point3D currentPickedPoint; interactionEvent->GetSender()->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), currentPickedPoint); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); Vector3D spacing = geometry->GetSpacing(); // pixel aligned bounding box Vector3D interactionMove; interactionMove[0] = (currentPickedPoint[0] - m_Impl->LastPickedWorldPoint[0]); interactionMove[1] = (currentPickedPoint[1] - m_Impl->LastPickedWorldPoint[1]); interactionMove[2] = (currentPickedPoint[2] - m_Impl->LastPickedWorldPoint[2]); std::vector faces = m_Impl->ActiveHandle.GetFaceIndices(); auto pointscontainer = mitk::BoundingBox::PointsContainer::New(); // calculate cornerpoints from geometry plus visualization offset std::vector cornerPoints = GetCornerPoints(geometry, true); unsigned int num = 0; for (const auto &point : cornerPoints) { pointscontainer->InsertElement(num++, point); } // calculate center based on half way of the distance between two opposing cornerpoints mitk::Point3D center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); Vector3D faceNormal; faceNormal[0] = handlePickedPoint[0] - center[0]; faceNormal[1] = handlePickedPoint[1] - center[1]; faceNormal[2] = handlePickedPoint[2] - center[2]; Vector3D faceShift = ((faceNormal * interactionMove) / (faceNormal.GetNorm() * faceNormal.GetNorm())) * faceNormal; // calculate cornerpoints from geometry without visualization offset to update actual geometry cornerPoints = GetCornerPoints(geometry, false); num = 0; for (const auto &point : cornerPoints) { pointscontainer->InsertElement(num++, point); } bool positionChangeThreshold = true; for (int numFaces = 0; numFaces < 8; numFaces++) // estimate the corresponding face and shift its assigned points { if ((numFaces != faces[0]) && (numFaces != faces[1]) && (numFaces != faces[2]) && (numFaces != faces[3])) { Point3D point = pointscontainer->GetElement(numFaces); if (m_Impl->RotationEnabled) // apply if geometry is rotated and a pixel aligned shift is not possible { point[0] += faceShift[0]; point[1] += faceShift[1]; point[2] += faceShift[2]; } else // shift pixelwise { point[0] += std::round(faceShift[0] / spacing[0]) * spacing[0]; point[1] += std::round(faceShift[1] / spacing[1]) * spacing[1]; point[2] += std::round(faceShift[2] / spacing[2]) * spacing[2]; } if (point == pointscontainer->GetElement(numFaces)) positionChangeThreshold = false; else m_Impl->LastPickedWorldPoint = point; pointscontainer->InsertElement(numFaces, point); } } if (positionChangeThreshold) // update only if bounding box is shifted at least by one pixel { auto inverse = mitk::AffineTransform3D::New(); geometry->GetIndexToWorldTransform()->GetInverse(inverse); for (unsigned int pointid = 0; pointid < 8; pointid++) { pointscontainer->InsertElement(pointid, inverse->TransformPoint(pointscontainer->GetElement(pointid))); } auto bbox = mitk::BoundingBox::New(); bbox->SetPoints(pointscontainer); bbox->ComputeBoundingBox(); mitk::Point3D BBmin = bbox->GetMinimum(); mitk::Point3D BBmax = bbox->GetMaximum(); if (std::abs(BBmin[0] - BBmax[0]) > 0.01 && std::abs(BBmin[1] - BBmax[1]) > 0.01 && std::abs(BBmin[2] - BBmax[2]) > 0.01) // TODO: check if the extent is greater than zero { geometry->SetBounds(bbox->GetBounds()); geometry->Modified(); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return; } void mitk::BoundingShapeInteractor::RestoreNodeProperties() { mitk::DataNode::Pointer inputNode = this->GetDataNode(); if (inputNode.IsNull()) return; mitk::ColorProperty::Pointer color = (mitk::ColorProperty::New(1.0, 1.0, 1.0)); if (color.IsNotNull()) { inputNode->GetPropertyList()->SetProperty("color", color); } inputNode->SetProperty("layer", mitk::IntProperty::New(99)); inputNode->SetProperty(boundingShapePropertyName, mitk::BoolProperty::New(false)); inputNode->GetPropertyList()->DeleteProperty(activeHandleIdPropertyName); EnableOriginalInteraction(); // update rendering mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::BoundingShapeInteractor::EnableOriginalInteraction() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractionConfig : m_Impl->DisplayInteractionConfigs) { if (displayInteractionConfig.first) { auto displayActionEventBroadcast = static_cast( us::GetModuleContext()->GetService(displayInteractionConfig.first)); if (nullptr != displayActionEventBroadcast) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(displayInteractionConfig.second); } } } m_Impl->DisplayInteractionConfigs.clear(); m_Impl->OriginalInteractionEnabled = true; } void mitk::BoundingShapeInteractor::DisableOriginalInteraction() { // dont deactivate twice, else we will clutter the config list ... if (false == m_Impl->OriginalInteractionEnabled) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_Impl->DisplayInteractionConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto *displayActionEventBroadcast = dynamic_cast( us::GetModuleContext()->GetService(eventObserver)); if (nullptr != displayActionEventBroadcast) { // remember the original configuration m_Impl->DisplayInteractionConfigs.insert(std::make_pair(eventObserver, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->AddEventConfig("DisplayConfigBlockLMB.xml"); } } m_Impl->OriginalInteractionEnabled = false; } diff --git a/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper2D.cpp b/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper2D.cpp index 4d6afbb5a3..5114a48201 100644 --- a/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper2D.cpp +++ b/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper2D.cpp @@ -1,467 +1,462 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "../DataManagement/mitkBoundingShapeUtil.h" #include #include #include #include #include -#include #include #include #include #include #include -#include #include #include #include -static vtkSmartPointer CreateHandle() -{ - auto handle = vtkSmartPointer::New(); - - handle->SetPhiResolution(8); - handle->SetThetaResolution(16); - - return handle; -} - namespace mitk { class BoundingShapeVtkMapper2D::Impl { public: Impl() { Point3D initialPoint; initialPoint.Fill(0); for (int i = 0; i < 6; ++i) HandlePropertyList.push_back(Handle(initialPoint, i, GetHandleIndices(i))); } std::vector HandlePropertyList; mitk::LocalStorageHandler LocalStorageHandler; }; } mitk::BoundingShapeVtkMapper2D::LocalStorage::LocalStorage() : m_Actor(vtkSmartPointer::New()), m_HandleActor(vtkSmartPointer::New()), m_SelectedHandleActor(vtkSmartPointer::New()), m_Mapper(vtkSmartPointer::New()), m_HandleMapper(vtkSmartPointer::New()), m_SelectedHandleMapper(vtkSmartPointer::New()), m_Cutter(vtkSmartPointer::New()), m_CuttingPlane(vtkSmartPointer::New()), m_LastSliceNumber(0), m_PropAssembly(vtkSmartPointer::New()), m_ZoomFactor(1.0) { m_Actor->SetMapper(m_Mapper); - m_Actor->GetProperty()->SetOpacity(0.3); m_Actor->VisibilityOn(); m_HandleActor->SetMapper(m_HandleMapper); m_HandleActor->VisibilityOn(); m_SelectedHandleActor->VisibilityOn(); m_SelectedHandleActor->GetProperty()->SetColor(0, 1.0, 0); m_SelectedHandleActor->SetMapper(m_SelectedHandleMapper); vtkCoordinate *tcoord = vtkCoordinate::New(); tcoord->SetCoordinateSystemToWorld(); m_SelectedHandleMapper->SetTransformCoordinate(tcoord); tcoord->Delete(); m_Cutter->SetCutFunction(m_CuttingPlane); for (int i = 0; i < 6; ++i) - m_Handles.push_back(CreateHandle()); + m_Handles.push_back(vtkSmartPointer::New()); m_PropAssembly->AddPart(m_Actor); m_PropAssembly->AddPart(m_HandleActor); m_PropAssembly->VisibilityOn(); } bool mitk::BoundingShapeVtkMapper2D::LocalStorage::IsUpdateRequired(mitk::BaseRenderer *renderer, mitk::Mapper *mapper, mitk::DataNode *dataNode) { const mitk::PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if (m_LastGenerateDataTime < worldGeometry->GetMTime()) return true; unsigned int sliceNumber = renderer->GetSlice(); if (m_LastSliceNumber != sliceNumber) return true; if (mapper && m_LastGenerateDataTime < mapper->GetMTime()) return true; if (dataNode) { if (m_LastGenerateDataTime < dataNode->GetMTime()) return true; mitk::BaseData *data = dataNode->GetData(); if (data && m_LastGenerateDataTime < data->GetMTime()) return true; } return false; } mitk::BoundingShapeVtkMapper2D::LocalStorage::~LocalStorage() { } void mitk::BoundingShapeVtkMapper2D::Update(mitk::BaseRenderer *renderer) { this->GenerateDataForRenderer(renderer); } void mitk::BoundingShapeVtkMapper2D::SetDefaultProperties(DataNode *node, BaseRenderer *renderer, bool overwrite) { Superclass::SetDefaultProperties(node, renderer, overwrite); + node->AddProperty("opacity", FloatProperty::New(0.2f), renderer, overwrite); } mitk::BoundingShapeVtkMapper2D::BoundingShapeVtkMapper2D() : m_Impl(new Impl) { } mitk::BoundingShapeVtkMapper2D::~BoundingShapeVtkMapper2D() { delete m_Impl; } void mitk::BoundingShapeVtkMapper2D::GenerateDataForRenderer(BaseRenderer *renderer) { const DataNode::Pointer node = GetDataNode(); if (node == nullptr) return; LocalStorage *localStorage = m_Impl->LocalStorageHandler.GetLocalStorage(renderer); // either update if GeometryData was modified or if the zooming was performed bool needGenerateData = localStorage->IsUpdateRequired( renderer, this, GetDataNode()); // true; // localStorage->GetLastGenerateDataTime() < node->GetMTime() || // localStorage->GetLastGenerateDataTime() < node->GetData()->GetMTime(); // //localStorage->IsGenerateDataRequired(renderer, this, GetDataNode()); double scale = renderer->GetScaleFactorMMPerDisplayUnit(); if (std::abs(scale - localStorage->m_ZoomFactor) > 0.001) { localStorage->m_ZoomFactor = scale; needGenerateData = true; } if (needGenerateData) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) { localStorage->m_Actor->VisibilityOff(); return; } GeometryData::Pointer shape = static_cast(node->GetData()); if (shape == nullptr) return; mitk::BaseGeometry::Pointer geometry = shape->GetGeometry(); mitk::Vector3D spacing = geometry->GetSpacing(); // calculate cornerpoints and extent from geometry with visualization offset std::vector cornerPoints = GetCornerPoints(geometry, true); Point3D p0 = cornerPoints[0]; Point3D p1 = cornerPoints[1]; Point3D p2 = cornerPoints[2]; Point3D p4 = cornerPoints[4]; Point3D extent; extent[0] = sqrt((p0[0] - p4[0]) * (p0[0] - p4[0]) + (p0[1] - p4[1]) * (p0[1] - p4[1]) + (p0[2] - p4[2]) * (p0[2] - p4[2])); extent[1] = sqrt((p0[0] - p2[0]) * (p0[0] - p2[0]) + (p0[1] - p2[1]) * (p0[1] - p2[1]) + (p0[2] - p2[2]) * (p0[2] - p2[2])); extent[2] = sqrt((p0[0] - p1[0]) * (p0[0] - p1[0]) + (p0[1] - p1[1]) * (p0[1] - p1[1]) + (p0[2] - p1[2]) * (p0[2] - p1[2])); // calculate center based on half way of the distance between two opposing cornerpoints mitk::Point3D center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); if (m_Impl->HandlePropertyList.size() == 6) { // set handle positions Point3D pointLeft = CalcAvgPoint(cornerPoints[5], cornerPoints[6]); Point3D pointRight = CalcAvgPoint(cornerPoints[1], cornerPoints[2]); Point3D pointTop = CalcAvgPoint(cornerPoints[0], cornerPoints[6]); Point3D pointBottom = CalcAvgPoint(cornerPoints[7], cornerPoints[1]); Point3D pointFront = CalcAvgPoint(cornerPoints[2], cornerPoints[7]); Point3D pointBack = CalcAvgPoint(cornerPoints[4], cornerPoints[1]); m_Impl->HandlePropertyList[0].SetPosition(pointLeft); m_Impl->HandlePropertyList[1].SetPosition(pointRight); m_Impl->HandlePropertyList[2].SetPosition(pointTop); m_Impl->HandlePropertyList[3].SetPosition(pointBottom); m_Impl->HandlePropertyList[4].SetPosition(pointFront); m_Impl->HandlePropertyList[5].SetPosition(pointBack); } // caculate face normals double cubeFaceNormal0[3], cubeFaceNormal1[3], cubeFaceNormal2[3]; double a[3], b[3]; a[0] = (cornerPoints[5][0] - cornerPoints[6][0]); a[1] = (cornerPoints[5][1] - cornerPoints[6][1]); a[2] = (cornerPoints[5][2] - cornerPoints[6][2]); b[0] = (cornerPoints[5][0] - cornerPoints[4][0]); b[1] = (cornerPoints[5][1] - cornerPoints[4][1]); b[2] = (cornerPoints[5][2] - cornerPoints[4][2]); vtkMath::Cross(a, b, cubeFaceNormal0); a[0] = (cornerPoints[0][0] - cornerPoints[6][0]); a[1] = (cornerPoints[0][1] - cornerPoints[6][1]); a[2] = (cornerPoints[0][2] - cornerPoints[6][2]); b[0] = (cornerPoints[0][0] - cornerPoints[2][0]); b[1] = (cornerPoints[0][1] - cornerPoints[2][1]); b[2] = (cornerPoints[0][2] - cornerPoints[2][2]); vtkMath::Cross(a, b, cubeFaceNormal1); a[0] = (cornerPoints[2][0] - cornerPoints[7][0]); a[1] = (cornerPoints[2][1] - cornerPoints[7][1]); a[2] = (cornerPoints[2][2] - cornerPoints[7][2]); b[0] = (cornerPoints[2][0] - cornerPoints[6][0]); b[1] = (cornerPoints[2][1] - cornerPoints[6][1]); b[2] = (cornerPoints[2][2] - cornerPoints[6][2]); vtkMath::Cross(a, b, cubeFaceNormal2); vtkMath::Normalize(cubeFaceNormal0); vtkMath::Normalize(cubeFaceNormal1); vtkMath::Normalize(cubeFaceNormal2); // create cube for rendering bounding box auto cube = vtkCubeSource::New(); cube->SetXLength(extent[0] / spacing[0]); cube->SetYLength(extent[1] / spacing[1]); cube->SetZLength(extent[2] / spacing[2]); // calculates translation based on offset+extent not on the transformation matrix vtkSmartPointer imageTransform = geometry->GetVtkTransform()->GetMatrix(); auto translation = vtkSmartPointer::New(); translation->Translate(center[0] - imageTransform->GetElement(0, 3), center[1] - imageTransform->GetElement(1, 3), center[2] - imageTransform->GetElement(2, 3)); auto transform = vtkSmartPointer::New(); transform->SetMatrix(imageTransform); transform->PostMultiply(); transform->Concatenate(translation); transform->Update(); cube->Update(); auto transformFilter = vtkSmartPointer::New(); transformFilter->SetInputData(cube->GetOutput()); transformFilter->SetTransform(transform); transformFilter->Update(); cube->Delete(); vtkSmartPointer polydata = transformFilter->GetPolyDataOutput(); if (polydata == nullptr || (polydata->GetNumberOfPoints() < 1)) { localStorage->m_Actor->VisibilityOff(); localStorage->m_HandleActor->VisibilityOff(); localStorage->m_SelectedHandleActor->VisibilityOff(); return; } // estimate current image plane to decide whether the cube is visible or not const PlaneGeometry *planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); if ((planeGeometry == nullptr) || (!planeGeometry->IsValid()) || (!planeGeometry->HasReferenceGeometry())) return; double origin[3]; origin[0] = planeGeometry->GetOrigin()[0]; origin[1] = planeGeometry->GetOrigin()[1]; origin[2] = planeGeometry->GetOrigin()[2]; double displayPlaneNormal[3]; displayPlaneNormal[0] = planeGeometry->GetNormal()[0]; displayPlaneNormal[1] = planeGeometry->GetNormal()[1]; displayPlaneNormal[2] = planeGeometry->GetNormal()[2]; vtkMath::Normalize(displayPlaneNormal); localStorage->m_CuttingPlane->SetOrigin(origin); localStorage->m_CuttingPlane->SetNormal(displayPlaneNormal); // add cube polydata to local storage localStorage->m_Cutter->SetInputData(polydata); localStorage->m_Cutter->SetGenerateCutScalars(1); localStorage->m_Cutter->Update(); if (localStorage->m_PropAssembly->GetParts()->IsItemPresent(localStorage->m_HandleActor)) localStorage->m_PropAssembly->RemovePart(localStorage->m_HandleActor); if (localStorage->m_PropAssembly->GetParts()->IsItemPresent(localStorage->m_Actor)) localStorage->m_PropAssembly->RemovePart(localStorage->m_Actor); vtkCoordinate *tcoord = vtkCoordinate::New(); tcoord->SetCoordinateSystemToWorld(); localStorage->m_HandleMapper->SetTransformCoordinate(tcoord); tcoord->Delete(); if (localStorage->m_Cutter->GetOutput()->GetNumberOfPoints() > 0) // if plane is visible in the renderwindow { mitk::DoubleProperty::Pointer handleSizeProperty = dynamic_cast(this->GetDataNode()->GetProperty("Bounding Shape.Handle Size Factor")); ScalarType initialHandleSize; if (handleSizeProperty != nullptr) initialHandleSize = handleSizeProperty->GetValue(); else - initialHandleSize = 1.0 / 40.0; + initialHandleSize = 0.02; mitk::Point2D displaySize = renderer->GetDisplaySizeInMM(); double handleSize = ((displaySize[0] + displaySize[1]) / 2.0) * initialHandleSize; auto appendPoly = vtkSmartPointer::New(); unsigned int handleIdx = 0; // add handles and their assigned properties to the local storage mitk::IntProperty::Pointer activeHandleId = dynamic_cast(node->GetProperty("Bounding Shape.Active Handle ID")); double angle0 = std::abs(vtkMath::DegreesFromRadians(vtkMath::AngleBetweenVectors(displayPlaneNormal, cubeFaceNormal0))); if (angle0 > 179.0) angle0 -= 180.0; double angle1 = std::abs(vtkMath::DegreesFromRadians(vtkMath::AngleBetweenVectors(displayPlaneNormal, cubeFaceNormal1))); if (angle1 > 179.0) angle1 -= 180.0; double angle2 = std::abs(vtkMath::DegreesFromRadians(vtkMath::AngleBetweenVectors(displayPlaneNormal, cubeFaceNormal2))); if (angle2 > 179.0) angle2 -= 180.0; bool visible = false; bool selected = false; for (auto& handle : localStorage->m_Handles) { Point3D handleCenter = m_Impl->HandlePropertyList[handleIdx].GetPosition(); - handle->SetRadius(handleSize); + handle->SetXLength(handleSize); + handle->SetYLength(handleSize); + handle->SetZLength(handleSize); handle->SetCenter(handleCenter[0], handleCenter[1], handleCenter[2]); // show handles only if the corresponding face is aligned to the render window if ( (handleIdx != 0 && handleIdx != 1 && std::abs(angle0) < 0.1) || // handles 0 and 1 (handleIdx != 2 && handleIdx != 3 && std::abs(angle1) < 0.1) || // handles 2 and 3 (handleIdx != 4 && handleIdx != 5 && std::abs(angle2) < 0.1) ) // handles 4 and 5 { if (activeHandleId == nullptr) { appendPoly->AddInputConnection(handle->GetOutputPort()); } else { if ((activeHandleId->GetValue() != m_Impl->HandlePropertyList[handleIdx].GetIndex())) { appendPoly->AddInputConnection(handle->GetOutputPort()); } else { handle->Update(); localStorage->m_SelectedHandleMapper->SetInputData(handle->GetOutput()); localStorage->m_SelectedHandleActor->VisibilityOn(); selected = true; } } visible = true; } ++handleIdx; } if (visible) { appendPoly->Update(); } else { localStorage->m_HandleActor->VisibilityOff(); localStorage->m_SelectedHandleActor->VisibilityOff(); } auto stripper = vtkSmartPointer::New(); stripper->SetInputData(localStorage->m_Cutter->GetOutput()); stripper->Update(); auto cutPolyData = vtkSmartPointer::New(); cutPolyData->SetPoints(stripper->GetOutput()->GetPoints()); cutPolyData->SetPolys(stripper->GetOutput()->GetLines()); localStorage->m_Actor->GetMapper()->SetInputDataObject(cutPolyData); - mitk::ColorProperty::Pointer selectedColor = dynamic_cast(node->GetProperty("color")); - if (selectedColor != nullptr) - { - mitk::Color color = selectedColor->GetColor(); - localStorage->m_Actor->GetProperty()->SetColor(color[0], color[1], color[2]); - } + + this->ApplyColorAndOpacityProperties(renderer, localStorage->m_Actor); if (activeHandleId != nullptr) { localStorage->m_HandleActor->GetProperty()->SetColor(1, 0, 0); } else { localStorage->m_HandleActor->GetProperty()->SetColor(1, 1, 1); } localStorage->m_HandleActor->GetMapper()->SetInputDataObject(appendPoly->GetOutput()); // add parts to the overall storage localStorage->m_PropAssembly->AddPart(localStorage->m_Actor); localStorage->m_PropAssembly->AddPart(localStorage->m_HandleActor); if (selected) { localStorage->m_PropAssembly->AddPart(localStorage->m_SelectedHandleActor); } localStorage->m_PropAssembly->VisibilityOn(); localStorage->m_Actor->VisibilityOn(); localStorage->m_HandleActor->VisibilityOn(); } else { localStorage->m_PropAssembly->VisibilityOff(); localStorage->m_Actor->VisibilityOff(); localStorage->m_HandleActor->VisibilityOff(); localStorage->m_SelectedHandleActor->VisibilityOff(); localStorage->UpdateGenerateDataTime(); } localStorage->UpdateGenerateDataTime(); } } vtkProp *mitk::BoundingShapeVtkMapper2D::GetVtkProp(BaseRenderer *renderer) { return m_Impl->LocalStorageHandler.GetLocalStorage(renderer)->m_PropAssembly; } -void mitk::BoundingShapeVtkMapper2D::ApplyColorAndOpacityProperties(BaseRenderer *, vtkActor *) +void mitk::BoundingShapeVtkMapper2D::ApplyColorAndOpacityProperties(BaseRenderer *renderer, vtkActor *actor) { + auto* property = actor->GetProperty(); + + std::array color = { 1.0, 0.0, 0.0 }; + this->GetDataNode()->GetColor(color.data(), renderer); + property->SetColor(color[0], color[1], color[2]); + + float opacity = 0.2f; + this->GetDataNode()->GetOpacity(opacity, renderer); + property->SetOpacity(opacity); } diff --git a/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper3D.cpp b/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper3D.cpp index 75d303f210..b07e626fa2 100644 --- a/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper3D.cpp +++ b/Modules/BoundingShape/src/Rendering/mitkBoundingShapeVtkMapper3D.cpp @@ -1,335 +1,333 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkBoundingShapeVtkMapper3D.h" #include "../DataManagement/mitkBoundingShapeUtil.h" #include #include #include #include #include #include #include #include -#include #include namespace mitk { class BoundingShapeVtkMapper3D::Impl { class LocalStorage : public Mapper::BaseLocalStorage { public: LocalStorage(); ~LocalStorage() override; LocalStorage(const LocalStorage &) = delete; LocalStorage &operator=(const LocalStorage &) = delete; - std::vector> Handles; + std::vector> Handles; vtkSmartPointer Actor; vtkSmartPointer HandleActor; vtkSmartPointer SelectedHandleActor; vtkSmartPointer PropAssembly; }; public: Impl() : DistanceFromCam(1.0) { Point3D initialPoint; initialPoint.Fill(0); for (int i = 0; i < 6; ++i) HandlePropertyList.push_back(Handle(initialPoint, i, GetHandleIndices(i))); } double DistanceFromCam; std::vector HandlePropertyList; mitk::LocalStorageHandler LocalStorageHandler; }; } mitk::BoundingShapeVtkMapper3D::Impl::LocalStorage::LocalStorage() : Actor(vtkSmartPointer::New()), HandleActor(vtkSmartPointer::New()), SelectedHandleActor(vtkSmartPointer::New()), PropAssembly(vtkSmartPointer::New()) { for (int i = 0; i < 6; i++) - Handles.push_back(vtkSmartPointer::New()); + Handles.push_back(vtkSmartPointer::New()); } mitk::BoundingShapeVtkMapper3D::Impl::LocalStorage::~LocalStorage() { } void mitk::BoundingShapeVtkMapper3D::SetDefaultProperties(DataNode *node, BaseRenderer *renderer, bool overwrite) { Superclass::SetDefaultProperties(node, renderer, overwrite); + node->AddProperty("opacity", FloatProperty::New(0.2f), renderer, overwrite); } mitk::BoundingShapeVtkMapper3D::BoundingShapeVtkMapper3D() : m_Impl(new Impl) { } mitk::BoundingShapeVtkMapper3D::~BoundingShapeVtkMapper3D() { delete m_Impl; } -void mitk::BoundingShapeVtkMapper3D::ApplyColorAndOpacityProperties(BaseRenderer*, vtkActor*) +void mitk::BoundingShapeVtkMapper3D::ApplyColorAndOpacityProperties(BaseRenderer *renderer, vtkActor *actor) { - //Superclass::ApplyColorAndOpacityProperties(renderer, actor); + auto* property = actor->GetProperty(); + + std::array color = { 1.0, 0.0, 0.0 }; + this->GetDataNode()->GetColor(color.data(), renderer); + property->SetColor(color[0], color[1], color[2]); + + float opacity = 0.2f; + this->GetDataNode()->GetOpacity(opacity, renderer); + property->SetOpacity(opacity); } void mitk::BoundingShapeVtkMapper3D::ApplyBoundingShapeProperties(BaseRenderer *renderer, vtkActor *actor) { if (actor == nullptr) return; auto dataNode = this->GetDataNode(); if (dataNode == nullptr) return; bool isVisible = false; dataNode->GetBoolProperty("Bounding Shape.3D Rendering", isVisible, renderer); actor->SetVisibility(isVisible); float lineWidth = 1.0f; dataNode->GetFloatProperty("Bounding Shape.Line.Width", lineWidth, renderer); auto property = actor->GetProperty(); property->SetLineWidth(lineWidth); } void mitk::BoundingShapeVtkMapper3D::GenerateDataForRenderer(BaseRenderer *renderer) { auto dataNode = this->GetDataNode(); if (dataNode == nullptr) return; vtkCamera *camera = renderer->GetVtkRenderer()->GetActiveCamera(); auto localStorage = m_Impl->LocalStorageHandler.GetLocalStorage(renderer); bool needGenerateData = localStorage->GetLastGenerateDataTime() < dataNode->GetMTime(); double distance = camera->GetDistance(); if (std::abs(distance - m_Impl->DistanceFromCam) > mitk::eps) { m_Impl->DistanceFromCam = distance; needGenerateData = true; } if (needGenerateData) { bool isVisible = true; dataNode->GetVisibility(isVisible, renderer); if (!isVisible) { localStorage->Actor->VisibilityOff(); return; } // set the input-object at time t for the mapper auto *geometryData = dynamic_cast(dataNode->GetData()); if (geometryData == nullptr) return; mitk::BaseGeometry::Pointer geometry = geometryData->GetGeometry(); mitk::Vector3D spacing = geometry->GetSpacing(); // calculate cornerpoints from geometry std::vector cornerPoints = GetCornerPoints(geometry, true); Point3D p0 = cornerPoints[0]; Point3D p1 = cornerPoints[1]; Point3D p2 = cornerPoints[2]; Point3D p4 = cornerPoints[4]; Point3D extent; extent[0] = sqrt((p0[0] - p4[0]) * (p0[0] - p4[0]) + (p0[1] - p4[1]) * (p0[1] - p4[1]) + (p0[2] - p4[2]) * (p0[2] - p4[2])); extent[1] = sqrt((p0[0] - p2[0]) * (p0[0] - p2[0]) + (p0[1] - p2[1]) * (p0[1] - p2[1]) + (p0[2] - p2[2]) * (p0[2] - p2[2])); extent[2] = sqrt((p0[0] - p1[0]) * (p0[0] - p1[0]) + (p0[1] - p1[1]) * (p0[1] - p1[1]) + (p0[2] - p1[2]) * (p0[2] - p1[2])); // calculate center based on half way of the distance between two opposing cornerpoints mitk::Point3D center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); if (m_Impl->HandlePropertyList.size() == 6) { // set handle positions Point3D pointLeft = CalcAvgPoint(cornerPoints[5], cornerPoints[6]); Point3D pointRight = CalcAvgPoint(cornerPoints[1], cornerPoints[2]); Point3D pointTop = CalcAvgPoint(cornerPoints[0], cornerPoints[6]); Point3D pointBottom = CalcAvgPoint(cornerPoints[7], cornerPoints[1]); Point3D pointFront = CalcAvgPoint(cornerPoints[2], cornerPoints[7]); Point3D pointBack = CalcAvgPoint(cornerPoints[4], cornerPoints[1]); m_Impl->HandlePropertyList[0].SetPosition(pointLeft); m_Impl->HandlePropertyList[1].SetPosition(pointRight); m_Impl->HandlePropertyList[2].SetPosition(pointTop); m_Impl->HandlePropertyList[3].SetPosition(pointBottom); m_Impl->HandlePropertyList[4].SetPosition(pointFront); m_Impl->HandlePropertyList[5].SetPosition(pointBack); } auto cube = vtkCubeSource::New(); cube->SetXLength(extent[0] / spacing[0]); cube->SetYLength(extent[1] / spacing[1]); cube->SetZLength(extent[2] / spacing[2]); // calculates translation based on offset+extent not on the transformation matrix vtkSmartPointer imageTransform = geometry->GetVtkTransform()->GetMatrix(); auto translation = vtkSmartPointer::New(); translation->Translate(center[0] - imageTransform->GetElement(0, 3), center[1] - imageTransform->GetElement(1, 3), center[2] - imageTransform->GetElement(2, 3)); auto transform = vtkSmartPointer::New(); transform->SetMatrix(imageTransform); transform->PostMultiply(); transform->Concatenate(translation); transform->Update(); cube->Update(); auto transformFilter = vtkSmartPointer::New(); transformFilter->SetInputData(cube->GetOutput()); transformFilter->SetTransform(transform); transformFilter->Update(); cube->Delete(); vtkSmartPointer polydata = transformFilter->GetPolyDataOutput(); if (polydata == nullptr) { localStorage->Actor->VisibilityOff(); return; } mitk::DoubleProperty::Pointer handleSizeProperty = dynamic_cast(this->GetDataNode()->GetProperty("Bounding Shape.Handle Size Factor")); ScalarType initialHandleSize; if (handleSizeProperty != nullptr) initialHandleSize = handleSizeProperty->GetValue(); else - initialHandleSize = 1.0 / 40.0; + initialHandleSize = 0.02; double handlesize = ((camera->GetDistance() * std::tan(vtkMath::RadiansFromDegrees(camera->GetViewAngle()))) / 2.0) * initialHandleSize; if (localStorage->PropAssembly->GetParts()->IsItemPresent(localStorage->HandleActor)) localStorage->PropAssembly->RemovePart(localStorage->HandleActor); if (localStorage->PropAssembly->GetParts()->IsItemPresent(localStorage->Actor)) localStorage->PropAssembly->RemovePart(localStorage->Actor); auto selectedhandlemapper = vtkSmartPointer::New(); auto appendPoly = vtkSmartPointer::New(); mitk::IntProperty::Pointer activeHandleId = dynamic_cast(dataNode->GetProperty("Bounding Shape.Active Handle ID")); int i = 0; for (auto &handle : localStorage->Handles) { Point3D handlecenter = m_Impl->HandlePropertyList[i].GetPosition(); handle->SetCenter(handlecenter[0], handlecenter[1], handlecenter[2]); - handle->SetRadius(handlesize); + handle->SetXLength(handlesize); + handle->SetYLength(handlesize); + handle->SetZLength(handlesize); handle->Update(); if (activeHandleId == nullptr) { appendPoly->AddInputConnection(handle->GetOutputPort()); } else { if (activeHandleId->GetValue() != m_Impl->HandlePropertyList[i].GetIndex()) { appendPoly->AddInputConnection(handle->GetOutputPort()); } else { selectedhandlemapper->SetInputData(handle->GetOutput()); localStorage->SelectedHandleActor->SetMapper(selectedhandlemapper); localStorage->SelectedHandleActor->GetProperty()->SetColor(0, 1, 0); localStorage->SelectedHandleActor->GetMapper()->SetInputDataObject(handle->GetOutput()); localStorage->PropAssembly->AddPart(localStorage->SelectedHandleActor); } } i++; } appendPoly->Update(); auto mapper = vtkSmartPointer::New(); mapper->SetInputData(polydata); auto handlemapper = vtkSmartPointer::New(); handlemapper->SetInputData(appendPoly->GetOutput()); localStorage->Actor->SetMapper(mapper); localStorage->Actor->GetMapper()->SetInputDataObject(polydata); - localStorage->Actor->GetProperty()->SetOpacity(0.3); - - mitk::ColorProperty::Pointer selectedColor = dynamic_cast(dataNode->GetProperty("color")); - if (selectedColor != nullptr) - { - mitk::Color color = selectedColor->GetColor(); - localStorage->Actor->GetProperty()->SetColor(color[0], color[1], color[2]); - } localStorage->HandleActor->SetMapper(handlemapper); if (activeHandleId == nullptr) { localStorage->HandleActor->GetProperty()->SetColor(1, 1, 1); } else { localStorage->HandleActor->GetProperty()->SetColor(1, 0, 0); } localStorage->HandleActor->GetMapper()->SetInputDataObject(appendPoly->GetOutput()); this->ApplyColorAndOpacityProperties(renderer, localStorage->Actor); this->ApplyBoundingShapeProperties(renderer, localStorage->Actor); - - this->ApplyColorAndOpacityProperties(renderer, localStorage->HandleActor); this->ApplyBoundingShapeProperties(renderer, localStorage->HandleActor); - - this->ApplyColorAndOpacityProperties(renderer, localStorage->SelectedHandleActor); this->ApplyBoundingShapeProperties(renderer, localStorage->SelectedHandleActor); // apply properties read from the PropertyList // this->ApplyProperties(localStorage->m_Actor, renderer); // this->ApplyProperties(localStorage->m_HandleActor, renderer); // this->ApplyProperties(localStorage->m_SelectedHandleActor, renderer); localStorage->Actor->VisibilityOn(); localStorage->HandleActor->VisibilityOn(); localStorage->SelectedHandleActor->VisibilityOn(); localStorage->PropAssembly->AddPart(localStorage->Actor); localStorage->PropAssembly->AddPart(localStorage->HandleActor); localStorage->PropAssembly->VisibilityOn(); localStorage->UpdateGenerateDataTime(); } } vtkProp *mitk::BoundingShapeVtkMapper3D::GetVtkProp(BaseRenderer *renderer) { return m_Impl->LocalStorageHandler.GetLocalStorage(renderer)->PropAssembly; } diff --git a/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.cpp b/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.cpp index e6ac3712c0..1e4f18c97f 100644 --- a/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.cpp +++ b/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.cpp @@ -1,59 +1,69 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCameraIntrinsicsProperty.h" namespace mitk { bool CameraIntrinsicsProperty::IsEqual(const BaseProperty& property) const { return this->m_Value->Equals(static_cast(property).m_Value.GetPointer()); } bool CameraIntrinsicsProperty::Assign(const BaseProperty& property) { this->m_Value = static_cast(property).m_Value; return true; } std::string CameraIntrinsicsProperty::GetValueAsString() const { std::stringstream myStr; myStr << GetValue(); return myStr.str(); } CameraIntrinsicsProperty::CameraIntrinsicsProperty() : BaseProperty() {} CameraIntrinsicsProperty::CameraIntrinsicsProperty(const CameraIntrinsicsProperty& other) : BaseProperty(other) { } CameraIntrinsicsProperty::CameraIntrinsicsProperty( mitk::CameraIntrinsics::Pointer value ) : BaseProperty(), m_Value( value ) {} itk::LightObject::Pointer CameraIntrinsicsProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } +bool CameraIntrinsicsProperty::ToJSON(nlohmann::json&) const +{ + return false; +} + +bool CameraIntrinsicsProperty::FromJSON(const nlohmann::json&) +{ + return false; +} + } // namespace mitk diff --git a/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.h b/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.h index c992948156..23d40b6c69 100644 --- a/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.h +++ b/Modules/CameraCalibration/mitkCameraIntrinsicsProperty.h @@ -1,73 +1,76 @@ /*============================================================================ 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 mitkCameraIntrinsicsProperty_h #define mitkCameraIntrinsicsProperty_h #include "mitkBaseProperty.h" #include "mitkCameraIntrinsics.h" namespace mitk { #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4522) #endif class MITKCAMERACALIBRATION_EXPORT CameraIntrinsicsProperty : public BaseProperty { public: typedef mitk::CameraIntrinsics::Pointer ValueType; mitkClassMacro(CameraIntrinsicsProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro1Param(CameraIntrinsicsProperty, mitk::CameraIntrinsics::Pointer); itkSetMacro(Value, mitk::CameraIntrinsics::Pointer ); itkGetConstMacro(Value, mitk::CameraIntrinsics::Pointer ); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; protected: mitk::CameraIntrinsics::Pointer m_Value; CameraIntrinsicsProperty(); CameraIntrinsicsProperty(const CameraIntrinsicsProperty&); CameraIntrinsicsProperty( mitk::CameraIntrinsics::Pointer value ); private: // purposely not implemented CameraIntrinsicsProperty& operator=(const CameraIntrinsicsProperty&); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty& property) const override; bool Assign(const BaseProperty& property) override; }; #ifdef _MSC_VER # pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx b/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx index dfd8c8383b..a744a463a1 100644 --- a/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx +++ b/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx @@ -1,396 +1,394 @@ /*============================================================================ 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. ============================================================================*/ /*========================================================================= * * Copyright Insight Software Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef __itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter_hxx #define __itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter_hxx #include "itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.h" #include "itkConstNeighborhoodIterator.h" #include "itkNeighborhood.h" #include "vnl/vnl_math.h" #include "itkMacro.h" #include "itkRescaleIntensityImageFilter.h" #include "itkMaskImageFilter.h" #include "itkLabelStatisticsImageFilter.h" #include "itkScalarConnectedComponentImageFilter.h" #include "itkRelabelComponentImageFilter.h" #include "itkCastImageFilter.h" #include namespace itk { namespace Statistics { template EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter() : m_NumberOfBinsPerAxis( itkGetStaticConstMacro( DefaultBinsPerAxis ) ), m_Min( NumericTraits::NonpositiveMin() ), m_Max( NumericTraits::max() ), m_MinDistance( NumericTraits::ZeroValue() ), m_MaxDistance( NumericTraits::max() ), m_InsidePixelValue( NumericTraits::OneValue() ) { this->SetNumberOfRequiredInputs( 1 ); this->SetNumberOfRequiredOutputs( 1 ); const unsigned int measurementVectorSize = 1; this->ProcessObject::SetNthOutput( 0, this->MakeOutput( 0 ) ); this->ProcessObject::SetNthOutput( 1, this->MakeOutput( 1 ) ); HistogramType *output = const_cast( this->GetOutput() ); output->SetMeasurementVectorSize( measurementVectorSize ); m_siMatrix = new double[m_NumberOfBinsPerAxis]; for(unsigned int i = 0; i < m_NumberOfBinsPerAxis; i++) { m_siMatrix[i] = 0; } this->m_LowerBound.SetSize( measurementVectorSize ); this->m_UpperBound.SetSize( measurementVectorSize ); this->m_LowerBound[0] = this->m_Min; this->m_LowerBound[1] = this->m_MinDistance; this->m_UpperBound[0] = this->m_Max; this->m_UpperBound[1] = this->m_MaxDistance; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetOffset( const OffsetType offset ) { OffsetVectorPointer offsetVector = OffsetVector::New(); offsetVector->push_back( offset ); this->SetOffsets( offsetVector ); MITK_WARN << "We now have " << this->GetOffsets()->size() << " offsets in matrixgenerator"; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::AddOffsets( const std::vector _offsets ) { OffsetVectorPointer offsetVector = OffsetVector::New(); - typename OffsetVector::ConstIterator offsets; - //MITK_WARN << "We have " << this->GetOffsets()->size() << " offsets!"; for( std::size_t i = 0; i < _offsets.size(); i++) { offsetVector->push_back(_offsets[i]); auto k = _offsets[i]; this->NormalizeOffsetDirection(k); offsetVector->push_back(k); } this->SetOffsets( offsetVector ); MITK_WARN << "We now have " << this->GetOffsets()->size() << " offsets in matrixgenerator"; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetInput( const ImageType *image ) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput( 0, const_cast( image ) ); } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetMaskImage( const ImageType *image ) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput( 1, const_cast( image ) ); } template const TImageType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetInput() const { if( this->GetNumberOfInputs() < 1 ) { return ITK_NULLPTR; } return static_cast( this->ProcessObject::GetInput( 0 ) ); } template const TImageType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetMaskImage() const { if( this->GetNumberOfInputs() < 2 ) { return ITK_NULLPTR; } return static_cast( this->ProcessObject::GetInput( 1 ) ); } template const typename EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter::HistogramType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetOutput() const { const HistogramType *output = static_cast( this->ProcessObject::GetOutput( 0 ) ); return output; } template double* EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetSiMatrix() const { return m_siMatrix; } template typename EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter::DataObjectPointer EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::MakeOutput( DataObjectPointerArraySizeType itkNotUsed( idx ) ) { return HistogramType::New().GetPointer(); } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GenerateData() { HistogramType *output = static_cast( this->ProcessObject::GetOutput( 0 ) ); const ImageType * inputImage = this->GetInput(); const ImageType * maskImage = this->GetMaskImage(); // First, create an appropriate histogram with the right number of bins // and mins and maxes correct for the image type. typename HistogramType::SizeType size( output->GetMeasurementVectorSize() ); size.Fill( this->m_NumberOfBinsPerAxis ); this->m_LowerBound[0] = this->m_Min; this->m_LowerBound[1] = this->m_MinDistance; this->m_UpperBound[0] = this->m_Max; this->m_UpperBound[1] = this->m_MaxDistance; output->Initialize( size, this->m_LowerBound, this->m_UpperBound ); MeasurementVectorType run( output->GetMeasurementVectorSize() ); typename HistogramType::IndexType hIndex; //Cast the image to a float image - with no respect to the incoming image //to prevent some non-templated itk issues typedef itk::Image FloatImageType; typedef itk::CastImageFilter CastFilterType; typename CastFilterType::Pointer caster = CastFilterType::New(); caster->SetInput(inputImage); caster->Update(); typename FloatImageType::Pointer floatImage = caster->GetOutput(); //Cast the mask to an unsigned short image - with no respect to the incomimg maskimage //to prevent some non-templated itk issues typedef unsigned short LabelPixelType; typedef itk::Image LabelImageType; typedef itk::CastImageFilter MaskCastFilterType; typename MaskCastFilterType::Pointer maskCaster = MaskCastFilterType::New(); maskCaster->SetInput(maskImage); maskCaster->Update(); //Set all values out of the mask to nans. typedef itk::MaskImageFilter< FloatImageType, LabelImageType, FloatImageType > MaskFilterType; typename MaskFilterType::Pointer maskFilter = MaskFilterType::New(); maskFilter->SetInput(floatImage); maskFilter->SetMaskImage(maskCaster->GetOutput()); maskFilter->SetOutsideValue( std::numeric_limits::quiet_NaN()); maskFilter->Update(); FloatImageType::Pointer floatImageMasked = maskFilter->GetOutput(); typedef ConstNeighborhoodIterator NeighborhoodIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill( 1 ); NeighborhoodIteratorType neighborIt( radius, inputImage, inputImage->GetRequestedRegion() ); for( neighborIt.GoToBegin(); !neighborIt.IsAtEnd(); ++neighborIt ) { const PixelType centerPixelIntensity = neighborIt.GetCenterPixel(); IndexType centerIndex = neighborIt.GetIndex(); FloatImageType::IndexType cIndex; cIndex[0] = centerIndex[0]; cIndex[1] = centerIndex[1]; cIndex[2] = centerIndex[2]; float centerFloatPixel = floatImageMasked->GetPixel(cIndex); int px = 0; PixelType sum = 0.0; bool canCalculate = true; typename OffsetVector::ConstIterator offsets; for( offsets = this->GetOffsets()->Begin(); offsets != this->GetOffsets()->End(); offsets++ ) { OffsetType offset = offsets.Value(); IndexType index; index = centerIndex + offset; if(!inputImage->GetRequestedRegion().IsInside(index)) { canCalculate = false; break; } PixelType offsetPixel = inputImage->GetPixel(index); FloatImageType::IndexType fIndex; fIndex[0] = index[0]; fIndex[1] = index[1]; fIndex[2] = index[2]; float floatPixel = floatImageMasked->GetPixel(fIndex); //We have a nan here if(floatPixel != floatPixel || centerFloatPixel!= centerFloatPixel) { canCalculate = false; break; } sum += offsetPixel; px++; } //If we have a nan in the neighbourhood, continue if(!canCalculate) continue; PixelType mean = sum / px; double si = std::abs(mean-centerPixelIntensity); run[0] = centerPixelIntensity; //Check for NaN and inf if(run[0] == run[0] && !std::isinf(std::abs(run[0]))) { output->GetIndex( run, hIndex ); output->IncreaseFrequencyOfIndex( hIndex, 1 ); m_siMatrix[hIndex[0]] += si; } //MITK_WARN << " -> In this round we added: " << centerIndex << " with value " << centerPixelIntensity << " and si = " << si; //MITK_WARN << " -> Values are now siMatrix["< Values are now niMatrix: " << output->GetFrequency(hIndex) << "/" << run; } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetPixelValueMinMax( PixelType min, PixelType max ) { if( this->m_Min != min || this->m_Max != max ) { itkDebugMacro( "setting Min to " << min << "and Max to " << max ); this->m_Min = min; this->m_Max = max; this->Modified(); } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetDistanceValueMinMax( RealType min, RealType max ) { if( this->m_MinDistance != min || this->m_MaxDistance != max ) { itkDebugMacro( "setting MinDistance to " << min << "and MaxDistance to " << max ); this->m_MinDistance = min; this->m_MaxDistance = max; this->Modified(); } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::PrintSelf( std::ostream& os, Indent indent ) const { Superclass::PrintSelf( os,indent ); os << indent << "Offsets: " << this->GetOffsets() << std::endl; os << indent << "Min: " << this->m_Min << std::endl; os << indent << "Max: " << this->m_Max << std::endl; os << indent << "Min distance: " << this->m_MinDistance << std::endl; os << indent << "Max distance: " << this->m_MaxDistance << std::endl; os << indent << "NumberOfBinsPerAxis: " << this->m_NumberOfBinsPerAxis << std::endl; os << indent << "InsidePixelValue: " << this->m_InsidePixelValue << std::endl; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::NormalizeOffsetDirection(OffsetType &offset) { //MITK_WARN <<" -> NGTDM old offset = " << offset; itkDebugMacro("old offset = " << offset << std::endl); int sign = 1; bool metLastNonZero = false; for (int i = offset.GetOffsetDimension()-1; i>=0; i--) { if (metLastNonZero) { offset[i] *= sign; } else if (offset[i] != 0) { sign = (offset[i] > 0 ) ? 1 : -1; metLastNonZero = true; offset[i] *= sign; } } //MITK_WARN << " ->NGTDM new offset = " << offset; itkDebugMacro("new offset = " << offset << std::endl); } } // end of namespace Statistics } // end of namespace itk #endif diff --git a/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp b/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp index 3b4130bc5c..026f64a6d2 100644 --- a/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp +++ b/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp @@ -1,515 +1,513 @@ /*============================================================================ 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 #include #include #include #include #include // ITK #include #include #include #include // VTK #include #include #include #include #include #include #include #include // STL #include #include // Eigen #include struct GIFVolumetricDensityStatisticsParameters { double volume; mitk::FeatureID id; }; template void CalculateVolumeDensityStatistic(const itk::Image* itkImage, const mitk::Image* mask, GIFVolumetricDensityStatisticsParameters params, mitk::GIFVolumetricDensityStatistics::FeatureListType & featureList) { typedef itk::Image ImageType; typedef itk::Image MaskType; double volume = params.volume; typename MaskType::Pointer maskImage = MaskType::New(); mitk::CastToItkImage(mask, maskImage); itk::ImageRegionConstIteratorWithIndex imgA(itkImage, itkImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex imgB(itkImage, itkImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex maskA(maskImage, maskImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex maskB(maskImage, maskImage->GetLargestPossibleRegion()); double moranA = 0; double moranB = 0; double geary = 0; double Nv = 0; double w_ij = 0; double mean = 0; typename ImageType::PointType pointA; typename ImageType::PointType pointB; while (!imgA.IsAtEnd()) { if (maskA.Get() > 0) { Nv += 1; mean += imgA.Get(); } ++imgA; ++maskA; } mean /= Nv; imgA.GoToBegin(); maskA.GoToBegin(); while (!imgA.IsAtEnd()) { if (maskA.Get() > 0) { imgB.GoToBegin(); maskB.GoToBegin(); while (!imgB.IsAtEnd()) { if ((imgA.GetIndex() == imgB.GetIndex()) || (maskB.Get() < 1)) { ++imgB; ++maskB; continue; } itkImage->TransformIndexToPhysicalPoint(maskA.GetIndex(), pointA); itkImage->TransformIndexToPhysicalPoint(maskB.GetIndex(), pointB); double w = 1 / pointA.EuclideanDistanceTo(pointB); moranA += w*(imgA.Get() - mean)* (imgB.Get() - mean); geary += w * (imgA.Get() - imgB.Get()) * (imgA.Get() - imgB.Get()); w_ij += w; ++imgB; ++maskB; } moranB += (imgA.Get() - mean)* (imgA.Get() - mean); } ++imgA; ++maskA; } MITK_INFO << "Volume: " << volume; MITK_INFO << " Mean: " << mean; featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume integrated intensity"), volume* mean)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume Moran's I index"), Nv / w_ij * moranA / moranB)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume Geary's C measure"), ( Nv -1 ) / 2 / w_ij * geary/ moranB)); } void calculateMOBB(vtkPointSet *pointset, double &volume, double &surface) { volume = std::numeric_limits::max(); for (int cellID = 0; cellID < pointset->GetNumberOfCells(); ++cellID) { auto cell = pointset->GetCell(cellID); for (int edgeID = 0; edgeID < 3; ++edgeID) { auto edge = cell->GetEdge(edgeID); double pA[3], pB[3]; double pAA[3], pBB[3]; vtkSmartPointer transform = vtkSmartPointer::New(); transform->PostMultiply(); pointset->GetPoint(edge->GetPointId(0), pA); pointset->GetPoint(edge->GetPointId(1), pB); double angleZ = std::atan2((- pA[2] + pB[2]) ,(pA[1] - pB[1])); angleZ *= 180 / vnl_math::pi; if (pA[2] == pB[2]) angleZ = 0; transform->RotateX(angleZ); transform->TransformPoint(pA, pAA); transform->TransformPoint(pB, pBB); double angleY = std::atan2((pAA[1] -pBB[1]) ,-(pAA[0] - pBB[0])); angleY *= 180 / vnl_math::pi; if (pAA[1] == pBB[1]) angleY = 0; transform->RotateZ(angleY); double p0[3]; pointset->GetPoint(edge->GetPointId(0), p0); double curMinX = std::numeric_limits::max(); double curMaxX = std::numeric_limits::lowest(); double curMinY = std::numeric_limits::max(); double curMaxY = std::numeric_limits::lowest(); double curMinZ = std::numeric_limits::max(); double curMaxZ = std::numeric_limits::lowest(); for (int pointID = 0; pointID < pointset->GetNumberOfPoints(); ++pointID) { double p[3]; double p2[3]; pointset->GetPoint(pointID, p); p[0] -= p0[0]; p[1] -= p0[1]; p[2] -= p0[2]; transform->TransformPoint(p, p2); curMinX = std::min(p2[0], curMinX); curMaxX = std::max(p2[0], curMaxX); curMinY = std::min(p2[1], curMinY); curMaxY = std::max(p2[1], curMaxY); curMinZ = std::min(p2[2], curMinZ); curMaxZ = std::max(p2[2], curMaxZ); } if ((curMaxX - curMinX)*(curMaxY - curMinY)*(curMaxZ - curMinZ) < volume) { volume = (curMaxX - curMinX)*(curMaxY - curMinY)*(curMaxZ - curMinZ); surface = (curMaxX - curMinX)*(curMaxX - curMinX) + (curMaxY - curMinY)*(curMaxY - curMinY) + (curMaxZ - curMinZ)*(curMaxZ - curMinZ); surface *= 2; } } } } void calculateMEE(vtkPointSet *pointset, double &vol, double &surf, double tolerance=0.0001) { // Inspired by https://github.com/smdabdoub/ProkaryMetrics/blob/master/calc/fitting.py int numberOfPoints = pointset->GetNumberOfPoints(); int dimension = 3; Eigen::MatrixXd points(3, numberOfPoints); Eigen::MatrixXd Q(3+1, numberOfPoints); double p[3]; std::cout << "Initialize Q " << std::endl; for (int i = 0; i < numberOfPoints; ++i) { pointset->GetPoint(i, p); points(0, i) = p[0]; points(1, i) = p[1]; points(2, i) = p[2]; Q(0, i) = p[0]; Q(1, i) = p[1]; Q(2, i) = p[2]; Q(3, i) = 1.0; } - int count = 1; double error = 1; Eigen::VectorXd u_vector(numberOfPoints); u_vector.fill(1.0 / numberOfPoints); Eigen::DiagonalMatrix u = u_vector.asDiagonal(); Eigen::VectorXd ones(dimension + 1); ones.fill(1); Eigen::MatrixXd Ones = ones.asDiagonal(); // Khachiyan Algorithm while (error > tolerance) { auto Qt = Q.transpose(); Eigen::MatrixXd X = Q*u*Qt; Eigen::FullPivHouseholderQR qr(X); Eigen::MatrixXd Xi = qr.solve(Ones); Eigen::MatrixXd M = Qt * Xi * Q; double maximumValue = M(0, 0); int maximumPosition = 0; for (int i = 0; i < numberOfPoints; ++i) { if (maximumValue < M(i, i)) { maximumValue = M(i, i); maximumPosition = i; } } double stepsize = (maximumValue - dimension - 1) / ((dimension + 1) * (maximumValue - 1)); Eigen::DiagonalMatrix new_u = (1.0 - stepsize) * u; new_u.diagonal()[maximumPosition] = (new_u.diagonal())(maximumPosition) + stepsize; - ++count; error = (new_u.diagonal() - u.diagonal()).norm(); u.diagonal() = new_u.diagonal(); } // U = u Eigen::MatrixXd Ai = points * u * points.transpose() - points * u *(points * u).transpose(); Eigen::FullPivHouseholderQR qr(Ai); Eigen::VectorXd ones2(dimension); ones2.fill(1); Eigen::MatrixXd Ones2 = ones2.asDiagonal(); Eigen::MatrixXd A = qr.solve(Ones2)*1.0/dimension; Eigen::JacobiSVD svd(A); double c = 1 / sqrt(svd.singularValues()[0]); double b = 1 / sqrt(svd.singularValues()[1]); double a = 1 / sqrt(svd.singularValues()[2]); double V = 4 * vnl_math::pi*a*b*c / 3; double ad_mvee= 0; double alpha = std::sqrt(1 - b*b / a / a); double beta = std::sqrt(1 - c*c / a / a); for (int i = 0; i < 20; ++i) { ad_mvee += 4 * vnl_math::pi*a*b*(alpha*alpha + beta*beta) / (2 * alpha*beta) * (std::pow(alpha*beta, i)) / (1 - 4 * i*i); } vol = V; surf = ad_mvee; } mitk::GIFVolumetricDensityStatistics::GIFVolumetricDensityStatistics() { SetLongName("volume-density"); SetShortName("volden"); SetFeatureClassName("Morphological Density"); } void mitk::GIFVolumetricDensityStatistics::AddArguments(mitkCommandLineParser& parser) const { std::string name = GetOptionPrefix(); parser.addArgument(GetLongName(), name, mitkCommandLineParser::Bool, "Use Volume-Density Statistic", "calculates volume density based features", us::Any()); } mitk::AbstractGlobalImageFeature::FeatureListType mitk::GIFVolumetricDensityStatistics::DoCalculateFeatures(const Image* image, const Image* mask) { FeatureListType featureList; if (image->GetDimension() < 3) { MITK_INFO << "Skipped calculating volumetric density features; only 3D images are supported ...."; return featureList; } MITK_INFO << "Start calculating volumetric density features ...."; vtkSmartPointer mesher = vtkSmartPointer::New(); vtkSmartPointer stats = vtkSmartPointer::New(); vtkSmartPointer stats2 = vtkSmartPointer::New(); auto nonconstVtkData = const_cast(mask->GetVtkImageData()); mesher->SetInputData(nonconstVtkData); mesher->SetValue(0, 0.5); stats->SetInputConnection(mesher->GetOutputPort()); stats->Update(); vtkSmartPointer delaunay = vtkSmartPointer< vtkDelaunay3D >::New(); delaunay->SetInputConnection(mesher->GetOutputPort()); delaunay->SetAlpha(0); delaunay->Update(); vtkSmartPointer geometryFilter = vtkSmartPointer::New(); geometryFilter->SetInputConnection(delaunay->GetOutputPort()); geometryFilter->Update(); stats2->SetInputConnection(geometryFilter->GetOutputPort()); stats2->Update(); double vol_mvee; double surf_mvee; calculateMEE(mesher->GetOutput(), vol_mvee, surf_mvee); double vol_mobb; double surf_mobb; calculateMOBB(geometryFilter->GetOutput(), vol_mobb, surf_mobb); double pi = vnl_math::pi; double meshVolume = stats->GetVolume(); double meshSurf = stats->GetSurfaceArea(); GIFVolumetricDensityStatisticsParameters params; params.volume = meshVolume; params.id = this->CreateTemplateFeatureID(); AccessByItk_3(image, CalculateVolumeDensityStatistic, mask, params, featureList); //Calculate center of mass shift int xx = mask->GetDimensions()[0]; int yy = mask->GetDimensions()[1]; int zz = mask->GetDimensions()[2]; double xd = mask->GetGeometry()->GetSpacing()[0]; double yd = mask->GetGeometry()->GetSpacing()[1]; double zd = mask->GetGeometry()->GetSpacing()[2]; int minimumX = xx; int maximumX = 0; int minimumY = yy; int maximumY = 0; int minimumZ = zz; int maximumZ = 0; vtkSmartPointer dataset1Arr = vtkSmartPointer::New(); vtkSmartPointer dataset2Arr = vtkSmartPointer::New(); vtkSmartPointer dataset3Arr = vtkSmartPointer::New(); dataset1Arr->SetNumberOfComponents(1); dataset2Arr->SetNumberOfComponents(1); dataset3Arr->SetNumberOfComponents(1); dataset1Arr->SetName("M1"); dataset2Arr->SetName("M2"); dataset3Arr->SetName("M3"); vtkSmartPointer dataset1ArrU = vtkSmartPointer::New(); vtkSmartPointer dataset2ArrU = vtkSmartPointer::New(); vtkSmartPointer dataset3ArrU = vtkSmartPointer::New(); dataset1ArrU->SetNumberOfComponents(1); dataset2ArrU->SetNumberOfComponents(1); dataset3ArrU->SetNumberOfComponents(1); dataset1ArrU->SetName("M1"); dataset2ArrU->SetName("M2"); dataset3ArrU->SetName("M3"); vtkSmartPointer points = vtkSmartPointer< vtkPoints >::New(); for (int x = 0; x < xx; x++) { for (int y = 0; y < yy; y++) { for (int z = 0; z < zz; z++) { itk::Image::IndexType index; index[0] = x; index[1] = y; index[2] = z; mitk::ScalarType pxImage; mitk::ScalarType pxMask; mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, image->GetChannelDescriptor().GetPixelType(), image, image->GetVolumeData(), index, pxImage, 0); mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, mask->GetChannelDescriptor().GetPixelType(), mask, mask->GetVolumeData(), index, pxMask, 0); //Check if voxel is contained in segmentation if (pxMask > 0) { minimumX = std::min(x, minimumX); minimumY = std::min(y, minimumY); minimumZ = std::min(z, minimumZ); maximumX = std::max(x, maximumX); maximumY = std::max(y, maximumY); maximumZ = std::max(z, maximumZ); points->InsertNextPoint(x * xd, y * yd, z * zd); if (pxImage == pxImage) { dataset1Arr->InsertNextValue(x * xd); dataset2Arr->InsertNextValue(y * yd); dataset3Arr->InsertNextValue(z * zd); } } } } } vtkSmartPointer datasetTable = vtkSmartPointer::New(); datasetTable->AddColumn(dataset1Arr); datasetTable->AddColumn(dataset2Arr); datasetTable->AddColumn(dataset3Arr); vtkSmartPointer pcaStatistics = vtkSmartPointer::New(); pcaStatistics->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, datasetTable); pcaStatistics->SetColumnStatus("M1", 1); pcaStatistics->SetColumnStatus("M2", 1); pcaStatistics->SetColumnStatus("M3", 1); pcaStatistics->RequestSelectedColumns(); pcaStatistics->SetDeriveOption(true); pcaStatistics->Update(); vtkSmartPointer eigenvalues = vtkSmartPointer::New(); pcaStatistics->GetEigenvalues(eigenvalues); std::vector eigen_val(3); eigen_val[2] = eigenvalues->GetValue(0); eigen_val[1] = eigenvalues->GetValue(1); eigen_val[0] = eigenvalues->GetValue(2); double major = 2 * sqrt(eigen_val[2]); double minor = 2 * sqrt(eigen_val[1]); double least = 2 * sqrt(eigen_val[0]); double alpha = std::sqrt(1 - minor * minor / major / major); double beta = std::sqrt(1 - least * least / major / major); double a = (maximumX - minimumX + 1) * xd; double b = (maximumY - minimumY + 1) * yd; double c = (maximumZ - minimumZ + 1) * zd; double vd_aabb = meshVolume / (a * b * c); double ad_aabb = meshSurf / (2 * a * b + 2 * a * c + 2 * b * c); double vd_aee = 3 * meshVolume / (4.0 * pi * major * minor * least); double ad_aee = 0; for (int i = 0; i < 20; ++i) { ad_aee += 4 * pi * major * minor * (alpha * alpha + beta * beta) / (2 * alpha * beta) * (std::pow(alpha * beta, i)) / (1 - 4 * i * i); } ad_aee = meshSurf / ad_aee; double vd_ch = meshVolume / stats2->GetVolume(); double ad_ch = meshSurf / stats2->GetSurfaceArea(); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density axis-aligned bounding box"), vd_aabb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density axis-aligned bounding box"), ad_aabb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density oriented minimum bounding box"), meshVolume / vol_mobb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density oriented minimum bounding box"), meshSurf / surf_mobb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density approx. enclosing ellipsoid"), vd_aee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density approx. enclosing ellipsoid"), ad_aee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density approx. minimum volume enclosing ellipsoid"), meshVolume / vol_mvee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density approx. minimum volume enclosing ellipsoid"), meshSurf / surf_mvee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density convex hull"), vd_ch)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density convex hull"), ad_ch)); MITK_INFO << "Finished calculating volumetric density features...."; return featureList; } mitk::AbstractGlobalImageFeature::FeatureListType mitk::GIFVolumetricDensityStatistics::CalculateFeatures(const Image* image, const Image* mask, const Image*) { return Superclass::CalculateFeatures(image, mask); } diff --git a/Modules/Core/CMakeLists.txt b/Modules/Core/CMakeLists.txt index 0ad7a726f5..74cada7281 100644 --- a/Modules/Core/CMakeLists.txt +++ b/Modules/Core/CMakeLists.txt @@ -1,74 +1,74 @@ set(TOOL_CPPS "") # temporary suppress warnings in the following files until image accessors are fully integrated. set_source_files_properties( src/DataManagement/mitkImage.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) set_source_files_properties( src/Controllers/mitkSliceNavigationController.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) mitk_create_module( INCLUDE_DIRS PUBLIC ${MITK_BINARY_DIR} PRIVATE src/Algorithms src/Controllers src/DataManagement src/Interactions src/IO src/Rendering DEPENDS PUBLIC MitkLog CppMicroServices PACKAGE_DEPENDS PUBLIC Boost + nlohmann_json ITK|IOImageBase+SpatialObjects+Statistics #ITK|Statistics+Transform VTK|FiltersTexture+FiltersParallel+ImagingStencil+ImagingMath+InteractionStyle+RenderingOpenGL2+RenderingVolumeOpenGL2+RenderingFreeType+RenderingLabel+InteractionWidgets+IOGeometry+IOImage+IOXML PRIVATE ITK|IOBioRad+IOBMP+IOBruker+IOCSV+IOGDCM+IOGE+IOGIPL+IOHDF5+IOIPL+IOJPEG+IOJPEG2000+IOLSM+IOMesh+IOMeta+IOMINC+IOMRC+IONIFTI+IONRRD+IOPNG+IOSiemens+IOSpatialObjects+IOStimulate+IOTIFF+IOTransformBase+IOTransformHDF5+IOTransformInsightLegacy+IOTransformMatlab+IOVTK+IOXML - nlohmann_json tinyxml2 ${optional_private_package_depends} # Do not automatically create CppMicroServices initialization code. # Because the VTK "auto-init" functionality injects file-local static # initialization code in every cpp file which includes a VTK header, # static initialization order becomes an issue again. For the Mitk # core library, we need to ensure that the VTK static initialization stuff # happens before the CppMicroServices initialization, since the latter # might already use VTK code which needs to access VTK object factories. # Hence, CppMicroServices initialization code is placed manually within # the mitkCoreActivator.cpp file. NO_INIT ) if(NOT TARGET ${MODULE_TARGET}) message(SEND_ERROR "Core target ${MODULE_TARGET} does not exist") endif() function(_itk_create_factory_register_manager) # In MITK_ITK_Config.cmake, we do *not* include ITK_USE_FILE, which # prevents multiple registrations/unregistrations of ITK IO factories # during library loading/unloading (of MITK libraries). However, we need # "one" place where the IO factories are registered at # least once. This could be the application executable, but every executable would # need to take care of that itself. Instead, we allow the auto registration in the # Mitk Core library. set(NO_DIRECTORY_SCOPED_ITK_COMPILE_DEFINITION 1) find_package(ITK) include(${ITK_USE_FILE}) if(NOT ITK_NO_IO_FACTORY_REGISTER_MANAGER) # We manually add the define which will be of target scope. MITK # patches ITK_USE_FILE to remove the directory scoped compile # definition since it would be propagated to other targets in the # same directory scope but these targets might want to *not* # use the ITK factory manager stuff. target_compile_definitions(${MODULE_TARGET} PRIVATE ITK_IO_FACTORY_REGISTER_MANAGER) endif() endfunction() _itk_create_factory_register_manager() if(BUILD_TESTING) add_subdirectory(TestingHelper) add_subdirectory(test) endif() diff --git a/Modules/Core/TestingHelper/src/mitkRenderingTestHelper.cpp b/Modules/Core/TestingHelper/src/mitkRenderingTestHelper.cpp index c3df456fad..fb77ebdb61 100644 --- a/Modules/Core/TestingHelper/src/mitkRenderingTestHelper.cpp +++ b/Modules/Core/TestingHelper/src/mitkRenderingTestHelper.cpp @@ -1,239 +1,240 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // VTK #include #include #include #include #include #include // MITK #include #include #include #include #include #include #include #include +#include // VTK Testing to compare the rendered image pixel-wise against a reference screen shot #include "vtkTesting.h" mitk::RenderingTestHelper::RenderingTestHelper(int width, int height, AntiAliasing antiAliasing) : m_AutomaticallyCloseRenderWindow(true) { this->Initialize(width, height, antiAliasing); } mitk::RenderingTestHelper::RenderingTestHelper( int width, int height, int argc, char *argv[], AntiAliasing antiAliasing) : m_AutomaticallyCloseRenderWindow(true) { this->Initialize(width, height, antiAliasing); this->SetInputFileNames(argc, argv); } void mitk::RenderingTestHelper::Initialize(int width, int height, AntiAliasing antiAliasing) { RenderingManager::GetInstance()->SetAntiAliasing(antiAliasing); mitk::UIDGenerator uidGen = mitk::UIDGenerator("UnnamedRenderer_"); m_RenderWindow = mitk::RenderWindow::New(nullptr, uidGen.GetUID().c_str()); auto renderWindow = m_RenderWindow->GetVtkRenderWindow(); if (0 == renderWindow->SupportsOpenGL()) { auto openGLRenderWindow = dynamic_cast(renderWindow); auto message = nullptr != openGLRenderWindow ? openGLRenderWindow->GetOpenGLSupportMessage() : std::string("No details available."); mitkThrowException(mitk::TestNotRunException) << "OpenGL not supported: " << message; } m_DataStorage = mitk::StandaloneDataStorage::New(); m_RenderWindow->GetRenderer()->SetDataStorage(m_DataStorage); this->SetMapperIDToRender2D(); this->GetVtkRenderWindow()->SetSize(width, height); m_RenderWindow->GetRenderer()->Resize(width, height); } mitk::RenderingTestHelper::~RenderingTestHelper() { } void mitk::RenderingTestHelper::SetMapperID(mitk::BaseRenderer::StandardMapperSlot id) { m_RenderWindow->GetRenderer()->SetMapperID(id); } void mitk::RenderingTestHelper::SetMapperIDToRender3D() { this->SetMapperID(mitk::BaseRenderer::Standard3D); mitk::RenderingManager::GetInstance()->InitializeViews( this->GetDataStorage()->ComputeBoundingGeometry3D(this->GetDataStorage()->GetAll())); } void mitk::RenderingTestHelper::SetMapperIDToRender2D() { this->SetMapperID(mitk::BaseRenderer::Standard2D); } void mitk::RenderingTestHelper::Render() { // if the datastorage is initialized and at least 1 image is loaded render it if (m_DataStorage.IsNotNull() && m_DataStorage->GetAll()->Size() >= 1) { // Prepare the VTK camera before rendering. m_RenderWindow->GetRenderer()->PrepareRender(); this->GetVtkRenderWindow()->Render(); this->GetVtkRenderWindow()->WaitForCompletion(); if (m_AutomaticallyCloseRenderWindow == false) { // Use interaction to stop the test this->GetVtkRenderWindow()->GetInteractor()->Start(); } } else { MITK_ERROR << "No images loaded in data storage!"; } } mitk::DataStorage::Pointer mitk::RenderingTestHelper::GetDataStorage() { return m_DataStorage; } void mitk::RenderingTestHelper::SetInputFileNames(int argc, char *argv[]) { // i is set 1, because 0 is the testname as string // parse parameters for (int i = 1; i < argc; ++i) { // add everything to a list but -T and -V std::string tmp = argv[i]; if ((tmp.compare("-T")) && (tmp.compare("-V"))) { this->AddToStorage(tmp); } else { break; } } } void mitk::RenderingTestHelper::SetViewDirection(mitk::AnatomicalPlane viewDirection) { mitk::BaseRenderer::GetInstance(m_RenderWindow->GetVtkRenderWindow()) ->GetSliceNavigationController() ->SetDefaultViewDirection(viewDirection); mitk::RenderingManager::GetInstance()->InitializeViews( m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll())); } void mitk::RenderingTestHelper::ReorientSlices(mitk::Point3D origin, mitk::Vector3D rotation) { mitk::SliceNavigationController::Pointer sliceNavigationController = mitk::BaseRenderer::GetInstance(m_RenderWindow->GetVtkRenderWindow())->GetSliceNavigationController(); sliceNavigationController->ReorientSlices(origin, rotation); } vtkRenderer *mitk::RenderingTestHelper::GetVtkRenderer() { return m_RenderWindow->GetRenderer()->GetVtkRenderer(); } void mitk::RenderingTestHelper::SetImageProperty(const char *propertyKey, mitk::BaseProperty *property) { this->m_DataStorage->GetNode(mitk::NodePredicateDataType::New("Image"))->SetProperty(propertyKey, property); } vtkRenderWindow *mitk::RenderingTestHelper::GetVtkRenderWindow() { return m_RenderWindow->GetVtkRenderWindow(); } bool mitk::RenderingTestHelper::CompareRenderWindowAgainstReference(int argc, char *argv[], double threshold) { this->Render(); // retVal meanings: (see VTK/Rendering/vtkTesting.h) // 0 = test failed // 1 = test passed // 2 = test not run // 3 = something with vtkInteraction if (vtkTesting::Test(argc, argv, this->GetVtkRenderWindow(), threshold) == 1) return true; else return false; } // method to save a screenshot of the renderwindow (e.g. create a reference screenshot) void mitk::RenderingTestHelper::SaveAsPNG(std::string fileName) { vtkSmartPointer renderer = this->GetVtkRenderer(); bool doubleBuffering(renderer->GetRenderWindow()->GetDoubleBuffer()); renderer->GetRenderWindow()->DoubleBufferOff(); vtkSmartPointer magnifier = vtkSmartPointer::New(); magnifier->SetInput(renderer); magnifier->SetMagnification(1); vtkSmartPointer fileWriter = vtkSmartPointer::New(); fileWriter->SetInputConnection(magnifier->GetOutputPort()); fileWriter->SetFileName(fileName.c_str()); fileWriter->Write(); renderer->GetRenderWindow()->SetDoubleBuffer(doubleBuffering); } void mitk::RenderingTestHelper::SetAutomaticallyCloseRenderWindow(bool automaticallyCloseRenderWindow) { m_AutomaticallyCloseRenderWindow = automaticallyCloseRenderWindow; } void mitk::RenderingTestHelper::SaveReferenceScreenShot(std::string fileName) { this->SaveAsPNG(fileName); } void mitk::RenderingTestHelper::AddToStorage(const std::string &filename) { try { mitk::IOUtil::Load(filename, *m_DataStorage.GetPointer()); mitk::RenderingManager::GetInstance()->InitializeViews( m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll())); } catch ( const itk::ExceptionObject &e ) { MITK_ERROR << "Failed loading test data '" << filename << "': " << e.what(); } } void mitk::RenderingTestHelper::AddNodeToStorage(mitk::DataNode::Pointer node) { this->m_DataStorage->Add(node); mitk::RenderingManager::GetInstance()->InitializeViews( m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll())); } diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index d8412455d1..1aa29e2075 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,329 +1,328 @@ 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/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/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp - DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp - DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable + DataManagement/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/mitkVector.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/mitkImageGenerator.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/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/mitkAnnotationProperty.h b/Modules/Core/include/mitkAnnotationProperty.h index 310a432405..9c03069090 100644 --- a/Modules/Core/include/mitkAnnotationProperty.h +++ b/Modules/Core/include/mitkAnnotationProperty.h @@ -1,79 +1,83 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkAnnotationProperty_h #define mitkAnnotationProperty_h #include "mitkBaseProperty.h" #include "mitkNumericTypes.h" #include #include #include namespace mitk { /** * \brief Property for annotations * \ingroup DataManagement */ class MITKCORE_EXPORT AnnotationProperty : public BaseProperty { public: mitkClassMacro(AnnotationProperty, BaseProperty); typedef std::string ValueType; itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro2Param(AnnotationProperty, const char *, const Point3D &); mitkNewMacro2Param(AnnotationProperty, const std::string &, const Point3D &); mitkNewMacro4Param(AnnotationProperty, const char *, ScalarType, ScalarType, ScalarType); mitkNewMacro4Param(AnnotationProperty, const std::string &, ScalarType, ScalarType, ScalarType); itkGetStringMacro(Label); itkSetStringMacro(Label); const Point3D &GetPosition() const; void SetPosition(const Point3D &position); std::string GetValueAsString() const override; + + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + virtual BaseProperty &operator=(const BaseProperty &other) { return Superclass::operator=(other); } using BaseProperty::operator=; protected: std::string m_Label; Point3D m_Position; AnnotationProperty(); AnnotationProperty(const char *label, const Point3D &position); AnnotationProperty(const std::string &label, const Point3D &position); AnnotationProperty(const char *label, ScalarType x, ScalarType y, ScalarType z); AnnotationProperty(const std::string &label, ScalarType x, ScalarType y, ScalarType z); AnnotationProperty(const AnnotationProperty &other); private: // purposely not implemented AnnotationProperty &operator=(const AnnotationProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; } // namespace mitk #endif diff --git a/Modules/Core/include/mitkBaseGeometry.h b/Modules/Core/include/mitkBaseGeometry.h index 4d00557d8d..6dff77d92a 100644 --- a/Modules/Core/include/mitkBaseGeometry.h +++ b/Modules/Core/include/mitkBaseGeometry.h @@ -1,763 +1,764 @@ /*============================================================================ 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 mitkBaseGeometry_h #define mitkBaseGeometry_h #include "mitkOperationActor.h" #include #include #include "itkScalableAffineTransform.h" #include "mitkNumericTypes.h" #include #include #include #include #include #include class vtkMatrix4x4; class vtkMatrixToLinearTransform; class vtkLinearTransform; namespace mitk { //##Documentation //## @brief Standard 3D-BoundingBox typedef //## //## Standard 3D-BoundingBox typedef to get rid of template arguments (3D, type). typedef itk::BoundingBox BoundingBox; //##Documentation //## @brief Standard typedef for time-bounds typedef itk::FixedArray TimeBounds; typedef itk::FixedArray FixedArrayType; //##Documentation //## @brief BaseGeometry Describes the geometry of a data object //## //## The class holds //## \li a bounding box which is axes-parallel in intrinsic coordinates //## (often integer indices of pixels), to be accessed by //## GetBoundingBox() //## \li a transform to convert intrinsic coordinates into a //## world-coordinate system with coordinates in millimeters //## and milliseconds (all are floating point values), to //## be accessed by GetIndexToWorldTransform() //## \li an origin and spacing to define the geometry //## //## BaseGeometry and its sub-classes allow converting between //## intrinsic coordinates (called index or unit coordinates) //## and world-coordinates (called world or mm coordinates), //## e.g. WorldToIndex. //## In case you need integer index coordinates, provide an //## mitk::Index3D (or itk::Index) as target variable to //## WorldToIndex, otherwise you will get a continuous index //## (floating point values). //## //## An important sub-class is SlicedGeometry3D, which describes //## data objects consisting of slices, e.g., objects of type Image. //## Conversions between world coordinates (in mm) and unit coordinates //## (e.g., pixels in the case of an Image) can be performed. //## //## For more information on related classes, see \ref Geometry. //## //## BaseGeometry instances referring to an Image need a slightly //## different definition of corners, see SetImageGeometry. This //## is usually automatically called by Image. //## //## BaseGeometry have to be initialized in the method GenerateOutputInformation() //## of BaseProcess (or CopyInformation/ UpdateOutputInformation of BaseData, //## if possible, e.g., by analyzing pic tags in Image) subclasses. See also //## itk::ProcessObject::GenerateOutputInformation(), //## itk::DataObject::CopyInformation() and //## itk::DataObject::UpdateOutputInformation(). //## //## At least, it can return the bounding box of the data object. //## //## The BaseGeometry class is an abstract class. The most simple implementation //## is the subclass Geometry3D. //## //## Rule: everything is in mm (ms) if not stated otherwise. //## @ingroup Geometry class MITKCORE_EXPORT BaseGeometry : public itk::Object, public OperationActor { public: mitkClassMacroItkParent(BaseGeometry, itk::Object); itkCloneMacro(Self); // ********************************** TypeDef ********************************** typedef GeometryTransformHolder::TransformType TransformType; typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::BoundsArrayType BoundsArrayType; typedef BoundingBoxType::Pointer BoundingBoxPointer; // ********************************** Origin, Spacing ********************************** //##Documentation //## @brief Get the origin, e.g. the upper-left corner of the plane const Point3D GetOrigin() const; //##Documentation //## @brief Set the origin, i.e. the upper-left corner of the plane //## void SetOrigin(const Point3D &origin); //##Documentation //## @brief Get the spacing (size of a pixel). //## const mitk::Vector3D GetSpacing() const; //##Documentation //## @brief Set the spacing (m_Spacing). //## //##The spacing is also changed in the IndexToWorldTransform. void SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing = false); //##Documentation //## @brief Get the origin as VnlVector //## //## \sa GetOrigin VnlVector GetOriginVnl() const; // ********************************** other functions ********************************** //##Documentation //## @brief Get the DICOM FrameOfReferenceID referring to the //## used world coordinate system itkGetConstMacro(FrameOfReferenceID, unsigned int); //##Documentation //## @brief Set the DICOM FrameOfReferenceID referring to the //## used world coordinate system itkSetMacro(FrameOfReferenceID, unsigned int); itkGetConstMacro(IndexToWorldTransformLastModified, unsigned long); //##Documentation //## @brief Overload of function Modified() to prohibit several calls of Modified() using the ModifiedLock class. //## //## For the use of Modified(), see class ModifiedLock. void Modified() const override; friend class ModifiedLock; //##Documentation //## @brief Is this BaseGeometry in a state that is valid? //## //## This function returns always true in the BaseGeometry class. Other implementations are possible in subclasses. virtual bool IsValid() const; // ********************************** Initialize ********************************** //##Documentation //## @brief Initialize the BaseGeometry void Initialize(); void InitializeGeometry(Self *newGeometry) const; // ********************************** Transformations Set/Get ********************************** //##Documentation //## @brief Get the transformation used to convert from index //## to world coordinates mitk::AffineTransform3D *GetIndexToWorldTransform(); //##Documentation //## @brief Get the transformation used to convert from index //## to world coordinates const mitk::AffineTransform3D *GetIndexToWorldTransform() const; //## @brief Set the transformation used to convert from index //## to world coordinates. The spacing of the new transform is //## copied to m_spacing. void SetIndexToWorldTransform(mitk::AffineTransform3D *transform); //##Documentation //## @brief Convenience method for setting the ITK transform //## (m_IndexToWorldTransform) via an vtkMatrix4x4.The spacing of //## the new transform is copied to m_spacing. //## \sa SetIndexToWorldTransform void SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4 *vtkmatrix); //## @brief Set the transformation used to convert from index //## to world coordinates.This function keeps the original spacing. void SetIndexToWorldTransformWithoutChangingSpacing(mitk::AffineTransform3D *transform); //##Documentation //## @brief Convenience method for setting the ITK transform //## (m_IndexToWorldTransform) via an vtkMatrix4x4. This function keeps the original spacing. //## \sa SetIndexToWorldTransform void SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkMatrix4x4 *vtkmatrix); //## Get the Vtk Matrix which describes the transform. vtkMatrix4x4 *GetVtkMatrix(); + const vtkMatrix4x4* GetVtkMatrix() const; //##Documentation //## @brief Get the m_IndexToWorldTransform as a vtkLinearTransform vtkLinearTransform *GetVtkTransform() const; //##Documentation //## @brief Set the transform to identity, the spacing to 1 and origin to 0 //## void SetIdentity(); // ********************************** Transformations ********************************** //##Documentation //## @brief Compose new IndexToWorldTransform with a given transform. //## //## This method composes m_IndexToWorldTransform with another transform, //## modifying self to be the composition of self and other. //## If the argument pre is true, then other is precomposed with self; //## that is, the resulting transformation consists of first applying //## other to the source, followed by self. If pre is false or omitted, //## then other is post-composed with self; that is the resulting //## transformation consists of first applying self to the source, //## followed by other. //## This method also changes m_spacing. void Compose(const TransformType *other, bool pre = false); //##Documentation //## @brief Compose new IndexToWorldTransform with a given vtkMatrix4x4. //## //## Converts the vtkMatrix4x4 into a itk-transform and calls the previous method. void Compose(const vtkMatrix4x4 *vtkmatrix, bool pre = false); //##Documentation //## @brief Translate the origin by a vector //## void Translate(const Vector3D &vector); //##Documentation //##@brief executes affine operations (translate, rotate, scale) void ExecuteOperation(Operation *operation) override; //##Documentation //## @brief Convert world coordinates (in mm) of a \em point to (continuous!) index coordinates //## \warning If you need (discrete) integer index coordinates (e.g., for iterating easily over an image), //## use WorldToIndex(const mitk::Point3D& pt_mm, itk::Index &index). //## For further information about coordinates types, please see the Geometry documentation void WorldToIndex(const mitk::Point3D &pt_mm, mitk::Point3D &pt_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## For further information about coordinates types, please see the Geometry documentation void WorldToIndex(const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em point to (discrete!) index coordinates. //## This method rounds to integer indices! //## For further information about coordinates types, please see the Geometry documentation template void WorldToIndex(const mitk::Point3D &pt_mm, itk::Index &index) const { typedef itk::Index IndexType; mitk::Point3D pt_units; this->WorldToIndex(pt_mm, pt_units); int i, dim = index.GetIndexDimension(); if (dim > 3) { index.Fill(0); dim = 3; } for (i = 0; i < dim; ++i) { index[i] = itk::Math::RoundHalfIntegerUp(pt_units[i]); } } //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation void IndexToWorld(const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em point to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation void IndexToWorld(const mitk::Point3D &pt_units, mitk::Point3D &pt_mm) const; //##Documentation //## @brief Convert (discrete) index coordinates of a \em point to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation template void IndexToWorld(const itk::Index &index, mitk::Point3D &pt_mm) const { mitk::Point3D pt_units; pt_units.Fill(0); int i, dim = index.GetIndexDimension(); if (dim > 3) { dim = 3; } for (i = 0; i < dim; ++i) { pt_units[i] = index[i]; } IndexToWorld(pt_units, pt_mm); } //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## @deprecated First parameter (Point3D) is not used. If possible, please use void IndexToWorld(const // mitk::Vector3D& vec_units, mitk::Vector3D& vec_mm) const. //## For further information about coordinates types, please see the Geometry documentation void IndexToWorld(const mitk::Point3D &atPt3d_units, const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## @deprecated First parameter (Point3D) is not used. If possible, please use void WorldToIndex(const // mitk::Vector3D& vec_mm, mitk::Vector3D& vec_units) const. //## For further information about coordinates types, please see the Geometry documentation void WorldToIndex(const mitk::Point3D &atPt3d_mm, const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const; //##Documentation //## @brief Deprecated for use with ITK version 3.10 or newer. //## Convert ITK physical coordinates of a \em point (in mm, //## but without a rotation) into MITK world coordinates (in mm) //## //## For more information, see WorldToItkPhysicalPoint. template void ItkPhysicalPointToWorld(const itk::Point &itkPhysicalPoint, mitk::Point3D &pt_mm) const { mitk::vtk2itk(itkPhysicalPoint, pt_mm); } //##Documentation //## @brief Deprecated for use with ITK version 3.10 or newer. //## Convert world coordinates (in mm) of a \em point to //## ITK physical coordinates (in mm, but without a possible rotation) //## //## This method is useful if you have want to access an mitk::Image //## via an itk::Image. ITK v3.8 and older did not support rotated (tilted) //## images, i.e., ITK images are always parallel to the coordinate axes. //## When accessing a (possibly rotated) mitk::Image via an itk::Image //## the rotational part of the transformation in the BaseGeometry is //## simply discarded; in other word: only the origin and spacing is //## used by ITK, not the complete matrix available in MITK. //## With WorldToItkPhysicalPoint you can convert an MITK world //## coordinate (including the rotation) into a coordinate that //## can be used with the ITK image as a ITK physical coordinate //## (excluding the rotation). template void WorldToItkPhysicalPoint(const mitk::Point3D &pt_mm, itk::Point &itkPhysicalPoint) const { mitk::vtk2itk(pt_mm, itkPhysicalPoint); } // ********************************** BoundingBox ********************************** /** Get the bounding box */ itkGetConstObjectMacro(BoundingBox, BoundingBoxType); // a bit of a misuse, but we want only doxygen to see the following: #ifdef DOXYGEN_SKIP //##Documentation //## @brief Get bounding box (in index/unit coordinates) itkGetConstObjectMacro(BoundingBox, BoundingBoxType); //##Documentation //## @brief Get bounding box (in index/unit coordinates) as a BoundsArrayType const BoundsArrayType GetBounds() const; #endif const BoundsArrayType GetBounds() const; //##Documentation //## \brief Set the bounding box (in index/unit coordinates) //## //## Only possible via the BoundsArray to make clear that a //## copy of the bounding-box is stored, not a reference to it. void SetBounds(const BoundsArrayType &bounds); //##Documentation //## @brief Set the bounding box (in index/unit coordinates) via a float array void SetFloatBounds(const float bounds[6]); //##Documentation //## @brief Set the bounding box (in index/unit coordinates) via a double array void SetFloatBounds(const double bounds[6]); //##Documentation //## @brief Get a VnlVector along bounding-box in the specified //## @a direction, length is spacing //## //## \sa GetAxisVector VnlVector GetMatrixColumn(unsigned int direction) const; //##Documentation //## @brief Calculates a bounding-box around the geometry relative //## to a coordinate system defined by a transform //## mitk::BoundingBox::Pointer CalculateBoundingBoxRelativeToTransform(const mitk::AffineTransform3D *transform) const; //##Documentation //## @brief Set the time bounds (in ms) // void SetTimeBounds(const TimeBounds& timebounds); // ********************************** Geometry ********************************** #ifdef DOXYGEN_SKIP //##Documentation //## @brief Get the extent of the bounding box (in index/unit coordinates) //## //## To access the extent in mm use GetExtentInMM ScalarType GetExtent(unsigned int direction) const; #endif /** Get the extent of the bounding box */ ScalarType GetExtent(unsigned int direction) const; //##Documentation //## @brief Get the extent of the bounding-box in the specified @a direction in mm //## //## Equals length of GetAxisVector(direction). ScalarType GetExtentInMM(int direction) const; //##Documentation //## @brief Get vector along bounding-box in the specified @a direction in mm //## //## The length of the vector is the size of the bounding-box in the //## specified @a direction in mm //## \sa GetMatrixColumn Vector3D GetAxisVector(unsigned int direction) const; //##Documentation //## @brief Checks, if the given geometry can be converted to 2D without information loss //## e.g. when a 2D image is saved, the matrix is usually cropped to 2x2, and when you load it back to MITK //## it will be filled with standard values. This function checks, if information would be lost during this //## procedure virtual bool Is2DConvertable(); //##Documentation //## @brief Get the center of the bounding-box in mm //## Point3D GetCenter() const; //##Documentation //## @brief Get the squared length of the diagonal of the bounding-box in mm //## double GetDiagonalLength2() const; //##Documentation //## @brief Get the length of the diagonal of the bounding-box in mm //## double GetDiagonalLength() const; //##Documentation //## @brief Get the position of the corner number \a id (in world coordinates) //## //## See SetImageGeometry for how a corner is defined on images. Point3D GetCornerPoint(int id) const; //##Documentation //## @brief Get the position of a corner (in world coordinates) //## //## See SetImageGeometry for how a corner is defined on images. Point3D GetCornerPoint(bool xFront = true, bool yFront = true, bool zFront = true) const; //##Documentation //## @brief Set the extent of the bounding-box in the specified @a direction in mm //## //## @note This changes the matrix in the transform, @a not the bounds, which are given in units! void SetExtentInMM(int direction, ScalarType extentInMM); //##Documentation //## @brief Test whether the point \a p (world coordinates in mm) is //## inside the bounding box bool IsInside(const mitk::Point3D &p) const; //##Documentation //## @brief Test whether the point \a p ((continuous!)index coordinates in units) is //## inside the bounding box bool IsIndexInside(const mitk::Point3D &index) const; //##Documentation //## @brief Convenience method for working with ITK indices template bool IsIndexInside(const itk::Index &index) const { int i, dim = index.GetIndexDimension(); Point3D pt_index; pt_index.Fill(0); for (i = 0; i < dim; ++i) { pt_index[i] = index[i]; } return IsIndexInside(pt_index); } // ********************************* Image Geometry ******************************** //##Documentation //## @brief When switching from an Image Geometry to a normal Geometry (and the other way around), you have to //change // the origin as well (See Geometry Documentation)! This function will change the "isImageGeometry" bool flag and // changes the origin respectively. virtual void ChangeImageGeometryConsideringOriginOffset(const bool isAnImageGeometry); //##Documentation //## @brief Is this an ImageGeometry? //## //## For more information, see SetImageGeometry itkGetConstMacro(ImageGeometry, bool) //##Documentation //## @brief Define that this BaseGeometry is referring to an Image //## //## A geometry referring to an Image needs a slightly different //## definition of the position of the corners (see GetCornerPoint). //## The position of a voxel is defined by the position of its center. //## If we would use the origin (position of the (center of) the first //## voxel) as a corner and display this point, it would seem to be //## \em not at the corner but a bit within the image. Even worse for //## the opposite corner of the image: here the corner would appear //## outside the image (by half of the voxel diameter). Thus, we have //## to correct for this and to be able to do that, we need to know //## that the BaseGeometry is referring to an Image. itkSetMacro(ImageGeometry, bool); itkBooleanMacro(ImageGeometry); const GeometryTransformHolder *GetGeometryTransformHolder() const; //##Documentation //## @brief One to one mapping of axes to world orientations. //## //## The result is stored in the output argument that must be an array of three int values. //## The elements of the array will be the axis indices that correspond to the sagittal, //## coronal and axial orientations, in this order. It is guaranteed that each axis will //## be mapped to different orientations. //## //## @param axes Output argument that will store the axis indices for each orientation. void MapAxesToOrientations(int axes[]) const; protected: // ********************************** Constructor ********************************** BaseGeometry(); BaseGeometry(const BaseGeometry &other); ~BaseGeometry() override; itk::LightObject::Pointer InternalClone() const override = 0; void PrintSelf(std::ostream &os, itk::Indent indent) const override; static const std::string GetTransformAsString(TransformType *transformType); itkGetConstMacro(NDimensions, unsigned int); bool IsBoundingBoxNull() const; bool IsIndexToWorldTransformNull() const; void SetVtkMatrixDeepCopy(vtkTransform *vtktransform); void _SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing = false); //##Documentation //## @brief PreSetSpacing //## //## These virtual function allows a different beahiour in subclasses. //## Do implement them in every subclass of BaseGeometry. If not needed, use //## {Superclass::PreSetSpacing();}; virtual void PreSetSpacing(const mitk::Vector3D & /*aSpacing*/){}; //##Documentation //## @brief CheckBounds //## //## This function is called in SetBounds. Assertions can be implemented in this function (see PlaneGeometry.cpp). //## If you implement this function in a subclass, make sure, that all classes were your class inherits from //## have an implementation of CheckBounds //## (e.g. inheritance BaseGeometry <- A <- B. Implementation of CheckBounds in class B needs implementation in A as // well!) virtual void CheckBounds(const BoundsArrayType & /*bounds*/){}; //##Documentation //## @brief CheckIndexToWorldTransform //## //## This function is called in SetIndexToWorldTransform. Assertions can be implemented in this function (see // PlaneGeometry.cpp). //## In Subclasses of BaseGeometry, implement own conditions or call Superclass::CheckBounds(bounds);. virtual void CheckIndexToWorldTransform(mitk::AffineTransform3D * /*transform*/){}; private: GeometryTransformHolder *m_GeometryTransform; void InitializeGeometryTransformHolder(const BaseGeometry *otherGeometry); //##Documentation //## @brief Bounding Box, which is axes-parallel in intrinsic coordinates //## (often integer indices of pixels) BoundingBoxPointer m_BoundingBox; unsigned int m_FrameOfReferenceID; // mitk::TimeBounds m_TimeBounds; static const unsigned int m_NDimensions = 3; mutable TransformType::Pointer m_InvertedTransform; mutable unsigned long m_IndexToWorldTransformLastModified; bool m_ImageGeometry; //##Documentation //## @brief ModifiedLockFlag is used to prohibit the call of Modified() //## //## For the use of this Flag, see class ModifiedLock. This flag should only be set //## by the ModifiedLock class! bool m_ModifiedLockFlag; //##Documentation //## @brief ModifiedcalledFlag is used to collect calls of Modified(). //## //## For the use of this Flag, see class ModifiedLock. This flag should only be set //## by the Modified() function! mutable bool m_ModifiedCalledFlag; }; // ********************************** Equal Functions ********************************** // // Static compare functions mainly for testing // /** * @brief Equal A function comparing two geometries for being identical. * * @ingroup MITKTestingAPI * * The function compares the spacing, origin, axisvectors, extents, the matrix of the * IndexToWorldTransform (elementwise), the bounding (elementwise) and the ImageGeometry flag. * * The parameter eps is a tolarence value for all methods which are internally used for comparison. * If you want to use different tolerance values for different parts of the geometry, feel free to use * the other comparison methods and write your own implementation of Equal. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param coordinateEps Tolerance for comparison of all spatial aspects (spacing, origin and grid alignment). * You can use mitk::eps in most cases. * @param directionEps Tolerance for comparison of all directional aspects (axis). You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITKCORE_EXPORT bool Equal(const mitk::BaseGeometry& leftHandSide, const mitk::BaseGeometry& rightHandSide, ScalarType coordinateEps, ScalarType directionEps, bool verbose = false); /** * @brief Equal A function comparing two geometries for being identical. * * @ingroup MITKTestingAPI * * This is an overloaded version that uses a single tolerance for spatial and directional aspects. For more details, * see the other overloaded version. * * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITKCORE_EXPORT bool Equal(const mitk::BaseGeometry &leftHandSide, const mitk::BaseGeometry &rightHandSide, ScalarType eps = mitk::eps, bool verbose = false); /** * @brief Equal A function comparing two transforms (TransformType) for being identical. * * @ingroup MITKTestingAPI * * The function compares the IndexToWorldTransform (elementwise). * * The parameter eps is a tolarence value for all methods which are internally used for comparison. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITKCORE_EXPORT bool Equal(const mitk::BaseGeometry::TransformType &leftHandSide, const mitk::BaseGeometry::TransformType &rightHandSide, ScalarType eps, bool verbose); /** * @brief Equal A function comparing two bounding boxes (BoundingBoxType) for being identical. * * @ingroup MITKTestingAPI * * The function compares the bounds (elementwise). * * The parameter eps is a tolarence value for all methods which are internally used for comparison. * @param rightHandSide Compare this against leftHandSide. * @param leftHandSide Compare this against rightHandSide. * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False in any other case. */ MITKCORE_EXPORT bool Equal(const mitk::BaseGeometry::BoundingBoxType &leftHandSide, const mitk::BaseGeometry::BoundingBoxType &rightHandSide, ScalarType eps, bool verbose); /** * @brief A function checks if a test geometry is a sub geometry of * a given reference geometry. * * Sub geometry means that both geometries have the same voxel grid (same spacing, same axes, * origin is on voxel grid), but the bounding box of the checked geometry is contained or equal * to the bounding box of the reference geometry.\n * By this definition equal geometries are always sub geometries of each other. * * The function checks the spacing, origin, axis vectors, extents, the matrix of the * IndexToWorldTransform (elementwise), the bounding (elementwise) and the ImageGeometry flag. * * The parameter eps is a tolerance value for all methods which are internally used for comparison. * @param testGeo Geometry that should be checked if it is a sub geometry of referenceGeo. * @param referenceGeo Geometry that should contain testedGeometry as sub geometry. * @param coordinateEps Tolerance for comparison of all spatial aspects (spacing, origin and grid alignment). * You can use mitk::eps in most cases. * @param directionEps Tolerance for comparison of all directional aspects (axis). You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparisons are true. False otherwise. */ MITKCORE_EXPORT bool IsSubGeometry(const mitk::BaseGeometry& testGeo, const mitk::BaseGeometry& referenceGeo, ScalarType coordinateEps, ScalarType directionEps, bool verbose = false); /** * @brief A function checks if a test geometry is a sub geometry of * a given reference geometry. * * This is a overloaded version that uses a single tolerance for spatial and directional aspects. For more details, * see the other overloaded version. * * @param testGeo Geometry that should be checked if it is a sub geometry of referenceGeo. * @param referenceGeo Geometry that should contain testedGeometry as sub geometry. * @param eps Tolarence for comparison (both spatial and directional). You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return True, if all comparison are true. False otherwise. */ MITKCORE_EXPORT bool IsSubGeometry(const mitk::BaseGeometry& testGeo, const mitk::BaseGeometry& referenceGeo, ScalarType eps = mitk::eps, bool verbose = false); } // namespace mitk #endif diff --git a/Modules/Core/include/mitkBaseProperty.h b/Modules/Core/include/mitkBaseProperty.h index 05a305dc50..f99c775f5d 100644 --- a/Modules/Core/include/mitkBaseProperty.h +++ b/Modules/Core/include/mitkBaseProperty.h @@ -1,98 +1,115 @@ /*============================================================================ 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 mitkBaseProperty_h #define mitkBaseProperty_h #include #include #include #include +#include namespace mitk { /*! \brief Abstract base class for properties \ingroup DataManagement Base class for properties. Properties are arbitrary additional information (to define a new type of information you have to define a subclass of BaseProperty) that can be added to a PropertyList. Concrete subclasses of BaseProperty should define Set-/Get-methods to assess the property value, which should be stored by value (not by reference). Subclasses must implement an operator==(const BaseProperty& property), which is used by PropertyList to check whether a property has been changed. */ class MITKCORE_EXPORT BaseProperty : public itk::Object { public: mitkClassMacroItkParent(BaseProperty, itk::Object); itkCloneMacro(Self); /*! @brief Subclasses must implement IsEqual(const BaseProperty&) to support comparison. operator== which is used by PropertyList to check whether a property has been changed. */ bool operator==(const BaseProperty &property) const; /*! @brief Assigns property to this BaseProperty instance. Subclasses must implement Assign(const BaseProperty&) and call the superclass Assign method for proper handling of polymorphic assignments. The assignment operator of the subclass should be disabled and the baseclass operator should be made visible using "using" statements. */ BaseProperty &operator=(const BaseProperty &property); /*! @brief Assigns property to this BaseProperty instance. This method is identical to the assignment operator, except for the return type. It allows to directly check if the assignment was successful. */ bool AssignProperty(const BaseProperty &property); virtual std::string GetValueAsString() const; + /** \brief Serialize property value(s) to JSON. + * + * Rely on exceptions for error handling when implementing serialization. + * + * \return False if not serializable by design, true otherwise. + */ + virtual bool ToJSON(nlohmann::json& j) const = 0; + + /** \brief Deserialize property value(s) from JSON. + * + * Rely on exceptions for error handling when implementing deserialization. + * + * \return False if not deserializable by design, true otherwise. + */ + virtual bool FromJSON(const nlohmann::json& j) = 0; + /** * @brief Default return value if a property which can not be returned as string */ static const std::string VALUE_CANNOT_BE_CONVERTED_TO_STRING; protected: BaseProperty(); BaseProperty(const BaseProperty &other); ~BaseProperty() override; private: /*! Override this method in subclasses to implement a meaningful comparison. The property argument is guaranteed to be castable to the type of the implementing subclass. */ virtual bool IsEqual(const BaseProperty &property) const = 0; /*! Override this method in subclasses to implement a meaningful assignment. The property argument is guaranteed to be castable to the type of the implementing subclass. @warning This is not yet exception aware/safe and if this method returns false, this property's state might be undefined. @return True if the argument could be assigned to this property. */ virtual bool Assign(const BaseProperty &) = 0; }; } // namespace mitk #endif diff --git a/Modules/Core/include/mitkClippingProperty.h b/Modules/Core/include/mitkClippingProperty.h index 87bdc056b1..9e3f532572 100644 --- a/Modules/Core/include/mitkClippingProperty.h +++ b/Modules/Core/include/mitkClippingProperty.h @@ -1,87 +1,90 @@ /*============================================================================ 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 mitkClippingProperty_h #define mitkClippingProperty_h #include "mitkBaseProperty.h" #include "mitkNumericTypes.h" #include #include #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * \brief Property for clipping datasets; currently only * clipping planes are possible * \ingroup DataManagement */ class MITKCORE_EXPORT ClippingProperty : public BaseProperty { public: mitkClassMacro(ClippingProperty, BaseProperty); typedef std::string ValueType; itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro2Param(ClippingProperty, const Point3D &, const Vector3D &); bool GetClippingEnabled() const; void SetClippingEnabled(bool enabled); const Point3D &GetOrigin() const; void SetOrigin(const Point3D &origin); const Vector3D &GetNormal() const; void SetNormal(const Vector3D &normal); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; protected: bool m_ClippingEnabled; Point3D m_Origin; Vector3D m_Normal; ClippingProperty(); ClippingProperty(const ClippingProperty &other); ClippingProperty(const Point3D &origin, const Vector3D &normal); private: // purposely not implemented ClippingProperty &operator=(const ClippingProperty &); bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; itk::LightObject::Pointer InternalClone() const override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkColorProperty.h b/Modules/Core/include/mitkColorProperty.h index 0b1809c0fb..8b31242324 100644 --- a/Modules/Core/include/mitkColorProperty.h +++ b/Modules/Core/include/mitkColorProperty.h @@ -1,96 +1,121 @@ /*============================================================================ 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 mitkColorProperty_h #define mitkColorProperty_h -#include "mitkBaseProperty.h" +#include #include + #include +#include + namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief Color Standard RGB color typedef (float) * * Standard RGB color typedef to get rid of template argument (float). * Color range is from 0.0f to 1.0f for each component. * * @ingroup Property */ typedef itk::RGBPixel Color; /** * @brief The ColorProperty class RGB color property * @ingroup DataManagement * * @note If you want to apply the mitk::ColorProperty to an mitk::Image * make sure to set the mitk::RenderingModeProperty to a mode which * supports color (e.g. LEVELWINDOW_COLOR). For an example how to use * the mitk::ColorProperty see mitkImageVtkMapper2DColorTest.cpp in * Core/Code/Rendering. */ class MITKCORE_EXPORT ColorProperty : public BaseProperty { protected: mitk::Color m_Color; ColorProperty(); ColorProperty(const ColorProperty &other); ColorProperty(const float red, const float green, const float blue); ColorProperty(const float color[3]); ColorProperty(const mitk::Color &color); public: mitkClassMacro(ColorProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro1Param(ColorProperty, const float *); mitkNewMacro1Param(ColorProperty, const mitk::Color &); mitkNewMacro3Param(ColorProperty, const float, const float, const float); typedef mitk::Color ValueType; const mitk::Color &GetColor() const; const mitk::Color &GetValue() const; std::string GetValueAsString() const override; void SetColor(const mitk::Color &color); void SetValue(const mitk::Color &color); void SetColor(float red, float green, float blue); + bool ToJSON(nlohmann::json &j) const override; + bool FromJSON(const nlohmann::json &j) override; + using BaseProperty::operator=; private: // purposely not implemented ColorProperty &operator=(const ColorProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk +namespace itk +{ + template + void to_json(nlohmann::json& j, const RGBPixel& c) + { + j = nlohmann::json::array(); + + for (size_t i = 0; i < 3; ++i) + j.push_back(c[i]); + } + + template + void from_json(const nlohmann::json& j, RGBPixel& c) + { + for (size_t i = 0; i < 3; ++i) + j.at(i).get_to(c[i]); + } +} // namespace itk + #endif diff --git a/Modules/Core/include/mitkCoreServices.h b/Modules/Core/include/mitkCoreServices.h index ce30201441..f02e895e09 100644 --- a/Modules/Core/include/mitkCoreServices.h +++ b/Modules/Core/include/mitkCoreServices.h @@ -1,187 +1,195 @@ /*============================================================================ 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 mitkCoreServices_h #define mitkCoreServices_h #include "MitkCoreExports.h" #include #include #include #include #include #include #include namespace mitk { struct IMimeTypeProvider; class IPropertyAliases; class IPropertyDescriptions; + class IPropertyDeserialization; class IPropertyExtensions; class IPropertyFilters; class IPropertyPersistence; class IPropertyRelations; class IPreferencesService; /** * @brief Access MITK core services. * * This class can be used to conveniently access common * MITK Core service objects. Some getter methods where implementations * exist in the core library are guaranteed to return a non-nullptr service object. * * To ensure that CoreServices::Unget() is called after the caller * has finished using a service object, you should use the CoreServicePointer * helper class which calls Unget() when it goes out of scope: * * \code * CoreServicePointer shaderRepo(CoreServices::GetShaderRepository()); * // Do something with shaderRepo * \endcode * * @see CoreServicePointer */ class MITKCORE_EXPORT CoreServices { public: /** * @brief Get an IPropertyAliases instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyAliases instance. */ static IPropertyAliases *GetPropertyAliases(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IPropertyDescriptions instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyDescriptions instance. */ static IPropertyDescriptions *GetPropertyDescriptions(us::ModuleContext *context = us::GetModuleContext()); + /** + * @brief Get an IPropertyDeserialization instance. + * @param context The module context of the module getting the service. + * @return A non-nullptr IPropertyDeserialization instance. + */ + static IPropertyDeserialization* GetPropertyDeserialization(us::ModuleContext* context = us::GetModuleContext()); + /** * @brief Get an IPropertyExtensions instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyExtensions instance. */ static IPropertyExtensions *GetPropertyExtensions(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IPropertyFilters instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyFilters instance. */ static IPropertyFilters *GetPropertyFilters(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IPropertyPersistence instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyPersistence instance. */ static IPropertyPersistence *GetPropertyPersistence(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IPropertyRelations instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPropertyRelations instance. */ static IPropertyRelations *GetPropertyRelations(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IMimeTypeProvider instance. * @param context The module context of the module getting the service. * @return A non-nullptr IMimeTypeProvider instance. */ static IMimeTypeProvider *GetMimeTypeProvider(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Get an IPreferencesService instance. * @param context The module context of the module getting the service. * @return A non-nullptr IPreferencesService instance. * @sa IPreferences */ static IPreferencesService *GetPreferencesService(us::ModuleContext *context = us::GetModuleContext()); /** * @brief Unget a previously acquired service instance. * @param service The service instance to be released. * @param context * @return \c true if ungetting the service was successful, \c false otherwise. */ template static bool Unget(S *service, us::ModuleContext *context = us::GetModuleContext()) { return Unget(context, us_service_interface_iid(), service); } private: static bool Unget(us::ModuleContext *context, const std::string &interfaceId, void *service); // purposely not implemented CoreServices(); CoreServices(const CoreServices &); CoreServices &operator=(const CoreServices &); }; /** * @brief A RAII helper class for core service objects. * * This is class is intended for usage in local scopes; it calls * CoreServices::Unget(S*) in its destructor. You should not construct * multiple CoreServicePointer instances using the same service pointer, * unless it is retrieved by a new call to a CoreServices getter method. * * @see CoreServices */ template class MITK_LOCAL CoreServicePointer { public: explicit CoreServicePointer(S *service, us::ModuleContext* context = us::GetModuleContext()) : m_Service(service), m_Context(context) { assert(service); } ~CoreServicePointer() { try { CoreServices::Unget(m_Service, m_Context); } catch (const std::exception &e) { MITK_ERROR << e.what(); } catch (...) { MITK_ERROR << "Ungetting core service failed."; } } S *operator->() const { return m_Service; } private: S *const m_Service; us::ModuleContext* m_Context; }; } #endif diff --git a/Modules/Core/include/mitkEnumerationProperty.h b/Modules/Core/include/mitkEnumerationProperty.h index 251f1cce82..7771762a5b 100644 --- a/Modules/Core/include/mitkEnumerationProperty.h +++ b/Modules/Core/include/mitkEnumerationProperty.h @@ -1,198 +1,208 @@ /*============================================================================ 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 mitkEnumerationProperty_h #define mitkEnumerationProperty_h #include #include #include #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4522) // "multiple assignment operators specified" #endif namespace mitk { /** * This class may be used to store properties similar to enumeration values. * Each enumeration value is identified by an id and a name. Note that both * name and id must be unique. Add enumeration values before you use the * Get/SetValue methods. * * To use this class, create a subclass that adds the possible enumeration * values in its constructor. You should override AddEnum() as protected so * that the user isn't able to add invalid enumeration values. * * As example see mitk::VtkRepresentationProperty or * mitk::VtkInterpolationProperty. * * @ingroup DataManagement */ class MITKCORE_EXPORT EnumerationProperty : public BaseProperty { public: mitkClassMacro(EnumerationProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** * Represents the unique id which is assigned to each enumeration name. */ typedef unsigned int IdType; /** * Type used to store a mapping from enumeration id to enumeration name. */ typedef std::map EnumIdsContainerType; /** * Type used to store a mapping from enumeration name to enumeration id. */ typedef std::map EnumStringsContainerType; /** * Type used for iterators over all defined enumeration values. */ typedef EnumIdsContainerType::const_iterator EnumConstIterator; /** * Adds an enumeration value into the enumeration. The name and id provided * must be unique. This is checked while adding the new enumeration value. * If it is not unique, false is returned. If addition was successful, true * is returned. * @param name The unique name of the enumeration value * @param id The unique id of the enumeration value * @returns True, if the name/id combination was successfully added to the * enumeration values. Otherwise false. */ virtual bool AddEnum(const std::string &name, const IdType &id); /** * Sets the current value of the enumeration. * @param name The name of the enumeration value to set * @returns True if the value was successfully set. Otherwise false. */ virtual bool SetValue(const std::string &name); /** * Sets the current value of the enumeration. * @param id The id of the enumeration value to set * @returns True, if the value was successfully set. Otherwise false. */ virtual bool SetValue(const IdType &id); /** * Returns the id of the current enumeration value. If it was not set so far, * the return value is unspecified. */ virtual IdType GetValueAsId() const; /** * Returns the name of the current enumeration value. If it was not set so far, * the return value is unspecified. */ std::string GetValueAsString() const override; /** * Clears all enumerations including the current one. */ virtual void Clear(); /** * Determines the number of enumeration values. */ virtual EnumIdsContainerType::size_type Size() const; /** * Provides access to the set of enumeration values. The name can be * accessed with iterator->second, the id via iterator->first. * @returns An iterator over all enumeration values. */ virtual EnumConstIterator Begin() const; /** * Specifies the end of the range of enumeration values. * @returns An iterator pointing past the last enumeration values. */ virtual EnumConstIterator End() const; /** * Returns the name for the given id. * @param id The id for which the name should be determined. * If the id is invalid, the return value is unspecified. * @returns The name of the determined enumeration value. */ virtual std::string GetEnumString(const IdType &id) const; /** * Returns the id for the given name. * @param name The enumeration name for which the id should be determined. * If the name is invalid, the return value is unspecified. * @returns The id of the determined enumeration value. */ virtual IdType GetEnumId(const std::string &name) const; /** * Determines if a given id is valid. * @param id The id to check * @returns True if the given id is valid. Otherwise false. */ virtual bool IsValidEnumerationValue(const IdType &id) const; /** * Determines if a given name is valid. * @param name The name to check * @returns True if the given name is valid. Otherwise false. */ virtual bool IsValidEnumerationValue(const std::string &name) const; const EnumIdsContainerType &GetEnumIds() const; const EnumStringsContainerType &GetEnumStrings() const; EnumIdsContainerType &GetEnumIds(); EnumStringsContainerType &GetEnumStrings(); + /** + * Serializes the property to JSON. + * @note Classes deriving from EnumerationProperty are covered by this implementation and do not + * need to override this method again. + */ + bool ToJSON(nlohmann::json& j) const override; + + /** + * Deserializes the property to JSON. + * @note Classes deriving from EnumerationProperty are covered by this implementation and do not + * need to override this method again. + */ + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; EnumerationProperty & operator=(const EnumerationProperty &) = delete; protected: /** * Default constructor. The current value of the enumeration is undefined. */ EnumerationProperty(); EnumerationProperty(const EnumerationProperty &); bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; mitkCloneMacro(Self); private: IdType m_CurrentValue; - - typedef std::map IdMapForClassNameContainerType; - typedef std::map StringMapForClassNameContainerType; - EnumIdsContainerType m_IdMap; EnumStringsContainerType m_NameMap; }; } #ifdef _MSC_VER # pragma warning(pop) #endif #endif diff --git a/Modules/Core/include/mitkGenericLookupTable.h b/Modules/Core/include/mitkGenericLookupTable.h index c0ad940072..a450e3b125 100644 --- a/Modules/Core/include/mitkGenericLookupTable.h +++ b/Modules/Core/include/mitkGenericLookupTable.h @@ -1,138 +1,158 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkGenericLookupTable_h #define mitkGenericLookupTable_h #include #include #include #include #include #include "mitkNumericTypes.h" #include +#include + namespace mitk { + template class GenericLookupTable; + template void from_json(const nlohmann::json&, GenericLookupTable&); + /** * @brief Template class for generating lookup-tables * * This class template can be instantiated for all classes/internal types that fulfills * these requirements: * - an operator<< so that the properties value can be put into a std::stringstream * - an operator== so that two properties can be checked for equality * * The main purpose of this class is to be used in conjunction with * GenericLookupTableProperty. This enables passing of arbitrary lookup * tables to mappers to configure the rendering process. */ template class GenericLookupTable { public: typedef unsigned int IdentifierType; typedef T ValueType; typedef std::map LookupTableType; typedef GenericLookupTable Self; GenericLookupTable() {} virtual ~GenericLookupTable() {} virtual const char *GetNameOfClass() const { return "GenericLookupTable"; } void SetTableValue(IdentifierType id, ValueType value) { m_LookupTable[id] = value; } bool ValueExists(IdentifierType id) const { auto it = m_LookupTable.find(id); return (it != m_LookupTable.end()); } ValueType GetTableValue(IdentifierType id) const { auto it = m_LookupTable.find(id); if (it != m_LookupTable.end()) return it->second; else throw std::range_error("id does not exist in the lookup table"); } const LookupTableType &GetLookupTable() const { return m_LookupTable; } bool operator==(const Self &lookupTable) const { return (m_LookupTable == lookupTable.m_LookupTable); } bool operator!=(const Self &lookupTable) const { return !(m_LookupTable == lookupTable.m_LookupTable); } virtual Self &operator=(const Self &other) // \TODO: this needs to be unit tested! { if (this == &other) { return *this; } else { m_LookupTable.clear(); m_LookupTable = other.m_LookupTable; return *this; } } + friend void from_json<>(const nlohmann::json&, GenericLookupTable&); + protected: LookupTableType m_LookupTable; }; + + template + void to_json(nlohmann::json& j, const GenericLookupTable& t) + { + j = t.GetLookupTable(); + } + + template + void from_json(const nlohmann::json& j, GenericLookupTable& t) + { + j.get_to(t.m_LookupTable); + } + } // namespace mitk /** * Generates a specialized subclass of mitk::GenericLookupTable. * This way, GetNameOfClass() returns the value provided by LookupTableName. * Please see mitkProperties.h for examples. * @param LookupTableName the name of the instantiation of GenericLookupTable * @param Type the value type of the GenericLookupTable */ #define mitkSpecializeGenericLookupTable(LookupTableName, Type) \ \ class MITKCORE_EXPORT LookupTableName : public GenericLookupTable \ \ { \ public: \ typedef LookupTableName Self; \ typedef GenericLookupTable Superclass; \ virtual const char *GetNameOfClass() const { return #LookupTableName; } \ LookupTableName() {} \ virtual Superclass &operator=(const Superclass &other) { return Superclass::operator=(other); } \ virtual ~LookupTableName() {} \ }; \ \ MITKCORE_EXPORT std::ostream &operator<<(std::ostream &stream, const LookupTableName & /*l*/); /** * Generates the ostream << operator for the lookuptable. This definition * of a global function must be in a cpp file, therefore it is split from the * class declaration macro mitkSpecializeGenericLookupTable. */ #define mitkSpecializeGenericLookupTableOperator(LookupTableName) \ \ std::ostream &mitk::operator<<(std::ostream &stream, const LookupTableName &l) \ \ { \ typedef LookupTableName::LookupTableType::const_iterator IterType; \ IterType e = l.GetLookupTable().end(); \ IterType b = l.GetLookupTable().begin(); \ stream << "["; \ for (IterType i = b; i != e; ++i) \ { \ if (i != b) \ { \ stream << ", "; \ } \ stream << i->first << " -> " << i->second; \ } \ return stream << "]"; \ }; #endif diff --git a/Modules/Core/include/mitkGenericProperty.h b/Modules/Core/include/mitkGenericProperty.h index ceff2ad5e2..b95907e77b 100644 --- a/Modules/Core/include/mitkGenericProperty.h +++ b/Modules/Core/include/mitkGenericProperty.h @@ -1,142 +1,167 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkGenericProperty_h #define mitkGenericProperty_h #include #include #include #include "mitkBaseProperty.h" #include "mitkNumericTypes.h" #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /*! @ brief Template class for generating properties for int, float, bool, etc. This class template can be instantiated for all classes/internal types that fulfills these requirements: - an operator<< so that the properties value can be put into a std::stringstream - an operator== so that two properties can be checked for equality - Note: you must use the macro mitkSpecializeGenericProperty to provide specializations - for concrete types (e.g. BoolProperty). Please see mitkProperties.h for examples. If you - don't use the mitkSpecializeGenericProperty Macro, GetNameOfClass() returns a wrong name. + Note: you must use the macros mitkDeclareGenericProperty and mitkDefineGenericProperty to + provide specializations for concrete types (e.g. BoolProperty). See mitkProperties.h for + examples. If you don't use these macros, GetNameOfClass() will return "GenericProperty", + which will mess up serialization for example. */ template class MITK_EXPORT GenericProperty : public BaseProperty { public: mitkClassMacro(GenericProperty, BaseProperty); mitkNewMacro1Param(GenericProperty, T); itkCloneMacro(Self); typedef T ValueType; itkSetMacro(Value, T); itkGetConstMacro(Value, T); std::string GetValueAsString() const override { std::stringstream myStr; myStr << GetValue(); return myStr.str(); } + bool ToJSON(nlohmann::json&) const override + { + return false; + } + + bool FromJSON(const nlohmann::json&) override + { + return false; + } + using BaseProperty::operator=; protected: GenericProperty() {} GenericProperty(T x) : m_Value(x) {} GenericProperty(const GenericProperty &other) : BaseProperty(other), m_Value(other.m_Value) {} T m_Value; private: // purposely not implemented GenericProperty &operator=(const GenericProperty &); itk::LightObject::Pointer InternalClone() const override { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } bool IsEqual(const BaseProperty &other) const override { return (this->m_Value == static_cast(other).m_Value); } bool Assign(const BaseProperty &other) override { this->m_Value = static_cast(other).m_Value; return true; } }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk /** * Generates a specialized subclass of mitk::GenericProperty. * This way, GetNameOfClass() returns the value provided by PropertyName. * Please see mitkProperties.h for examples. * @param PropertyName the name of the subclass of GenericProperty * @param Type the value type of the GenericProperty * @param Export the export macro for DLL usage */ #define mitkDeclareGenericProperty(PropertyName, Type, Export) \ \ class Export PropertyName : public GenericProperty \ \ { \ public: \ mitkClassMacro(PropertyName, GenericProperty); \ itkFactorylessNewMacro(Self); \ itkCloneMacro(Self); \ mitkNewMacro1Param(PropertyName, Type); \ \ + bool ToJSON(nlohmann::json& j) const override; \ + bool FromJSON(const nlohmann::json& j) override; \ + \ using BaseProperty::operator=; \ \ protected: \ PropertyName(); \ PropertyName(const PropertyName &); \ PropertyName(Type x); \ \ private: \ itk::LightObject::Pointer InternalClone() const override; \ }; #define mitkDefineGenericProperty(PropertyName, Type, DefaultValue) \ mitk::PropertyName::PropertyName() : Superclass(DefaultValue) {} \ mitk::PropertyName::PropertyName(const PropertyName &other) : GenericProperty(other) {} \ mitk::PropertyName::PropertyName(Type x) : Superclass(x) {} \ itk::LightObject::Pointer mitk::PropertyName::InternalClone() const \ { \ itk::LightObject::Pointer result(new Self(*this)); \ result->UnRegister(); \ return result; \ + } \ + bool mitk::PropertyName::ToJSON(nlohmann::json& j) const \ + { \ + j = this->GetValue(); \ + return true; \ + } \ + \ + bool mitk::PropertyName::FromJSON(const nlohmann::json& j) \ + { \ + this->SetValue(j.get()); \ + return true; \ } #endif diff --git a/Modules/Core/include/mitkGroupTagProperty.h b/Modules/Core/include/mitkGroupTagProperty.h index e83ff9b0e5..9d17596bc1 100644 --- a/Modules/Core/include/mitkGroupTagProperty.h +++ b/Modules/Core/include/mitkGroupTagProperty.h @@ -1,62 +1,65 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkGroupTagProperty_h #define mitkGroupTagProperty_h #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /*! @brief Property class that has no value. @ingroup DataManagement The GroupTag property is used to tag a datatree node to show, that it is member of a group of datatree nodes. This can be used to build groups of datatreenodes without the need to contain them in a specific hiearchic order in the datatree */ class MITKCORE_EXPORT GroupTagProperty : public BaseProperty { public: mitkClassMacro(GroupTagProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - using BaseProperty::operator=; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + + using BaseProperty::operator=; protected: GroupTagProperty(); GroupTagProperty(const GroupTagProperty &); private: // purposely not implemented GroupTagProperty &operator=(const GroupTagProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkIPropertyDeserialization.h b/Modules/Core/include/mitkIPropertyDeserialization.h new file mode 100644 index 0000000000..7911bceb00 --- /dev/null +++ b/Modules/Core/include/mitkIPropertyDeserialization.h @@ -0,0 +1,61 @@ +/*============================================================================ + +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 mitkIPropertyDeserialization_h +#define mitkIPropertyDeserialization_h + +#include +#include +#include + +#include + +#include +#include + +namespace mitk +{ + /** + * \ingroup MicroServices_Interfaces + * \brief Interface of property deserialization service. + * + * This service allows you to register custom property types (derived from BaseProperty) for deserialization. + * If a property type is not registered, it cannot be deserialized, e.g. when deserializing a PropertyList. + */ + class MITKCORE_EXPORT IPropertyDeserialization + { + public: + virtual ~IPropertyDeserialization(); + + virtual BaseProperty::Pointer CreateInstance(const std::string& className) = 0; + + /** + * \brief Register a custom property type for deserialization. + * + * The module activator of the module defining a property type is a good location to register + * custom property types of that module. See the implementation of MitkCoreActivator for + * examples. + */ + template >> + void RegisterProperty() + { + this->InternalRegisterProperty(T::New()); + } + + protected: + virtual void InternalRegisterProperty(const BaseProperty* property) = 0; + }; +} + +MITK_DECLARE_SERVICE_INTERFACE(mitk::IPropertyDeserialization, "org.mitk.IPropertyDeserialization") + +#endif diff --git a/Modules/Core/include/mitkLevelWindow.h b/Modules/Core/include/mitkLevelWindow.h index f81f33c200..3b0c2006ad 100644 --- a/Modules/Core/include/mitkLevelWindow.h +++ b/Modules/Core/include/mitkLevelWindow.h @@ -1,263 +1,268 @@ /*============================================================================ 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 mitkLevelWindow_h #define mitkLevelWindow_h #include "mitkNumericTypes.h" #include +#include namespace mitk { class Image; /** * @brief The LevelWindow class Class to store level/window values. * * Current min and max value are stored in m_LowerWindowBound and m_UpperWindowBound. * m_DefaultLevel amd m_DefaultWindow store the initial Level/Window values for the image. * m_DefaultRangeMin and m_DefaultRangeMax store the initial minrange and maxrange for the image. * * The finite maximum and minimum of valid value range is stored in m_RangeMin and m_RangeMax. * If deduced from an image by default the minimum or maximum of it statistics is used. If one * of these values are infinite the 2nd extrimum (which is guaranteed to be finite), will be used. * * See documentation of SetAuto for information on how the level window is initialized from an image. * * @ingroup DataManagement * * @note If you want to apply the mitk::LevelWindow to an mitk::Image, make sure * to use the mitk::LevelWindowProperty and set the mitk::RenderingModeProperty * to a mode which supports level window (e.g. LEVELWINDOW_COLOR). * Make sure to check the documentation of the mitk::RenderingModeProperty. For a * code example how to use the mitk::LevelWindowProperty check the * mitkImageVtkMapper2DLevelWindowTest.cpp in Core/Code/Testing. */ class MITKCORE_EXPORT LevelWindow { public: LevelWindow(ScalarType level = 127.5, ScalarType window = 255.0); LevelWindow(const mitk::LevelWindow &levWin); virtual ~LevelWindow(); /*! * \brief method that returns the level value, i.e. the center of * the current grey value interval */ ScalarType GetLevel() const; /*! * \brief returns the current window size, i.e the range size of the current grey value interval */ ScalarType GetWindow() const; /*! * \brief method returns the default level value for the image */ ScalarType GetDefaultLevel() const; /*! * \brief returns the default window size for the image */ ScalarType GetDefaultWindow() const; /*! * \brief Resets the level and the window value to the default values */ void ResetDefaultLevelWindow(); /*! * Returns the minimum Value of the window */ ScalarType GetLowerWindowBound() const; /*! * Returns the upper window bound value of the window */ ScalarType GetUpperWindowBound() const; /*! * To set the level and the window value */ void SetLevelWindow(ScalarType level, ScalarType window, bool expandRangesIfNecessary = true); /*! * Set the lower and upper bound of the window, restricted to the range from -10^300 to 10^300. Higher/lower values are clamped to these boundaries. */ void SetWindowBounds(ScalarType lowerBound, ScalarType upperBound, bool expandRangesIfNecessary = true); /*! * sets the window to its maximum Size in scaleRange */ void SetToMaxWindowSize(); /*! * Set the range minimum and maximum value */ void SetRangeMinMax(ScalarType min, ScalarType max); /*! * Get the range minimum value */ ScalarType GetRangeMin() const; /*! * Get the range maximum value */ ScalarType GetRangeMax() const; /*! * Get the default range minimum value */ ScalarType GetDefaultLowerBound() const; /*! * Get the default range maximum value */ ScalarType GetDefaultUpperBound() const; /*! * \brief the default min and max range for image will be reset */ void ResetDefaultRangeMinMax(); /**! * \brief returns the size of the grey value range */ ScalarType GetRange() const; /*! * set the default level and window value */ void SetDefaultLevelWindow(ScalarType level, ScalarType window); /*! * set the default Boundaries */ void SetDefaultBoundaries(ScalarType low, ScalarType up); /**! * \brief sets level/window to optimize the contrast of the given Image */ void SetAuto(const Image *image, bool tryPicTags = true, bool guessByCentralSlice = true, unsigned selectedComponent = 0); /**! * \brief sets level/window to the min/max greyvalues of the given Image */ void SetToImageRange(const Image *image); /** * If a level window is set to fixed, the set and get methods won't accept * modifications to the level window settings anymore. This behaviour can * be turned of by setting fixed to false; */ void SetFixed(bool fixed); /** * Returns whether the level window settings are fixed (@see SetFixed(bool)) or not */ bool GetFixed() const; /** * Returns whether the level window settings are fixed (@see SetFixed(bool)) or not */ bool IsFixed() const; /*! * \brief equality operator implementation that allows to compare two level windows */ virtual bool operator==(const LevelWindow &levWin) const; /*! * \brief non equality operator implementation that allows to compare two level windows */ virtual bool operator!=(const LevelWindow &levWin) const; /*! * \brief implementation necessary because operator made * private in itk::Object */ virtual LevelWindow &operator=(const LevelWindow &levWin); /*! * \brief Shows if floating values are accepted */ bool IsFloatingValues() const; /*! * \brief Sets the floating image value */ void SetFloatingValues(bool value); protected: /*! * lower bound of current window */ ScalarType m_LowerWindowBound; /*! * upper bound of current window */ ScalarType m_UpperWindowBound; /*! * minimum gray value of the window */ ScalarType m_RangeMin; /*! * maximum gray value of the window */ ScalarType m_RangeMax; /*! * default minimum gray value of the window */ ScalarType m_DefaultLowerBound; /*! * default maximum gray value of the window */ ScalarType m_DefaultUpperBound; /*! * Image with floating values */ bool m_IsFloatingImage; /*! * Defines whether the level window settings may be changed after * initialization or not. */ bool m_Fixed; /*! * confidence tests * * if m_LowerWindowBound > m_UpperWindowBound, then the values for m_LowerWindowBound and m_UpperWindowBound will be * exchanged * * if m_LowerWindowBound < m_RangeMin, m_LowerWindowBound will be set to m_RangeMin. m_UpperWindowBound will be * decreased the same as m_LowerWindowBound will be increased, but minimum value for m_UpperWindowBound is also * m_RangeMin. * * if m_UpperWindowBound > m_RangeMax, m_UpperWindowBound will be set to m_RangeMax. m_LowerWindowBound will be * increased the same as m_UpperWindowBound will be decreased, but maximum value for m_LowerWindowBound is also * m_RangeMax. * */ inline void EnsureConsistency(); }; + + MITKCORE_EXPORT void to_json(nlohmann::json& j, const LevelWindow& lw); + MITKCORE_EXPORT void from_json(const nlohmann::json& j, LevelWindow& lw); + } // namespace mitk #endif diff --git a/Modules/Core/include/mitkLevelWindowProperty.h b/Modules/Core/include/mitkLevelWindowProperty.h index 4c4325de22..30209a6f26 100755 --- a/Modules/Core/include/mitkLevelWindowProperty.h +++ b/Modules/Core/include/mitkLevelWindowProperty.h @@ -1,85 +1,88 @@ /*============================================================================ 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 mitkLevelWindowProperty_h #define mitkLevelWindowProperty_h #include "mitkBaseProperty.h" #include "mitkLevelWindow.h" namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief The LevelWindowProperty class Property for the mitk::LevelWindow * * @ingroup DataManagement * * @note If you want to apply the mitk::LevelWindowProperty to an mitk::Image, * make sure to set the mitk::RenderingModeProperty to a mode which supports * level window (e.g. LEVELWINDOW_COLOR). Make sure to check the documentation * of the mitk::RenderingModeProperty. For a code example how to use the * mitk::LevelWindowProperty check the mitkImageVtkMapper2DLevelWindowTest.cpp * in Core/Code/Testing. */ class MITKCORE_EXPORT LevelWindowProperty : public BaseProperty { protected: LevelWindow m_LevWin; LevelWindowProperty(); LevelWindowProperty(const LevelWindowProperty &other); LevelWindowProperty(const mitk::LevelWindow &levWin); public: mitkClassMacro(LevelWindowProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro1Param(LevelWindowProperty, const mitk::LevelWindow &); typedef LevelWindow ValueType; ~LevelWindowProperty() override; const mitk::LevelWindow &GetLevelWindow() const; const mitk::LevelWindow &GetValue() const; void SetLevelWindow(const LevelWindow &levWin); void SetValue(const ValueType &levWin); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; private: // purposely not implemented LevelWindowProperty &operator=(const LevelWindowProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkLookupTableProperty.h b/Modules/Core/include/mitkLookupTableProperty.h index 8a57cc0e83..3b72837858 100644 --- a/Modules/Core/include/mitkLookupTableProperty.h +++ b/Modules/Core/include/mitkLookupTableProperty.h @@ -1,84 +1,87 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLookupTableProperty_h #define mitkLookupTableProperty_h #include "mitkBaseProperty.h" #include "mitkLookupTable.h" #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief The LookupTableProperty class Property to associate mitk::LookupTable * to an mitk::DataNode. * @ingroup DataManagement * * @note If you want to use this property to colorize an mitk::Image, make sure * to set the mitk::RenderingModeProperty to a mode which supports lookup tables * (e.g. LOOKUPTABLE_COLOR). Make sure to check the documentation of the * mitk::RenderingModeProperty. For a code example how to use the mitk::LookupTable * and this property check the mitkImageVtkMapper2DLookupTableTest.cpp in * Core/Code/Testing. */ class MITKCORE_EXPORT LookupTableProperty : public BaseProperty { protected: LookupTable::Pointer m_LookupTable; LookupTableProperty(); LookupTableProperty(const LookupTableProperty &); LookupTableProperty(const mitk::LookupTable::Pointer lut); public: typedef LookupTable::Pointer ValueType; mitkClassMacro(LookupTableProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro1Param(LookupTableProperty, const mitk::LookupTable::Pointer); itkGetObjectMacro(LookupTable, LookupTable); ValueType GetValue() const; void SetLookupTable(const mitk::LookupTable::Pointer aLookupTable); void SetValue(const ValueType &); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; private: // purposely not implemented LookupTableProperty &operator=(const LookupTableProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkPoint.h b/Modules/Core/include/mitkPoint.h index 0436d5ae23..0f9c36682b 100644 --- a/Modules/Core/include/mitkPoint.h +++ b/Modules/Core/include/mitkPoint.h @@ -1,135 +1,153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPoint_h #define mitkPoint_h #include -#include "mitkArray.h" -#include "mitkEqual.h" -#include "mitkNumericConstants.h" +#include +#include +#include + +#include namespace mitk { //##Documentation //##@brief enumeration of the type a point can be enum PointSpecificationType { PTUNDEFINED = 0, PTSTART, PTCORNER, PTEDGE, PTEND }; template class Point : public itk::Point { public: /** Default constructor has nothing to do. */ explicit Point() : itk::Point() {} /** Pass-through constructors for the Array base class. */ template explicit Point(const Point &r) : itk::Point(r) { } template explicit Point(const TPointValueType r[NPointDimension]) : itk::Point(r) { } template explicit Point(const TPointValueType &v) : itk::Point(v) { } Point(const mitk::Point &r) : itk::Point(r) { } Point(const TCoordRep r[NPointDimension]) : itk::Point(r) {} Point(const TCoordRep &v) : itk::Point(v) {} Point(const itk::Point &p) : itk::Point(p) { } /** * Copies the elements from array array to this. * Note that this method will assign doubles to floats without complaining! * * @param array the array whose values shall be copied. Must overload [] operator. */ template void FillPoint(const ArrayType &array) { itk::FixedArray *thisP = dynamic_cast *>(this); mitk::FillArray(*thisP, array); } /** * Copies the values stored in this point into the array array. * * @param array the array which should store the values of this. */ template void ToArray(ArrayType array) const { mitk::ToArray(array, *this); } }; + template + void to_json(nlohmann::json& j, const Point& p) + { + j = nlohmann::json::array(); + + for (size_t i = 0; i < NPointDimension; ++i) + j.push_back(p[i]); + } + + template + void from_json(const nlohmann::json& j, Point& p) + { + for (size_t i = 0; i < NPointDimension; ++i) + j.at(i).get_to(p[i]); + } + typedef Point Point2D; typedef Point Point3D; typedef Point Point4D; typedef Point Point2I; typedef Point Point3I; typedef Point Point4I; /** * @ingroup MITKTestingAPI * * @param point1 Point to compare. * @param point2 Point to compare. * @param eps Tolerance for floating point comparison. * @param verbose Flag indicating detailed console output. * @return True if points are equal. */ template inline bool Equal(const itk::Point &point1, const itk::Point &point2, TCoordRep eps = mitk::eps, bool verbose = false) { bool isEqual = true; typename itk::Point::VectorType diff = point1 - point2; for (unsigned int i = 0; i < NPointDimension; i++) { if (DifferenceBiggerOrEqualEps(diff[i], eps)) { isEqual = false; break; } } ConditionalOutputOfDifference(point1, point2, eps, verbose, isEqual); return isEqual; } } // namespace mitk #endif diff --git a/Modules/Core/include/mitkPropertyDeserialization.h b/Modules/Core/include/mitkPropertyDeserialization.h new file mode 100644 index 0000000000..274bb1d22f --- /dev/null +++ b/Modules/Core/include/mitkPropertyDeserialization.h @@ -0,0 +1,41 @@ +/*============================================================================ + +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 mitkPropertyDeserialization_h +#define mitkPropertyDeserialization_h + +#include +#include + +namespace mitk +{ + class PropertyDeserialization : public IPropertyDeserialization + { + public: + PropertyDeserialization(); + virtual ~PropertyDeserialization(); + + PropertyDeserialization(const PropertyDeserialization&) = delete; + PropertyDeserialization& operator=(const PropertyDeserialization&) = delete; + + itk::SmartPointer CreateInstance(const std::string& className) override; + + protected: + void InternalRegisterProperty(const BaseProperty* property) override; + + private: + using MapType = std::map; + MapType m_Map; + }; +} + +#endif diff --git a/Modules/Core/include/mitkPropertyList.h b/Modules/Core/include/mitkPropertyList.h index 40f05857cf..763715f97b 100644 --- a/Modules/Core/include/mitkPropertyList.h +++ b/Modules/Core/include/mitkPropertyList.h @@ -1,254 +1,268 @@ /*============================================================================ 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 mitkPropertyList_h #define mitkPropertyList_h -#include "mitkBaseProperty.h" -#include "mitkGenericProperty.h" -#include "mitkUIDGenerator.h" -#include "mitkIPropertyOwner.h" -#include +#include +#include -#include - -#include -#include +#include namespace mitk { - class XMLWriter; - /** * @brief Key-value list holding instances of BaseProperty * * This list is meant to hold an arbitrary list of "properties", * which should describe the object associated with this list. * * Usually you will use PropertyList as part of a DataNode * object - in this context the properties describe the data object * held by the DataNode (e.g. whether the object is rendered at * all, which color is used for rendering, what name should be * displayed for the object, etc.) * * The values in the list are not fixed, you may introduce any kind * of property that seems useful - all you have to do is inherit * from BaseProperty. * * The list is organized as a key-value pairs, i.e. * * \li "name" : pointer to a StringProperty * \li "visible" : pointer to a BoolProperty * \li "color" : pointer to a ColorProperty * \li "volume" : pointer to a FloatProperty * * Please see the documentation of SetProperty and ReplaceProperty for two * quite different semantics. Normally SetProperty is what you want - this * method will try to change the value of an existing property and will * not allow you to replace e.g. a ColorProperty with an IntProperty. * * Please also regard, that the key of a property must be a none empty string. * This is a precondition. Setting properties with empty keys will raise an exception. * * @ingroup DataManagement */ class MITKCORE_EXPORT PropertyList : public itk::Object, public IPropertyOwner { public: mitkClassMacroItkParent(PropertyList, itk::Object); /** * Method for creation through the object factory. */ itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** * Map structure to hold the properties: the map key is a string, * the value consists of the actual property object (BaseProperty). */ typedef std::map PropertyMap; typedef std::pair PropertyMapElementType; // IPropertyProvider 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; // IPropertyOwner BaseProperty * GetNonConstProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = true) override; void SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; void RemoveProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; /** * @brief Get a property by its name. */ mitk::BaseProperty *GetProperty(const std::string &propertyKey) const; /** * @brief Set a property object in the list/map by reference. * * The actual OBJECT holding the value of the property is replaced by this function. * This is useful if you want to change the type of the property, like from BoolProperty to StringProperty. * Another use is to share one and the same property object among several PropertyList/DataNode objects, which * makes them appear synchronized. */ void ReplaceProperty(const std::string &propertyKey, BaseProperty *property); /** * @brief Set a property object in the list/map by reference. */ void ConcatenatePropertyList(PropertyList *pList, bool replace = false); //##Documentation //## @brief Convenience access method for GenericProperty properties //## (T being the type of the second parameter) //## @return @a true property was found template bool GetPropertyValue(const char *propertyKey, T &value) const { GenericProperty *gp = dynamic_cast *>(GetProperty(propertyKey)); if (gp != nullptr) { value = gp->GetValue(); return true; } return false; } /** * @brief Convenience method to access the value of a BoolProperty */ bool GetBoolProperty(const char *propertyKey, bool &boolValue) const; /** * @brief ShortCut for the above method */ bool Get(const char *propertyKey, bool &boolValue) const; /** * @brief Convenience method to set the value of a BoolProperty */ void SetBoolProperty(const char *propertyKey, bool boolValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, bool boolValue); /** * @brief Convenience method to access the value of an IntProperty */ bool GetIntProperty(const char *propertyKey, int &intValue) const; /** * @brief ShortCut for the above method */ bool Get(const char *propertyKey, int &intValue) const; /** * @brief Convenience method to set the value of an IntProperty */ void SetIntProperty(const char *propertyKey, int intValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, int intValue); /** * @brief Convenience method to access the value of a FloatProperty */ bool GetFloatProperty(const char *propertyKey, float &floatValue) const; /** * @brief ShortCut for the above method */ bool Get(const char *propertyKey, float &floatValue) const; /** * @brief Convenience method to set the value of a FloatProperty */ void SetFloatProperty(const char *propertyKey, float floatValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, float floatValue); /** * @brief Convenience method to access the value of a DoubleProperty */ bool GetDoubleProperty(const char *propertyKey, double &doubleValue) const; /** * @brief ShortCut for the above method */ bool Get(const char *propertyKey, double &doubleValue) const; /** * @brief Convenience method to set the value of a DoubleProperty */ void SetDoubleProperty(const char *propertyKey, double doubleValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, double doubleValue); /** * @brief Convenience method to access the value of a StringProperty */ bool GetStringProperty(const char *propertyKey, std::string &stringValue) const; /** * @brief ShortCut for the above method */ bool Get(const char *propertyKey, std::string &stringValue) const; /** * @brief Convenience method to set the value of a StringProperty */ void SetStringProperty(const char *propertyKey, const char *stringValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, const char *stringValue); /** * @brief ShortCut for the above method */ void Set(const char *propertyKey, const std::string &stringValue); /** * @brief Get the timestamp of the last change of the map or the last change of one of * the properties store in the list (whichever is later). */ itk::ModifiedTimeType GetMTime() const override; /** * @brief Remove a property from the list/map. */ bool DeleteProperty(const std::string &propertyKey); const PropertyMap *GetMap() const { return &m_Properties; } bool IsEmpty() const { return m_Properties.empty(); } virtual void Clear(); + /** + * @brief Serialize the property list to JSON. + * + * @note Properties of a certain type can only be deseralized again if their type has been + * registered via the IPropertyDeserialization core service. + * + * @sa CoreServices + * @sa IPropertyDeserialization::RegisterProperty + */ + void ToJSON(nlohmann::json& j) const; + + /** + * @brief Deserialize the property list from JSON. + * + * @note Properties of a certain type can only be deseralized again if their type has been + * registered via the IPropertyDeserialization core service. + * + * @sa CoreServices + * @sa IPropertyDeserialization::RegisterProperty + */ + void FromJSON(const nlohmann::json& j); + protected: PropertyList(); PropertyList(const PropertyList &other); ~PropertyList() override; /** * @brief Map of properties. */ PropertyMap m_Properties; private: itk::LightObject::Pointer InternalClone() const override; }; } // namespace mitk #endif diff --git a/Modules/Core/include/mitkSmartPointerProperty.h b/Modules/Core/include/mitkSmartPointerProperty.h index aac5f37d69..ddf84f66b3 100644 --- a/Modules/Core/include/mitkSmartPointerProperty.h +++ b/Modules/Core/include/mitkSmartPointerProperty.h @@ -1,98 +1,101 @@ /*============================================================================ 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 mitkSmartPointerProperty_h #define mitkSmartPointerProperty_h #include "mitkBaseProperty.h" #include "mitkUIDGenerator.h" #include #include #include #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif //##Documentation //## @brief Property containing a smart-pointer //## @ingroup DataManagement class MITKCORE_EXPORT SmartPointerProperty : public BaseProperty { public: mitkClassMacro(SmartPointerProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); mitkNewMacro1Param(SmartPointerProperty, itk::Object*); typedef itk::Object::Pointer ValueType; itk::Object::Pointer GetSmartPointer() const; ValueType GetValue() const; void SetSmartPointer(itk::Object *); void SetValue(const ValueType &); /// mainly for XML output std::string GetValueAsString() const override; static void PostProcessXMLReading(); /// Return the number of SmartPointerProperties that reference the object given as parameter static unsigned int GetReferenceCountFor(itk::Object *); static std::string GetReferenceUIDFor(itk::Object *); static void RegisterPointerTarget(itk::Object *, const std::string uid); + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; protected: SmartPointerProperty(itk::Object * = nullptr); SmartPointerProperty(const SmartPointerProperty &); itk::Object::Pointer m_SmartPointer; private: // purposely not implemented SmartPointerProperty &operator=(const SmartPointerProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &) const override; bool Assign(const BaseProperty &) override; typedef std::map ReferenceCountMapType; typedef std::map ReferencesUIDMapType; typedef std::map ReadInSmartPointersMapType; typedef std::map ReadInTargetsMapType; /// for each itk::Object* count how many SmartPointerProperties point to it static ReferenceCountMapType m_ReferenceCount; static ReferencesUIDMapType m_ReferencesUID; static ReadInSmartPointersMapType m_ReadInInstances; static ReadInTargetsMapType m_ReadInTargets; /// to generate unique IDs for the objects pointed at (during XML writing) static UIDGenerator m_UIDGenerator; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkStringProperty.h b/Modules/Core/include/mitkStringProperty.h index 04e7d744af..41962df4e1 100644 --- a/Modules/Core/include/mitkStringProperty.h +++ b/Modules/Core/include/mitkStringProperty.h @@ -1,78 +1,81 @@ /*============================================================================ 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 mitkStringProperty_h #define mitkStringProperty_h #include #include "mitkBaseProperty.h" #include #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief Property for strings * @ingroup DataManagement */ class MITKCORE_EXPORT StringProperty : public BaseProperty { protected: std::string m_Value; StringProperty(const char *string = nullptr); StringProperty(const std::string &s); StringProperty(const StringProperty &); public: mitkClassMacro(StringProperty, BaseProperty); typedef std::string ValueType; itkFactorylessNewMacro(Self); itkCloneMacro(Self); mitkNewMacro1Param(StringProperty, const char*); mitkNewMacro1Param(StringProperty, const std::string&); itkGetStringMacro(Value); itkSetStringMacro(Value); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + static const char *PATH; using BaseProperty::operator=; private: // purposely not implemented StringProperty &operator=(const StringProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkTemporoSpatialStringProperty.h b/Modules/Core/include/mitkTemporoSpatialStringProperty.h index 595c8d1aca..8f6f090f93 100644 --- a/Modules/Core/include/mitkTemporoSpatialStringProperty.h +++ b/Modules/Core/include/mitkTemporoSpatialStringProperty.h @@ -1,136 +1,139 @@ /*============================================================================ 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); + 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); } #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkTransferFunctionProperty.h b/Modules/Core/include/mitkTransferFunctionProperty.h index e8bf9439cc..fd3c20d736 100644 --- a/Modules/Core/include/mitkTransferFunctionProperty.h +++ b/Modules/Core/include/mitkTransferFunctionProperty.h @@ -1,79 +1,82 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkTransferFunctionProperty_h #define mitkTransferFunctionProperty_h #include "mitkBaseProperty.h" #include "mitkTransferFunction.h" namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif /** * @brief The TransferFunctionProperty class Property class for the mitk::TransferFunction. * @ingroup DataManagement * * @note If you want to use this property for an mitk::Image, make sure * to set the mitk::RenderingModeProperty to a mode which supports transfer * functions (e.g. COLORTRANSFERFUNCTION_COLOR). Make sure to check the * documentation of the mitk::RenderingModeProperty. For a code example how * to use the mitk::TransferFunction check the * mitkImageVtkMapper2DTransferFunctionTest.cpp in Core/Code/Testing. */ class MITKCORE_EXPORT TransferFunctionProperty : public BaseProperty { public: typedef mitk::TransferFunction::Pointer ValueType; mitkClassMacro(TransferFunctionProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self) mitkNewMacro1Param(TransferFunctionProperty, mitk::TransferFunction::Pointer); itkSetMacro(Value, mitk::TransferFunction::Pointer); itkGetConstMacro(Value, mitk::TransferFunction::Pointer); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; protected: mitk::TransferFunction::Pointer m_Value; TransferFunctionProperty(); TransferFunctionProperty(const TransferFunctionProperty &other); TransferFunctionProperty(mitk::TransferFunction::Pointer value); private: // purposely not implemented TransferFunctionProperty &operator=(const TransferFunctionProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/include/mitkVector.h b/Modules/Core/include/mitkVector.h index fca4c9047d..6c5bc2ddaf 100644 --- a/Modules/Core/include/mitkVector.h +++ b/Modules/Core/include/mitkVector.h @@ -1,239 +1,257 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkVector_h #define mitkVector_h #include #include #include -#include "mitkArray.h" -#include "mitkEqual.h" -#include "mitkExceptionMacro.h" -#include "mitkNumericConstants.h" +#include +#include +#include +#include + +#include namespace mitk { template class Vector : public itk::Vector { public: /** * @brief Default constructor has nothing to do. */ explicit Vector() : itk::Vector() {} /** * @brief Copy constructor. */ explicit Vector(const mitk::Vector &r) : itk::Vector(r) { } /** Pass-through assignment operator for the Vector base class. */ Vector & operator=(const Vector & r) { itk::Vector::operator=(r); return *this; } /** * @brief Constructor to convert from itk::Vector to mitk::Vector. */ Vector(const itk::Vector &r) : itk::Vector(r) { } /** * @brief Constructor to convert an array to mitk::Vector * @param r the array. * @attention must have NVectorDimension valid arguments! */ Vector(const TCoordRep r[NVectorDimension]) : itk::Vector(r) { } /** * Constructor to initialize entire vector to one value. */ Vector(const TCoordRep &v) : itk::Vector(v) {} /** * @brief Constructor for vnl_vectors. * @throws mitk::Exception if vnl_vector.size() != NVectorDimension. */ Vector(const vnl_vector &vnlVector) : itk::Vector() { if (vnlVector.size() != NVectorDimension) mitkThrow() << "when constructing mitk::Vector from vnl_vector: sizes didn't match: mitk::Vector " << NVectorDimension << "; vnl_vector " << vnlVector.size(); for (unsigned int var = 0; (var < NVectorDimension) && (var < vnlVector.size()); ++var) { this->SetElement(var, vnlVector.get(var)); } } /** * @brief Constructor for vnl_vector_fixed. */ Vector(const vnl_vector_fixed &vnlVectorFixed) : itk::Vector() { for (unsigned int var = 0; var < NVectorDimension; ++var) { this->SetElement(var, vnlVectorFixed[var]); } }; /** * Copies the elements from array array to this. * Note that this method will assign doubles to floats without complaining! * * @param array the array whose values shall be copied. Must overload [] operator. */ template void FillVector(const ArrayType &array) { itk::FixedArray *thisP = dynamic_cast *>(this); mitk::FillArray(*thisP, array); } /** * Copies the values stored in this vector into the array array.d * * @param array the array which should store the values of this. */ template void ToArray(ArrayType array) const { mitk::ToArray(array, *this); } /** * @brief User defined conversion of mitk::Vector to vnl_vector. * Note: the conversion to mitk::Vector to vnl_vector_fixed has not been implemented since this * would collide with the conversion vnl_vector to vnl_vector_fixed provided by vnl. */ operator vnl_vector() const { return this->GetVnlVector(); } }; // end mitk::Vector + template + void to_json(nlohmann::json &j, const Vector &v) + { + j = nlohmann::json::array(); + + for (size_t i = 0; i < NVectorDimension; ++i) + j.push_back(v[i]); + } + + template + void from_json(const nlohmann::json &j, Vector &v) + { + for (size_t i = 0; i < NVectorDimension; ++i) + j.at(i).get_to(v[i]); + } + // convenience typedefs for often used mitk::Vector representations. typedef Vector Vector2D; typedef Vector Vector3D; typedef Vector Vector4D; // other vector types used in MITK typedef vnl_vector VnlVector; // The equal methods to compare vectors for equality are below: /** * @ingroup MITKTestingAPI * * @param vector1 Vector to compare. * @param vector2 Vector to compare. * @param eps Tolerance for floating point comparison. * @param verbose Flag indicating detailed console output. * @return True if vectors are equal. */ template inline bool Equal(const itk::Vector &vector1, const itk::Vector &vector2, TCoordRep eps = mitk::eps, bool verbose = false) { bool isEqual = true; typename itk::Vector::VectorType diff = vector1 - vector2; for (unsigned int i = 0; i < NPointDimension; i++) { if (DifferenceBiggerOrEqualEps(diff[i], eps)) { isEqual = false; break; } } ConditionalOutputOfDifference(vector1, vector2, eps, verbose, isEqual); return isEqual; } /** * @ingroup MITKTestingAPI * * @param vector1 Vector to compare. * @param vector2 Vector to compare. * @param eps Tolerance for floating point comparison. * @param verbose Flag indicating detailed console output. * @return True if vectors are equal. */ inline bool Equal(const mitk::VnlVector &vector1, const mitk::VnlVector &vector2, ScalarType eps = mitk::eps, bool verbose = false) { bool isEqual = true; mitk::VnlVector diff = vector1 - vector2; for (unsigned int i = 0; i < diff.size(); i++) { if (DifferenceBiggerOrEqualEps(diff[i], eps)) { isEqual = false; break; } } ConditionalOutputOfDifference(vector1, vector2, eps, verbose, isEqual); return isEqual; } /** * @ingroup MITKTestingAPI * * @param vector1 Vector to compare. * @param vector2 Vector to compare. * @param eps Tolerance for floating point comparison. * @param verbose Flag indicating detailed console output. * @return True if vectors are equal. */ template inline bool Equal(const vnl_vector_fixed &vector1, const vnl_vector_fixed &vector2, TCoordRep eps = mitk::eps, bool verbose = false) { vnl_vector_fixed diff = vector1 - vector2; bool isEqual = true; for (unsigned int i = 0; i < diff.size(); i++) { if (DifferenceBiggerOrEqualEps(diff[i], eps)) { isEqual = false; break; } } ConditionalOutputOfDifference(vector1, vector2, eps, verbose, isEqual); return isEqual; } } // end namespace mitk #endif diff --git a/Modules/Core/include/mitkVectorProperty.h b/Modules/Core/include/mitkVectorProperty.h index be0b00f767..547a5d5caf 100644 --- a/Modules/Core/include/mitkVectorProperty.h +++ b/Modules/Core/include/mitkVectorProperty.h @@ -1,133 +1,136 @@ /*============================================================================ 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 mitkVectorProperty_h #define mitkVectorProperty_h // MITK #include #include // STL #include namespace mitk { /** \brief Helper for VectorProperty to determine a good ITK ClassName. This template is specialized for special instantiations that need a serializer of this VectorProperty. */ template struct VectorPropertyDataType { static const char *prefix() { return "Invalid"; } }; /** \brief Providing a std::vector as property. Templated over the data type of the std::vector that is held by this class. Nothing special about data handling, setting and getting of std::vectors is implemented by-value. When checking the declaration of this class, you'll notice that it does not use the mitkClassMacro but instead writes all of its definition manually. This is in order to specifically override the GetNameOfClass() method without having to inherit again from the template (see comments in code). */ template class MITKCORE_EXPORT VectorProperty : public BaseProperty { public: typedef std::vector VectorType; // Manually expand most of mitkClassMacro: // mitkClassMacro(VectorProperty, mitk::BaseProperty); // This manual expansion is done to override explicitly // the GetNameOfClass() and GetStaticNameOfClass() methods typedef VectorProperty Self; typedef BaseProperty SuperClass; typedef itk::SmartPointer Pointer; typedef itk::SmartPointer ConstPointer; std::vector GetClassHierarchy() const override { return mitk::GetClassHierarchy(); } /// This function must return different /// strings in function of the template parameter! /// Serialization depends on this feature. static const char *GetStaticNameOfClass() { // concatenate a prefix dependent on the template type and our own classname static std::string nameOfClass = std::string(VectorPropertyDataType::prefix()) + "VectorProperty"; return nameOfClass.c_str(); } const char *GetNameOfClass() const override { return this->GetStaticNameOfClass(); } itkFactorylessNewMacro(Self); itkCloneMacro(Self); /// Returns the property value as a std::string. /// /// Since VectorProperty potentially holds many /// elements, it implements this function in a way /// that only the first and the last couple of /// elements really appear in the string. /// Missing central elements are indicated by /// an ellipsis ("...") std::string GetValueAsString() const override; /// returns a const reference to the contained vector virtual const VectorType &GetValue() const; /// sets the content vector virtual void SetValue(const VectorType ¶meter_vector); + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + private: /// purposely not implemented VectorProperty &operator=(const Self &); /// creates a copy of it self itk::LightObject::Pointer InternalClone() const override; /// compares two properties. bool IsEqual(const BaseProperty &an_other_property) const override; /// assigns the content of an_other_property to this bool Assign(const BaseProperty &an_other_property) override; /// property content VectorType m_PropertyContent; }; /// This should be used in .h files. #define MITK_DECLARE_VECTOR_PROPERTY(TYPE, PREFIX) \ \ typedef VectorProperty PREFIX##VectorProperty; \ \ template <> \ \ struct VectorPropertyDataType \ { \ static const char *prefix() { return #PREFIX; } \ }; /// This should be used in a .cpp file #define MITK_DEFINE_VECTOR_PROPERTY(TYPE) template class VectorProperty; MITK_DECLARE_VECTOR_PROPERTY(double, Double) MITK_DECLARE_VECTOR_PROPERTY(int, Int) } // namespace #endif diff --git a/Modules/Core/include/mitkWeakPointerProperty.h b/Modules/Core/include/mitkWeakPointerProperty.h index aba93c9c37..db11e06a19 100644 --- a/Modules/Core/include/mitkWeakPointerProperty.h +++ b/Modules/Core/include/mitkWeakPointerProperty.h @@ -1,78 +1,81 @@ /*============================================================================ 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 mitkWeakPointerProperty_h #define mitkWeakPointerProperty_h #include "itkWeakPointer.h" #include "mitkBaseProperty.h" #include namespace mitk { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4522) #endif //##Documentation //## @brief Property containing a smart-pointer //## //## @ingroup DataManagement class MITKCORE_EXPORT WeakPointerProperty : public BaseProperty { public: mitkClassMacro(WeakPointerProperty, BaseProperty); itkFactorylessNewMacro(Self); itkCloneMacro(Self); mitkNewMacro1Param(WeakPointerProperty, itk::Object*); ~WeakPointerProperty() override; typedef itk::WeakPointer ValueType; ValueType GetWeakPointer() const; ValueType GetValue() const; void SetWeakPointer(itk::Object *pointer); void SetValue(const ValueType &value); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; protected: itk::WeakPointer m_WeakPointer; WeakPointerProperty(const WeakPointerProperty &); WeakPointerProperty(itk::Object *pointer = nullptr); private: // purposely not implemented WeakPointerProperty &operator=(const WeakPointerProperty &); itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty &property) const override; bool Assign(const BaseProperty &property) override; }; #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace mitk #endif diff --git a/Modules/Core/src/DataManagement/mitkAnnotationProperty.cpp b/Modules/Core/src/DataManagement/mitkAnnotationProperty.cpp index 303e0c80f0..d3463856e3 100644 --- a/Modules/Core/src/DataManagement/mitkAnnotationProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkAnnotationProperty.cpp @@ -1,98 +1,114 @@ /*============================================================================ 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 "mitkAnnotationProperty.h" mitk::AnnotationProperty::AnnotationProperty() : m_Position(0.0) { } mitk::AnnotationProperty::AnnotationProperty(const char *label, const Point3D &position) : m_Label(""), m_Position(position) { if (label != nullptr) { m_Label = label; } } mitk::AnnotationProperty::AnnotationProperty(const std::string &label, const Point3D &position) : m_Label(label), m_Position(position) { } mitk::AnnotationProperty::AnnotationProperty(const char *label, ScalarType x, ScalarType y, ScalarType z) : m_Label("") { if (label != nullptr) { m_Label = label; } m_Position[0] = x; m_Position[1] = y; m_Position[2] = z; } mitk::AnnotationProperty::AnnotationProperty(const std::string &label, ScalarType x, ScalarType y, ScalarType z) : m_Label(label) { m_Position[0] = x; m_Position[1] = y; m_Position[2] = z; } mitk::AnnotationProperty::AnnotationProperty(const mitk::AnnotationProperty &other) : BaseProperty(other), m_Label(other.m_Label), m_Position(other.m_Position) { } const mitk::Point3D &mitk::AnnotationProperty::GetPosition() const { return m_Position; } void mitk::AnnotationProperty::SetPosition(const mitk::Point3D &position) { if (m_Position != position) { m_Position = position; this->Modified(); } } bool mitk::AnnotationProperty::IsEqual(const BaseProperty &property) const { return ((this->m_Label == static_cast(property).m_Label) && (this->m_Position == static_cast(property).m_Position)); } bool mitk::AnnotationProperty::Assign(const BaseProperty &property) { this->m_Label = static_cast(property).m_Label; this->m_Position = static_cast(property).m_Position; return true; } std::string mitk::AnnotationProperty::GetValueAsString() const { std::stringstream myStr; myStr << this->GetLabel() << this->GetPosition(); return myStr.str(); } itk::LightObject::Pointer mitk::AnnotationProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } + +bool mitk::AnnotationProperty::ToJSON(nlohmann::json& j) const +{ + j = nlohmann::json{ + {"Label", this->GetLabel()}, + {"Position", this->GetPosition()}}; + + return true; +} + +bool mitk::AnnotationProperty::FromJSON(const nlohmann::json& j) +{ + this->SetLabel(j["Label"].get()); + this->SetPosition(j["Position"].get()); + return true; +} diff --git a/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp b/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp index 6a3a8352e7..183e402aad 100644 --- a/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp +++ b/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp @@ -1,1185 +1,1190 @@ /*============================================================================ 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 "mitkApplyTransformMatrixOperation.h" #include "mitkBaseGeometry.h" #include "mitkGeometryTransformHolder.h" #include "mitkInteractionConst.h" #include "mitkMatrixConvert.h" #include "mitkModifiedLock.h" #include "mitkPointOperation.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkRotationOperation.h" #include "mitkScaleOperation.h" #include "mitkVector.h" #include "mitkMatrix.h" mitk::BaseGeometry::BaseGeometry() : Superclass(), mitk::OperationActor(), m_FrameOfReferenceID(0), m_IndexToWorldTransformLastModified(0), m_ImageGeometry(false), m_ModifiedLockFlag(false), m_ModifiedCalledFlag(false) { m_GeometryTransform = new GeometryTransformHolder(); Initialize(); } mitk::BaseGeometry::BaseGeometry(const BaseGeometry &other) : Superclass(), mitk::OperationActor(), m_FrameOfReferenceID(other.m_FrameOfReferenceID), m_IndexToWorldTransformLastModified(other.m_IndexToWorldTransformLastModified), m_ImageGeometry(other.m_ImageGeometry), m_ModifiedLockFlag(false), m_ModifiedCalledFlag(false) { m_GeometryTransform = new GeometryTransformHolder(*other.GetGeometryTransformHolder()); other.InitializeGeometry(this); } mitk::BaseGeometry::~BaseGeometry() { delete m_GeometryTransform; } void mitk::BaseGeometry::SetVtkMatrixDeepCopy(vtkTransform *vtktransform) { m_GeometryTransform->SetVtkMatrixDeepCopy(vtktransform); } const mitk::Point3D mitk::BaseGeometry::GetOrigin() const { return m_GeometryTransform->GetOrigin(); } void mitk::BaseGeometry::SetOrigin(const Point3D &origin) { mitk::ModifiedLock lock(this); if (origin != GetOrigin()) { m_GeometryTransform->SetOrigin(origin); Modified(); } } const mitk::Vector3D mitk::BaseGeometry::GetSpacing() const { return m_GeometryTransform->GetSpacing(); } void mitk::BaseGeometry::Initialize() { float b[6] = {0, 1, 0, 1, 0, 1}; SetFloatBounds(b); m_GeometryTransform->Initialize(); m_FrameOfReferenceID = 0; m_ImageGeometry = false; } void mitk::BaseGeometry::SetFloatBounds(const float bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const float *input = bounds; int i = 0; for (mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6; ++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } void mitk::BaseGeometry::SetFloatBounds(const double bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const double *input = bounds; int i = 0; for (mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6; ++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } /** Initialize the geometry */ void mitk::BaseGeometry::InitializeGeometry(BaseGeometry *newGeometry) const { newGeometry->SetBounds(m_BoundingBox->GetBounds()); newGeometry->SetFrameOfReferenceID(GetFrameOfReferenceID()); newGeometry->InitializeGeometryTransformHolder(this); newGeometry->m_ImageGeometry = m_ImageGeometry; } void mitk::BaseGeometry::InitializeGeometryTransformHolder(const BaseGeometry *otherGeometry) { this->m_GeometryTransform->Initialize(otherGeometry->GetGeometryTransformHolder()); } /** Set the bounds */ void mitk::BaseGeometry::SetBounds(const BoundsArrayType &bounds) { mitk::ModifiedLock lock(this); this->CheckBounds(bounds); m_BoundingBox = BoundingBoxType::New(); BoundingBoxType::PointsContainer::Pointer pointscontainer = BoundingBoxType::PointsContainer::New(); BoundingBoxType::PointType p; BoundingBoxType::PointIdentifier pointid; for (pointid = 0; pointid < 2; ++pointid) { unsigned int i; for (i = 0; i < m_NDimensions; ++i) { p[i] = bounds[2 * i + pointid]; } pointscontainer->InsertElement(pointid, p); } m_BoundingBox->SetPoints(pointscontainer); m_BoundingBox->ComputeBoundingBox(); this->Modified(); } void mitk::BaseGeometry::SetIndexToWorldTransform(mitk::AffineTransform3D *transform) { mitk::ModifiedLock lock(this); CheckIndexToWorldTransform(transform); m_GeometryTransform->SetIndexToWorldTransform(transform); Modified(); } void mitk::BaseGeometry::SetIndexToWorldTransformWithoutChangingSpacing(mitk::AffineTransform3D *transform) { // security check mitk::Vector3D originalSpacing = this->GetSpacing(); mitk::ModifiedLock lock(this); CheckIndexToWorldTransform(transform); m_GeometryTransform->SetIndexToWorldTransformWithoutChangingSpacing(transform); Modified(); // Security check. Spacig must not have changed if (!mitk::Equal(originalSpacing, this->GetSpacing())) { MITK_WARN << "Spacing has changed in a method, where the spacing must not change."; assert(false); } } const mitk::BaseGeometry::BoundsArrayType mitk::BaseGeometry::GetBounds() const { assert(m_BoundingBox.IsNotNull()); return m_BoundingBox->GetBounds(); } bool mitk::BaseGeometry::IsValid() const { return true; } void mitk::BaseGeometry::SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing) { PreSetSpacing(aSpacing); _SetSpacing(aSpacing, enforceSetSpacing); } void mitk::BaseGeometry::_SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing) { m_GeometryTransform->SetSpacing(aSpacing, enforceSetSpacing); } mitk::Vector3D mitk::BaseGeometry::GetAxisVector(unsigned int direction) const { Vector3D frontToBack; frontToBack.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction).as_ref()); frontToBack *= GetExtent(direction); return frontToBack; } mitk::ScalarType mitk::BaseGeometry::GetExtent(unsigned int direction) const { assert(m_BoundingBox.IsNotNull()); if (direction >= m_NDimensions) mitkThrow() << "Direction is too big. This geometry is for 3D Data"; BoundsArrayType bounds = m_BoundingBox->GetBounds(); return bounds[direction * 2 + 1] - bounds[direction * 2]; } bool mitk::BaseGeometry::Is2DConvertable() { bool isConvertableWithoutLoss = true; do { if (this->GetSpacing()[2] != 1) { isConvertableWithoutLoss = false; break; } if (this->GetOrigin()[2] != 0) { isConvertableWithoutLoss = false; break; } mitk::Vector3D col0, col1, col2; col0.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(0).as_ref()); col1.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(1).as_ref()); col2.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(2).as_ref()); if ((col0[2] != 0) || (col1[2] != 0) || (col2[0] != 0) || (col2[1] != 0) || (col2[2] != 1)) { isConvertableWithoutLoss = false; break; } } while (false); return isConvertableWithoutLoss; } mitk::Point3D mitk::BaseGeometry::GetCenter() const { assert(m_BoundingBox.IsNotNull()); Point3D c = m_BoundingBox->GetCenter(); if (m_ImageGeometry) { // Get Center returns the middel of min and max pixel index. In corner based images, this is the right position. // In center based images (imageGeometry == true), the index needs to be shifted back. c[0] -= 0.5; c[1] -= 0.5; c[2] -= 0.5; } this->IndexToWorld(c, c); return c; } double mitk::BaseGeometry::GetDiagonalLength2() const { Vector3D diagonalvector = GetCornerPoint() - GetCornerPoint(false, false, false); return diagonalvector.GetSquaredNorm(); } double mitk::BaseGeometry::GetDiagonalLength() const { return sqrt(GetDiagonalLength2()); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(int id) const { assert(id >= 0); assert(this->IsBoundingBoxNull() == false); BoundingBox::BoundsArrayType bounds = this->GetBoundingBox()->GetBounds(); Point3D cornerpoint; switch (id) { case 0: FillVector3D(cornerpoint, bounds[0], bounds[2], bounds[4]); break; case 1: FillVector3D(cornerpoint, bounds[0], bounds[2], bounds[5]); break; case 2: FillVector3D(cornerpoint, bounds[0], bounds[3], bounds[4]); break; case 3: FillVector3D(cornerpoint, bounds[0], bounds[3], bounds[5]); break; case 4: FillVector3D(cornerpoint, bounds[1], bounds[2], bounds[4]); break; case 5: FillVector3D(cornerpoint, bounds[1], bounds[2], bounds[5]); break; case 6: FillVector3D(cornerpoint, bounds[1], bounds[3], bounds[4]); break; case 7: FillVector3D(cornerpoint, bounds[1], bounds[3], bounds[5]); break; default: { itkExceptionMacro(<< "A cube only has 8 corners. These are labeled 0-7."); } } if (m_ImageGeometry) { // Here i have to adjust the 0.5 offset manually, because the cornerpoint is the corner of the // bounding box. The bounding box itself is no image, so it is corner-based FillVector3D(cornerpoint, cornerpoint[0] - 0.5, cornerpoint[1] - 0.5, cornerpoint[2] - 0.5); } return this->GetIndexToWorldTransform()->TransformPoint(cornerpoint); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(bool xFront, bool yFront, bool zFront) const { assert(this->IsBoundingBoxNull() == false); BoundingBox::BoundsArrayType bounds = this->GetBoundingBox()->GetBounds(); Point3D cornerpoint; cornerpoint[0] = (xFront ? bounds[0] : bounds[1]); cornerpoint[1] = (yFront ? bounds[2] : bounds[3]); cornerpoint[2] = (zFront ? bounds[4] : bounds[5]); if (m_ImageGeometry) { // Here i have to adjust the 0.5 offset manually, because the cornerpoint is the corner of the // bounding box. The bounding box itself is no image, so it is corner-based FillVector3D(cornerpoint, cornerpoint[0] - 0.5, cornerpoint[1] - 0.5, cornerpoint[2] - 0.5); } return this->GetIndexToWorldTransform()->TransformPoint(cornerpoint); } mitk::ScalarType mitk::BaseGeometry::GetExtentInMM(int direction) const { return this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction).magnitude() * GetExtent(direction); } void mitk::BaseGeometry::SetExtentInMM(int direction, ScalarType extentInMM) { mitk::ModifiedLock lock(this); ScalarType len = GetExtentInMM(direction); if (fabs(len - extentInMM) >= mitk::eps) { AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = m_GeometryTransform->GetVnlMatrix(); if (len > extentInMM) vnlmatrix.set_column(direction, vnlmatrix.get_column(direction) / len * extentInMM); else vnlmatrix.set_column(direction, vnlmatrix.get_column(direction) * extentInMM / len); Matrix3D matrix; matrix = vnlmatrix; m_GeometryTransform->SetMatrix(matrix); Modified(); } } bool mitk::BaseGeometry::IsInside(const mitk::Point3D &p) const { mitk::Point3D index; WorldToIndex(p, index); return IsIndexInside(index); } bool mitk::BaseGeometry::IsIndexInside(const mitk::Point3D &index) const { bool inside = false; // if it is an image geometry, we need to convert the index to discrete values // this is done by applying the rounding function also used in WorldToIndex (see line 323) if (m_ImageGeometry) { mitk::Point3D discretIndex; discretIndex[0] = itk::Math::RoundHalfIntegerUp(index[0]); discretIndex[1] = itk::Math::RoundHalfIntegerUp(index[1]); discretIndex[2] = itk::Math::RoundHalfIntegerUp(index[2]); inside = this->GetBoundingBox()->IsInside(discretIndex); // we have to check if the index is at the upper border of each dimension, // because the boundingbox is not centerbased if (inside) { const BoundingBox::BoundsArrayType &bounds = this->GetBoundingBox()->GetBounds(); if ((discretIndex[0] == bounds[1]) || (discretIndex[1] == bounds[3]) || (discretIndex[2] == bounds[5])) inside = false; } } else inside = this->GetBoundingBox()->IsInside(index); return inside; } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D &pt_mm, mitk::Point3D &pt_units) const { mitk::Vector3D tempIn, tempOut; const TransformType::OffsetType &offset = this->GetIndexToWorldTransform()->GetOffset(); tempIn = pt_mm.GetVectorFromOrigin() - offset; WorldToIndex(tempIn, tempOut); pt_units = Point3D(tempOut); } void mitk::BaseGeometry::WorldToIndex(const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { // Get WorldToIndex transform if (m_IndexToWorldTransformLastModified != this->GetIndexToWorldTransform()->GetMTime()) { if (!m_InvertedTransform) { m_InvertedTransform = TransformType::New(); } if (!this->GetIndexToWorldTransform()->GetInverse(m_InvertedTransform.GetPointer())) { itkExceptionMacro("Internal ITK matrix inversion error, cannot proceed."); } m_IndexToWorldTransformLastModified = this->GetIndexToWorldTransform()->GetMTime(); } // Check for valid matrix inversion const TransformType::MatrixType &inverse = m_InvertedTransform->GetMatrix(); if (inverse.GetVnlMatrix().has_nans()) { itkExceptionMacro("Internal ITK matrix inversion error, cannot proceed. Matrix was: " << std::endl << this->GetIndexToWorldTransform()->GetMatrix() << "Suggested inverted matrix is:" << std::endl << inverse); } vec_units = inverse * vec_mm; } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D & /*atPt3d_mm*/, const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { MITK_WARN << "Warning! Call of the deprecated function BaseGeometry::WorldToIndex(point, vec, vec). Use " "BaseGeometry::WorldToIndex(vec, vec) instead!"; this->WorldToIndex(vec_mm, vec_units); } mitk::VnlVector mitk::BaseGeometry::GetOriginVnl() const { return GetOrigin().GetVnlVector(); } vtkLinearTransform *mitk::BaseGeometry::GetVtkTransform() const { return m_GeometryTransform->GetVtkTransform(); } void mitk::BaseGeometry::SetIdentity() { mitk::ModifiedLock lock(this); m_GeometryTransform->SetIdentity(); Modified(); } void mitk::BaseGeometry::Compose(const mitk::BaseGeometry::TransformType *other, bool pre) { mitk::ModifiedLock lock(this); m_GeometryTransform->Compose(other, pre); Modified(); } void mitk::BaseGeometry::Compose(const vtkMatrix4x4 *vtkmatrix, bool pre) { mitk::BaseGeometry::TransformType::Pointer itkTransform = mitk::BaseGeometry::TransformType::New(); TransferVtkMatrixToItkTransform(vtkmatrix, itkTransform.GetPointer()); Compose(itkTransform, pre); } void mitk::BaseGeometry::Translate(const Vector3D &vector) { if ((vector[0] != 0) || (vector[1] != 0) || (vector[2] != 0)) { this->SetOrigin(this->GetOrigin() + vector); } } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D &pt_units, mitk::Point3D &pt_mm) const { pt_mm = this->GetIndexToWorldTransform()->TransformPoint(pt_units); } void mitk::BaseGeometry::IndexToWorld(const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { vec_mm = this->GetIndexToWorldTransform()->TransformVector(vec_units); } void mitk::BaseGeometry::ExecuteOperation(Operation *operation) { mitk::ModifiedLock lock(this); vtkTransform *vtktransform = vtkTransform::New(); vtktransform->SetMatrix(this->GetVtkMatrix()); switch (operation->GetOperationType()) { case OpNOTHING: break; case OpMOVE: { auto *pointOp = dynamic_cast(operation); if (pointOp == nullptr) { MITK_ERROR << "Point move operation is null!"; return; } mitk::Point3D newPos = pointOp->GetPoint(); ScalarType data[3]; vtktransform->GetPosition(data); vtktransform->PostMultiply(); vtktransform->Translate(newPos[0], newPos[1], newPos[2]); vtktransform->PreMultiply(); break; } case OpSCALE: { auto *scaleOp = dynamic_cast(operation); if (scaleOp == nullptr) { MITK_ERROR << "Scale operation is null!"; return; } mitk::Point3D newScale = scaleOp->GetScaleFactor(); ScalarType scalefactor[3]; scalefactor[0] = 1 + (newScale[0] / GetMatrixColumn(0).magnitude()); scalefactor[1] = 1 + (newScale[1] / GetMatrixColumn(1).magnitude()); scalefactor[2] = 1 + (newScale[2] / GetMatrixColumn(2).magnitude()); mitk::Point3D anchor = scaleOp->GetScaleAnchorPoint(); vtktransform->PostMultiply(); vtktransform->Translate(-anchor[0], -anchor[1], -anchor[2]); vtktransform->Scale(scalefactor[0], scalefactor[1], scalefactor[2]); vtktransform->Translate(anchor[0], anchor[1], anchor[2]); break; } case OpROTATE: { auto *rotateOp = dynamic_cast(operation); if (rotateOp == nullptr) { MITK_ERROR << "Rotation operation is null!"; return; } Vector3D rotationVector = rotateOp->GetVectorOfRotation(); Point3D center = rotateOp->GetCenterOfRotation(); ScalarType angle = rotateOp->GetAngleOfRotation(); vtktransform->PostMultiply(); vtktransform->Translate(-center[0], -center[1], -center[2]); vtktransform->RotateWXYZ(angle, rotationVector[0], rotationVector[1], rotationVector[2]); vtktransform->Translate(center[0], center[1], center[2]); vtktransform->PreMultiply(); break; } case OpRESTOREPLANEPOSITION: { // Copy necessary to avoid vtk warning vtkMatrix4x4 *matrix = vtkMatrix4x4::New(); TransferItkTransformToVtkMatrix( dynamic_cast(operation)->GetTransform().GetPointer(), matrix); vtktransform->SetMatrix(matrix); matrix->Delete(); break; } case OpAPPLYTRANSFORMMATRIX: { auto *applyMatrixOp = dynamic_cast(operation); vtktransform->SetMatrix(applyMatrixOp->GetMatrix()); break; } default: vtktransform->Delete(); return; } this->SetVtkMatrixDeepCopy(vtktransform); Modified(); vtktransform->Delete(); } mitk::VnlVector mitk::BaseGeometry::GetMatrixColumn(unsigned int direction) const { return this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction).as_ref(); } mitk::BoundingBox::Pointer mitk::BaseGeometry::CalculateBoundingBoxRelativeToTransform( const mitk::AffineTransform3D *transform) const { mitk::BoundingBox::PointsContainer::Pointer pointscontainer = mitk::BoundingBox::PointsContainer::New(); mitk::BoundingBox::PointIdentifier pointid = 0; unsigned char i; if (transform != nullptr) { mitk::AffineTransform3D::Pointer inverse = mitk::AffineTransform3D::New(); transform->GetInverse(inverse); for (i = 0; i < 8; ++i) pointscontainer->InsertElement(pointid++, inverse->TransformPoint(GetCornerPoint(i))); } else { for (i = 0; i < 8; ++i) pointscontainer->InsertElement(pointid++, GetCornerPoint(i)); } mitk::BoundingBox::Pointer result = mitk::BoundingBox::New(); result->SetPoints(pointscontainer); result->ComputeBoundingBox(); return result; } const std::string mitk::BaseGeometry::GetTransformAsString(TransformType *transformType) { std::ostringstream out; out << '['; for (int i = 0; i < 3; ++i) { out << '['; for (int j = 0; j < 3; ++j) out << transformType->GetMatrix().GetVnlMatrix().get(i, j) << ' '; out << ']'; } out << "]["; for (int i = 0; i < 3; ++i) out << transformType->GetOffset()[i] << ' '; out << "]\0"; return out.str(); } void mitk::BaseGeometry::SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4 *vtkmatrix) { m_GeometryTransform->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); } void mitk::BaseGeometry::SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkMatrix4x4 *vtkmatrix) { m_GeometryTransform->SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkmatrix); } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D & /*atPt3d_units*/, const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { MITK_WARN << "Warning! Call of the deprecated function BaseGeometry::IndexToWorld(point, vec, vec). Use " "BaseGeometry::IndexToWorld(vec, vec) instead!"; // vec_mm = m_IndexToWorldTransform->TransformVector(vec_units); this->IndexToWorld(vec_units, vec_mm); } vtkMatrix4x4 *mitk::BaseGeometry::GetVtkMatrix() { return m_GeometryTransform->GetVtkMatrix(); } +const vtkMatrix4x4* mitk::BaseGeometry::GetVtkMatrix() const +{ + return m_GeometryTransform->GetVtkMatrix(); +} + bool mitk::BaseGeometry::IsBoundingBoxNull() const { return m_BoundingBox.IsNull(); } bool mitk::BaseGeometry::IsIndexToWorldTransformNull() const { return m_GeometryTransform->IsIndexToWorldTransformNull(); } void mitk::BaseGeometry::ChangeImageGeometryConsideringOriginOffset(const bool isAnImageGeometry) { // If Geometry is switched to ImageGeometry, you have to put an offset to the origin, because // imageGeometries origins are pixel-center-based // ... and remove the offset, if you switch an imageGeometry back to a normal geometry // For more information please see the Geometry documentation page if (m_ImageGeometry == isAnImageGeometry) return; const BoundingBox::BoundsArrayType &boundsarray = this->GetBoundingBox()->GetBounds(); Point3D originIndex; FillVector3D(originIndex, boundsarray[0], boundsarray[2], boundsarray[4]); if (isAnImageGeometry == true) FillVector3D(originIndex, originIndex[0] + 0.5, originIndex[1] + 0.5, originIndex[2] + 0.5); else FillVector3D(originIndex, originIndex[0] - 0.5, originIndex[1] - 0.5, originIndex[2] - 0.5); Point3D originWorld; originWorld = GetIndexToWorldTransform()->TransformPoint(originIndex); // instead could as well call IndexToWorld(originIndex,originWorld); SetOrigin(originWorld); this->SetImageGeometry(isAnImageGeometry); } void mitk::BaseGeometry::PrintSelf(std::ostream &os, itk::Indent indent) const { os << indent << " IndexToWorldTransform: "; if (this->IsIndexToWorldTransformNull()) os << "nullptr" << std::endl; else { // from itk::MatrixOffsetTransformBase unsigned int i, j; os << std::endl; os << indent << "Matrix: " << std::endl; for (i = 0; i < 3; i++) { os << indent.GetNextIndent(); for (j = 0; j < 3; j++) { os << this->GetIndexToWorldTransform()->GetMatrix()[i][j] << " "; } os << std::endl; } os << indent << "Offset: " << this->GetIndexToWorldTransform()->GetOffset() << std::endl; os << indent << "Center: " << this->GetIndexToWorldTransform()->GetCenter() << std::endl; os << indent << "Translation: " << this->GetIndexToWorldTransform()->GetTranslation() << std::endl; auto inverse = mitk::AffineTransform3D::New(); if (this->GetIndexToWorldTransform()->GetInverse(inverse)) { os << indent << "Inverse: " << std::endl; for (i = 0; i < 3; i++) { os << indent.GetNextIndent(); for (j = 0; j < 3; j++) { os << inverse->GetMatrix()[i][j] << " "; } os << std::endl; } } // from itk::ScalableAffineTransform os << indent << "Scale : "; for (i = 0; i < 3; i++) { os << this->GetIndexToWorldTransform()->GetScale()[i] << " "; } os << std::endl; } os << indent << " BoundingBox: "; if (this->IsBoundingBoxNull()) os << "nullptr" << std::endl; else { os << indent << "( "; for (unsigned int i = 0; i < 3; i++) { os << this->GetBoundingBox()->GetBounds()[2 * i] << "," << this->GetBoundingBox()->GetBounds()[2 * i + 1] << " "; } os << " )" << std::endl; } os << indent << " Origin: " << this->GetOrigin() << std::endl; os << indent << " ImageGeometry: " << this->GetImageGeometry() << std::endl; os << indent << " Spacing: " << this->GetSpacing() << std::endl; } void mitk::BaseGeometry::Modified() const { if (!m_ModifiedLockFlag) Superclass::Modified(); else m_ModifiedCalledFlag = true; } mitk::AffineTransform3D *mitk::BaseGeometry::GetIndexToWorldTransform() { return m_GeometryTransform->GetIndexToWorldTransform(); } const mitk::AffineTransform3D *mitk::BaseGeometry::GetIndexToWorldTransform() const { return m_GeometryTransform->GetIndexToWorldTransform(); } const mitk::GeometryTransformHolder *mitk::BaseGeometry::GetGeometryTransformHolder() const { return m_GeometryTransform; } void mitk::BaseGeometry::MapAxesToOrientations(int axes[]) const { auto affineTransform = this->GetIndexToWorldTransform(); auto matrix = affineTransform->GetMatrix(); matrix.GetVnlMatrix().normalize_columns(); auto inverseMatrix = matrix.GetInverse(); bool mapped[3] = {false, false, false}; // We need to allow an epsilon difference to ignore rounding. const double eps = 0.0001; for (int orientation = 0; orientation < 3; ++orientation) { auto absX = std::abs(inverseMatrix[0][orientation]); auto absY = std::abs(inverseMatrix[1][orientation]); auto absZ = std::abs(inverseMatrix[2][orientation]); // First we check if there is a single maximum value. If there is, we found the axis // that corresponds to the given orientation. If there is no single maximum value, // we choose one from the the two or three axes that have the maximum value, but we // need to make sure that we do not map the same axis to different orientations. // Equal values are valid if the volume is rotated by exactly 45 degrees around one // axis. If the volume is rotated by 45 degrees around two axes, you will get single // maximum values at the same axes for two different orientations. In this case, // the axis is mapped to one of the orientations, and for the other orientation we // choose a different axis that has not been mapped yet, even if it is not a maximum. if (absX > absY + eps) { if (absX > absZ + eps) { // x is the greatest int axis = !mapped[0] ? 0 : !mapped[1] ? 1 : 2; axes[orientation] = axis; mapped[axis] = true; } else { // z is the greatest OR x and z are equal and greater than y int axis = !mapped[2] ? 2 : !mapped[0] ? 0 : 1; axes[orientation] = axis; mapped[axis] = true; } } else if (absY > absX + eps) { if (absY > absZ + eps) { // y is the greatest int axis = !mapped[1] ? 1 : !mapped[2] ? 2 : 0; axes[orientation] = axis; mapped[axis] = true; } else { // z is the greatest OR y and z are equal and greater than x int axis = !mapped[2] ? 2 : !mapped[1] ? 1 : 0; axes[orientation] = axis; mapped[axis] = true; } } else { if (absZ > absX + eps) { // z is the greatest int axis = !mapped[2] ? 2 : !mapped[0] ? 0 : 1; axes[orientation] = axis; mapped[axis] = true; } else { // x and y are equal and greater than z OR x and y and z are equal int axis = !mapped[0] ? 0 : !mapped[1] ? 1 : 2; axes[orientation] = axis; mapped[axis] = true; } } } assert(mapped[0] && mapped[1] && mapped[2]); } bool mitk::Equal(const mitk::BaseGeometry::BoundingBoxType &leftHandSide, const mitk::BaseGeometry::BoundingBoxType &rightHandSide, ScalarType eps, bool verbose) { bool result = true; BaseGeometry::BoundsArrayType rightBounds = rightHandSide.GetBounds(); BaseGeometry::BoundsArrayType leftBounds = leftHandSide.GetBounds(); BaseGeometry::BoundsArrayType::Iterator itLeft = leftBounds.Begin(); for (BaseGeometry::BoundsArrayType::Iterator itRight = rightBounds.Begin(); itRight != rightBounds.End(); ++itRight) { if ((!mitk::Equal(*itLeft, *itRight, eps))) { if (verbose) { MITK_INFO << "[( Geometry3D::BoundingBoxType )] bounds are not equal."; MITK_INFO << "rightHandSide is " << setprecision(12) << *itRight << " : leftHandSide is " << *itLeft << " and tolerance is " << eps; } result = false; } itLeft++; } return result; } bool mitk::Equal(const mitk::BaseGeometry &leftHandSide, const mitk::BaseGeometry &rightHandSide, ScalarType coordinateEps, ScalarType directionEps, bool verbose) { bool result = true; // Compare spacings if (!mitk::Equal(leftHandSide.GetSpacing(), rightHandSide.GetSpacing(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Spacing differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetSpacing() << " : leftHandSide is " << leftHandSide.GetSpacing() << " and tolerance is " << coordinateEps; } result = false; } // Compare Origins if (!mitk::Equal(leftHandSide.GetOrigin(), rightHandSide.GetOrigin(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Origin differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetOrigin() << " : leftHandSide is " << leftHandSide.GetOrigin() << " and tolerance is " << coordinateEps; } result = false; } // Compare Axis and Extents for (unsigned int i = 0; i < 3; ++i) { if (!mitk::Equal(leftHandSide.GetAxisVector(i), rightHandSide.GetAxisVector(i), directionEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] AxisVector #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetAxisVector(i) << " : leftHandSide is " << leftHandSide.GetAxisVector(i) << " and tolerance is " << directionEps; } result = false; } if (!mitk::Equal(leftHandSide.GetExtent(i), rightHandSide.GetExtent(i), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Extent #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetExtent(i) << " : leftHandSide is " << leftHandSide.GetExtent(i) << " and tolerance is " << coordinateEps; } result = false; } } // Compare ImageGeometry Flag if (rightHandSide.GetImageGeometry() != leftHandSide.GetImageGeometry()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetImageGeometry is different."; MITK_INFO << "rightHandSide is " << rightHandSide.GetImageGeometry() << " : leftHandSide is " << leftHandSide.GetImageGeometry(); } result = false; } // Compare FrameOfReference ID if (rightHandSide.GetFrameOfReferenceID() != leftHandSide.GetFrameOfReferenceID()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetFrameOfReferenceID is different."; MITK_INFO << "rightHandSide is " << rightHandSide.GetFrameOfReferenceID() << " : leftHandSide is " << leftHandSide.GetFrameOfReferenceID(); } result = false; } // Compare BoundingBoxes if (!mitk::Equal(*leftHandSide.GetBoundingBox(), *rightHandSide.GetBoundingBox(), coordinateEps, verbose)) { result = false; } // Compare IndexToWorldTransform Matrix if (!mitk::Equal(*leftHandSide.GetIndexToWorldTransform(), *rightHandSide.GetIndexToWorldTransform(), directionEps, verbose)) { result = false; } return result; } bool mitk::Equal(const mitk::BaseGeometry& leftHandSide, const mitk::BaseGeometry& rightHandSide, ScalarType eps, bool verbose) { return Equal(leftHandSide, rightHandSide, eps, eps, verbose); } bool mitk::Equal(const mitk::BaseGeometry::TransformType &leftHandSide, const mitk::BaseGeometry::TransformType &rightHandSide, ScalarType eps, bool verbose) { // Compare IndexToWorldTransform Matrix if (!mitk::MatrixEqualElementWise(leftHandSide.GetMatrix(), rightHandSide.GetMatrix(), eps)) { if (verbose) { MITK_INFO << "[( Geometry3D::TransformType )] Index to World Transformation matrix differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetMatrix() << " : leftHandSide is " << leftHandSide.GetMatrix() << " and tolerance is " << eps; } return false; } return true; } bool mitk::IsSubGeometry(const mitk::BaseGeometry& testGeo, const mitk::BaseGeometry& referenceGeo, ScalarType coordinateEps, ScalarType directionEps, bool verbose) { bool result = true; // Compare spacings (must be equal) const auto testedSpacing = testGeo.GetSpacing(); if (!mitk::Equal(testedSpacing, referenceGeo.GetSpacing(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Spacing differs."; MITK_INFO << "testedGeometry is " << setprecision(12) << testedSpacing << " : referenceGeometry is " << referenceGeo.GetSpacing() << " and tolerance is " << coordinateEps; } result = false; } // Compare ImageGeometry Flag (must be equal) if (referenceGeo.GetImageGeometry() != testGeo.GetImageGeometry()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetImageGeometry is different."; MITK_INFO << "referenceGeo is " << referenceGeo.GetImageGeometry() << " : testGeo is " << testGeo.GetImageGeometry(); } result = false; } // Compare IndexToWorldTransform Matrix (must be equal -> same axis directions) if (!Equal(*(testGeo.GetIndexToWorldTransform()), *(referenceGeo.GetIndexToWorldTransform()), directionEps, verbose)) { result = false; } //check if the geometry is within or equal to the bounds of reference geomentry. for (int i = 0; i<8; ++i) { auto testCorner = testGeo.GetCornerPoint(i); bool isInside = false; mitk::Point3D testCornerIndex; referenceGeo.WorldToIndex(testCorner, testCornerIndex); std::bitset bs(i); //To regard the coordinateEps, we subtract or add it to the index elements //depending on whether it was constructed by a lower or an upper bound value //(see implementation of BaseGeometry::GetCorner()). if (bs.test(0)) { testCornerIndex[2] -= coordinateEps; } else { testCornerIndex[2] += coordinateEps; } if (bs.test(1)) { testCornerIndex[1] -= coordinateEps; } else { testCornerIndex[1] += coordinateEps; } if (bs.test(2)) { testCornerIndex[0] -= coordinateEps; } else { testCornerIndex[0] += coordinateEps; } isInside = referenceGeo.IsIndexInside(testCornerIndex); if (!isInside) { if (verbose) { MITK_INFO << "[( Geometry3D )] corner point is not inside. "; MITK_INFO << "referenceGeo is " << setprecision(12) << referenceGeo << " : tested corner is " << testGeo.GetCornerPoint(i); } result = false; } } // check grid of test geometry is on the grid of the reference geometry. This is important as the // boundingbox is only checked for containing the tested geometry, but if a corner (one is enough // as we know that axis and spacing are equal, due to equal transfor (see above)) of the tested geometry // is on the grid it is really a sub geometry (as they have the same spacing and axis). auto cornerOffset = testGeo.GetCornerPoint(0) - referenceGeo.GetCornerPoint(0); mitk::Vector3D cornerIndexOffset; referenceGeo.WorldToIndex(cornerOffset, cornerIndexOffset); for (unsigned int i = 0; i < 3; ++i) { auto pixelCountContinous = cornerIndexOffset[i]; auto pixelCount = std::round(pixelCountContinous); if (std::abs(pixelCount - pixelCountContinous) > coordinateEps) { if (verbose) { MITK_INFO << "[( Geometry3D )] Tested geometry is not on the grid of the reference geometry. "; MITK_INFO << "referenceGeo is " << setprecision(15) << referenceGeo << " : tested corner offset in pixels is " << pixelCountContinous << " for axis "<Modified(); } } const Point3D &ClippingProperty::GetOrigin() const { return m_Origin; } void ClippingProperty::SetOrigin(const Point3D &origin) { if (m_Origin != origin) { m_Origin = origin; this->Modified(); } } const Vector3D &ClippingProperty::GetNormal() const { return m_Normal; } void ClippingProperty::SetNormal(const Vector3D &normal) { if (m_Normal != normal) { m_Normal = normal; this->Modified(); } } bool ClippingProperty::IsEqual(const BaseProperty &property) const { return ((this->m_ClippingEnabled == static_cast(property).m_ClippingEnabled) && (this->m_Origin == static_cast(property).m_Origin) && (this->m_Normal == static_cast(property).m_Normal)); } bool ClippingProperty::Assign(const BaseProperty &property) { this->m_ClippingEnabled = static_cast(property).m_ClippingEnabled; this->m_Origin = static_cast(property).m_Origin; this->m_Normal = static_cast(property).m_Normal; return true; } std::string ClippingProperty::GetValueAsString() const { std::stringstream myStr; myStr << this->GetClippingEnabled() << this->GetOrigin() << this->GetNormal(); return myStr.str(); } + bool ClippingProperty::ToJSON(nlohmann::json& j) const + { + j = nlohmann::json{ + {"Enabled", this->GetClippingEnabled()}, + {"Origin", this->GetOrigin()}, + {"Normal", this->GetNormal()}}; + + return true; + } + + bool ClippingProperty::FromJSON(const nlohmann::json& j) + { + this->SetClippingEnabled(j["Enabled"].get()); + this->SetOrigin(j["Origin"].get()); + this->SetNormal(j["Normal"].get()); + + return true; + } + itk::LightObject::Pointer ClippingProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } } // namespace diff --git a/Modules/Core/src/DataManagement/mitkColorProperty.cpp b/Modules/Core/src/DataManagement/mitkColorProperty.cpp index 9288a7343e..25d48f86a3 100644 --- a/Modules/Core/src/DataManagement/mitkColorProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkColorProperty.cpp @@ -1,90 +1,103 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkColorProperty.h" #include +#include mitk::ColorProperty::ColorProperty() : m_Color(0.0f) { } mitk::ColorProperty::ColorProperty(const mitk::ColorProperty &other) : BaseProperty(other), m_Color(other.m_Color) { } mitk::ColorProperty::ColorProperty(const float color[3]) : m_Color(color) { } mitk::ColorProperty::ColorProperty(const float red, const float green, const float blue) { m_Color.Set(red, green, blue); } mitk::ColorProperty::ColorProperty(const mitk::Color &color) : m_Color(color) { } bool mitk::ColorProperty::IsEqual(const BaseProperty &property) const { return this->m_Color == static_cast(property).m_Color; } bool mitk::ColorProperty::Assign(const BaseProperty &property) { this->m_Color = static_cast(property).m_Color; return true; } const mitk::Color &mitk::ColorProperty::GetColor() const { return m_Color; } void mitk::ColorProperty::SetColor(const mitk::Color &color) { if (m_Color != color) { m_Color = color; Modified(); } } void mitk::ColorProperty::SetValue(const mitk::Color &color) { SetColor(color); } void mitk::ColorProperty::SetColor(float red, float green, float blue) { float tmp[3] = {red, green, blue}; SetColor(mitk::Color(tmp)); } std::string mitk::ColorProperty::GetValueAsString() const { std::stringstream myStr; myStr.imbue(std::locale::classic()); myStr << GetValue(); return myStr.str(); } const mitk::Color &mitk::ColorProperty::GetValue() const { return GetColor(); } +bool mitk::ColorProperty::ToJSON(nlohmann::json& j) const +{ + j = this->GetColor(); + return true; +} + +bool mitk::ColorProperty::FromJSON(const nlohmann::json& j) +{ + this->SetColor(j.get()); + return true; +} + itk::LightObject::Pointer mitk::ColorProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkEnumerationProperty.cpp b/Modules/Core/src/DataManagement/mitkEnumerationProperty.cpp index e6e3d27852..2019bf5375 100644 --- a/Modules/Core/src/DataManagement/mitkEnumerationProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkEnumerationProperty.cpp @@ -1,166 +1,183 @@ /*============================================================================ 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 mitk::EnumerationProperty::EnumerationProperty() : m_CurrentValue(0) { } mitk::EnumerationProperty::EnumerationProperty(const EnumerationProperty &other) : BaseProperty(other), m_CurrentValue(other.m_CurrentValue), m_IdMap(other.m_IdMap), m_NameMap(other.m_NameMap) { } bool mitk::EnumerationProperty::AddEnum(const std::string &name, const IdType &id) { if (false == this->IsValidEnumerationValue(id) && false == this->IsValidEnumerationValue(name)) { this->GetEnumIds().insert(std::make_pair(id, name)); this->GetEnumStrings().insert(std::make_pair(name, id)); return true; } return false; } bool mitk::EnumerationProperty::SetValue(const std::string &name) { if (this->IsValidEnumerationValue(name)) { m_CurrentValue = this->GetEnumId(name); this->Modified(); return true; } return false; } bool mitk::EnumerationProperty::SetValue(const IdType &id) { if (this->IsValidEnumerationValue(id)) { m_CurrentValue = id; this->Modified(); return true; } return false; } mitk::EnumerationProperty::IdType mitk::EnumerationProperty::GetValueAsId() const { return m_CurrentValue; } std::string mitk::EnumerationProperty::GetValueAsString() const { return this->GetEnumString(m_CurrentValue); } void mitk::EnumerationProperty::Clear() { this->GetEnumIds().clear(); this->GetEnumStrings().clear(); m_CurrentValue = 0; } mitk::EnumerationProperty::EnumIdsContainerType::size_type mitk::EnumerationProperty::Size() const { return this->GetEnumIds().size(); } mitk::EnumerationProperty::EnumConstIterator mitk::EnumerationProperty::Begin() const { return this->GetEnumIds().cbegin(); } mitk::EnumerationProperty::EnumConstIterator mitk::EnumerationProperty::End() const { return this->GetEnumIds().cend(); } std::string mitk::EnumerationProperty::GetEnumString(const IdType &id) const { return this->IsValidEnumerationValue(id) ? this->GetEnumIds().find(id)->second : std::string("invalid enum id or enums empty"); } mitk::EnumerationProperty::IdType mitk::EnumerationProperty::GetEnumId(const std::string &name) const { return this->IsValidEnumerationValue(name) ? this->GetEnumStrings().find(name)->second : 0; } bool mitk::EnumerationProperty::IsEqual(const BaseProperty &property) const { const auto &other = static_cast(property); return this->Size() == other.Size() && this->GetValueAsId() == other.GetValueAsId() && std::equal(this->Begin(), this->End(), other.Begin()); } bool mitk::EnumerationProperty::Assign(const BaseProperty &property) { const auto &other = static_cast(property); this->GetEnumIds() = other.GetEnumIds(); this->GetEnumStrings() = other.GetEnumStrings(); m_CurrentValue = other.m_CurrentValue; return true; } bool mitk::EnumerationProperty::IsValidEnumerationValue(const IdType &id) const { return this->GetEnumIds().end() != this->GetEnumIds().find(id); } bool mitk::EnumerationProperty::IsValidEnumerationValue(const std::string &name) const { return this->GetEnumStrings().end() != this->GetEnumStrings().find(name); } mitk::EnumerationProperty::EnumIdsContainerType & mitk::EnumerationProperty::GetEnumIds() { return m_IdMap; } const mitk::EnumerationProperty::EnumIdsContainerType & mitk::EnumerationProperty::GetEnumIds() const { return m_IdMap; } mitk::EnumerationProperty::EnumStringsContainerType & mitk::EnumerationProperty::GetEnumStrings() { return m_NameMap; } const mitk::EnumerationProperty::EnumStringsContainerType & mitk::EnumerationProperty::GetEnumStrings() const { return m_NameMap; } + +bool mitk::EnumerationProperty::ToJSON(nlohmann::json& j) const +{ + j = this->GetValueAsString(); + return true; +} + +bool mitk::EnumerationProperty::FromJSON(const nlohmann::json& j) +{ + auto name = j.get(); + + if (!this->SetValue(name)) + mitkThrow() << '"' << name << "\" is not a valid enumeration value for " << this->GetNameOfClass() << '!'; + + return true; +} diff --git a/Modules/Core/src/DataManagement/mitkGroupTagProperty.cpp b/Modules/Core/src/DataManagement/mitkGroupTagProperty.cpp index 2fc2aa5e05..272bbec8ff 100644 --- a/Modules/Core/src/DataManagement/mitkGroupTagProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkGroupTagProperty.cpp @@ -1,39 +1,51 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkGroupTagProperty.h" +#include mitk::GroupTagProperty::GroupTagProperty() : mitk::BaseProperty() { } mitk::GroupTagProperty::GroupTagProperty(const GroupTagProperty &other) : mitk::BaseProperty(other) { } bool mitk::GroupTagProperty::IsEqual(const BaseProperty & /*property*/) const { // if other property is also a GroupTagProperty, then it is equal to us, because tags have no value themselves return true; } bool mitk::GroupTagProperty::Assign(const BaseProperty & /*property*/) { return true; } +bool mitk::GroupTagProperty::ToJSON(nlohmann::json& j) const +{ + j = nlohmann::json::object(); + return true; +} + +bool mitk::GroupTagProperty::FromJSON(const nlohmann::json&) +{ + return true; +} + itk::LightObject::Pointer mitk::GroupTagProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkVector.cpp b/Modules/Core/src/DataManagement/mitkIPropertyDeserialization.cpp similarity index 78% rename from Modules/Core/src/DataManagement/mitkVector.cpp rename to Modules/Core/src/DataManagement/mitkIPropertyDeserialization.cpp index a9ca6ccfb5..bb3e4b2c12 100644 --- a/Modules/Core/src/DataManagement/mitkVector.cpp +++ b/Modules/Core/src/DataManagement/mitkIPropertyDeserialization.cpp @@ -1,13 +1,17 @@ /*============================================================================ 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 "mitkVector.h" +#include "mitkIPropertyDeserialization.h" + +mitk::IPropertyDeserialization::~IPropertyDeserialization() +{ +} diff --git a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp index c3d8841919..be8a030d6a 100644 --- a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp +++ b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp @@ -1,510 +1,548 @@ /*============================================================================ 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(); } } 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 distict 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/mitkLevelWindowProperty.cpp b/Modules/Core/src/DataManagement/mitkLevelWindowProperty.cpp index 5376d63183..2f82af0ba7 100755 --- a/Modules/Core/src/DataManagement/mitkLevelWindowProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkLevelWindowProperty.cpp @@ -1,80 +1,92 @@ /*============================================================================ 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 "mitkLevelWindowProperty.h" mitk::LevelWindowProperty::LevelWindowProperty() { } mitk::LevelWindowProperty::LevelWindowProperty(const mitk::LevelWindowProperty &other) : BaseProperty(other), m_LevWin(other.m_LevWin) { } mitk::LevelWindowProperty::LevelWindowProperty(const mitk::LevelWindow &levWin) { SetLevelWindow(levWin); } mitk::LevelWindowProperty::~LevelWindowProperty() { } bool mitk::LevelWindowProperty::IsEqual(const BaseProperty &property) const { return this->m_LevWin == static_cast(property).m_LevWin; } bool mitk::LevelWindowProperty::Assign(const BaseProperty &property) { this->m_LevWin = static_cast(property).m_LevWin; return true; } const mitk::LevelWindow &mitk::LevelWindowProperty::GetLevelWindow() const { return m_LevWin; } const mitk::LevelWindow &mitk::LevelWindowProperty::GetValue() const { return GetLevelWindow(); } void mitk::LevelWindowProperty::SetLevelWindow(const mitk::LevelWindow &levWin) { if (m_LevWin != levWin) { m_LevWin = levWin; Modified(); } } void mitk::LevelWindowProperty::SetValue(const ValueType &levWin) { SetLevelWindow(levWin); } std::string mitk::LevelWindowProperty::GetValueAsString() const { std::stringstream myStr; myStr << "L:" << m_LevWin.GetLevel() << " W:" << m_LevWin.GetWindow(); return myStr.str(); } +bool mitk::LevelWindowProperty::ToJSON(nlohmann::json& j) const +{ + j = this->GetLevelWindow(); + return true; +} + +bool mitk::LevelWindowProperty::FromJSON(const nlohmann::json& j) +{ + this->SetLevelWindow(j.get()); + return true; +} + itk::LightObject::Pointer mitk::LevelWindowProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkLookupTableProperty.cpp b/Modules/Core/src/DataManagement/mitkLookupTableProperty.cpp index c079e4f0c4..43bf40cf23 100644 --- a/Modules/Core/src/DataManagement/mitkLookupTableProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkLookupTableProperty.cpp @@ -1,73 +1,153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLookupTableProperty.h" +#include mitk::LookupTableProperty::LookupTableProperty() { mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); this->SetLookupTable(lut); } mitk::LookupTableProperty::LookupTableProperty(const LookupTableProperty &other) : mitk::BaseProperty(other), m_LookupTable(other.m_LookupTable) { } mitk::LookupTableProperty::LookupTableProperty(const mitk::LookupTable::Pointer lut) { this->SetLookupTable(lut); } bool mitk::LookupTableProperty::IsEqual(const BaseProperty &property) const { return *(this->m_LookupTable) == *(static_cast(property).m_LookupTable); } bool mitk::LookupTableProperty::Assign(const BaseProperty &property) { this->m_LookupTable = static_cast(property).m_LookupTable; return true; } std::string mitk::LookupTableProperty::GetValueAsString() const { std::stringstream ss; ss << m_LookupTable; return ss.str(); } mitk::LookupTableProperty::ValueType mitk::LookupTableProperty::GetValue() const { return m_LookupTable; } void mitk::LookupTableProperty::SetLookupTable(const mitk::LookupTable::Pointer aLookupTable) { if ((m_LookupTable != aLookupTable) || (*m_LookupTable != *aLookupTable)) { m_LookupTable = aLookupTable; Modified(); } } void mitk::LookupTableProperty::SetValue(const ValueType &value) { SetLookupTable(value); } +bool mitk::LookupTableProperty::ToJSON(nlohmann::json& j) const +{ + auto lut = this->GetValue()->GetVtkLookupTable(); + + auto table = nlohmann::json::array(); + auto numTableValues = lut->GetNumberOfTableValues(); + + for (decltype(numTableValues) i = 0; i < numTableValues; ++i) + { + auto value = nlohmann::json::array(); + + for (size_t j = 0; j < 4; ++j) + value.push_back(lut->GetTableValue(i)[j]); + + table.push_back(value); + } + + j = nlohmann::json::object(); + j["NumberOfColors"] = static_cast(lut->GetNumberOfTableValues()); + j["Scale"] = lut->GetScale(); + j["Ramp"] = lut->GetRamp(); + j["HueRange"] = nlohmann::json::array({ lut->GetHueRange()[0], lut->GetHueRange()[1] }); + j["ValueRange"] = nlohmann::json::array({ lut->GetValueRange()[0], lut->GetValueRange()[1] }); + j["SaturationRange"] = nlohmann::json::array({ lut->GetSaturationRange()[0], lut->GetSaturationRange()[1] }); + j["AlphaRange"] = nlohmann::json::array({ lut->GetAlphaRange()[0], lut->GetAlphaRange()[1] }); + j["TableRange"] = nlohmann::json::array({ lut->GetTableRange()[0], lut->GetTableRange()[1] }); + j["Table"] = table; + + return true; +} + +bool mitk::LookupTableProperty::FromJSON(const nlohmann::json& j) +{ + auto lut = vtkSmartPointer::New(); + + lut->SetNumberOfTableValues(j["NumberOfColors"].get()); + lut->SetScale(j["Scale"].get()); + lut->SetScale(j["Ramp"].get()); + + std::array range; + + j["HueRange"][0].get_to(range[0]); + j["HueRange"][1].get_to(range[1]); + lut->SetHueRange(range.data()); + + j["ValueRange"][0].get_to(range[0]); + j["ValueRange"][1].get_to(range[1]); + lut->SetValueRange(range.data()); + + j["SaturationRange"][0].get_to(range[0]); + j["SaturationRange"][1].get_to(range[1]); + lut->SetSaturationRange(range.data()); + + j["AlphaRange"][0].get_to(range[0]); + j["AlphaRange"][1].get_to(range[1]); + lut->SetAlphaRange(range.data()); + + j["TableRange"][0].get_to(range[0]); + j["TableRange"][1].get_to(range[1]); + lut->SetTableRange(range.data()); + + std::array rgba; + vtkIdType i = 0; + + for (const auto& value : j["Table"]) + { + for (size_t j = 0; j < 4; ++j) + value[j].get_to(rgba[j]); + + lut->SetTableValue(i++, rgba.data()); + } + + auto mitkLut = LookupTable::New(); + mitkLut->SetVtkLookupTable(lut); + this->SetLookupTable(mitkLut); + + return true; +} + itk::LightObject::Pointer mitk::LookupTableProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkPropertyDeserialization.cpp b/Modules/Core/src/DataManagement/mitkPropertyDeserialization.cpp new file mode 100644 index 0000000000..251f2b9ff6 --- /dev/null +++ b/Modules/Core/src/DataManagement/mitkPropertyDeserialization.cpp @@ -0,0 +1,37 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +mitk::PropertyDeserialization::PropertyDeserialization() +{ +} + +mitk::PropertyDeserialization::~PropertyDeserialization() +{ +} + +mitk::BaseProperty::Pointer mitk::PropertyDeserialization::CreateInstance(const std::string& className) +{ + auto it = m_Map.find(className); + + if (it != m_Map.end()) + return static_cast(it->second->CreateAnother().GetPointer()); + + return nullptr; +} + +void mitk::PropertyDeserialization::InternalRegisterProperty(const BaseProperty* property) +{ + m_Map[property->GetNameOfClass()] = property; +} diff --git a/Modules/Core/src/DataManagement/mitkPropertyList.cpp b/Modules/Core/src/DataManagement/mitkPropertyList.cpp index d15eac3f6e..88d5d7e34c 100644 --- a/Modules/Core/src/DataManagement/mitkPropertyList.cpp +++ b/Modules/Core/src/DataManagement/mitkPropertyList.cpp @@ -1,376 +1,422 @@ /*============================================================================ 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 "mitkPropertyList.h" - -#include "mitkNumericTypes.h" -#include "mitkProperties.h" -#include "mitkStringProperty.h" +#include +#include +#include +#include +#include mitk::BaseProperty::ConstPointer mitk::PropertyList::GetConstProperty(const std::string &propertyKey, const std::string &/*contextName*/, bool /*fallBackOnDefaultContext*/) const { PropertyMap::const_iterator it; it = m_Properties.find(propertyKey); if (it != m_Properties.cend()) return it->second.GetPointer(); else return nullptr; }; std::vector mitk::PropertyList::GetPropertyKeys(const std::string &contextName, bool includeDefaultContext) const { std::vector propertyKeys; if (contextName.empty() || includeDefaultContext) { for (const auto &property : this->m_Properties) propertyKeys.push_back(property.first); } return propertyKeys; }; std::vector mitk::PropertyList::GetPropertyContextNames() const { return std::vector(); }; mitk::BaseProperty *mitk::PropertyList::GetProperty(const std::string &propertyKey) const { PropertyMap::const_iterator it; it = m_Properties.find(propertyKey); if (it != m_Properties.cend()) return it->second; else return nullptr; } mitk::BaseProperty * mitk::PropertyList::GetNonConstProperty(const std::string &propertyKey, const std::string &/*contextName*/, bool /*fallBackOnDefaultContext*/) { return this->GetProperty(propertyKey); } void mitk::PropertyList::SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &/*contextName*/, bool /*fallBackOnDefaultContext*/) { if (propertyKey.empty()) mitkThrow() << "Property key is empty."; if (!property) return; // make sure that BaseProperty*, which may have just been created and never been // assigned to a SmartPointer, is registered/unregistered properly. If we do not // do that, it will a) not deleted in case it is identical to the old one or // b) possibly deleted when temporarily added to a smartpointer somewhere below. BaseProperty::Pointer tmpSmartPointerToProperty = property; auto it(m_Properties.find(propertyKey)); // Is a property with key @a propertyKey contained in the list? if (it != m_Properties.cend()) { // yes // is the property contained in the list identical to the new one? if (it->second->operator==(*property)) { // yes? do nothing and return. return; } if (it->second->AssignProperty(*property)) { // The assignment was successful this->Modified(); } else { MITK_ERROR << "In " __FILE__ ", l." << __LINE__ << ": Trying to set existing property " << it->first << " of type " << it->second->GetNameOfClass() << " to a property with different type " << property->GetNameOfClass() << "." << " Use ReplaceProperty() instead." << std::endl; } return; } // no? add it. m_Properties.insert(PropertyMap::value_type(propertyKey, property)); this->Modified(); } void mitk::PropertyList::ReplaceProperty(const std::string &propertyKey, BaseProperty *property) { if (!property) return; auto it(m_Properties.find(propertyKey)); // Is a property with key @a propertyKey contained in the list? if (it != m_Properties.cend()) { it->second = nullptr; m_Properties.erase(it); } // no? add/replace it. m_Properties.insert(PropertyMap::value_type(propertyKey, property)); Modified(); } void mitk::PropertyList::RemoveProperty(const std::string &propertyKey, const std::string &/*contextName*/, bool /*fallBackOnDefaultContext*/) { auto it(m_Properties.find(propertyKey)); // Is a property with key @a propertyKey contained in the list? if (it != m_Properties.cend()) { it->second = nullptr; m_Properties.erase(it); Modified(); } } mitk::PropertyList::PropertyList() { } mitk::PropertyList::PropertyList(const mitk::PropertyList &other) : itk::Object() { for (auto i = other.m_Properties.cbegin(); i != other.m_Properties.cend(); ++i) { m_Properties.insert(std::make_pair(i->first, i->second->Clone())); } } mitk::PropertyList::~PropertyList() { Clear(); } /** * Consider the list as changed when any of the properties has changed recently. */ itk::ModifiedTimeType mitk::PropertyList::GetMTime() const { for (auto it = m_Properties.cbegin(); it != m_Properties.cend(); ++it) { if (it->second.IsNull()) { itkWarningMacro(<< "Property '" << it->first << "' contains nothing (nullptr)."); continue; } if (Superclass::GetMTime() < it->second->GetMTime()) { Modified(); break; } } return Superclass::GetMTime(); } bool mitk::PropertyList::DeleteProperty(const std::string &propertyKey) { auto it = m_Properties.find(propertyKey); if (it != m_Properties.end()) { it->second = nullptr; m_Properties.erase(it); Modified(); return true; } return false; } void mitk::PropertyList::Clear() { auto it = m_Properties.begin(), end = m_Properties.end(); while (it != end) { it->second = nullptr; ++it; } m_Properties.clear(); } itk::LightObject::Pointer mitk::PropertyList::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } void mitk::PropertyList::ConcatenatePropertyList(PropertyList *pList, bool replace) { if (pList) { const PropertyMap *propertyMap = pList->GetMap(); for (auto iter = propertyMap->cbegin(); // m_PropertyList is created in the constructor, so we don't check it here iter != propertyMap->cend(); ++iter) { const std::string key = iter->first; BaseProperty *value = iter->second; if (replace) { ReplaceProperty(key.c_str(), value); } else { SetProperty(key.c_str(), value); } } } } bool mitk::PropertyList::GetBoolProperty(const char *propertyKey, bool &boolValue) const { BoolProperty *gp = dynamic_cast(GetProperty(propertyKey)); if (gp != nullptr) { boolValue = gp->GetValue(); return true; } return false; // Templated Method does not work on Macs // return GetPropertyValue(propertyKey, boolValue); } bool mitk::PropertyList::GetIntProperty(const char *propertyKey, int &intValue) const { IntProperty *gp = dynamic_cast(GetProperty(propertyKey)); if (gp != nullptr) { intValue = gp->GetValue(); return true; } return false; // Templated Method does not work on Macs // return GetPropertyValue(propertyKey, intValue); } bool mitk::PropertyList::GetFloatProperty(const char *propertyKey, float &floatValue) const { FloatProperty *gp = dynamic_cast(GetProperty(propertyKey)); if (gp != nullptr) { floatValue = gp->GetValue(); return true; } return false; // Templated Method does not work on Macs // return GetPropertyValue(propertyKey, floatValue); } bool mitk::PropertyList::GetStringProperty(const char *propertyKey, std::string &stringValue) const { StringProperty *sp = dynamic_cast(GetProperty(propertyKey)); if (sp != nullptr) { stringValue = sp->GetValue(); return true; } return false; } void mitk::PropertyList::SetIntProperty(const char *propertyKey, int intValue) { SetProperty(propertyKey, mitk::IntProperty::New(intValue)); } void mitk::PropertyList::SetBoolProperty(const char *propertyKey, bool boolValue) { SetProperty(propertyKey, mitk::BoolProperty::New(boolValue)); } void mitk::PropertyList::SetFloatProperty(const char *propertyKey, float floatValue) { SetProperty(propertyKey, mitk::FloatProperty::New(floatValue)); } void mitk::PropertyList::SetStringProperty(const char *propertyKey, const char *stringValue) { SetProperty(propertyKey, mitk::StringProperty::New(stringValue)); } void mitk::PropertyList::Set(const char *propertyKey, bool boolValue) { this->SetBoolProperty(propertyKey, boolValue); } void mitk::PropertyList::Set(const char *propertyKey, int intValue) { this->SetIntProperty(propertyKey, intValue); } void mitk::PropertyList::Set(const char *propertyKey, float floatValue) { this->SetFloatProperty(propertyKey, floatValue); } void mitk::PropertyList::Set(const char *propertyKey, double doubleValue) { this->SetDoubleProperty(propertyKey, doubleValue); } void mitk::PropertyList::Set(const char *propertyKey, const char *stringValue) { this->SetStringProperty(propertyKey, stringValue); } void mitk::PropertyList::Set(const char *propertyKey, const std::string &stringValue) { this->SetStringProperty(propertyKey, stringValue.c_str()); } bool mitk::PropertyList::Get(const char *propertyKey, bool &boolValue) const { return this->GetBoolProperty(propertyKey, boolValue); } bool mitk::PropertyList::Get(const char *propertyKey, int &intValue) const { return this->GetIntProperty(propertyKey, intValue); } bool mitk::PropertyList::Get(const char *propertyKey, float &floatValue) const { return this->GetFloatProperty(propertyKey, floatValue); } bool mitk::PropertyList::Get(const char *propertyKey, double &doubleValue) const { return this->GetDoubleProperty(propertyKey, doubleValue); } bool mitk::PropertyList::Get(const char *propertyKey, std::string &stringValue) const { return this->GetStringProperty(propertyKey, stringValue); } bool mitk::PropertyList::GetDoubleProperty(const char *propertyKey, double &doubleValue) const { DoubleProperty *gp = dynamic_cast(GetProperty(propertyKey)); if (gp != nullptr) { doubleValue = gp->GetValue(); return true; } return false; } void mitk::PropertyList::SetDoubleProperty(const char *propertyKey, double doubleValue) { SetProperty(propertyKey, mitk::DoubleProperty::New(doubleValue)); } + +void mitk::PropertyList::ToJSON(nlohmann::json& j) const +{ + j = nlohmann::json::object(); + + for (const auto& [name, property] : m_Properties) + { + nlohmann::json serializedProperty; + + if (property->ToJSON(serializedProperty)) + j[property->GetNameOfClass()][name] = serializedProperty; + } + + auto test = PropertyList::New(); + test->FromJSON(j); +} + +void mitk::PropertyList::FromJSON(const nlohmann::json& j) +{ + mitk::CoreServicePointer service(mitk::CoreServices::GetPropertyDeserialization()); + PropertyMap properties; + + auto jObject = j.get(); + + for (const auto& [type, serializedProperties] : jObject) + { + auto prototype = service->CreateInstance(type); + + if (prototype.IsNull()) + { + MITK_ERROR << "Cannot create instance(s) of class \"" << type << "\"!"; + continue; + } + + auto serializedPropertiesObject = serializedProperties.get(); + + for (const auto& [name, serializedProperty] : serializedPropertiesObject) + { + BaseProperty::Pointer property = static_cast(prototype->CreateAnother().GetPointer()); + property->FromJSON(serializedProperty); + properties[name] = property; + } + } + + m_Properties = properties; +} diff --git a/Modules/Core/src/DataManagement/mitkSmartPointerProperty.cpp b/Modules/Core/src/DataManagement/mitkSmartPointerProperty.cpp index 0ca671f20d..dad9fa882d 100644 --- a/Modules/Core/src/DataManagement/mitkSmartPointerProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkSmartPointerProperty.cpp @@ -1,136 +1,146 @@ /*============================================================================ 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 "mitkSmartPointerProperty.h" mitk::SmartPointerProperty::ReferenceCountMapType mitk::SmartPointerProperty::m_ReferenceCount; mitk::SmartPointerProperty::ReferencesUIDMapType mitk::SmartPointerProperty::m_ReferencesUID; mitk::SmartPointerProperty::ReadInSmartPointersMapType mitk::SmartPointerProperty::m_ReadInInstances; mitk::SmartPointerProperty::ReadInTargetsMapType mitk::SmartPointerProperty::m_ReadInTargets; mitk::UIDGenerator mitk::SmartPointerProperty::m_UIDGenerator("POINTER_"); void mitk::SmartPointerProperty::PostProcessXMLReading() { for (auto iter = m_ReadInInstances.begin(); iter != m_ReadInInstances.end(); ++iter) { if (m_ReadInTargets.find(iter->second) != m_ReadInTargets.end()) { iter->first->SetSmartPointer(m_ReadInTargets[iter->second]); } } m_ReadInInstances.clear(); } /// \return The number of SmartPointerProperties that point to @param object unsigned int mitk::SmartPointerProperty::GetReferenceCountFor(itk::Object *object) { if (m_ReferenceCount.find(object) != m_ReferenceCount.end()) { return m_ReferenceCount[object]; } else { return 0; } } void mitk::SmartPointerProperty::RegisterPointerTarget(itk::Object *object, const std::string uid) { m_ReadInTargets[uid] = object; } std::string mitk::SmartPointerProperty::GetReferenceUIDFor(itk::Object *object) { if (m_ReferencesUID.find(object) != m_ReferencesUID.end()) { return m_ReferencesUID[object]; } else { return std::string("invalid"); } } bool mitk::SmartPointerProperty::IsEqual(const BaseProperty &property) const { return this->m_SmartPointer == static_cast(property).m_SmartPointer; } bool mitk::SmartPointerProperty::Assign(const BaseProperty &property) { this->m_SmartPointer = static_cast(property).m_SmartPointer; return true; } mitk::SmartPointerProperty::SmartPointerProperty(itk::Object *pointer) { SetSmartPointer(pointer); } mitk::SmartPointerProperty::SmartPointerProperty(const SmartPointerProperty &other) : BaseProperty(other), m_SmartPointer(other.m_SmartPointer) { } itk::Object::Pointer mitk::SmartPointerProperty::GetSmartPointer() const { return m_SmartPointer; } itk::Object::Pointer mitk::SmartPointerProperty::GetValue() const { return this->GetSmartPointer(); } void mitk::SmartPointerProperty::SetSmartPointer(itk::Object *pointer) { if (m_SmartPointer.GetPointer() != pointer) { // keep track of referenced objects if (m_SmartPointer.GetPointer() && --m_ReferenceCount[m_SmartPointer.GetPointer()] == 0) // if there is no reference left, delete entry { m_ReferenceCount.erase(m_SmartPointer.GetPointer()); m_ReferencesUID.erase(m_SmartPointer.GetPointer()); } if (pointer && ++m_ReferenceCount[pointer] == 1) // first reference --> generate UID { m_ReferencesUID[pointer] = m_UIDGenerator.GetUID(); } // change pointer m_SmartPointer = pointer; Modified(); } } void mitk::SmartPointerProperty::SetValue(const ValueType &value) { this->SetSmartPointer(value.GetPointer()); } std::string mitk::SmartPointerProperty::GetValueAsString() const { if (m_SmartPointer.IsNotNull()) return m_ReferencesUID[m_SmartPointer.GetPointer()]; else return std::string("nullptr"); } +bool mitk::SmartPointerProperty::ToJSON(nlohmann::json&) const +{ + return false; +} + +bool mitk::SmartPointerProperty::FromJSON(const nlohmann::json&) +{ + return false; +} + itk::LightObject::Pointer mitk::SmartPointerProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkStringProperty.cpp b/Modules/Core/src/DataManagement/mitkStringProperty.cpp index 767d6101c1..cc1ecf9e69 100644 --- a/Modules/Core/src/DataManagement/mitkStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkStringProperty.cpp @@ -1,51 +1,64 @@ /*============================================================================ 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 "mitkStringProperty.h" +#include const char *mitk::StringProperty::PATH = "path"; mitk::StringProperty::StringProperty(const char *string) : m_Value() { if (string) m_Value = string; } mitk::StringProperty::StringProperty(const std::string &s) : m_Value(s) { } mitk::StringProperty::StringProperty(const StringProperty &other) : BaseProperty(other), m_Value(other.m_Value) { } bool mitk::StringProperty::IsEqual(const BaseProperty &property) const { return this->m_Value == static_cast(property).m_Value; } bool mitk::StringProperty::Assign(const BaseProperty &property) { this->m_Value = static_cast(property).m_Value; return true; } std::string mitk::StringProperty::GetValueAsString() const { return m_Value; } +bool mitk::StringProperty::ToJSON(nlohmann::json& j) const +{ + j = this->GetValueAsString(); + return true; +} + +bool mitk::StringProperty::FromJSON(const nlohmann::json& j) +{ + this->SetValue(j.get()); + return true; +} + itk::LightObject::Pointer mitk::StringProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp index a3e0b1f549..4764497413 100644 --- a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp @@ -1,493 +1,426 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include "mitkTemporoSpatialStringProperty.h" #include -using namespace nlohmann; +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) { this->Modified(); m_Values.clear(); this->SetValue(0, 0, value); }; -// Create necessary escape sequences from illegal characters -// REMARK: This code is based upon code from boost::ptree::json_writer. -// The corresponding boost function was not used directly, because it is not part of -// the public interface of ptree::json_writer. :( -// A own serialization strategy was implemented instead of using boost::ptree::json_write because -// currently (<= boost 1.60) everything (even numbers) are converted into string representations -// by the writer, so e.g. it becomes "t":"2" instead of "t":2 -template -std::basic_string CreateJSONEscapes(const std::basic_string &s) +bool mitk::TemporoSpatialStringProperty::ToJSON(nlohmann::json& j) const { - std::basic_string result; - typename std::basic_string::const_iterator b = s.begin(); - typename std::basic_string::const_iterator e = s.end(); - while (b != e) - { - using UCh = std::make_unsigned_t; - UCh c(*b); - // This assumes an ASCII superset. - // We escape everything outside ASCII, because this code can't - // handle high unicode characters. - if (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x2E) || (c >= 0x30 && c <= 0x5B) || (c >= 0x5D && c <= 0x7F)) - result += *b; - else if (*b == Ch('\b')) - result += Ch('\\'), result += Ch('b'); - else if (*b == Ch('\f')) - result += Ch('\\'), result += Ch('f'); - else if (*b == Ch('\n')) - result += Ch('\\'), result += Ch('n'); - else if (*b == Ch('\r')) - result += Ch('\\'), result += Ch('r'); - else if (*b == Ch('\t')) - result += Ch('\\'), result += Ch('t'); - else if (*b == Ch('/')) - result += Ch('\\'), result += Ch('/'); - else if (*b == Ch('"')) - result += Ch('\\'), result += Ch('"'); - else if (*b == Ch('\\')) - result += Ch('\\'), result += Ch('\\'); - else - { - const char *hexdigits = "0123456789ABCDEF"; - unsigned long u = (std::min)(static_cast(static_cast(*b)), 0xFFFFul); - int d1 = u / 4096; - u -= d1 * 4096; - int d2 = u / 256; - u -= d2 * 256; - int d3 = u / 16; - u -= d3 * 16; - int d4 = u; - result += Ch('\\'); - result += Ch('u'); - result += Ch(hexdigits[d1]); - result += Ch(hexdigits[d2]); - result += Ch(hexdigits[d3]); - result += Ch(hexdigits[d4]); - } - ++b; - } - return result; -} - -using CondensedTimeKeyType = std::pair; -using CondensedTimePointsType = std::map; - -using CondensedSliceKeyType = std::pair; -using CondensedSlicesType = std::map; - -/** 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; -} - -::std::string mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON( - const mitk::BaseProperty *prop) -{ - // REMARK: Implemented own serialization instead of using boost::ptree::json_write because - // currently (<= boost 1.60) everything (even numbers) are converted into string representations - // by the writer, so e.g. it becomes "t":"2" instead of "t":2 - // If this problem is fixed with boost, we should switch back to json_writer (and remove the custom - // implementation of CreateJSONEscapes (see above)). - const auto *tsProp = dynamic_cast(prop); - - if (!tsProp) - { - mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty."; - } - - std::ostringstream stream; - stream.imbue(std::locale("C")); - stream << "{\"values\":["; - - //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" to a higher rate. - //We don't wanted to change the internal structure of the property as it would - //introduce API inconvenience and subtle changes in behavior. - CondensedSlicesType uncondensedSlices = CondenseTimePointValuesOfProperty(tsProp); - - //now condense the slices + // 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()) { - CondensedTimePointsType& masterSlice = uncondensedSlices.begin()->second; - CondensedSliceKeyType masterSliceKey = uncondensedSlices.begin()->first; + 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(); - bool first = true; for (const auto& z : condensedSlices) { for (const auto& t : z.second) { - if (first) - { - first = false; - } - else - { - stream << ", "; - } - const auto& minSliceID = z.first.first; const auto& maxSliceID = z.first.second; const auto& minTimePointID = t.first.first; const auto& maxTimePointID = t.first.second; - stream << "{\"z\":" << minSliceID << ", "; + auto value = nlohmann::json::object(); + value["z"] = minSliceID; + if (minSliceID != maxSliceID) - { - stream << "\"zmax\":" << maxSliceID << ", "; - } - stream << "\"t\":" << minTimePointID << ", "; + value["zmax"] = maxSliceID; + + value["t"] = minTimePointID; + if (minTimePointID != maxTimePointID) - { - stream << "\"tmax\":" << maxTimePointID << ", "; - } + value["tmax"] = maxTimePointID; + + value["value"] = t.second; - const auto& value = t.second; - stream << "\"value\":\"" << CreateJSONEscapes(value) << "\"}"; + values.push_back(value); } } - stream << "]}"; + j = nlohmann::json{{"values", values}}; - return stream.str(); + return true; } -mitk::BaseProperty::Pointer mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty( - const std::string &value) +bool mitk::TemporoSpatialStringProperty::FromJSON(const nlohmann::json& j) { - if (value.empty()) - return nullptr; - - mitk::TemporoSpatialStringProperty::Pointer prop = mitk::TemporoSpatialStringProperty::New(); - - auto root = json::parse(value); - - for (const auto& element : root["values"]) + 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) { - prop->SetValue(currentT, currentZ, value); + 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(); } diff --git a/Modules/Core/src/DataManagement/mitkTransferFunctionProperty.cpp b/Modules/Core/src/DataManagement/mitkTransferFunctionProperty.cpp index 3e5bce2ca6..20a59af2a7 100644 --- a/Modules/Core/src/DataManagement/mitkTransferFunctionProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTransferFunctionProperty.cpp @@ -1,53 +1,125 @@ /*============================================================================ 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 "mitkTransferFunctionProperty.h" +#include namespace mitk { bool TransferFunctionProperty::IsEqual(const BaseProperty &property) const { return *(this->m_Value) == *(static_cast(property).m_Value); } bool TransferFunctionProperty::Assign(const BaseProperty &property) { this->m_Value = static_cast(property).m_Value; return true; } std::string TransferFunctionProperty::GetValueAsString() const { std::stringstream myStr; myStr << GetValue(); return myStr.str(); } TransferFunctionProperty::TransferFunctionProperty() : BaseProperty(), m_Value(mitk::TransferFunction::New()) {} TransferFunctionProperty::TransferFunctionProperty(const TransferFunctionProperty &other) : BaseProperty(other), m_Value(other.m_Value->Clone()) { } TransferFunctionProperty::TransferFunctionProperty(mitk::TransferFunction::Pointer value) : BaseProperty(), m_Value(value) { } + bool TransferFunctionProperty::ToJSON(nlohmann::json& j) const + { + auto tf = this->GetValue(); + + auto scalarOpacity = nlohmann::json::array(); + + for (const auto& point : tf->GetScalarOpacityPoints()) + scalarOpacity.push_back(point); + + auto gradientOpacity = nlohmann::json::array(); + + for (const auto& point : tf->GetGradientOpacityPoints()) + gradientOpacity.push_back(point); + + auto* ctf = tf->GetColorTransferFunction(); + auto size = ctf->GetSize(); + + std::array value; + auto color = nlohmann::json::array(); + + for (int i = 0; i < size; ++i) + { + ctf->GetNodeValue(i, value.data()); + color.push_back(value); + } + + j = nlohmann::json{ + {"ScalarOpacity", scalarOpacity}, + {"GradientOpacity", gradientOpacity}, + {"Color", color}}; + + return true; + } + + bool TransferFunctionProperty::FromJSON(const nlohmann::json& j) + { + auto tf = TransferFunction::New(); + TransferFunction::ControlPoints::value_type point; + + tf->ClearScalarOpacityPoints(); + + for (const auto& opacity : j["ScalarOpacity"]) + { + opacity.get_to(point); + tf->AddScalarOpacityPoint(point.first, point.second); + } + + tf->ClearGradientOpacityPoints(); + + for (const auto& opacity : j["GradientOpacity"]) + { + opacity.get_to(point); + tf->AddGradientOpacityPoint(point.first, point.second); + } + + auto* ctf = tf->GetColorTransferFunction(); + ctf->RemoveAllPoints(); + + std::array value; + + for (const auto& color : j["Color"]) + { + color.get_to(value); + ctf->AddRGBPoint(value[0], value[1], value[2], value[3], value[4], value[5]); + } + + this->SetValue(tf); + + return true; + } + itk::LightObject::Pointer TransferFunctionProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } } // namespace mitk diff --git a/Modules/Core/src/DataManagement/mitkVectorProperty.cpp b/Modules/Core/src/DataManagement/mitkVectorProperty.cpp index d2fa075f52..f6e986629f 100644 --- a/Modules/Core/src/DataManagement/mitkVectorProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkVectorProperty.cpp @@ -1,90 +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. ============================================================================*/ #include "mitkVectorProperty.h" +#include namespace mitk { template bool VectorProperty::IsEqual(const BaseProperty &property) const { return this->m_PropertyContent == static_cast(property).m_PropertyContent; } template bool VectorProperty::Assign(const BaseProperty &property) { this->m_PropertyContent = static_cast(property).m_PropertyContent; return true; } template itk::LightObject::Pointer VectorProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); return result; } template std::string VectorProperty::GetValueAsString() const { const size_t displayBlockLength = 3; size_t beginningElementsCount = displayBlockLength; size_t endElementsCount = displayBlockLength; if (m_PropertyContent.size() <= 2 * displayBlockLength) { beginningElementsCount = m_PropertyContent.size(); endElementsCount = 0; } // return either a block of all items // if the total number of maximum 2*displayBlockLength // // or return the first and last "displayBlockLength" // number of items separated by "[...]"; std::stringstream string_collector; for (size_t i = 0; i < beginningElementsCount; i++) string_collector << m_PropertyContent[i] << "\n"; if (endElementsCount) string_collector << "[... " << m_PropertyContent.size() - 2 * displayBlockLength << " more]\n"; for (size_t i = m_PropertyContent.size() - endElementsCount; i < m_PropertyContent.size(); ++i) string_collector << m_PropertyContent[i] << "\n"; std::string return_value = string_collector.str(); // remove last '\n' if (!return_value.empty()) return_value.erase(return_value.size() - 1); return return_value; } template void VectorProperty::SetValue(const VectorType &newValue) { m_PropertyContent = newValue; } template const typename VectorProperty::VectorType &VectorProperty::GetValue() const { return m_PropertyContent; } + template + bool VectorProperty::ToJSON(nlohmann::json& j) const + { + j = this->GetValue(); + return true; + } + + template + bool VectorProperty::FromJSON(const nlohmann::json& j) + { + this->SetValue(j.get()); + return true; + } + // Explicit instantiation for defined types. MITK_DEFINE_VECTOR_PROPERTY(double) MITK_DEFINE_VECTOR_PROPERTY(int) MITK_DEFINE_VECTOR_PROPERTY(unsigned int) MITK_DEFINE_VECTOR_PROPERTY(std::string) } // namespace mitk diff --git a/Modules/Core/src/DataManagement/mitkWeakPointerProperty.cpp b/Modules/Core/src/DataManagement/mitkWeakPointerProperty.cpp index 33249b73cf..e3b8f46fde 100644 --- a/Modules/Core/src/DataManagement/mitkWeakPointerProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkWeakPointerProperty.cpp @@ -1,75 +1,85 @@ /*============================================================================ 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 "mitkWeakPointerProperty.h" bool mitk::WeakPointerProperty::IsEqual(const BaseProperty &property) const { return this->m_WeakPointer == static_cast(property).m_WeakPointer; } bool mitk::WeakPointerProperty::Assign(const BaseProperty &property) { this->m_WeakPointer = static_cast(property).m_WeakPointer; return true; } mitk::WeakPointerProperty::WeakPointerProperty(itk::Object *pointer) : m_WeakPointer(pointer) { } mitk::WeakPointerProperty::WeakPointerProperty(const WeakPointerProperty &other) : mitk::BaseProperty(other), m_WeakPointer(other.m_WeakPointer) { } mitk::WeakPointerProperty::~WeakPointerProperty() { } std::string mitk::WeakPointerProperty::GetValueAsString() const { std::stringstream ss; ss << m_WeakPointer.GetPointer(); return ss.str(); } mitk::WeakPointerProperty::ValueType mitk::WeakPointerProperty::GetWeakPointer() const { return m_WeakPointer.GetPointer(); } mitk::WeakPointerProperty::ValueType mitk::WeakPointerProperty::GetValue() const { return GetWeakPointer(); } void mitk::WeakPointerProperty::SetWeakPointer(itk::Object *pointer) { if (m_WeakPointer.GetPointer() != pointer) { m_WeakPointer = pointer; Modified(); } } void mitk::WeakPointerProperty::SetValue(const ValueType &value) { SetWeakPointer(value.GetPointer()); } +bool mitk::WeakPointerProperty::ToJSON(nlohmann::json&) const +{ + return false; +} + +bool mitk::WeakPointerProperty::FromJSON(const nlohmann::json&) +{ + return false; +} + itk::LightObject::Pointer mitk::WeakPointerProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } diff --git a/Modules/Core/src/IO/mitkImageGenerator.cpp b/Modules/Core/src/IO/mitkImageGenerator.cpp deleted file mode 100644 index f92933ddeb..0000000000 --- a/Modules/Core/src/IO/mitkImageGenerator.cpp +++ /dev/null @@ -1,11 +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. - -============================================================================*/ diff --git a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp index cfecd85744..f093067910 100644 --- a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp @@ -1,621 +1,626 @@ /*============================================================================ 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 "mitkPointSetDataInteractor.h" #include "mitkMouseMoveEvent.h" #include "mitkInteractionConst.h" // TODO: refactor file #include "mitkInternalEvent.h" #include "mitkOperationEvent.h" #include "mitkRenderingManager.h" #include // #include "mitkBaseRenderer.h" #include "mitkDispatcher.h" #include "mitkUndoController.h" void mitk::PointSetDataInteractor::ConnectActionsAndFunctions() { // Condition which is evaluated before transition is taken // following actions in the statemachine are only executed if it returns TRUE CONNECT_CONDITION("isoverpoint", CheckSelection); CONNECT_FUNCTION("addpoint", AddPoint); CONNECT_FUNCTION("selectpoint", SelectPoint); CONNECT_FUNCTION("unselect", UnSelectPointAtPosition); CONNECT_FUNCTION("unselectAll", UnSelectAll); CONNECT_FUNCTION("initMove", InitMove); CONNECT_FUNCTION("movePoint", MovePoint); CONNECT_FUNCTION("finishMovement", FinishMove); CONNECT_FUNCTION("removePoint", RemovePoint); } void mitk::PointSetDataInteractor::AddPoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); // disallow adding of new points if maximum number of points is reached if (m_MaxNumberOfPoints > 1 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { return; } // To add a point the minimal information is the position, this method accepts all InteractionsPositionEvents auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); this->UnselectAll(timeStep, timeInMs); int lastPosition = 0; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { if (!m_PointSet->IndexExists(lastPosition, timeStep)) break; ++it; ++lastPosition; } // Insert a Point to the PointSet // 2) Create the Operation inserting the point if (m_PointSet->IsEmpty()) { lastPosition = 0; } auto *doOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, lastPosition); // 3) If Undo is enabled, also create the inverse Operation if (m_UndoEnabled) { auto *undoOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, lastPosition); // 4) Do and Undo Operations are combined in an Operation event which also contains the target of the operations // (here m_PointSet) OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Add point"); // 5) Store the Operation in the UndoController OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // 6) Execute the Operation performs the actual insertion of the point into the PointSet m_PointSet->ExecuteOperation(doOp); // 7) If Undo is not enabled the Do-Operation is to be dropped to prevent memory leaks. if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); // Check if points form a closed contour now, if so fire an InternalEvent IsClosedContour(stateMachineAction, interactionEvent); if (m_MaxNumberOfPoints > 0 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { // Signal that DataNode is fully filled this->NotifyResultReady(); // Send internal event that can be used by StateMachines to switch in a different state InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "MaximalNumberOfPoints"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) { // first deselect the other points // undoable deselect of all points in the DataList this->UnselectAll(timeStep, timeInMs); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMs,point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } mitk::PointSetDataInteractor::PointSetDataInteractor() : m_MaxNumberOfPoints(0), m_SelectionAccuracy(3.5) { } mitk::PointSetDataInteractor::~PointSetDataInteractor() { } void mitk::PointSetDataInteractor::RemovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); // search the point in the list int position = m_PointSet->SearchPoint(itkPoint, m_SelectionAccuracy, timeStep); if (position >= 0) // found a point { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) // write to UndoMechanism { auto *undoOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Remove point"); mitk::OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; /*now select the point "position-1", and if it is the first in list, then continue at the last in list*/ // only then a select of a point is possible! if (m_PointSet->GetSize(timeStep) > 0) { this->SelectPoint(m_PointSet->Begin(timeStep)->Index(), timeStep, timeInMs); } } RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PointSetDataInteractor::IsClosedContour(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected if (GetPointIndexByPosition(point, timeStep) != -1 && m_PointSet->GetSize(timeStep) >= 3) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "ClosedContour"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::MovePoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { IsClosedContour(stateMachineAction, interactionEvent); mitk::Point3D newPoint, resultPoint; newPoint = positionEvent->GetPositionInWorld(); // search the elements in the list that are selected then calculate the // vector, because only with the vector we can move several elements in // the same direction // newPoint - lastPoint = vector // then move all selected and set the lastPoint = newPoint. // then add all vectors to a summeryVector (to be able to calculate the // startpoint for undoOperation) mitk::Vector3D dirVector = newPoint - m_LastPoint; // sum up all Movement for Undo in FinishMovement m_SumVec = m_SumVec + dirVector; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); mitk::Point3D sumVec; sumVec[0] = pt[0]; sumVec[1] = pt[1]; sumVec[2] = pt[2]; resultPoint = sumVec + dirVector; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, resultPoint, position); // execute the Operation // here no undo is stored, because the movement-steps aren't interesting. // only the start and the end is interisting to store for undo. m_PointSet->ExecuteOperation(doOp); delete doOp; } ++it; } m_LastPoint = newPoint; // for calculation of the direction vector // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); IsClosedContour(stateMachineAction, interactionEvent); } } void mitk::PointSetDataInteractor::UnSelectPointAtPosition(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); // here it is ensured that we don't switch from one point being selected to another one being selected, // without accepting the unselect of the current point if (index != -1) { auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT,timeInMs, point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Unselect Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } void mitk::PointSetDataInteractor::UnSelectAll(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D positioninWorld = positionEvent->GetPositionInWorld(); PointSet::PointsContainer::Iterator it, end; PointSet::DataType *itkPointSet = m_PointSet->GetPointSet(timeStep); end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); // then declare an operation which unselects this point; // UndoOperation as well! if (m_PointSet->GetSelectInfo(position, timeStep)) { float distance = sqrt(positioninWorld.SquaredEuclideanDistanceTo(m_PointSet->GetPoint(position, timeStep))); if (distance > m_SelectionAccuracy) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( m_PointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } } else { this->UnselectAll(timeStep, timeInMs); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PointSetDataInteractor::UpdatePointSet(mitk::StateMachineAction *, mitk::InteractionEvent *) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); if (pointSet == nullptr) { MITK_ERROR << "PointSetDataInteractor:: No valid point set ."; return; } m_PointSet = pointSet; } void mitk::PointSetDataInteractor::Abort(StateMachineAction *, InteractionEvent *interactionEvent) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, IntDeactivateMe); interactionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } /* * Check whether the DataNode contains a pointset, if not create one and add it. */ void mitk::PointSetDataInteractor::DataNodeChanged() { if (GetDataNode() != nullptr) { auto *points = dynamic_cast(GetDataNode()->GetData()); if (points == nullptr) { m_PointSet = PointSet::New(); GetDataNode()->SetData(m_PointSet); } else { m_PointSet = points; } // load config file parameter: maximal number of points mitk::PropertyList::Pointer properties = GetAttributes(); std::string strNumber; if (properties->GetStringProperty("MaxPoints", strNumber)) { m_MaxNumberOfPoints = atoi(strNumber.c_str()); } } } void mitk::PointSetDataInteractor::InitMove(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; // start of the Movement is stored to calculate the undoKoordinate // in FinishMovement m_LastPoint = positionEvent->GetPositionInWorld(); // initialize a value to calculate the movement through all // MouseMoveEvents from MouseClick to MouseRelease m_SumVec.Fill(0); GetDataNode()->SetProperty("contourcolor", ColorProperty::New(1.0, 1.0, 1.0)); } void mitk::PointSetDataInteractor::FinishMove(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { // finish the movement: // the final point is m_LastPoint // m_SumVec stores the movement in a vector // the operation would not be necessary, but we need it for the undo Operation. // m_LastPoint is for the Operation // the point for undoOperation calculates from all selected // elements (point) - m_SumVec // search all selected elements and move them with undo-functionality. mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); Point3D itkPoint; itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) //&& (posEvent->GetType() == mitk::Type_MouseButtonRelease) { // set the undo-operation, so the final position is undo-able // calculate the old Position from the already moved position - m_SumVec mitk::Point3D undoPoint = (itkPoint - m_SumVec); auto *undoOp = new mitk::PointOperation(OpMOVE, timeInMs, undoPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Move point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } ++it; } // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); } else { return; } this->NotifyResultReady(); } void mitk::PointSetDataInteractor::SetAccuracy(float accuracy) { m_SelectionAccuracy = accuracy; } void mitk::PointSetDataInteractor::SetMaxPoints(unsigned int maxNumber) { m_MaxNumberOfPoints = maxNumber; } int mitk::PointSetDataInteractor::GetPointIndexByPosition(Point3D position, unsigned int time, float accuracy) { - // iterate over point set and check if it contains a point close enough to the pointer to be selected + // Iterate over point set and check if any point is close enough to the pointer to be selected. + // Choose the closest one of these candidates. + auto *points = dynamic_cast(GetDataNode()->GetData()); int index = -1; if (points == nullptr) { return index; } if (points->GetPointSet(time) == nullptr) return -1; PointSet::PointsContainer *pointsContainer = points->GetPointSet(time)->GetPoints(); - float minDistance = m_SelectionAccuracy; - if (accuracy != -1) - minDistance = accuracy; + if (accuracy == -1) + accuracy = m_SelectionAccuracy; + + const auto end = pointsContainer->End(); + float minDistance = accuracy; + float distance; - for (PointSet::PointsIterator it = pointsContainer->Begin(); it != pointsContainer->End(); it++) + for (auto it = pointsContainer->Begin(); it != end; ++it) { - float distance = sqrt(position.SquaredEuclideanDistanceTo(points->GetPoint(it->Index(), time))); - if (distance < - minDistance) // if several points fall within the margin, choose the one with minimal distance to position + distance = sqrtf(position.SquaredEuclideanDistanceTo(points->GetPoint(it->Index(), time))); + if (distance < minDistance) { + minDistance = distance; index = it->Index(); } } return index; } bool mitk::PointSetDataInteractor::CheckSelection(const mitk::InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) return true; } return false; } void mitk::PointSetDataInteractor::UnselectAll(unsigned int timeStep, ScalarType timeInMs) { auto *pointSet = dynamic_cast(GetDataNode()->GetData()); if (pointSet == nullptr) { return; } mitk::PointSet::DataType *itkPointSet = pointSet->GetPointSet(timeStep); if (itkPointSet == nullptr) { return; } mitk::PointSet::PointsContainer::Iterator it, end; end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); PointSet::PointDataType pointData = {0, false, PTUNDEFINED}; itkPointSet->GetPointData(position, &pointData); // then declare an operation which unselects this point; // UndoOperation as well! if (pointData.selected) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation *undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( pointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } void mitk::PointSetDataInteractor::SelectPoint(int position, unsigned int timeStep, ScalarType timeInMS) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); // if List is empty, then no selection of a point can be done! if ((pointSet == nullptr) || (pointSet->GetSize(timeStep) <= 0)) { return; } // dummyPoint... not needed anyway mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMS, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMS, noPoint, position); OperationEvent *operationEvent = new OperationEvent(pointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } diff --git a/Modules/Core/src/Rendering/mitkAnnotation.cpp b/Modules/Core/src/Rendering/mitkAnnotation.cpp index c9ac002908..9568fc0541 100644 --- a/Modules/Core/src/Rendering/mitkAnnotation.cpp +++ b/Modules/Core/src/Rendering/mitkAnnotation.cpp @@ -1,348 +1,349 @@ /*============================================================================ 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 "mitkAnnotation.h" -#include "usGetModuleContext.h" +#include +#include +#include const std::string mitk::Annotation::US_INTERFACE_NAME = "org.mitk.services.Annotation"; const std::string mitk::Annotation::US_PROPKEY_AnnotationNAME = US_INTERFACE_NAME + ".name"; const std::string mitk::Annotation::US_PROPKEY_ID = US_INTERFACE_NAME + ".id"; const std::string mitk::Annotation::US_PROPKEY_MODIFIED = US_INTERFACE_NAME + ".modified"; const std::string mitk::Annotation::US_PROPKEY_RENDERER_ID = US_INTERFACE_NAME + ".rendererId"; const std::string mitk::Annotation::US_PROPKEY_AR_ID = US_INTERFACE_NAME + ".arId"; mitk::Annotation::Annotation() : m_PropertyListModifiedObserverTag(0) { m_PropertyList = mitk::PropertyList::New(); itk::MemberCommand::Pointer _PropertyListModifiedCommand = itk::MemberCommand::New(); _PropertyListModifiedCommand->SetCallbackFunction(this, &mitk::Annotation::PropertyListModified); m_PropertyListModifiedObserverTag = m_PropertyList->AddObserver(itk::ModifiedEvent(), _PropertyListModifiedCommand); this->SetName(this->GetNameOfClass()); this->SetVisibility(true); this->SetOpacity(1.0); } void mitk::Annotation::PropertyListModified(const itk::Object * /*caller*/, const itk::EventObject &) { AnnotationModified(); } mitk::Annotation::~Annotation() { this->UnRegisterMicroservice(); } void mitk::Annotation::SetUSProperty(const std::string &propertyKey, us::Any value) { if (this->m_ServiceRegistration) { us::ServiceProperties props; std::vector propertyKeys; m_ServiceRegistration.GetReference().GetPropertyKeys(propertyKeys); for (const std::string &key : propertyKeys) { props[key] = m_ServiceRegistration.GetReference().GetProperty(key); } props[propertyKey] = value; m_ServiceRegistration.SetProperties(props); } } void mitk::Annotation::SetProperty(const std::string &propertyKey, const BaseProperty::Pointer &propertyValue) { this->m_PropertyList->SetProperty(propertyKey, propertyValue); } void mitk::Annotation::ReplaceProperty(const std::string &propertyKey, const BaseProperty::Pointer &propertyValue) { this->m_PropertyList->ReplaceProperty(propertyKey, propertyValue); } void mitk::Annotation::AddProperty(const std::string &propertyKey, const BaseProperty::Pointer &propertyValue, bool overwrite) { if ((overwrite) || (GetProperty(propertyKey) == nullptr)) { SetProperty(propertyKey, propertyValue); } } void mitk::Annotation::ConcatenatePropertyList(PropertyList *pList, bool replace) { m_PropertyList->ConcatenatePropertyList(pList, replace); } mitk::BaseProperty *mitk::Annotation::GetProperty(const std::string &propertyKey) const { mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty(propertyKey); if (property.IsNotNull()) return property; // only to satisfy compiler! return nullptr; } bool mitk::Annotation::GetBoolProperty(const std::string &propertyKey, bool &boolValue) const { mitk::BoolProperty::Pointer boolprop = dynamic_cast(GetProperty(propertyKey)); if (boolprop.IsNull()) return false; boolValue = boolprop->GetValue(); return true; } bool mitk::Annotation::GetIntProperty(const std::string &propertyKey, int &intValue) const { mitk::IntProperty::Pointer intprop = dynamic_cast(GetProperty(propertyKey)); if (intprop.IsNull()) return false; intValue = intprop->GetValue(); return true; } bool mitk::Annotation::GetFloatProperty(const std::string &propertyKey, float &floatValue) const { mitk::FloatProperty::Pointer floatprop = dynamic_cast(GetProperty(propertyKey)); if (floatprop.IsNull()) return false; floatValue = floatprop->GetValue(); return true; } bool mitk::Annotation::GetStringProperty(const std::string &propertyKey, std::string &string) const { mitk::StringProperty::Pointer stringProp = dynamic_cast(GetProperty(propertyKey)); if (stringProp.IsNull()) { return false; } else { // memcpy((void*)string, stringProp->GetValue(), strlen(stringProp->GetValue()) + 1 ); // looks dangerous string = stringProp->GetValue(); return true; } } void mitk::Annotation::SetIntProperty(const std::string &propertyKey, int intValue) { this->m_PropertyList->SetProperty(propertyKey, mitk::IntProperty::New(intValue)); Modified(); } void mitk::Annotation::SetBoolProperty(const std::string &propertyKey, bool boolValue) { this->m_PropertyList->SetProperty(propertyKey, mitk::BoolProperty::New(boolValue)); Modified(); } void mitk::Annotation::SetFloatProperty(const std::string &propertyKey, float floatValue) { this->m_PropertyList->SetProperty(propertyKey, mitk::FloatProperty::New(floatValue)); Modified(); } void mitk::Annotation::SetDoubleProperty(const std::string &propertyKey, double doubleValue) { this->m_PropertyList->SetProperty(propertyKey, mitk::DoubleProperty::New(doubleValue)); Modified(); } void mitk::Annotation::SetStringProperty(const std::string &propertyKey, const std::string &stringValue) { this->m_PropertyList->SetProperty(propertyKey, mitk::StringProperty::New(stringValue)); Modified(); } std::string mitk::Annotation::GetName() const { mitk::StringProperty *sp = dynamic_cast(this->GetProperty("name")); if (sp == nullptr) return ""; return sp->GetValue(); } void mitk::Annotation::SetName(const std::string &name) { this->SetStringProperty("name", name); } bool mitk::Annotation::GetName(std::string &nodeName, const std::string &propertyKey) const { return GetStringProperty(propertyKey, nodeName); } void mitk::Annotation::SetText(std::string text) { SetStringProperty("Text", text.c_str()); } std::string mitk::Annotation::GetText() const { std::string text; GetStringProperty("Text", text); return text; } void mitk::Annotation::SetFontSize(int fontSize) { SetIntProperty("FontSize", fontSize); } int mitk::Annotation::GetFontSize() const { int fontSize = 1; GetIntProperty("FontSize", fontSize); return fontSize; } bool mitk::Annotation::GetVisibility(bool &visible, const std::string &propertyKey) const { return GetBoolProperty(propertyKey, visible); } bool mitk::Annotation::IsVisible(const std::string &propertyKey, bool defaultIsOn) const { return IsOn(propertyKey, defaultIsOn); } bool mitk::Annotation::GetColor(float rgb[], const std::string &propertyKey) const { mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetProperty(propertyKey)); if (colorprop.IsNull()) return false; memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3 * sizeof(float)); return true; } void mitk::Annotation::SetColor(const mitk::Color &color, const std::string &propertyKey) { mitk::ColorProperty::Pointer prop; prop = mitk::ColorProperty::New(color); this->m_PropertyList->SetProperty(propertyKey, prop); } void mitk::Annotation::SetColor(float red, float green, float blue, const std::string &propertyKey) { float color[3]; color[0] = red; color[1] = green; color[2] = blue; SetColor(color, propertyKey); } void mitk::Annotation::SetColor(const float rgb[], const std::string &propertyKey) { mitk::ColorProperty::Pointer prop; prop = mitk::ColorProperty::New(rgb); this->m_PropertyList->SetProperty(propertyKey, prop); } bool mitk::Annotation::GetOpacity(float &opacity, const std::string &propertyKey) const { mitk::FloatProperty::Pointer opacityprop = dynamic_cast(GetProperty(propertyKey)); if (opacityprop.IsNull()) return false; opacity = opacityprop->GetValue(); return true; } void mitk::Annotation::SetOpacity(float opacity, const std::string &propertyKey) { mitk::FloatProperty::Pointer prop; prop = mitk::FloatProperty::New(opacity); this->m_PropertyList->SetProperty(propertyKey, prop); } void mitk::Annotation::SetVisibility(bool visible, const std::string &propertyKey) { mitk::BoolProperty::Pointer prop; prop = mitk::BoolProperty::New(visible); this->m_PropertyList->SetProperty(propertyKey, prop); Modified(); } bool mitk::Annotation::BaseLocalStorage::IsGenerateDataRequired(mitk::BaseRenderer *renderer, mitk::Annotation *Annotation) { if (m_LastGenerateDataTime < Annotation->GetMTime()) return true; if (m_LastGenerateDataTime < Annotation->GetPropertyList()->GetMTime()) return true; if (renderer && m_LastGenerateDataTime < renderer->GetTimeStepUpdateTime()) return true; return false; } mitk::Annotation::Bounds mitk::Annotation::GetBoundsOnDisplay(mitk::BaseRenderer *) const { mitk::Annotation::Bounds bounds; bounds.Position[0] = bounds.Position[1] = bounds.Size[0] = bounds.Size[1] = 0; return bounds; } void mitk::Annotation::SetBoundsOnDisplay(mitk::BaseRenderer *, const mitk::Annotation::Bounds &) { } void mitk::Annotation::SetForceInForeground(bool forceForeground) { m_ForceInForeground = forceForeground; } bool mitk::Annotation::IsForceInForeground() const { return m_ForceInForeground; } mitk::PropertyList *mitk::Annotation::GetPropertyList() const { return m_PropertyList; } std::string mitk::Annotation::GetMicroserviceID() { return this->m_ServiceRegistration.GetReference().GetProperty(US_PROPKEY_ID).ToString(); } void mitk::Annotation::RegisterAsMicroservice(us::ServiceProperties props) { if (m_ServiceRegistration != nullptr) m_ServiceRegistration.Unregister(); us::ModuleContext *context = us::GetModuleContext(); // Define ServiceProps mitk::UIDGenerator uidGen = mitk::UIDGenerator("org.mitk.services.Annotation.id_"); props[US_PROPKEY_ID] = uidGen.GetUID(); m_ServiceRegistration = context->RegisterService(this, props); } void mitk::Annotation::UnRegisterMicroservice() { if (m_ServiceRegistration != nullptr) m_ServiceRegistration.Unregister(); m_ServiceRegistration = 0; } void mitk::Annotation::AnnotationModified() { Modified(); this->SetUSProperty(US_PROPKEY_MODIFIED, this->GetMTime()); } diff --git a/Modules/Core/src/mitkCoreActivator.cpp b/Modules/Core/src/mitkCoreActivator.cpp index 594cdf5490..a5c00479ad 100644 --- a/Modules/Core/src/mitkCoreActivator.cpp +++ b/Modules/Core/src/mitkCoreActivator.cpp @@ -1,368 +1,437 @@ /*============================================================================ 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 -void HandleMicroServicesMessages(us::MsgType type, const char *msg) +namespace { - switch (type) + 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) + void AddMitkAutoLoadPaths(const std::string& programPath) { - std::size_t index = additionalPath.find_last_of('/'); - if (index != std::string::npos) + 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) { - additionalPath = additionalPath.substr(0, index); + std::size_t index = additionalPath.find_last_of('/'); + if (index != std::string::npos) + { + additionalPath = additionalPath.substr(0, index); + } + else + { + addPath = false; + break; + } } - else + if (addPath) { - addPath = false; - break; + us::ModuleSettings::AddAutoLoadPath(additionalPath); } +#endif } - if (addPath) + + void AddPropertyPersistence(const mitk::PropertyKeyPath& propPath) { - us::ModuleSettings::AddAutoLoadPath(additionalPath); - } -#endif -} + mitk::CoreServicePointer persistenceService(mitk::CoreServices::GetPropertyPersistence()); -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); + } - 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); + persistenceService->AddInfo(info); } - else + + void RegisterProperties() { - 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); + 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(); } - - persistenceService->AddInfo(info); } 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::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/src/mitkCoreActivator.h b/Modules/Core/src/mitkCoreActivator.h index 19c513bba7..d28f97a70a 100644 --- a/Modules/Core/src/mitkCoreActivator.h +++ b/Modules/Core/src/mitkCoreActivator.h @@ -1,82 +1,84 @@ /*============================================================================ 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 mitkCoreActivator_h #define mitkCoreActivator_h // File IO #include #include #include #include #include #include #include +#include #include #include #include #include #include // Micro Services #include #include #include #include #include /* * This is the module activator for the "Mitk" module. It registers core services * like ... */ class MitkCoreActivator : public us::ModuleActivator { public: void Load(us::ModuleContext *context) override; void Unload(us::ModuleContext *) override; private: void HandleModuleEvent(const us::ModuleEvent moduleEvent); void RegisterDefaultMimeTypes(); void RegisterItkReaderWriter(); void RegisterVtkReaderWriter(); void RegisterLegacyWriter(); // mitk::RenderingManager::Pointer m_RenderingManager; std::unique_ptr m_PlanePositionManager; std::unique_ptr m_PropertyAliases; std::unique_ptr m_PropertyDescriptions; + std::unique_ptr m_PropertyDeserialization; std::unique_ptr m_PropertyExtensions; std::unique_ptr m_PropertyFilters; std::unique_ptr m_PropertyPersistence; std::unique_ptr m_PropertyRelations; std::unique_ptr m_MimeTypeProvider; std::unique_ptr m_PreferencesService; // File IO std::vector m_FileReaders; std::vector m_FileWriters; std::vector m_FileIOs; std::vector m_LegacyWriters; std::vector m_DefaultMimeTypes; us::ServiceRegistration m_MimeTypeProviderReg; us::ModuleContext *m_Context; }; #endif diff --git a/Modules/Core/src/mitkCoreServices.cpp b/Modules/Core/src/mitkCoreServices.cpp index 720730347f..96dce2bcde 100644 --- a/Modules/Core/src/mitkCoreServices.cpp +++ b/Modules/Core/src/mitkCoreServices.cpp @@ -1,133 +1,139 @@ /*============================================================================ 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 "mitkCoreServices.h" #include #include #include +#include #include #include #include #include #include #include #include #include #include #include namespace mitk { std::mutex &s_ContextToServicesMapMutex() { static std::mutex mutex; return mutex; } std::map> &s_ContextToServicesMap() { static std::map> serviceMap; return serviceMap; } template static S *GetCoreService(us::ModuleContext *context) { if (context == nullptr) context = us::GetModuleContext(); S *coreService = nullptr; us::ServiceReference serviceRef = context->GetServiceReference(); if (serviceRef) { coreService = context->GetService(serviceRef); } assert(coreService && "Asserting non-nullptr MITK core service"); { std::lock_guard l(s_ContextToServicesMapMutex()); s_ContextToServicesMap()[context].insert(std::make_pair(coreService, serviceRef)); } return coreService; } IPropertyAliases *CoreServices::GetPropertyAliases(us::ModuleContext *context) { return GetCoreService(context); } IPropertyDescriptions *CoreServices::GetPropertyDescriptions(us::ModuleContext *context) { return GetCoreService(context); } + IPropertyDeserialization* CoreServices::GetPropertyDeserialization(us::ModuleContext* context) + { + return GetCoreService(context); + } + IPropertyExtensions *CoreServices::GetPropertyExtensions(us::ModuleContext *context) { return GetCoreService(context); } IPropertyFilters *CoreServices::GetPropertyFilters(us::ModuleContext *context) { return GetCoreService(context); } IPropertyPersistence *CoreServices::GetPropertyPersistence(us::ModuleContext *context) { return GetCoreService(context); } IPropertyRelations *CoreServices::GetPropertyRelations(us::ModuleContext *context) { return GetCoreService(context); } IMimeTypeProvider *CoreServices::GetMimeTypeProvider(us::ModuleContext *context) { return GetCoreService(context); } IPreferencesService *CoreServices::GetPreferencesService(us::ModuleContext *context) { return GetCoreService(context); } bool CoreServices::Unget(us::ModuleContext *context, const std::string & /*interfaceId*/, void *service) { bool success = false; std::lock_guard l(s_ContextToServicesMapMutex()); auto iter = s_ContextToServicesMap().find(context); if (iter != s_ContextToServicesMap().end()) { auto iter2 = iter->second.find(service); if (iter2 != iter->second.end()) { us::ServiceReferenceU serviceRef = iter2->second; if (serviceRef) { success = context->UngetService(serviceRef); if (success) { iter->second.erase(iter2); } } } } return success; } } diff --git a/Modules/Core/test/mitkGeometry3DTest.cpp b/Modules/Core/test/mitkGeometry3DTest.cpp index c4395e1a28..a699e40249 100644 --- a/Modules/Core/test/mitkGeometry3DTest.cpp +++ b/Modules/Core/test/mitkGeometry3DTest.cpp @@ -1,622 +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. ============================================================================*/ #include "mitkGeometry3D.h" #include #include #include "mitkInteractionConst.h" #include "mitkRotationOperation.h" #include #include #include "mitkTestingMacros.h" #include #include bool testGetAxisVectorVariants(mitk::Geometry3D *geometry) { int direction; for (direction = 0; direction < 3; ++direction) { mitk::Vector3D frontToBack(0.); switch (direction) { case 0: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(true, false, false); break; // 7-3 case 1: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(false, true, false); break; // 7-5 case 2: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(false, false, true); break; // 7-2 } std::cout << "Testing GetAxisVector(int) vs GetAxisVector(bool, bool, bool): "; if (mitk::Equal(geometry->GetAxisVector(direction), frontToBack) == false) { std::cout << "[FAILED]" << std::endl; return false; } std::cout << "[PASSED]" << std::endl; } return true; } bool testGetAxisVectorExtent(mitk::Geometry3D *geometry) { int direction; for (direction = 0; direction < 3; ++direction) { if (mitk::Equal(geometry->GetAxisVector(direction).GetNorm(), geometry->GetExtentInMM(direction)) == false) { std::cout << "[FAILED]" << std::endl; return false; } std::cout << "[PASSED]" << std::endl; } return true; } // a part of the test requires axis-parallel coordinates int testIndexAndWorldConsistency(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems: "); mitk::Point3D origin = geometry3d->GetOrigin(); mitk::Point3D dummy; MITK_TEST_OUTPUT(<< " Testing index->world->index conversion consistency"); geometry3d->WorldToIndex(origin, dummy); geometry3d->IndexToWorld(dummy, dummy); MITK_TEST_CONDITION_REQUIRED(dummy == origin, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin, mitk::Point3D)==(0,0,0)"); mitk::Point3D globalOrigin; mitk::FillVector3D(globalOrigin, 0, 0, 0); mitk::Point3D originContinuousIndex; geometry3d->WorldToIndex(origin, originContinuousIndex); MITK_TEST_CONDITION_REQUIRED(originContinuousIndex == globalOrigin, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin, itk::Index)==(0,0,0)"); itk::Index<3> itkindex; geometry3d->WorldToIndex(origin, itkindex); itk::Index<3> globalOriginIndex; mitk::vtk2itk(globalOrigin, globalOriginIndex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin-0.5*spacing, itk::Index)==(0,0,0)"); mitk::Vector3D halfSpacingStep = geometry3d->GetSpacing() * 0.5; mitk::Matrix3D rotation; mitk::Point3D originOffCenter = origin - halfSpacingStep; geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin+0.5*spacing-eps, itk::Index)==(0,0,0)"); originOffCenter = origin + halfSpacingStep; originOffCenter -= mitk::Vector(0.0001); geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin+0.5*spacing, itk::Index)==(1,1,1)"); originOffCenter = origin + halfSpacingStep; itk::Index<3> global111; mitk::FillVector3D(global111, 1, 1, 1); geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == global111, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(GetCenter())==BoundingBox.GetCenter: "); mitk::Point3D center = geometry3d->GetCenter(); mitk::Point3D centerContIndex; geometry3d->WorldToIndex(center, centerContIndex); mitk::BoundingBox::ConstPointer boundingBox = geometry3d->GetBoundingBox(); mitk::BoundingBox::PointType centerBounds = boundingBox->GetCenter(); // itk assumes corner based geometry. If our geometry is center based (imageGoe == true), everything needs to be // shifted if (geometry3d->GetImageGeometry()) { centerBounds[0] -= 0.5; centerBounds[1] -= 0.5; centerBounds[2] -= 0.5; } MITK_TEST_CONDITION_REQUIRED(mitk::Equal(centerContIndex, centerBounds), ""); MITK_TEST_OUTPUT(<< " Testing GetCenter()==IndexToWorld(BoundingBox.GetCenter): "); center = geometry3d->GetCenter(); mitk::Point3D centerBoundsInWorldCoords; geometry3d->IndexToWorld(centerBounds, centerBoundsInWorldCoords); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(center, centerBoundsInWorldCoords), ""); return EXIT_SUCCESS; } int testIndexAndWorldConsistencyForVectors(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems for vectors: "); mitk::Vector3D xAxisMM = geometry3d->GetAxisVector(0); mitk::Vector3D xAxisContinuousIndex; mitk::Vector3D xAxisContinuousIndexDeprecated; mitk::Point3D p, pIndex, origin; origin = geometry3d->GetOrigin(); p[0] = xAxisMM[0]; p[1] = xAxisMM[1]; p[2] = xAxisMM[2]; geometry3d->WorldToIndex(p, pIndex); geometry3d->WorldToIndex(xAxisMM, xAxisContinuousIndexDeprecated); geometry3d->WorldToIndex(xAxisMM, xAxisContinuousIndex); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == pIndex[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == pIndex[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == pIndex[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == xAxisContinuousIndexDeprecated[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == xAxisContinuousIndexDeprecated[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == xAxisContinuousIndexDeprecated[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[0] == pIndex[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[1] == pIndex[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[2] == pIndex[2], ""); geometry3d->IndexToWorld(xAxisContinuousIndex, xAxisContinuousIndex); geometry3d->IndexToWorld(xAxisContinuousIndexDeprecated, xAxisContinuousIndexDeprecated); geometry3d->IndexToWorld(pIndex, p); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex == xAxisMM, ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == p[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == p[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == p[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated == xAxisMM, ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[0] == p[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[1] == p[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[2] == p[2], ""); return EXIT_SUCCESS; } int testIndexAndWorldConsistencyForIndex(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems: "); // creating testing data itk::Index<4> itkIndex4, itkIndex4b; itk::Index<3> itkIndex3, itkIndex3b; itk::Index<2> itkIndex2, itkIndex2b; itk::Index<3> mitkIndex, mitkIndexb; itkIndex4[0] = itkIndex4[1] = itkIndex4[2] = itkIndex4[3] = 4; itkIndex3[0] = itkIndex3[1] = itkIndex3[2] = 6; itkIndex2[0] = itkIndex2[1] = 2; mitkIndex[0] = mitkIndex[1] = mitkIndex[2] = 13; // check for consistency mitk::Point3D point; geometry3d->IndexToWorld(itkIndex2, point); geometry3d->WorldToIndex(point, itkIndex2b); MITK_TEST_CONDITION_REQUIRED(((itkIndex2b[0] == itkIndex2[0]) && (itkIndex2b[1] == itkIndex2[1])), "Testing itk::index<2> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(itkIndex3, point); geometry3d->WorldToIndex(point, itkIndex3b); MITK_TEST_CONDITION_REQUIRED( ((itkIndex3b[0] == itkIndex3[0]) && (itkIndex3b[1] == itkIndex3[1]) && (itkIndex3b[2] == itkIndex3[2])), "Testing itk::index<3> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(itkIndex4, point); geometry3d->WorldToIndex(point, itkIndex4b); MITK_TEST_CONDITION_REQUIRED(((itkIndex4b[0] == itkIndex4[0]) && (itkIndex4b[1] == itkIndex4[1]) && (itkIndex4b[2] == itkIndex4[2]) && (itkIndex4b[3] == 0)), "Testing itk::index<3> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(mitkIndex, point); geometry3d->WorldToIndex(point, mitkIndexb); MITK_TEST_CONDITION_REQUIRED( ((mitkIndexb[0] == mitkIndex[0]) && (mitkIndexb[1] == mitkIndex[1]) && (mitkIndexb[2] == mitkIndex[2])), "Testing mitk::Index for IndexToWorld/WorldToIndex consistency"); return EXIT_SUCCESS; } #include int testItkImageIsCenterBased() { MITK_TEST_OUTPUT(<< "Testing whether itk::Image coordinates are center-based."); typedef itk::Image ItkIntImage3D; ItkIntImage3D::Pointer itkintimage = ItkIntImage3D::New(); ItkIntImage3D::SizeType size; size.Fill(10); mitk::Point3D origin; mitk::FillVector3D(origin, 2, 3, 7); itkintimage->Initialize(); itkintimage->SetRegions(size); itkintimage->SetOrigin(origin); std::cout << "[PASSED]" << std::endl; MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToContinuousIndex(origin)==(0,0,0)"); mitk::Point3D globalOrigin; mitk::FillVector3D(globalOrigin, 0, 0, 0); - itk::ContinuousIndex originContinuousIndex; - itkintimage->TransformPhysicalPointToContinuousIndex(origin, originContinuousIndex); + auto originContinuousIndex = itkintimage->TransformPhysicalPointToContinuousIndex(origin); MITK_TEST_CONDITION_REQUIRED(originContinuousIndex == globalOrigin, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin)==(0,0,0)"); - itk::Index<3> itkindex; - itkintimage->TransformPhysicalPointToIndex(origin, itkindex); + itk::Index<3> itkindex = itkintimage->TransformPhysicalPointToIndex(origin); itk::Index<3> globalOriginIndex; mitk::vtk2itk(globalOrigin, globalOriginIndex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin-0.5*spacing)==(0,0,0)"); mitk::Vector3D halfSpacingStep = itkintimage->GetSpacing() * 0.5; mitk::Matrix3D rotation; mitk::Point3D originOffCenter = origin - halfSpacingStep; - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT( << " Testing itk::Image::TransformPhysicalPointToIndex(origin+0.5*spacing-eps, itk::Index)==(0,0,0)"); originOffCenter = origin + halfSpacingStep; originOffCenter -= mitk::Vector(0.0001); - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin+0.5*spacing, itk::Index)==(1,1,1)"); originOffCenter = origin + halfSpacingStep; itk::Index<3> global111; mitk::FillVector3D(global111, 1, 1, 1); - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == global111, ""); MITK_TEST_OUTPUT(<< "=> Yes, itk::Image coordinates are center-based."); return EXIT_SUCCESS; } int testGeometry3D(bool imageGeometry) { // Build up a new image Geometry mitk::Geometry3D::Pointer geometry3d = mitk::Geometry3D::New(); float bounds[] = {-10.0, 17.0, -12.0, 188.0, 13.0, 211.0}; MITK_TEST_OUTPUT(<< "Initializing"); geometry3d->Initialize(); MITK_TEST_OUTPUT(<< "Setting ImageGeometry to " << imageGeometry); geometry3d->SetImageGeometry(imageGeometry); MITK_TEST_OUTPUT(<< "Setting bounds by SetFloatBounds(): " << bounds); geometry3d->SetFloatBounds(bounds); MITK_TEST_OUTPUT(<< "Testing AxisVectors"); if (testGetAxisVectorVariants(geometry3d) == false) return EXIT_FAILURE; if (testGetAxisVectorExtent(geometry3d) == false) return EXIT_FAILURE; MITK_TEST_OUTPUT(<< "Creating an AffineTransform3D transform"); mitk::AffineTransform3D::MatrixType matrix; matrix.SetIdentity(); matrix(1, 1) = 2; mitk::AffineTransform3D::Pointer transform; transform = mitk::AffineTransform3D::New(); transform->SetMatrix(matrix); MITK_TEST_OUTPUT(<< "Testing a SetIndexToWorldTransform"); geometry3d->SetIndexToWorldTransform(transform); MITK_TEST_OUTPUT(<< "Testing correctness of value returned by GetSpacing"); const mitk::Vector3D &spacing1 = geometry3d->GetSpacing(); mitk::Vector3D expectedSpacing; expectedSpacing.Fill(1.0); expectedSpacing[1] = 2; if (mitk::Equal(spacing1, expectedSpacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } MITK_TEST_OUTPUT(<< "Testing a Compose(transform)"); geometry3d->Compose(transform); MITK_TEST_OUTPUT(<< "Testing correctness of value returned by GetSpacing"); const mitk::Vector3D &spacing2 = geometry3d->GetSpacing(); expectedSpacing[1] = 4; if (mitk::Equal(spacing2, expectedSpacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } MITK_TEST_OUTPUT(<< "Testing correctness of SetSpacing"); mitk::Vector3D newspacing; mitk::FillVector3D(newspacing, 1.5, 2.5, 3.5); geometry3d->SetSpacing(newspacing); const mitk::Vector3D &spacing3 = geometry3d->GetSpacing(); if (mitk::Equal(spacing3, newspacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } // Separate Test function for Index and World consistency testIndexAndWorldConsistency(geometry3d); testIndexAndWorldConsistencyForVectors(geometry3d); testIndexAndWorldConsistencyForIndex(geometry3d); MITK_TEST_OUTPUT(<< "Testing a rotation of the geometry"); double angle = 35.0; mitk::Vector3D rotationVector; mitk::FillVector3D(rotationVector, 1, 0, 0); mitk::Point3D center = geometry3d->GetCenter(); auto op = new mitk::RotationOperation(mitk::OpROTATE, center, rotationVector, angle); geometry3d->ExecuteOperation(op); MITK_TEST_OUTPUT(<< "Testing mitk::GetRotation() and success of rotation"); mitk::Matrix3D rotation; mitk::GetRotation(geometry3d, rotation); mitk::Vector3D voxelStep = rotation * newspacing; mitk::Vector3D voxelStepIndex; geometry3d->WorldToIndex(voxelStep, voxelStepIndex); mitk::Vector3D expectedVoxelStepIndex; expectedVoxelStepIndex.Fill(1); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(voxelStepIndex, expectedVoxelStepIndex), ""); delete op; std::cout << "[PASSED]" << std::endl; MITK_TEST_OUTPUT(<< "Testing that ImageGeometry is still " << imageGeometry); MITK_TEST_CONDITION_REQUIRED(geometry3d->GetImageGeometry() == imageGeometry, ""); // Test if the translate function moves the origin correctly. mitk::Point3D oldOrigin = geometry3d->GetOrigin(); // use some random values for translation mitk::Vector3D translationVector; translationVector.SetElement(0, 17.5f); translationVector.SetElement(1, -32.3f); translationVector.SetElement(2, 4.0f); // compute ground truth mitk::Point3D tmpResult = geometry3d->GetOrigin() + translationVector; geometry3d->Translate(translationVector); MITK_TEST_CONDITION(mitk::Equal(geometry3d->GetOrigin(), tmpResult), "Testing if origin was translated."); translationVector *= -1; // vice versa geometry3d->Translate(translationVector); MITK_TEST_CONDITION(mitk::Equal(geometry3d->GetOrigin(), oldOrigin), "Testing if the translation could be done vice versa."); return EXIT_SUCCESS; } int testGeometryAfterCasting() { // Epsilon. Allowed difference for rotationvalue float eps = 0.0001; // Cast ITK and MITK images and see if geometry stays typedef itk::Image Image2DType; typedef itk::Image Image3DType; // Create 3D ITK Image from Scratch, cast to 3D MITK image, compare Geometries Image3DType::Pointer image3DItk = Image3DType::New(); Image3DType::RegionType myRegion; Image3DType::SizeType mySize; Image3DType::IndexType myIndex; Image3DType::SpacingType mySpacing; Image3DType::DirectionType myDirection, rotMatrixX, rotMatrixY, rotMatrixZ; mySpacing[0] = 31; mySpacing[1] = 0.1; mySpacing[2] = 2.9; myIndex[0] = -15; myIndex[1] = 15; myIndex[2] = 12; mySize[0] = 10; mySize[1] = 2; mySize[2] = 5; myRegion.SetSize(mySize); myRegion.SetIndex(myIndex); image3DItk->SetSpacing(mySpacing); image3DItk->SetRegions(myRegion); image3DItk->Allocate(); image3DItk->FillBuffer(0); myDirection.SetIdentity(); rotMatrixX.SetIdentity(); rotMatrixY.SetIdentity(); rotMatrixZ.SetIdentity(); mitk::Image::Pointer mitkImage; // direction [row] [column] MITK_TEST_OUTPUT(<< "Casting a rotated 3D ITK Image to a MITK Image and check if Geometry is still same"); for (double rotX = 0; rotX < itk::Math::pi * 2; rotX += itk::Math::pi * 0.4) { // Set Rotation X rotMatrixX[1][1] = cos(rotX); rotMatrixX[1][2] = -sin(rotX); rotMatrixX[2][1] = sin(rotX); rotMatrixX[2][2] = cos(rotX); for (double rotY = 0; rotY < itk::Math::pi * 2; rotY += itk::Math::pi * 0.3) { // Set Rotation Y rotMatrixY[0][0] = cos(rotY); rotMatrixY[0][2] = sin(rotY); rotMatrixY[2][0] = -sin(rotY); rotMatrixY[2][2] = cos(rotY); for (double rotZ = 0; rotZ < itk::Math::pi * 2; rotZ += itk::Math::pi * 0.2) { // Set Rotation Z rotMatrixZ[0][0] = cos(rotZ); rotMatrixZ[0][1] = -sin(rotZ); rotMatrixZ[1][0] = sin(rotZ); rotMatrixZ[1][1] = cos(rotZ); // Multiply matrizes myDirection = myDirection * rotMatrixX * rotMatrixY * rotMatrixZ; image3DItk->SetDirection(myDirection); mitk::CastToMitkImage(image3DItk, mitkImage); const mitk::AffineTransform3D::MatrixType &matrix = mitkImage->GetGeometry()->GetIndexToWorldTransform()->GetMatrix(); for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { double mitkValue = matrix[row][col] / mitkImage->GetGeometry()->GetSpacing()[col]; double itkValue = myDirection[row][col]; double diff = mitkValue - itkValue; // if you decrease this value, you can see that there might be QUITE high inaccuracy!!! if (diff > eps) // need to check, how exact it SHOULD be .. since it is NOT EXACT! { std::cout << "Had a difference of : " << diff; std::cout << "Error: Casting altered Geometry!"; std::cout << "ITK Matrix:\n" << myDirection; std::cout << "Mitk Matrix (With Spacing):\n" << matrix; std::cout << "Mitk Spacing: " << mitkImage->GetGeometry()->GetSpacing(); MITK_TEST_CONDITION_REQUIRED(false == true, ""); return false; } } } } } } // Create 2D ITK Image from Scratch, cast to 2D MITK image, compare Geometries Image2DType::Pointer image2DItk = Image2DType::New(); Image2DType::RegionType myRegion2D; Image2DType::SizeType mySize2D; Image2DType::IndexType myIndex2D; Image2DType::SpacingType mySpacing2D; Image2DType::DirectionType myDirection2D, rotMatrix; mySpacing2D[0] = 31; mySpacing2D[1] = 0.1; myIndex2D[0] = -15; myIndex2D[1] = 15; mySize2D[0] = 10; mySize2D[1] = 2; myRegion2D.SetSize(mySize2D); myRegion2D.SetIndex(myIndex2D); image2DItk->SetSpacing(mySpacing2D); image2DItk->SetRegions(myRegion2D); image2DItk->Allocate(); image2DItk->FillBuffer(0); myDirection2D.SetIdentity(); rotMatrix.SetIdentity(); // direction [row] [column] MITK_TEST_OUTPUT(<< "Casting a rotated 2D ITK Image to a MITK Image and check if Geometry is still same"); for (double rotTheta = 0; rotTheta < itk::Math::pi * 2; rotTheta += itk::Math::pi * 0.2) { // Set Rotation rotMatrix[0][0] = cos(rotTheta); rotMatrix[0][1] = -sin(rotTheta); rotMatrix[1][0] = sin(rotTheta); rotMatrix[1][1] = cos(rotTheta); // Multiply matrizes myDirection2D = myDirection2D * rotMatrix; image2DItk->SetDirection(myDirection2D); mitk::CastToMitkImage(image2DItk, mitkImage); const mitk::AffineTransform3D::MatrixType &matrix = mitkImage->GetGeometry()->GetIndexToWorldTransform()->GetMatrix(); // Compare MITK and ITK matrix for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { double mitkValue = matrix[row][col] / mitkImage->GetGeometry()->GetSpacing()[col]; if ((row == 2) && (col == row)) { if (mitkValue != 1) { MITK_TEST_OUTPUT(<< "After casting a 2D ITK to 3D MITK images, MITK matrix values for 0|2, 1|2, 2|0, 2|1 " "MUST be 0 and value for 2|2 must be 1"); return false; } } else if ((row == 2) || (col == 2)) { if (mitkValue != 0) { MITK_TEST_OUTPUT(<< "After casting a 2D ITK to 3D MITK images, MITK matrix values for 0|2, 1|2, 2|0, 2|1 " "MUST be 0 and value for 2|2 must be 1"); return false; } } else { double itkValue = myDirection2D[row][col]; double diff = mitkValue - itkValue; // if you decrease this value, you can see that there might be QUITE high inaccuracy!!! if (diff > eps) // need to check, how exact it SHOULD be .. since it is NOT EXACT! { std::cout << "Had a difference of : " << diff; std::cout << "Error: Casting altered Geometry!"; std::cout << "ITK Matrix:\n" << myDirection2D; std::cout << "Mitk Matrix (With Spacing):\n" << matrix; std::cout << "Mitk Spacing: " << mitkImage->GetGeometry()->GetSpacing(); MITK_TEST_CONDITION_REQUIRED(false == true, ""); return false; } } } } } // THIS WAS TESTED: // 2D ITK -> 2D MITK, // 3D ITK -> 3D MITK, // Still need to test: 2D MITK Image with ADDITIONAL INFORMATION IN MATRIX -> 2D ITK // 1. Possibility: 3x3 MITK matrix can be converted without loss into 2x2 ITK matrix // 2. Possibility: 3x3 MITK matrix can only be converted with loss into 2x2 ITK matrix // .. before implementing this, we wait for further development in geometry classes (e.g. Geoemtry3D::SetRotation(..)) return EXIT_SUCCESS; } int mitkGeometry3DTest(int /*argc*/, char * /*argv*/ []) { MITK_TEST_BEGIN(mitkGeometry3DTest); int result; MITK_TEST_CONDITION_REQUIRED((result = testItkImageIsCenterBased()) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running main part of test with ImageGeometry = false"); MITK_TEST_CONDITION_REQUIRED((result = testGeometry3D(false)) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running main part of test with ImageGeometry = true"); MITK_TEST_CONDITION_REQUIRED((result = testGeometry3D(true)) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running test to see if Casting MITK to ITK and the other way around destroys geometry"); MITK_TEST_CONDITION_REQUIRED((result = testGeometryAfterCasting()) == EXIT_SUCCESS, ""); MITK_TEST_END(); return EXIT_SUCCESS; } diff --git a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp index ae79c6260b..48db4613df 100644 --- a/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp +++ b/Modules/Core/test/mitkTemporoSpatialStringPropertyTest.cpp @@ -1,242 +1,245 @@ /*============================================================================ 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); 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() { - std::string data = mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refProp); - CPPUNIT_ASSERT(refJSON == - data); //"Testing serializeTemporoSpatialStringPropertyToJSON() producing correct string."); + auto data = nlohmann::json::parse(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refProp)); + auto ref = nlohmann::json::parse(refJSON); - data = mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refPartlyCondensibleProp); - CPPUNIT_ASSERT(refPartlyCondensibleJSON == - data); + CPPUNIT_ASSERT(ref == data); //"Testing serializeTemporoSpatialStringPropertyToJSON() producing correct string."); - data = mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(refCondensibleProp); - CPPUNIT_ASSERT(refCondensibleJSON == - data); + 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); } }; MITK_TEST_SUITE_REGISTRATION(mitkTemporoSpatialStringProperty) diff --git a/Modules/CppMicroServices/core/src/module/usModuleContext.cpp b/Modules/CppMicroServices/core/src/module/usModuleContext.cpp index 1f92098b8b..44df19c0e2 100644 --- a/Modules/CppMicroServices/core/src/module/usModuleContext.cpp +++ b/Modules/CppMicroServices/core/src/module/usModuleContext.cpp @@ -1,196 +1,196 @@ /*============================================================================ Library: CppMicroServices Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================================================================*/ #include "usModuleContext.h" #include "usModuleRegistry.h" #include "usModulePrivate.h" #include "usModuleSettings.h" #include "usCoreModuleContext_p.h" #include "usServiceRegistry_p.h" #include "usServiceReferenceBasePrivate.h" #include US_BEGIN_NAMESPACE class ModuleContextPrivate { public: ModuleContextPrivate(ModulePrivate* module) : module(module) {} ModulePrivate* module; }; ModuleContext::ModuleContext(ModulePrivate* module) : d(new ModuleContextPrivate(module)) {} ModuleContext::~ModuleContext() { delete d; } Module* ModuleContext::GetModule() const { return d->module->q; } Module* ModuleContext::GetModule(long id) const { return d->module->coreCtx->moduleHooks.FilterModule(this, ModuleRegistry::GetModule(id)); } Module* ModuleContext::GetModule(const std::string& name) { return ModuleRegistry::GetModule(name); } std::vector ModuleContext::GetModules() const { std::vector modules = ModuleRegistry::GetModules(); d->module->coreCtx->moduleHooks.FilterModules(this, modules); return modules; } ServiceRegistrationU ModuleContext::RegisterService(const InterfaceMap& service, const ServiceProperties& properties) { return d->module->coreCtx->services.RegisterService(d->module, service, properties); } std::vector ModuleContext::GetServiceReferences(const std::string& clazz, const std::string& filter) { std::vector result; std::vector refs; d->module->coreCtx->services.Get(clazz, filter, d->module, refs); for (std::vector::const_iterator iter = refs.begin(); iter != refs.end(); ++iter) { result.push_back(ServiceReferenceU(*iter)); } return result; } ServiceReferenceU ModuleContext::GetServiceReference(const std::string& clazz) { return d->module->coreCtx->services.Get(d->module, clazz); } void* ModuleContext::GetService(const ServiceReferenceBase& reference) { if (!reference) { throw std::invalid_argument("Default constructed ServiceReference is not a valid input to GetService()"); } return reference.d->GetService(d->module->q); } InterfaceMap ModuleContext::GetService(const ServiceReferenceU& reference) { if (!reference) { throw std::invalid_argument("Default constructed ServiceReference is not a valid input to GetService()"); } return reference.d->GetServiceInterfaceMap(d->module->q); } bool ModuleContext::UngetService(const ServiceReferenceBase& reference) { ServiceReferenceBase ref = reference; return ref.d->UngetService(d->module->q, true); } void ModuleContext::AddServiceListener(const ServiceListener& delegate, const std::string& filter) { d->module->coreCtx->listeners.AddServiceListener(this, delegate, nullptr, filter); } void ModuleContext::RemoveServiceListener(const ServiceListener& delegate) { d->module->coreCtx->listeners.RemoveServiceListener(this, delegate, nullptr); } void ModuleContext::AddModuleListener(const ModuleListener& delegate) { d->module->coreCtx->listeners.AddModuleListener(this, delegate, nullptr); } void ModuleContext::RemoveModuleListener(const ModuleListener& delegate) { d->module->coreCtx->listeners.RemoveModuleListener(this, delegate, nullptr); } void ModuleContext::AddServiceListener(const ServiceListener& delegate, void* data, const std::string &filter) { d->module->coreCtx->listeners.AddServiceListener(this, delegate, data, filter); } void ModuleContext::RemoveServiceListener(const ServiceListener& delegate, void* data) { d->module->coreCtx->listeners.RemoveServiceListener(this, delegate, data); } void ModuleContext::AddModuleListener(const ModuleListener& delegate, void* data) { d->module->coreCtx->listeners.AddModuleListener(this, delegate, data); } void ModuleContext::RemoveModuleListener(const ModuleListener& delegate, void* data) { d->module->coreCtx->listeners.RemoveModuleListener(this, delegate, data); } std::string ModuleContext::GetDataFile(const std::string &filename) const { // compute the module storage path #ifdef US_PLATFORM_WINDOWS static const char separator = '\\'; #else static const char separator = '/'; #endif std::string baseStoragePath = ModuleSettings::GetStoragePath(); if (baseStoragePath.empty()) return std::string(); if (baseStoragePath != d->module->baseStoragePath) { d->module->baseStoragePath = baseStoragePath; d->module->storagePath.clear(); } if (d->module->storagePath.empty()) { char buf[50]; - sprintf(buf, "%ld", d->module->info.id); + snprintf(buf, sizeof(buf), "%ld", d->module->info.id); d->module->storagePath = baseStoragePath + separator + buf + "_" + d->module->info.name + separator; } return d->module->storagePath + filename; } US_END_NAMESPACE diff --git a/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp b/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp index b4f4bf51bd..1fa4d4d681 100644 --- a/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp +++ b/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp @@ -1,334 +1,329 @@ /*============================================================================ Library: CppMicroServices Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================================================================*/ #include "usUtils_p.h" #include "usServiceListeners_p.h" #include "usServiceReferenceBasePrivate.h" #include "usCoreModuleContext_p.h" #include "usModule.h" #include "usModuleContext.h" US_BEGIN_NAMESPACE const int ServiceListeners::OBJECTCLASS_IX = 0; const int ServiceListeners::SERVICE_ID_IX = 1; ServiceListeners::ServiceListeners(CoreModuleContext* coreCtx) : coreCtx(coreCtx) { hashedServiceKeys.push_back(ServiceConstants::OBJECTCLASS()); hashedServiceKeys.push_back(ServiceConstants::SERVICE_ID()); } void ServiceListeners::AddServiceListener(ModuleContext* mc, const ServiceListenerEntry::ServiceListener& listener, void* data, const std::string& filter) { US_UNUSED(Lock(this)); ServiceListenerEntry sle(mc, listener, data, filter); RemoveServiceListener_unlocked(sle); serviceSet.insert(sle); coreCtx->serviceHooks.HandleServiceListenerReg(sle); CheckSimple(sle); } void ServiceListeners::RemoveServiceListener(ModuleContext* mc, const ServiceListenerEntry::ServiceListener& listener, void* data) { ServiceListenerEntry entryToRemove(mc, listener, data); US_UNUSED(Lock(this)); RemoveServiceListener_unlocked(entryToRemove); } void ServiceListeners::RemoveServiceListener_unlocked(const ServiceListenerEntry& entryToRemove) { ServiceListenerEntries::const_iterator it = serviceSet.find(entryToRemove); if (it != serviceSet.end()) { it->SetRemoved(true); coreCtx->serviceHooks.HandleServiceListenerUnreg(*it); RemoveFromCache(*it); serviceSet.erase(it); } } void ServiceListeners::AddModuleListener(ModuleContext* mc, const ModuleListener& listener, void* data) { MutexLock lock(moduleListenerMapMutex); ModuleListenerMap::value_type::second_type& listeners = moduleListenerMap[mc]; if (std::find_if(listeners.begin(), listeners.end(), std::bind(ModuleListenerCompare(), std::make_pair(listener, data), std::placeholders::_1)) == listeners.end()) { listeners.push_back(std::make_pair(listener, data)); } } void ServiceListeners::RemoveModuleListener(ModuleContext* mc, const ModuleListener& listener, void* data) { MutexLock lock(moduleListenerMapMutex); moduleListenerMap[mc].remove_if(std::bind(ModuleListenerCompare(), std::make_pair(listener, data), std::placeholders::_1)); } void ServiceListeners::ModuleChanged(const ModuleEvent& evt) { ModuleListenerMap filteredModuleListeners; coreCtx->moduleHooks.FilterModuleEventReceivers(evt, filteredModuleListeners); for(ModuleListenerMap::iterator iter = filteredModuleListeners.begin(), end = filteredModuleListeners.end(); iter != end; ++iter) { for (ModuleListenerMap::mapped_type::iterator iter2 = iter->second.begin(), end2 = iter->second.end(); iter2 != end2; ++iter2) { try { (iter2->first)(evt); } catch (const std::exception& e) { US_WARN << "Module listener threw an exception: " << e.what(); } } } } void ServiceListeners::RemoveAllListeners(ModuleContext* mc) { { US_UNUSED(Lock(this)); for (ServiceListenerEntries::iterator it = serviceSet.begin(); it != serviceSet.end(); ) { if (it->GetModuleContext() == mc) { RemoveFromCache(*it); serviceSet.erase(it++); } else { ++it; } } } { MutexLock lock(moduleListenerMapMutex); moduleListenerMap.erase(mc); } } void ServiceListeners::HooksModuleStopped(ModuleContext* mc) { US_UNUSED(Lock(this)); std::vector entries; for (ServiceListenerEntries::iterator it = serviceSet.begin(); it != serviceSet.end(); ) { if (it->GetModuleContext() == mc) { entries.push_back(*it); } } coreCtx->serviceHooks.HandleServiceListenerUnreg(entries); } void ServiceListeners::ServiceChanged(ServiceListenerEntries& receivers, const ServiceEvent& evt) { ServiceListenerEntries matchBefore; ServiceChanged(receivers, evt, matchBefore); } void ServiceListeners::ServiceChanged(ServiceListenerEntries& receivers, const ServiceEvent& evt, ServiceListenerEntries& matchBefore) { - int n = 0; - if (!matchBefore.empty()) { for (ServiceListenerEntries::const_iterator l = receivers.begin(); l != receivers.end(); ++l) { matchBefore.erase(*l); } } for (ServiceListenerEntries::const_iterator l = receivers.begin(); l != receivers.end(); ++l) { if (!l->IsRemoved()) { try { - ++n; l->CallDelegate(evt); } catch (...) { US_WARN << "Service listener" << " in " << l->GetModuleContext()->GetModule()->GetName() << " threw an exception!"; } } } - - //US_DEBUG << "Notified " << n << " listeners"; } void ServiceListeners::GetMatchingServiceListeners(const ServiceEvent& evt, ServiceListenerEntries& set, bool lockProps) { US_UNUSED(Lock(this)); // Filter the original set of listeners ServiceListenerEntries receivers = serviceSet; coreCtx->serviceHooks.FilterServiceEventReceivers(evt, receivers); // Check complicated or empty listener filters for (std::list::const_iterator sse = complicatedListeners.begin(); sse != complicatedListeners.end(); ++sse) { if (receivers.count(*sse) == 0) continue; const LDAPExpr& ldapExpr = sse->GetLDAPExpr(); if (ldapExpr.IsNull() || ldapExpr.Evaluate(evt.GetServiceReference().d->GetProperties(), false)) { set.insert(*sse); } } //US_DEBUG << "Added " << set.size() << " out of " << n // << " listeners with complicated filters"; // Check the cache const std::vector c(any_cast > (evt.GetServiceReference().d->GetProperty(ServiceConstants::OBJECTCLASS(), lockProps))); for (std::vector::const_iterator objClass = c.begin(); objClass != c.end(); ++objClass) { AddToSet(set, receivers, OBJECTCLASS_IX, *objClass); } long service_id = any_cast(evt.GetServiceReference().d->GetProperty(ServiceConstants::SERVICE_ID(), lockProps)); std::stringstream ss; ss << service_id; AddToSet(set, receivers, SERVICE_ID_IX, ss.str()); } std::vector ServiceListeners::GetListenerInfoCollection() const { US_UNUSED(Lock(this)); std::vector result; result.reserve(serviceSet.size()); for (ServiceListenerEntries::const_iterator iter = serviceSet.begin(), iterEnd = serviceSet.end(); iter != iterEnd; ++iter) { result.push_back(*iter); } return result; } void ServiceListeners::RemoveFromCache(const ServiceListenerEntry& sle) { if (!sle.GetLocalCache().empty()) { for (std::size_t i = 0; i < hashedServiceKeys.size(); ++i) { CacheType& keymap = cache[i]; std::vector& l = sle.GetLocalCache()[i]; for (std::vector::const_iterator it = l.begin(); it != l.end(); ++it) { std::list& sles = keymap[*it]; sles.remove(sle); if (sles.empty()) { keymap.erase(*it); } } } } else { complicatedListeners.remove(sle); } } void ServiceListeners::CheckSimple(const ServiceListenerEntry& sle) { if (sle.GetLDAPExpr().IsNull()) { complicatedListeners.push_back(sle); } else { LDAPExpr::LocalCache local_cache; if (sle.GetLDAPExpr().IsSimple(hashedServiceKeys, local_cache, false)) { sle.GetLocalCache() = local_cache; for (std::size_t i = 0; i < hashedServiceKeys.size(); ++i) { for (std::vector::const_iterator it = local_cache[i].begin(); it != local_cache[i].end(); ++it) { std::list& sles = cache[i][*it]; sles.push_back(sle); } } } else { //US_DEBUG << "Too complicated filter: " << sle.GetFilter(); complicatedListeners.push_back(sle); } } } void ServiceListeners::AddToSet(ServiceListenerEntries& set, const ServiceListenerEntries& receivers, int cache_ix, const std::string& val) { std::list& l = cache[cache_ix][val]; if (!l.empty()) { //US_DEBUG << hashedServiceKeys[cache_ix] << " matches " << l.size(); for (std::list::const_iterator entry = l.begin(); entry != l.end(); ++entry) { if (receivers.count(*entry)) { set.insert(*entry); } } } else { //US_DEBUG << hashedServiceKeys[cache_ix] << " matches none"; } } US_END_NAMESPACE diff --git a/Modules/CppMicroServices/core/test/CMakeLists.txt b/Modules/CppMicroServices/core/test/CMakeLists.txt index f0c606b453..5c21ab2162 100644 --- a/Modules/CppMicroServices/core/test/CMakeLists.txt +++ b/Modules/CppMicroServices/core/test/CMakeLists.txt @@ -1,100 +1,101 @@ #----------------------------------------------------------------------------- # Configure files, include dirs, etc. #----------------------------------------------------------------------------- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/usTestingConfig.h.in" "${PROJECT_BINARY_DIR}/include/usTestingConfig.h") include_directories(${CMAKE_CURRENT_SOURCE_DIR}) #----------------------------------------------------------------------------- # Create test modules #----------------------------------------------------------------------------- include(usFunctionCreateTestModule) set(_us_test_module_libs "" CACHE INTERNAL "" FORCE) add_subdirectory(modules) #----------------------------------------------------------------------------- # Add unit tests #----------------------------------------------------------------------------- set(_tests usAnyTest usLDAPFilterTest usLogTest usModuleHooksTest usModuleManifestTest usModuleTest usModuleResourceTest usServiceFactoryTest usServiceHooksTest usServiceRegistryPerformanceTest usServiceRegistryTest usServiceTemplateTest usServiceTrackerTest usStaticModuleResourceTest usStaticModuleTest ) if(US_BUILD_SHARED_LIBS) list(APPEND _tests usServiceListenerTest usSharedLibraryTest ) if(US_ENABLE_AUTOLOADING_SUPPORT) list(APPEND _tests usModuleAutoLoadTest) endif() endif() list(TRANSFORM _tests APPEND ".cpp" OUTPUT_VARIABLE _test_files) set(_additional_srcs usTestDriverActivator.cpp usTestManager.cpp usTestUtilModuleListener.cpp ) set(_test_driver us${PROJECT_NAME}TestDriver) set(_test_sourcelist_extra_args ) create_test_sourcelist(_srcs ${_test_driver}.cpp ${_test_files} ${_test_sourcelist_extra_args}) # Generate a custom "module init" file for the test driver executable usFunctionGenerateModuleInit(_srcs) usFunctionGetResourceSource(TARGET ${_test_driver} OUT _srcs) add_executable(${_test_driver} ${_srcs} ${_additional_srcs}) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=main) set_property(TARGET ${_test_driver} PROPERTY US_MODULE_NAME main) +set_property(TARGET ${_test_driver} PROPERTY ENABLE_EXPORTS YES) set_property(TARGET ${_test_driver} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CppMicroServices/Tests") if(NOT US_BUILD_SHARED_LIBS) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_STATIC_MODULE) target_link_libraries(${_test_driver} ${_us_test_module_libs}) endif() target_link_libraries(${_test_driver} ${Core_TARGET}) if(UNIX AND NOT APPLE) target_link_libraries(${_test_driver} rt) endif() # Add resources usFunctionEmbedResources(TARGET ${_test_driver} FILES usTestResource.txt manifest.json ZIP_ARCHIVES ${Core_TARGET} ${_us_test_module_libs}) # Register tests foreach(_test ${_tests}) add_test(NAME ${_test} COMMAND ${_test_driver} ${_test}) endforeach() #----------------------------------------------------------------------------- # Add dependencies for shared libraries #----------------------------------------------------------------------------- if(US_BUILD_SHARED_LIBS) foreach(_test_module ${_us_test_module_libs}) add_dependencies(${_test_driver} ${_test_module}) endforeach() endif() diff --git a/Modules/DICOM/CMakeLists.txt b/Modules/DICOM/CMakeLists.txt index 7a333dad07..4af61879f1 100644 --- a/Modules/DICOM/CMakeLists.txt +++ b/Modules/DICOM/CMakeLists.txt @@ -1,11 +1,12 @@ MITK_CREATE_MODULE( DEPENDS MitkCore PACKAGE_DEPENDS PUBLIC tinyxml2 PRIVATE DCMTK|dcmdata+ofstd ITK|IOGDCM TARGET_DEPENDS PUBLIC gdcmMSFF ) add_subdirectory(test) add_subdirectory(autoload/DICOMImageIO) +add_subdirectory(cmdapps) \ No newline at end of file diff --git a/Modules/DICOM/cmdapps/CMakeLists.txt b/Modules/DICOM/cmdapps/CMakeLists.txt new file mode 100644 index 0000000000..b02aa8fddc --- /dev/null +++ b/Modules/DICOM/cmdapps/CMakeLists.txt @@ -0,0 +1,8 @@ +option(BUILD_DICOMCmdApps "Build command-line apps of the MitkDICOM module" OFF) + +if(BUILD_DICOMCmdApps OR MITK_BUILD_ALL_APPS) + mitkFunctionCreateCommandLineApp( + NAME DICOMVolumeDiagnostics + DEPENDS MitkDICOM + ) +endif() diff --git a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp new file mode 100644 index 0000000000..ae7d47f01d --- /dev/null +++ b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp @@ -0,0 +1,199 @@ +/*============================================================================ + +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 + { + 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 = std::filesystem::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) + { + std::filesystem::path framePath(frame->Filename); + std::filesystem::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(); + 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 (!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/mitkDICOMFilesHelper.h b/Modules/DICOM/include/mitkDICOMFilesHelper.h index 355b30834c..a561cc8689 100644 --- a/Modules/DICOM/include/mitkDICOMFilesHelper.h +++ b/Modules/DICOM/include/mitkDICOMFilesHelper.h @@ -1,41 +1,41 @@ /*============================================================================ 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 mitkDICOMFilesHelper_h #define mitkDICOMFilesHelper_h #include #include #include namespace mitk { typedef std::vector DICOMFilePathList; /** Helper functions. Searches for all files in the directory of the passed file path. All files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList GetDICOMFilesInSameDirectory(const std::string& filePath); +DICOMFilePathList MITKDICOM_EXPORT GetDICOMFilesInSameDirectory(const std::string& filePath); /** All passed files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList FilterForDICOMFiles(const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterForDICOMFiles(const DICOMFilePathList& fileList); /** Returns all DICOM files passed with fileList that have the same series instance UID then the passed refFilePath. @pre refFilePath must point to a valid DICOM file.*/ -DICOMFilePathList FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); } #endif diff --git a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp index 19a910e832..a24947359c 100644 --- a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,622 +1,628 @@ /*============================================================================ 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_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_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()); // 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; 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.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(); ++groupIndex, ++blockIter ) + for ( auto blockIter = input.cbegin(); blockIter != input.cend(); +#if defined( MBILOG_ENABLE_DEBUG ) + ++groupIndex, +#endif + ++blockIter ) { const DICOMDatasetAccessingImageFrameList& gdcmInfoFrameList = *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 ); } } 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/mitkDICOMTagBasedSorter.cpp b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp index d84f0914dc..1f93af9f62 100644 --- a/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp @@ -1,596 +1,601 @@ /*============================================================================ 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() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 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); } } 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; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { 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 ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { 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(); - ++groupIndex, ++gIter) +#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; 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++; 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) { 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++; } } 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++; } } MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance; constantDistance = currentDistance; constantDistanceInitialized = true; } } consecutiveGroups[groupKey.str()].push_back(*dataset); 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) { 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; } } } groups = sortedResultBlocks; } #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/DICOMTesting/include/mitkTestDICOMLoading.h b/Modules/DICOMTesting/include/mitkTestDICOMLoading.h index dbbd479113..5b96a0d499 100644 --- a/Modules/DICOMTesting/include/mitkTestDICOMLoading.h +++ b/Modules/DICOMTesting/include/mitkTestDICOMLoading.h @@ -1,110 +1,113 @@ /*============================================================================ 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 mitkTestDICOMLoading_h #define mitkTestDICOMLoading_h #include "mitkClassicDICOMSeriesReader.h" #include "mitkPropertyKeyPath.h" #include "MitkDICOMTestingExports.h" namespace mitk { class MITKDICOMTESTING_EXPORT TestDICOMLoading { public: typedef std::list ImageList; TestDICOMLoading(); ImageList LoadFiles( const StringList & files ); Image::Pointer DecorateVerifyCachedImage( const StringList& files, mitk::Image::Pointer cachedImage ); Image::Pointer DecorateVerifyCachedImage( const StringList& files, DICOMTagCache*, mitk::Image::Pointer cachedImage ); /** \brief Dump relevant image information for later comparison. \sa CompareImageInformationDumps */ std::string DumpImageInformation( const Image* image ); /** \brief Compare two image information dumps. \return true, if dumps are sufficiently equal (see parameters) \sa DumpImageInformation */ bool CompareImageInformationDumps( const std::string& reference, const std::string& test ); private: typedef std::map KeyValueMap; ClassicDICOMSeriesReader::Pointer BuildDICOMReader(); void SetDefaultLocale(); void ResetUserLocale(); std::string ComponentTypeToString( itk::IOComponentEnum type ); KeyValueMap ParseDump( const std::string& dump ); bool CompareSpacedValueFields( const std::string& reference, const std::string& test, double eps = mitk::eps ); + bool CompareJSON( const std::string& reference, + const std::string& test ); + /** Compress whitespace in string \param pString input string \param pFill replacement whitespace (only whitespace in string after reduction) \param pWhitespace characters handled as whitespace */ std::string reduce(const std::string& pString, const std::string& pFill = " ", const std::string& pWhitespace = " \t"); /** Remove leading and trailing whitespace \param pString input string \param pWhitespace characters handled as whitespace */ std::string trim(const std::string& pString, const std::string& pWhitespace = " \t"); template bool StringToNumber(const std::string& s, T& value) { std::stringstream stream(s); stream >> value; return (!stream.fail()) && (std::abs(value) <= std::numeric_limits::max()); } static void AddPropertyToDump(const mitk::PropertyKeyPath& key, const mitk::Image* image, std::stringstream& result); const char* m_PreviousCLocale; std::locale m_PreviousCppLocale; }; } #endif diff --git a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp index f20a80829b..62c86a2614 100644 --- a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp +++ b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp @@ -1,585 +1,609 @@ /*============================================================================ 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 "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); 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 { 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/DataTypesExt/include/mitkBoundingObject.h b/Modules/DataTypesExt/include/mitkBoundingObject.h index 9a7c6128a7..9487df2037 100644 --- a/Modules/DataTypesExt/include/mitkBoundingObject.h +++ b/Modules/DataTypesExt/include/mitkBoundingObject.h @@ -1,64 +1,62 @@ /*============================================================================ 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 mitkBoundingObject_h #define mitkBoundingObject_h #include "MitkDataTypesExtExports.h" #include namespace mitk { //##Documentation //## @brief superclass of all bounding objects (cylinder, cuboid,...) //## //## Manages generic functions and provides an interface for IsInside() //## calculates a generic bounding box //## @ingroup Data class MITKDATATYPESEXT_EXPORT BoundingObject : public mitk::Surface // BaseData { public: mitkClassMacro(BoundingObject, mitk::Surface); virtual bool IsInside(const mitk::Point3D &p) const = 0; virtual mitk::ScalarType GetVolume(); itkGetMacro(Positive, bool); itkSetMacro(Positive, bool); itkBooleanMacro(Positive); //##Documentation //## @brief Sets the Geometry3D of the bounding object to fit the given //## geometry. //## //## The fit is done once, so if the given geometry changes it will //## \em not effect the bounding object. virtual void FitGeometry(BaseGeometry *aGeometry3D); protected: BoundingObject(); ~BoundingObject() override; - bool WriteXMLData(XMLWriter &xmlWriter); - //##Documentation //## \brief If \a true, the Boundingobject describes a positive volume, //## if \a false a negative volume. //## bool m_Positive; private: BoundingObject(const BoundingObject &); BoundingObject &operator=(const BoundingObject &); }; } #endif diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx index 1e4a4c2756..edaba4950e 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx @@ -1,897 +1,889 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __itkShortestPathImageFilter_txx #define __itkShortestPathImageFilter_txx #include "itkShortestPathImageFilter.h" #include "mitkMemoryUtilities.h" #include #include #include #include namespace itk { // Constructor (initialize standard values) template ShortestPathImageFilter::ShortestPathImageFilter() : m_Nodes(nullptr), m_Graph_NumberOfNodes(0), m_Graph_fullNeighbors(false), m_useCostFunction(true), m_FullNeighborsMode(false), m_MakeOutputImage(true), m_StoreVectorOrder(false), m_CalcAllDistances(false), multipleEndPoints(false), m_ActivateTimeOut(false), m_Initialized(false) { m_endPoints.clear(); m_endPointsClosed.clear(); if (m_MakeOutputImage) { this->SetNumberOfRequiredOutputs(1); this->SetNthOutput(0, OutputImageType::New()); } } template ShortestPathImageFilter::~ShortestPathImageFilter() { delete[] m_Nodes; } template inline typename ShortestPathImageFilter::IndexType ShortestPathImageFilter::NodeToCoord(NodeNumType node) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; IndexType coord; if (dim == 2) { coord[1] = node / size[0]; coord[0] = node % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1])) { coord[0] = 0; coord[1] = 0; } } if (dim == 3) { coord[2] = node / (size[0] * size[1]); coord[1] = (node % (size[0] * size[1])) / size[0]; coord[0] = (node % (size[0] * size[1])) % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1]) || ((unsigned long)coord[2] >= size[2])) { coord[0] = 0; coord[1] = 0; coord[2] = 0; } } return coord; } template inline typename itk::NodeNumType ShortestPathImageFilter::CoordToNode( IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; NodeNumType node = 0; if (dim == 2) { node = (coord[1] * size[0]) + coord[0]; } if (dim == 3) { node = (coord[2] * size[0] * size[1]) + (coord[1] * size[0]) + coord[0]; } if ((m_Graph_NumberOfNodes > 0) && (node >= m_Graph_NumberOfNodes)) { /*MITK_INFO << "WARNING! Coordinates outside image!"; MITK_INFO << "Coords = " << coord ; MITK_INFO << "ImageDim = " << dim ; MITK_INFO << "RequestedRegionSize = " << size ;*/ node = 0; } return node; } template inline bool ShortestPathImageFilter::CoordIsInBounds(IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; if (dim == 2) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1])) { return true; } } if (dim == 3) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1]) && (coord[2] >= 0) && ((unsigned long)coord[2] < size[2])) { return true; } } return false; } template inline std::vector ShortestPathImageFilter::GetNeighbors( unsigned int nodeNum, bool FullNeighbors) { // returns a vector of nodepointers.. these nodes are the neighbors int dim = InputImageType::ImageDimension; IndexType Coord = NodeToCoord(nodeNum); IndexType NeighborCoord; std::vector nodeList; int neighborDistance = 1; // if i increase that, i might not hit the endnote // maybe use itkNeighborhoodIterator here, might be faster if (dim == 2) { // N4 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N8 NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } if (dim == 3) { // N6 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N26 // Middle Slice NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice(Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } return nodeList; } template void ShortestPathImageFilter::SetStartIndex( const typename TInputImageType::IndexType &StartIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_StartIndex[i] = StartIndex[i]; } m_Graph_StartNode = CoordToNode(m_StartIndex); // MITK_INFO << "StartIndex = " << StartIndex; // MITK_INFO << "StartNode = " << m_Graph_StartNode; m_Initialized = false; } template void ShortestPathImageFilter::SetEndIndex( const typename TInputImageType::IndexType &EndIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_EndIndex[i] = EndIndex[i]; } m_Graph_EndNode = CoordToNode(m_EndIndex); // MITK_INFO << "EndNode = " << m_Graph_EndNode; } template void ShortestPathImageFilter::AddEndIndex( const typename TInputImageType::IndexType &index) { // ONLY FOR MULTIPLE END POINTS SEARCH IndexType newEndIndex; for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { newEndIndex[i] = index[i]; } m_endPoints.push_back(newEndIndex); SetEndIndex(m_endPoints[0]); multipleEndPoints = true; } template inline double ShortestPathImageFilter::getEstimatedCostsToTarget( const typename TInputImageType::IndexType &a) { // Returns the minimal possible costs for a path from "a" to targetnode. itk::Vector v; v[0] = m_EndIndex[0] - a[0]; v[1] = m_EndIndex[1] - a[1]; v[2] = m_EndIndex[2] - a[2]; return m_CostFunction->GetMinCost() * v.GetNorm(); } template void ShortestPathImageFilter::InitGraph() { if (!m_Initialized) { // Clean up previous stuff CleanUp(); // Calc Number of nodes auto imageDimensions = TInputImageType::ImageDimension; const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); m_Graph_NumberOfNodes = 1; for (NodeNumType i = 0; i < imageDimensions; ++i) m_Graph_NumberOfNodes = m_Graph_NumberOfNodes * size[i]; // Initialize mainNodeList with that number m_Nodes = new ShortestPathNode[m_Graph_NumberOfNodes]; // Initialize each node in nodelist for (NodeNumType i = 0; i < m_Graph_NumberOfNodes; i++) { m_Nodes[i].distAndEst = -1; m_Nodes[i].distance = -1; m_Nodes[i].prevNode = -1; m_Nodes[i].mainListIndex = i; m_Nodes[i].closed = false; } m_Initialized = true; } // In the beginning, the Startnode needs a distance of 0 m_Nodes[m_Graph_StartNode].distance = 0; m_Nodes[m_Graph_StartNode].distAndEst = 0; // initalize cost function m_CostFunction->Initialize(); } template void ShortestPathImageFilter::StartShortestPathSearch() { // Setup Timer clock_t startAll = clock(); clock_t stopAll = clock(); // init variables double durationAll = 0; bool timeout = false; NodeNumType mainNodeListIndex = 0; DistanceType curNodeDistance = 0; - NodeNumType numberOfNodesChecked = 0; // Create Multimap (tree structure for fast searching) std::multimap myMap; std::pair::iterator, std::multimap::iterator> ret; std::multimap::iterator it; // At first, only startNote is discovered. myMap.insert( std::pair(m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode])); // While there are discovered Nodes, pick the one with lowest distance, // update its neighbors and eventually delete it from the discovered Nodes list. while (!myMap.empty()) { - numberOfNodesChecked++; - /*if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) - { - MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() - << "\n"; - }*/ - // Get element with lowest score mainNodeListIndex = myMap.begin()->second->mainListIndex; curNodeDistance = myMap.begin()->second->distance; myMap.begin()->second->closed = true; // close it // Debug: // MITK_INFO << "INFO: size " << myMap.size(); /* for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ // Kicks out element with lowest score myMap.erase(myMap.begin()); // if wanted, store vector order if (m_StoreVectorOrder) { m_VectorOrder.push_back(mainNodeListIndex); } // Check neighbors std::vector neighborNodes = GetNeighbors(mainNodeListIndex, m_Graph_fullNeighbors); for (NodeNumType i = 0; i < neighborNodes.size(); i++) { if (neighborNodes[i]->closed) continue; // this nodes is already closed, go to next neighbor IndexType coordCurNode = NodeToCoord(mainNodeListIndex); IndexType coordNeighborNode = NodeToCoord(neighborNodes[i]->mainListIndex); // calculate the new Distance to the current neighbor double newDistance = curNodeDistance + (m_CostFunction->GetCost(coordCurNode, coordNeighborNode)); // if it is shorter than any yet known path to this neighbor, than the current path is better. Save that! if ((newDistance < neighborNodes[i]->distance) || (neighborNodes[i]->distance == -1)) { // if that neighbornode is not in discoverednodeList yet, Push it there and update if (neighborNodes[i]->distance == -1) { neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); /* MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ } // or if is already in discoverednodelist, update else { /* it = myMap.find(neighborNodes[i]->distAndEst); if (it == myMap.end() ) { MITK_INFO << "Nothing!"; // look further for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; } } } */ // 1st : find and delete old element bool found = false; ret = myMap.equal_range(neighborNodes[i]->distAndEst); if ((ret.first == ret.second)) { /*+++++++++++++ no exact match +++++++++++++*/ // MITK_INFO << "No exact match!"; // if this happens, you are screwed /* MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; if (ret.first != myMap.end() ) { it = ret.first; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; ++it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; --it; --it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; } // look if that ID is found in the map for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; } } */ } else { for (it = ret.first; it != ret.second; ++it) { if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) { found = true; myMap.erase(it); /* MITK_INFO << "INFO: size " << myMap.size(); MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ break; } } } if (!found) { // MITK_INFO << "Could not find it! :("; continue; } // 2nd: update and insert new element neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; // myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); // MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << // m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; // MITK_INFO << "INFO: size " << myMap.size(); /*for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; }*/ } } } // finished with checking all neighbors. // Check Timeout, if activated if (m_ActivateTimeOut) { stopAll = clock(); durationAll = (double)(stopAll - startAll) / CLOCKS_PER_SEC; if (durationAll >= 30) { // MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; timeout = true; } } // Check end criteria: // For multiple points if (multipleEndPoints) { // super slow, make it faster for (unsigned int i = 0; i < m_endPoints.size(); i++) { if (CoordToNode(m_endPoints[i]) == mainNodeListIndex) { m_endPointsClosed.push_back(NodeToCoord(mainNodeListIndex)); m_endPoints.erase(m_endPoints.begin() + i); if (m_endPoints.empty()) { // Finished! break return; } if (m_Graph_EndNode == mainNodeListIndex) { // set new end SetEndIndex(m_endPoints[0]); } } } } // if single end point, then end, if this one is reached or timeout happened. else if ((mainNodeListIndex == m_Graph_EndNode || timeout) && !m_CalcAllDistances) { /*if (m_StoreVectorOrder) MITK_INFO << "Number of Nodes checked: " << m_VectorOrder.size() ;*/ return; } } } template void ShortestPathImageFilter::MakeOutputs() { // MITK_INFO << "Make Output"; if (m_MakeOutputImage) { OutputImagePointer output0 = this->GetOutput(0); output0->SetRegions(this->GetInput()->GetLargestPossibleRegion()); output0->Allocate(); OutputImageIteratorType shortestPathImageIt(output0, output0->GetRequestedRegion()); // Create ShortestPathImage (Output 0) for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) { // First intialize with background color shortestPathImageIt.Set(BACKGROUND); } if (!multipleEndPoints) // Only one path was calculated { for (unsigned int i = 0; i < m_VectorPath.size(); i++) { shortestPathImageIt.SetIndex(m_VectorPath[i]); shortestPathImageIt.Set(FOREGROUND); } } else // multiple pathes has been calculated, draw all { for (unsigned int i = 0; i < m_MultipleVectorPaths.size(); i++) { for (unsigned int j = 0; j < m_MultipleVectorPaths[i].size(); j++) { shortestPathImageIt.SetIndex(m_MultipleVectorPaths[i][j]); shortestPathImageIt.Set(FOREGROUND); } } } } } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetVectorOrderImage() { // Create Vector Order Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); OutputImageIteratorType vectorOrderImageIt(image, image->GetRequestedRegion()); // MITK_INFO << "GetVectorOrderImage"; for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) { // First intialize with background color vectorOrderImageIt.Value() = BACKGROUND; } for (int i = 0; i < m_VectorOrder.size(); i++) { vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i])); vectorOrderImageIt.Set(BACKGROUND + i); } return image; } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetDistanceImage() { // Create Distance Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); ; OutputImageIteratorType distanceImageIt(image, image->GetRequestedRegion()); // Create Distance Image (Output 1) NodeNumType myNodeNum; for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) { IndexType index = distanceImageIt.GetIndex(); myNodeNum = CoordToNode(index); double newVal = m_Nodes[myNodeNum].distance; distanceImageIt.Set(newVal); } } template std::vector::IndexType> ShortestPathImageFilter::GetVectorPath() { return m_VectorPath; } template std::vector::IndexType>> ShortestPathImageFilter::GetMultipleVectorPaths() { return m_MultipleVectorPaths; } template void ShortestPathImageFilter::MakeShortestPathVector() { // MITK_INFO << "Make ShortestPath Vec"; if (m_useCostFunction == false) { m_VectorPath.push_back(NodeToCoord(m_Graph_StartNode)); m_VectorPath.push_back(NodeToCoord(m_Graph_EndNode)); return; } // single end point if (!multipleEndPoints) { // fill m_VectorPath with the Shortest Path m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = m_Graph_EndNode; while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); } // Multiple end end points and pathes else { for (unsigned int i = 0; i < m_endPointsClosed.size(); i++) { m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = CoordToNode(m_endPointsClosed[i]); while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); // push_back m_MultipleVectorPaths.push_back(m_VectorPath); } } } template void ShortestPathImageFilter::CleanUp() { m_VectorOrder.clear(); m_VectorPath.clear(); // TODO: if multiple Path, clear all multiple Paths if (m_Nodes) delete[] m_Nodes; } template void ShortestPathImageFilter::GenerateData() { // Build Graph InitGraph(); // Calc Shortest Parth StartShortestPathSearch(); // Fill Shortest Path MakeShortestPathVector(); // Make Outputs MakeOutputs(); } template void ShortestPathImageFilter::PrintSelf(std::ostream &os, Indent indent) const { Superclass::PrintSelf(os, indent); } } /* end namespace itk */ #endif // __itkShortestPathImageFilter_txx diff --git a/Modules/IGT/TrackingDevices/mitkPolhemusInterface.cpp b/Modules/IGT/TrackingDevices/mitkPolhemusInterface.cpp index 6ec9715e98..89c0b1e9cf 100644 --- a/Modules/IGT/TrackingDevices/mitkPolhemusInterface.cpp +++ b/Modules/IGT/TrackingDevices/mitkPolhemusInterface.cpp @@ -1,465 +1,465 @@ /*============================================================================ 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 BYTE MotionBuf[0x1FA400]; mitk::PolhemusInterface::PolhemusInterface() : m_continousTracking(false) { m_pdiDev = new CPDIdev(); m_numberOfTools = 0; } mitk::PolhemusInterface::~PolhemusInterface() { delete m_pdiDev; } bool mitk::PolhemusInterface::InitializeDevice() { m_pdiDev->ResetTracker(); m_pdiDev->ResetSAlignment(-1); m_pdiDev->Trace(TRUE, 7); m_continousTracking = false; return true; } bool mitk::PolhemusInterface::SetupDevice() { m_pdiDev->SetPnoBuffer(MotionBuf, 0x1FA400); m_pdiDev->SetMetric(true); //use cm instead of inches - m_pdiDev->StartPipeExport(); + //m_pdiDev->StartPipeExport(); CPDImdat pdiMDat; pdiMDat.Empty(); pdiMDat.Append(PDI_MODATA_FRAMECOUNT); pdiMDat.Append(PDI_MODATA_POS); pdiMDat.Append(PDI_MODATA_ORI); pdiMDat.Append(PDI_MODATA_DISTLEV); m_pdiDev->SetSDataList(-1, pdiMDat); CPDIbiterr cBE; m_pdiDev->GetBITErrs(cBE); if (!(cBE.IsClear())) { m_pdiDev->ClearBITErrs(); } return true; } bool mitk::PolhemusInterface::StartTracking() { m_continousTracking = true; return m_pdiDev->StartContPno(0); } bool mitk::PolhemusInterface::StopTracking() { m_continousTracking = false; m_pdiDev->StopContPno(); return true; } bool mitk::PolhemusInterface::OpenConnection() { bool returnValue; //Initialize, and if it is not successful, return false. if (!InitializeDevice()) { returnValue = false; } //Connect else if (m_pdiDev->CnxReady()) { returnValue = true; } //If it is not successful, search for connections. else { CPDIser pdiSer; m_pdiDev->SetSerialIF(&pdiSer); ePiCommType eType = m_pdiDev->DiscoverCnx(); switch (eType) { case PI_CNX_USB: - MITK_INFO << "USB Connection: " << m_pdiDev->GetLastResultStr(); + MITK_INFO << "USB Connection"; break; case PI_CNX_SERIAL: - MITK_INFO << "Serial Connection: " << m_pdiDev->GetLastResultStr(); + MITK_INFO << "Serial Connection"; break; default: - MITK_INFO << "DiscoverCnx result: " << m_pdiDev->GetLastResultStr(); + MITK_INFO << "DiscoverCnx"; break; } //Setup device if (!SetupDevice()) { returnValue = false; } else { returnValue = m_pdiDev->CnxReady(); } } return returnValue; } bool mitk::PolhemusInterface::Connect() { bool returnValue = OpenConnection(); if (!returnValue) { return returnValue; } m_numberOfTools = this->GetNumberOfTools(); //Get the tracking data to find out which tools are available. std::vector _trackingData = GetFrame(); //if we have more/less tools than before, reset all data. //check with toolStorage changes is nto enough, 'cause a sensor could just have been unplugged. if (m_ToolPorts.size() != _trackingData.size()) { m_ToolPorts.clear(); m_Hemispheres.clear(); m_HemisphereTracking.clear(); } //if we have the same number of tools as before, check if they are still the same. if (m_ToolPorts.size() == _trackingData.size()) { for (size_t i = 0; i < _trackingData.size(); ++i) { //if they are not the same, clear hemispheres and toolNames and break. if (m_ToolPorts[i] != _trackingData.at(i).id) { m_ToolPorts.clear(); m_Hemispheres.clear(); m_HemisphereTracking.clear(); break; } } } //if we don't have old tool names or if the old ones don't match any more, assign them again. if (m_ToolPorts.size() == 0) { for (size_t i = 0; i < _trackingData.size(); ++i) { m_ToolPorts.push_back(_trackingData.at(i).id); } //and reset the hemisphere parameters m_Hemispheres.clear(); m_HemisphereTracking.clear(); mitk::Vector3D temp; mitk::FillVector3D(temp, 1, 0, 0); m_Hemispheres.assign(m_numberOfTools, temp); m_HemisphereTracking.assign(m_numberOfTools, false); } return returnValue; } bool mitk::PolhemusInterface::Disconnect() { bool returnValue = true; //If Tracking is running, stop tracking first if (m_continousTracking) { this->StopTracking(); } returnValue = m_pdiDev->Disconnect(); - MITK_INFO << "Disconnect: " << m_pdiDev->GetLastResultStr(); + MITK_INFO << "Disconnect"; return returnValue; } std::vector mitk::PolhemusInterface::AutoDetectTools() { OpenConnection(); std::vector frame = GetSingleFrame(); m_pdiDev->Disconnect(); return frame; } unsigned int mitk::PolhemusInterface::GetNumberOfTools() { std::vector _trackingData = GetFrame(); return _trackingData.size(); } std::vector mitk::PolhemusInterface::GetFrame() { if (m_continousTracking) return this->GetLastFrame(); else return this->GetSingleFrame(); } std::vector mitk::PolhemusInterface::GetLastFrame() { PBYTE pBuf; DWORD dwSize; //read one frame - if (!m_pdiDev->LastPnoPtr(pBuf, dwSize)) { MITK_WARN << m_pdiDev->GetLastResultStr(); } + if (!m_pdiDev->LastPnoPtr(pBuf, dwSize)) { MITK_WARN << "There is an issue"; } std::vector returnValue = ParsePolhemusRawData(pBuf, dwSize); if (returnValue.empty()) { MITK_WARN << "Cannot parse data / no tools present"; } return returnValue; } std::vector mitk::PolhemusInterface::GetSingleFrame() { if (m_continousTracking) { MITK_WARN << "Cannot get a single frame when continuous tracking is on!"; return std::vector(); } PBYTE pBuf; DWORD dwSize; //read one frame if (!m_pdiDev->ReadSinglePnoBuf(pBuf, dwSize)) { - MITK_WARN << m_pdiDev->GetLastResultStr(); + MITK_WARN << "There is an issue"; return std::vector(); } return ParsePolhemusRawData(pBuf, dwSize); } std::vector mitk::PolhemusInterface::ParsePolhemusRawData(PBYTE pBuf, DWORD dwSize) { std::vector returnValue; DWORD i = 0; while (i < dwSize) { BYTE ucSensor = pBuf[i + 2]; SHORT shSize = pBuf[i + 6]; // skip rest of header i += 8; PDWORD pFC = (PDWORD)(&pBuf[i]); PFLOAT pPno = (PFLOAT)(&pBuf[i + 4]); PINT pDistLevel = (PINT)(&pBuf[i + 28]); mitk::PolhemusInterface::trackingData currentTrackingData; currentTrackingData.id = ucSensor; currentTrackingData.pos[0] = pPno[0] * 10; //from cm to mm currentTrackingData.pos[1] = pPno[1] * 10; currentTrackingData.pos[2] = pPno[2] * 10; double azimuthAngle = pPno[3] / 180 * itk::Math::pi; //from degree to rad double elevationAngle = pPno[4] / 180 * itk::Math::pi; double rollAngle = pPno[5] / 180 * itk::Math::pi; vnl_quaternion eulerQuat(rollAngle, elevationAngle, azimuthAngle); currentTrackingData.rot = eulerQuat; currentTrackingData.distortionLevel = *pDistLevel; returnValue.push_back(currentTrackingData); i += shSize; } return returnValue; } void mitk::PolhemusInterface::SetHemisphereTrackingEnabled(bool _HemisphereTrackingEnabled, int _tool) { //only if connection is ready! if (!this->m_pdiDev->CnxReady()) return; if (m_Hemispheres.empty()) { MITK_ERROR << "No Hemispheres. This should never happen when connected. Check your code!"; } //HemisphereTracking is switched on by SetSHemiTrack(-1). "-1" means for all sensors. //To switch hemisphere tracking off, you need to set a hemisphere vector e.g. by calling SetSHemisphere(-1, { (float)1,0,0 }) if (_HemisphereTrackingEnabled) { m_pdiDev->SetSHemiTrack(_tool); if (_tool != -1) { m_HemisphereTracking.at(GetToolIndex(_tool)) = true; } else { m_HemisphereTracking.assign(m_numberOfTools, true); } } //switch HemiTracking OFF else { //Get Tool Position. ToDo, this should not be the tool tip but the sensor position. Any chance, to get that from Polhemus interface?! std::vector _position = GetFrame(); for (int index : GetToolIterator(_tool)) { //Scalar product between mitk::point and mitk::vector double _scalarProduct = _position.at(index).pos.GetVectorFromOrigin() * m_Hemispheres.at(index); //if scalar product is negative, then the tool is in the opposite sphere then when we started to track. //Hence, we have to set the inverted hemisphere. //For default (1|0|0) this means, if x is negative, we have to set (-1|0|0). But we want to keep it generic if user sets different hemisphere... if (_scalarProduct < 0) { m_Hemispheres.at(index) = -1. * m_Hemispheres.at(index); } else if (_scalarProduct == 0) MITK_ERROR << "Something went wrong. Hemisphere or Position should not be zero."; SetHemisphere(m_ToolPorts[index], m_Hemispheres.at(index)); } } } void mitk::PolhemusInterface::ToggleHemisphere(int _tool) { //only if connection is ready! if (!this->m_pdiDev->CnxReady()) return; //toggle. for (int index : GetToolIterator(_tool)) { if (m_HemisphereTracking.at(index)) { SetHemisphereTrackingEnabled(false, m_ToolPorts[index]); this->SetHemisphere(m_ToolPorts[index], -1.*m_Hemispheres.at(index)); SetHemisphereTrackingEnabled(true, m_ToolPorts[index]); } else { this->SetHemisphere(m_ToolPorts[index], -1.*m_Hemispheres.at(index)); } } } void mitk::PolhemusInterface::AdjustHemisphere(int _tool) { //only if connection is ready! if (!this->m_pdiDev->CnxReady()) return; mitk::Vector3D _hemisphere; mitk::FillVector3D(_hemisphere, 1, 0, 0); for (int index : GetToolIterator(_tool)) { if (m_HemisphereTracking.at(index)) { SetHemisphereTrackingEnabled(false, m_ToolPorts[index]); this->SetHemisphere(m_ToolPorts[index], _hemisphere); SetHemisphereTrackingEnabled(true, m_ToolPorts[index]); } else { this->SetHemisphere(m_ToolPorts[index], _hemisphere); } } } void mitk::PolhemusInterface::SetHemisphere(int _tool, mitk::Vector3D _hemisphere) { //only if connection is ready! if (!this->m_pdiDev->CnxReady()) return; m_pdiDev->SetSHemisphere(_tool, { (float)_hemisphere[0], (float)_hemisphere[1], (float)_hemisphere[2] }); for (int index : GetToolIterator(_tool)) { if (_hemisphere.GetNorm() != 0) { m_HemisphereTracking.at(index) = false; m_Hemispheres.at(index) = _hemisphere; } else { m_HemisphereTracking.at(index) = true; //don't set the Hemisphere to (0|0|0), as we want to remember the old one. } } } mitk::Vector3D mitk::PolhemusInterface::GetHemisphere(int _tool) { if (_tool == -1) { MITK_WARN << "Can't return hemisphere for all tools. Returning Hemisphere of first tool " << m_ToolPorts[0]; return m_Hemispheres.at(0); } return m_Hemispheres.at(GetToolIndex(_tool)); } bool mitk::PolhemusInterface::GetHemisphereTrackingEnabled(int _tool) { //if tool is -1, this means "All Tools". We return true if HemiTracking is enabled for all tools, and false if it is off for at least one tool. if (_tool == -1) { bool _returnValue = true; for (bool currentValue : m_HemisphereTracking) _returnValue = _returnValue && currentValue; return _returnValue; } else return m_HemisphereTracking.at(GetToolIndex(_tool)); } std::vector mitk::PolhemusInterface::GetToolPorts() { return m_ToolPorts; } int mitk::PolhemusInterface::GetToolIndex(int _tool) { if (_tool == -1) return -1; else return std::find(m_ToolPorts.begin(), m_ToolPorts.end(), _tool) - m_ToolPorts.begin(); } std::vector mitk::PolhemusInterface::GetToolIterator(int _tool) { std::vector _iterator; if (_tool == -1) { for (int i = 0; i < static_cast(m_numberOfTools); ++i) _iterator.push_back(i); } else { _iterator.push_back(GetToolIndex(_tool)); } return _iterator; } void mitk::PolhemusInterface::PrintStatus() { MITK_INFO << "Polhemus status: " << this->m_pdiDev->CnxReady(); } diff --git a/Modules/IGTUI/Qmitk/QmitkPolhemusTrackerWidget.cpp b/Modules/IGTUI/Qmitk/QmitkPolhemusTrackerWidget.cpp index 89b5715c1e..77b20e3dae 100644 --- a/Modules/IGTUI/Qmitk/QmitkPolhemusTrackerWidget.cpp +++ b/Modules/IGTUI/Qmitk/QmitkPolhemusTrackerWidget.cpp @@ -1,299 +1,301 @@ /*============================================================================ 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 "QmitkPolhemusTrackerWidget.h" #include #include #include #include #include #include #include #include #include "vtkRenderer.h" #include "vtkCamera.h" const std::string QmitkPolhemusTrackerWidget::VIEW_ID = "org.mitk.views.PolhemusTrackerWidget"; QmitkPolhemusTrackerWidget::QmitkPolhemusTrackerWidget(QWidget* parent, Qt::WindowFlags f) : QmitkAbstractTrackingDeviceWidget(parent, f) , m_Controls(nullptr) { } void QmitkPolhemusTrackerWidget::Initialize() { InitializeSuperclassWidget(); CreateQtPartControl(this); SetAdvancedSettingsEnabled(false); on_m_AdvancedSettings_clicked(); //hide advanced settings on setup } QmitkPolhemusTrackerWidget::~QmitkPolhemusTrackerWidget() { delete m_Controls; } void QmitkPolhemusTrackerWidget::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { // create GUI widgets m_Controls = new Ui::QmitkPolhemusTrackerWidget; m_Controls->setupUi(parent); } } void QmitkPolhemusTrackerWidget::OnToolStorageChanged() { this->m_TrackingDevice = nullptr; MITK_DEBUG<<"Resetting Polhemus Tracking Device, because tool storage changed."; } void QmitkPolhemusTrackerWidget::CreateConnections() { if (m_Controls) { connect((QObject*)(m_Controls->m_hemisphereTracking), SIGNAL(clicked()), this, SLOT(on_m_hemisphereTracking_clicked())); connect((QObject*)(m_Controls->m_ToggleHemisphere), SIGNAL(clicked()), this, SLOT(on_m_ToggleHemisphere_clicked())); connect((QObject*)(m_Controls->m_SetHemisphere), SIGNAL(clicked()), this, SLOT(on_m_SetHemisphere_clicked())); connect((QObject*)(m_Controls->m_GetHemisphere), SIGNAL(clicked()), this, SLOT(on_m_GetHemisphere_clicked())); connect((QObject*)(m_Controls->m_AdjustHemisphere), SIGNAL(clicked()), this, SLOT(on_m_AdjustHemisphere_clicked())); connect((QObject*)(m_Controls->m_AdvancedSettings), SIGNAL(clicked()), this, SLOT(on_m_AdvancedSettings_clicked())); connect((QObject*)(m_Controls->m_ToggleToolTipCalibration), SIGNAL(clicked()), this, SLOT(on_m_ToggleToolTipCalibration_clicked())); } } mitk::TrackingDevice::Pointer QmitkPolhemusTrackerWidget::GetTrackingDevice() { if (m_TrackingDevice.IsNull()) { m_TrackingDevice = mitk::PolhemusTrackingDevice::New(); m_TrackingDevice->SetHemisphereTrackingEnabled(m_Controls->m_hemisphereTracking->isChecked()); } return static_cast(m_TrackingDevice); } QmitkPolhemusTrackerWidget* QmitkPolhemusTrackerWidget::Clone(QWidget* parent) const { QmitkPolhemusTrackerWidget* clonedWidget = new QmitkPolhemusTrackerWidget(parent); clonedWidget->Initialize(); return clonedWidget; } void QmitkPolhemusTrackerWidget::on_m_hemisphereTracking_clicked() { m_TrackingDevice->SetHemisphereTrackingEnabled(m_Controls->m_hemisphereTracking->isChecked()); } void QmitkPolhemusTrackerWidget::on_m_ToggleHemisphere_clicked() { // Index 0 == All Tools == -1 for Polhemus interface; Index 2 == Tool 2 == 1 for Polhemus; etc... m_TrackingDevice->ToggleHemisphere(GetSelectedToolIndex()); MITK_INFO << "Toggle Hemisphere for tool " << m_Controls->m_ToolSelection->currentText().toStdString(); } void QmitkPolhemusTrackerWidget::on_m_SetHemisphere_clicked() { mitk::Vector3D _hemisphere; mitk::FillVector3D(_hemisphere, m_Controls->m_Hemisphere_X->value(), m_Controls->m_Hemisphere_Y->value(), m_Controls->m_Hemisphere_Z->value()); m_TrackingDevice->SetHemisphere(GetSelectedToolIndex(), _hemisphere); //If you set a hemisphere vector which is unequal (0|0|0), this means, that there is no hemisphere tracking any more //disable the checkbox in case it was on before, so that it can be reactivated... if (_hemisphere.GetNorm() != 0) m_Controls->m_hemisphereTracking->setChecked(false); MITK_INFO << "Hemisphere set for tool " << m_Controls->m_ToolSelection->currentText().toStdString(); } void QmitkPolhemusTrackerWidget::on_m_GetHemisphere_clicked() { mitk::Vector3D _hemisphere = m_TrackingDevice->GetHemisphere(GetSelectedToolIndex()); m_Controls->m_Hemisphere_X->setValue(_hemisphere[0]); m_Controls->m_Hemisphere_Y->setValue(_hemisphere[1]); m_Controls->m_Hemisphere_Z->setValue(_hemisphere[2]); QString label; if (m_TrackingDevice->GetHemisphereTrackingEnabled(GetSelectedToolIndex())) { label = "HemisphereTracking is ON for tool "; label.append(m_Controls->m_ToolSelection->currentText()); } else if (GetSelectedToolIndex() == -1) { label = "HemisphereTracking is OFF for at least one tool."; } else { label = "HemisphereTracking is OFF for tool "; label.append(m_Controls->m_ToolSelection->currentText()); } m_Controls->m_StatusLabelHemisphereTracking->setText(label); MITK_INFO << "Updated SpinBox for Hemisphere of tool " << m_Controls->m_ToolSelection->currentText().toStdString(); } void QmitkPolhemusTrackerWidget::on_m_AdjustHemisphere_clicked() { int _tool = GetSelectedToolIndex(); QMessageBox msgBox; QString _text; if (_tool == -1) { _text.append("Adjusting hemisphere for all tools."); msgBox.setText(_text); _text.clear(); _text = tr("Please make sure, that the entire tools (including tool tip AND sensor) are placed in the positive x hemisphere. Press 'Adjust hemisphere' if you are ready."); msgBox.setInformativeText(_text); } else { _text.append("Adjusting hemisphere for tool '"); _text.append(m_Controls->m_ToolSelection->currentText()); _text.append(tr("' at port %2.").arg(_tool)); msgBox.setText(_text); _text.clear(); _text = tr("Please make sure, that the entire tool (including tool tip AND sensor) is placed in the positive x hemisphere. Press 'Adjust hemisphere' if you are ready."); msgBox.setInformativeText(_text); } QPushButton *adjustButton = msgBox.addButton(tr("Adjust hemisphere"), QMessageBox::ActionRole); QPushButton *cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.exec(); if (msgBox.clickedButton() == adjustButton) { // adjust m_TrackingDevice->AdjustHemisphere(_tool); MITK_INFO << "Adjusting Hemisphere for tool " << m_Controls->m_ToolSelection->currentText().toStdString(); } else if (msgBox.clickedButton() == cancelButton) { // abort MITK_INFO << "Cancel 'Adjust hemisphere'. No harm done..."; } } void QmitkPolhemusTrackerWidget::on_m_ToggleToolTipCalibration_clicked() { if (m_Controls->m_ToolSelection->currentIndex() != 0) { mitk::PolhemusTool* _tool = dynamic_cast (this->m_TrackingDevice->GetToolByName(m_Controls->m_ToolSelection->currentText().toStdString())); - mitk::Point3D tip = _tool->GetToolTipPosition().GetVectorFromOrigin()*(-1.); + auto tip = _tool->GetToolTipPosition().GetVectorFromOrigin()*(-1.); + auto tipPoint = mitk::Point3D(tip); mitk::Quaternion quat = _tool->GetToolAxisOrientation().inverse(); - _tool->SetToolTipPosition(tip, quat); + _tool->SetToolTipPosition(tipPoint, quat); } else { for (int i = 0; i < m_TrackingDevice->GetToolCount(); ++i) { mitk::PolhemusTool* _tool = dynamic_cast (this->m_TrackingDevice->GetTool(i)); - mitk::Point3D tip = _tool->GetToolTipPosition().GetVectorFromOrigin()*(-1.); + auto tip = _tool->GetToolTipPosition().GetVectorFromOrigin()*(-1.); + auto tipPoint = mitk::Point3D(tip); mitk::Quaternion quat = _tool->GetToolAxisOrientation().inverse(); - _tool->SetToolTipPosition(tip, quat); + _tool->SetToolTipPosition(tipPoint, quat); } } } void QmitkPolhemusTrackerWidget::OnConnected(bool _success) { if (!_success) { this->m_TrackingDevice = nullptr; return; } SetAdvancedSettingsEnabled(true); if (m_TrackingDevice->GetToolCount() != m_Controls->m_ToolSelection->count()) { m_Controls->m_ToolSelection->clear(); m_Controls->m_ToolSelection->addItem("All Tools"); for (int i = 0; i < m_TrackingDevice->GetToolCount(); ++i) { m_Controls->m_ToolSelection->addItem(m_TrackingDevice->GetTool(i)->GetToolName()); } } } void QmitkPolhemusTrackerWidget::OnStartTracking(bool _success) { if (!_success) return; auto allRenderWindows = mitk::BaseRenderer::GetAll3DRenderWindows(); for (auto mapit = allRenderWindows.begin(); mapit != allRenderWindows.end(); ++mapit) { // rotate 3D render windows, so that the view matches the sensor. Positive x == right, y == front, z == down; mapit->second->GetCameraController()->SetViewToPosterior(); mapit->second->GetVtkRenderer()->GetActiveCamera()->SetViewUp(0, 0, -1); } } void QmitkPolhemusTrackerWidget::OnDisconnected(bool _success) { if (!_success) return; SetAdvancedSettingsEnabled(false); } void QmitkPolhemusTrackerWidget::SetAdvancedSettingsEnabled(bool _enable) { m_Controls->m_ToolSelection->setEnabled(_enable); m_Controls->label_toolsToChange->setEnabled(_enable); m_Controls->label_UpdateOnRequest->setEnabled(_enable); m_Controls->m_GetHemisphere->setEnabled(_enable); m_Controls->m_Hemisphere_X->setEnabled(_enable); m_Controls->m_Hemisphere_Y->setEnabled(_enable); m_Controls->m_Hemisphere_Z->setEnabled(_enable); m_Controls->m_SetHemisphere->setEnabled(_enable); m_Controls->m_ToggleHemisphere->setEnabled(_enable); m_Controls->m_AdjustHemisphere->setEnabled(_enable); m_Controls->m_ToggleToolTipCalibration->setEnabled(_enable); } void QmitkPolhemusTrackerWidget::on_m_AdvancedSettings_clicked() { bool _enable = m_Controls->m_AdvancedSettings->isChecked(); m_Controls->m_ToolSelection->setVisible(_enable); m_Controls->label_toolsToChange->setVisible(_enable); m_Controls->label_UpdateOnRequest->setVisible(_enable); m_Controls->m_GetHemisphere->setVisible(_enable); m_Controls->m_Hemisphere_X->setVisible(_enable); m_Controls->m_Hemisphere_Y->setVisible(_enable); m_Controls->m_Hemisphere_Z->setVisible(_enable); m_Controls->m_SetHemisphere->setVisible(_enable); m_Controls->m_ToggleHemisphere->setVisible(_enable); m_Controls->m_AdjustHemisphere->setVisible(_enable); m_Controls->m_ToggleToolTipCalibration->setVisible(_enable); m_Controls->m_StatusLabelHemisphereTracking->setVisible(_enable); } int QmitkPolhemusTrackerWidget::GetSelectedToolIndex() { // Index 0 == All Tools == -1 for Polhemus interface; Index 1 == Tool 1 == 1 for Polhemus Interface; etc... int _index = m_Controls->m_ToolSelection->currentIndex() - 1; if (_index != -1) { //we need to find the internal Polhemus index for this tool. This is stored in the identifier of a navigation tool or as Port in PolhemusTool. mitk::PolhemusTool* _tool = dynamic_cast(m_TrackingDevice->GetToolByName(m_Controls->m_ToolSelection->currentText().toStdString())); _index = _tool->GetToolPort(); } return _index; } diff --git a/Modules/IOExt/Internal/mitkParRecFileReader.cpp b/Modules/IOExt/Internal/mitkParRecFileReader.cpp index 975e0bff4a..c76470e697 100644 --- a/Modules/IOExt/Internal/mitkParRecFileReader.cpp +++ b/Modules/IOExt/Internal/mitkParRecFileReader.cpp @@ -1,280 +1,280 @@ /*============================================================================ 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 "mitkParRecFileReader.h" #include #include #ifdef __GNUC__ #define stricmp strcasecmp #endif void mitk::ParRecFileReader::GenerateOutputInformation() { mitk::Image::Pointer output = this->GetOutput(); if ((output->IsInitialized()) && (this->GetMTime() <= m_ReadHeaderTime.GetMTime())) return; itkDebugMacro(<< "Reading PAR file for GenerateOutputInformation()" << m_FileName); // Check to see if we can read the file given the name or prefix // if (m_FileName == "" && m_FilePrefix == "") { throw itk::ImageFileReaderException(__FILE__, __LINE__, "One of FileName or FilePrefix must be non-empty"); } m_RecFileName = ""; if (m_FileName != "") { int extPos = m_FileName.find_last_of("."); if (extPos >= -1) { const char *ext = m_FileName.c_str() + extPos + 1; if (stricmp(ext, "par") == 0) m_RecFileName = m_FileName.substr(0, extPos); else m_RecFileName = m_FileName; } else m_RecFileName = m_FileName; m_RecFileName.append(".rec"); bool headerRead = false; bool signedCharType = true; unsigned int dimension = 0; unsigned int dimensions[4] = {0, 0, 1, 1}; float sliceThickness = 0.0; float sliceGap = 0.0; float sliceSpacing = 0.0; mitk::Vector3D thickness; thickness.Fill(1.0); mitk::Vector3D gap; gap.Fill(0.0); mitk::Vector3D spacing; FILE *f; f = fopen(m_FileName.c_str(), "r"); if (f != nullptr) { while (!feof(f)) { char s[300], *p; - char *ignored = fgets(s, 200, f); - ++ignored; + if (fgets(s, 200, f) == nullptr) + throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not read header"); if (strstr(s, "Max. number of cardiac phases")) { p = strchr(s, ':') + 1; dimensions[3] = atoi(p); if (dimensions[3] > 1) dimension = 4; } else if (strstr(s, "Max. number of slices/locations")) { p = strchr(s, ':') + 1; dimensions[2] = atoi(p); if (dimension == 0) { if (dimensions[2] > 1) dimension = 3; else dimension = 2; } } else if (strstr(s, "Image pixel size")) { p = strchr(s, ':') + 1; int bpe = atoi(p); if (bpe != 8) signedCharType = false; } else if (strstr(s, "Recon resolution")) { p = s + strcspn(s, "0123456789"); sscanf(p, "%u %u", dimensions, dimensions + 1); } else if (strstr(s, "FOV (ap,fh,rl) [mm]")) { p = s + strcspn(s, "0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%lf %lf %lf", &thickness[0], &thickness[1], &thickness[2]); } else if (strstr(s, "Slice thickness [mm]")) { p = s + strcspn(s, "0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%f", &sliceThickness); } else if (strstr(s, "Slice gap [mm]")) { p = s + strcspn(s, "-0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%f", &sliceGap); } } fclose(f); // C:\home\ivo\data\coronaries\ucsf-wholeheart-2.par sliceSpacing = sliceThickness + sliceGap; if ((dimension > 0) && (dimensions[0] > 0) && (dimensions[1] > 0) && (sliceThickness > 0) && (sliceSpacing > 0)) { headerRead = true; if (fabs(thickness[0] / dimensions[2] - sliceSpacing) < 0.0001) thickness[0] = thickness[1]; else if (fabs(thickness[1] / dimensions[2] - sliceSpacing) < 0.0001) thickness[1] = thickness[0]; thickness[2] = sliceSpacing; thickness[0] /= dimensions[0]; thickness[1] /= dimensions[1]; spacing = thickness + gap; } } if (headerRead == false) { itk::ImageFileReaderException e(__FILE__, __LINE__); std::ostringstream msg; msg << " Could not read file " << m_FileName.c_str(); e.SetDescription(msg.str().c_str()); throw e; return; } // define types mitk::PixelType SCType = mitk::MakeScalarPixelType(); mitk::PixelType SSType = mitk::MakeScalarPixelType(); if (signedCharType) output->Initialize(SCType, dimension, dimensions); else output->Initialize(SSType, dimension, dimensions); output->GetSlicedGeometry()->SetSpacing(spacing); // output->GetSlicedGeometry()->SetPlaneGeometry(mitk::Image::BuildStandardPlanePlaneGeometry(output->GetSlicedGeometry(), // dimensions).GetPointer(), 0); output->GetSlicedGeometry()->SetEvenlySpaced(); } m_ReadHeaderTime.Modified(); } void mitk::ParRecFileReader::GenerateData() { mitk::Image::Pointer output = this->GetOutput(); // Check to see if we can read the file given the name or prefix // if (m_RecFileName == "") { throw itk::ImageFileReaderException(__FILE__, __LINE__, "FileName for rec-file empty"); } if (m_RecFileName != "") { FILE *f = fopen(m_RecFileName.c_str(), "r"); if (f == nullptr) { throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not open rec-file."); } int zstart, zmax; int tstart, tmax; zstart = output->GetRequestedRegion().GetIndex(2); tstart = output->GetRequestedRegion().GetIndex(3); zmax = zstart + output->GetRequestedRegion().GetSize(2); tmax = tstart + output->GetRequestedRegion().GetSize(3); int sliceSize = output->GetDimension(0) * output->GetDimension(1) * output->GetPixelType().GetBpe() / 8; void *data = malloc(sliceSize); bool ignore4Dtopogram = false; { int slicePlusTimeSize = output->GetDimension(0) * output->GetDimension(1) * output->GetDimension(3) * output->GetPixelType().GetBpe() / 8; if (output->GetDimension(3) > 1) ignore4Dtopogram = true; int z, t; for (t = tstart; t < tmax; ++t) for (z = zstart; z < zmax; ++z) { if (ignore4Dtopogram) fseek(f, slicePlusTimeSize * z + (sliceSize + 1) * t, SEEK_SET); else fseek(f, slicePlusTimeSize * z + sliceSize * t, SEEK_SET); - size_t ignored = fread(data, sliceSize, 1, f); - ++ignored; + if(fread(data, sliceSize, 1, f) < 1) + throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not read slice."); output->SetSlice(data, z, t, 0); } } // else //{ // for(;zSetSlice(data,z,0,0); // } //} free(data); fclose(f); } } bool mitk::ParRecFileReader::CanReadFile(const std::string filename, const std::string /*filePrefix*/, const std::string /*filePattern*/) { // First check the extension if (filename == "") { // MITK_INFO<<"No filename specified."< #include #include #include #include namespace mitk { template void MaskUtilities::SetImage(const ImageType* image) { if (image != m_Image) { m_Image = image; } } template void MaskUtilities::SetMask(const MaskType* mask) { if (mask != m_Mask) { m_Mask = mask; } } template bool MaskUtilities::CheckMaskSanity() { if (m_Mask==nullptr || m_Image==nullptr) { MITK_ERROR << "Set an image and a mask first"; } typedef itk::Image< TPixel, VImageDimension > ImageType; typedef typename ImageType::PointType PointType; typedef typename ImageType::DirectionType DirectionType; bool maskSanity = true; if (m_Mask==nullptr) { MITK_ERROR << "Something went wrong when casting the mitk mask image to an itk mask image. Do the mask and the input image have the same dimension?"; // note to self: We could try to convert say a 2d mask to a 3d mask if the image is 3d. (mask and image dimension have to match.) } // check direction DirectionType imageDirection = m_Image->GetDirection(); DirectionType maskDirection = m_Mask->GetDirection(); for(unsigned int i = 0; i < imageDirection.ColumnDimensions; ++i ) { for(unsigned int j = 0; j < imageDirection.ColumnDimensions; ++j ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if (fabs(differenceDirection) > MASK_SUITABILITY_TOLERANCE_DIRECTION) { maskSanity = false; MITK_INFO << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")"; } } } // check spacing PointType imageSpacing(m_Image->GetSpacing().GetDataPointer()); PointType maskSpacing(m_Mask->GetSpacing().GetDataPointer()); for (unsigned int i = 0; i < VImageDimension; i++) { if ( fabs( maskSpacing[i] - imageSpacing[i] ) > MASK_SUITABILITY_TOLERANCE_COORDINATE ) { maskSanity = false; MITK_INFO << "Spacing of mask and image is not equal. Mask: " << maskSpacing << " image: " << imageSpacing; } } // check alignment // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images - PointType imageOrigin = m_Image->GetOrigin(); PointType maskOrigin = m_Mask->GetOrigin(); - typedef itk::ContinuousIndex ContinousIndexType; - ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; - - m_Image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); - m_Image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); + auto maskOriginContinousIndex = m_Image->template TransformPhysicalPointToContinuousIndex(maskOrigin); for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); // misalignment must be a multiple (int) of spacing in that direction if ( fmod(misalignment,imageSpacing[i]) > MASK_SUITABILITY_TOLERANCE_COORDINATE) { maskSanity = false; MITK_INFO << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << fmod(misalignment,imageSpacing[i]) << ")"; } } // mask must be completely inside image region // Make sure that mask region is contained within image region if ( m_Mask!=nullptr && !m_Image->GetLargestPossibleRegion().IsInside( m_Mask->GetLargestPossibleRegion() ) ) { maskSanity = false; MITK_INFO << "Mask region needs to be inside of image region! (Image region: " << m_Image->GetLargestPossibleRegion() << "; Mask region: " << m_Mask->GetLargestPossibleRegion() << ")"; } return maskSanity; } template typename MaskUtilities::ImageType::ConstPointer MaskUtilities::ExtractMaskImageRegion() { if (m_Mask==nullptr || m_Image==nullptr) { MITK_ERROR << "Set an image and a mask first"; } bool maskSanity = CheckMaskSanity(); if (!maskSanity) { MITK_ERROR << "Mask and image are not compatible"; } typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; typename ImageType::SizeType imageSize = m_Image->GetBufferedRegion().GetSize(); typename ImageType::SizeType maskSize = m_Mask->GetBufferedRegion().GetSize(); typename itk::Image::ConstPointer resultImg; bool maskSmallerImage = false; for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { if ( maskSize[i] < imageSize[i] ) { maskSmallerImage = true; } } if ( maskSmallerImage ) { typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); typename MaskType::PointType maskOrigin = m_Mask->GetOrigin(); typename ImageType::PointType imageOrigin = m_Image->GetOrigin(); typename MaskType::SpacingType maskSpacing = m_Mask->GetSpacing(); typename ImageType::RegionType extractionRegion; typename ImageType::IndexType extractionRegionIndex; for (unsigned int i=0; i < maskOrigin.GetPointDimension(); i++) { extractionRegionIndex[i] = (maskOrigin[i] - imageOrigin[i]) / maskSpacing[i]; } extractionRegion.SetIndex(extractionRegionIndex); extractionRegion.SetSize(m_Mask->GetLargestPossibleRegion().GetSize()); extractImageFilter->SetInput( m_Image ); extractImageFilter->SetExtractionRegion( extractionRegion ); extractImageFilter->SetCoordinateTolerance(MASK_SUITABILITY_TOLERANCE_COORDINATE); extractImageFilter->SetDirectionTolerance(MASK_SUITABILITY_TOLERANCE_DIRECTION); extractImageFilter->Update(); auto extractedImg = extractImageFilter->GetOutput(); extractedImg->SetOrigin(m_Mask->GetOrigin()); extractedImg->SetLargestPossibleRegion(m_Mask->GetLargestPossibleRegion()); extractedImg->SetBufferedRegion(m_Mask->GetBufferedRegion()); resultImg = extractedImg; } else { resultImg = m_Image; } return resultImg; } } #endif diff --git a/Modules/MatchPointRegistration/CMakeLists.txt b/Modules/MatchPointRegistration/CMakeLists.txt index 50eae7426e..e2b6e37ba9 100644 --- a/Modules/MatchPointRegistration/CMakeLists.txt +++ b/Modules/MatchPointRegistration/CMakeLists.txt @@ -1,30 +1,36 @@ mitk_create_module( INCLUDE_DIRS PUBLIC algorithms PRIVATE src/Helper src/Rendering DEPENDS MitkCore MitkSceneSerializationBase MitkMultilabel PACKAGE_DEPENDS PUBLIC MatchPoint PRIVATE VTK|ImagingGeneral+ImagingHybrid ) if(BUILD_TESTING) add_subdirectory(Testing) endif() add_subdirectory(autoload/IO) add_subdirectory(cmdapps) if(TARGET ${MODULE_TARGET}) + if(MSVC) + # Warning 4834 leaks into MITK from MatchPoint (remove after MatchPoint is fixed): + # Discarding return value of function with nodiscard attribute. + target_compile_options(${MODULE_TARGET} PUBLIC /wd4834) + endif() + set(ALG_PROFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/algorithms) include(${MatchPoint_SOURCE_DIR}/CMake/mapFunctionCreateAlgorithmProfile.cmake) file(GLOB ALG_PROFILE_FILES LIST_DIRECTORIES false RELATIVE ${ALG_PROFILE_DIR} "${ALG_PROFILE_DIR}/*.profile") foreach(profile_file ${ALG_PROFILE_FILES}) get_filename_component(profile_name ${profile_file} NAME_WE) message(STATUS "Generate MDRA profile ${profile_name} (from ${profile_file})") CREATE_ALGORITHM_PROFILE(${profile_name} ${ALG_PROFILE_DIR}/${profile_file}) endforeach() add_subdirectory(deployment) endif() diff --git a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt index 8fbb245098..42465f083c 100644 --- a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt +++ b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt @@ -1,34 +1,34 @@ option(BUILD_MatchPointCmdApps "Build commandline tools for the MatchPoint module" OFF) if(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of CmdApps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( cmdapps - StitchImagesMiniApp^^ - MatchImageMiniApp^^ - MapImageMiniApp^^ + StitchImages^^ + MatchImage^^ + MapImage^^ ) foreach(cmdapp ${cmdapps}) # extract cmd name and dependencies string(REPLACE "^^" "\\;" cmdapp_info ${cmdapp}) set(cmdapp_info_list ${cmdapp_info}) list(GET cmdapp_info_list 0 appname) list(GET cmdapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkMatchPointRegistration ${dependencies_list} ) endforeach() endif(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MapImage.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MapImage.cpp diff --git a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp similarity index 62% rename from Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MatchImage.cpp index bbcb447ea4..17e05af7f5 100644 --- a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp +++ b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp @@ -1,318 +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("Image Matcher"); + 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 registred into the target space.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "target", "t", mitkCommandLineParser::File, "Tareget image files", "Path to the data that should be the target data on which the moving data should be registered.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "algorithm", "a", mitkCommandLineParser::File, "Registration algorithm", "Path to the registration algorithm that should be used for registration.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file path", "Path to the generated registration.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); - parser.addArgument("template", - "t", - mitkCommandLineParser::File, - "Output template image.", - "File path to an image that serves as template for the output geometry.", - us::Any(), - false, false, false, mitkCommandLineParser::Input); parser.addArgument( - "registrations", "r", mitkCommandLineParser::StringList, "Registration files", "Pathes to the registrations that should be used to map the input images. If this parameter is not set, identity transforms are assumed. If this parameter is set, it must have the same number of entries then the parameter inputs. If you want to use and identity transform for a specific input, specify an empty string. The application assumes that inputs and registrations have the same order, so the n-th input should use thr n-th registration.", us::Any(), true, false, false, mitkCommandLineParser::Input); + "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 << "... libary opened..." << std::endl; std::cout << "Algorithm information: " << std::endl; spHandle->getAlgorithmUID().Print(std::cout, 2); std::cout << std::endl; //Now load the algorthm from DLL spAlgorithmBase = map::deployment::getRegistrationAlgorithm(spHandle); if (spAlgorithmBase.IsNotNull()) { std::cout << "... done" << std::endl << std::endl; } else { mapDefaultExceptionStaticMacro(<< "Cannot create algorithm instance"); } return spAlgorithmBase; }; mitk::Image::Pointer ExtractFirstFrame(const mitk::Image* dynamicImage) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(dynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } +template +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(); + } + + 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(); + } + + 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 << "MitkRegistrationMiniApp - Generic light weight image registration tool based on MatchPoint." << std::endl; + 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; - //load algorithm try { auto algorithm = loadAlgorithm(settings); auto command = ::itk::CStyleCommand::New(); command->SetCallback(OnMapAlgorithmEvent); algorithm->AddObserver(::map::events::AlgorithmEvent(), command); + auto metaPropInterface = dynamic_cast(algorithm.GetPointer()); + + if (!settings.parameters.empty()) + { + if (nullptr == metaPropInterface) + { + MITK_WARN << "loaded algorithm does not support custom parameterization. Passed user parameters are ignored."; + } + else + { + nlohmann::json paramMap; + + std::string parseError = ""; + try + { + paramMap = nlohmann::json::parse(settings.parameters); + } + catch (const std::exception& e) + { + parseError = e.what(); + } + if (!parseError.empty()) + { + mitkThrow() << "Cannot parametrize algorithm. Passed JSON parameter string seems to be invalid. Passed string: \"" << settings.parameters << "\". Error details: " << parseError; + } + + std::cout << "Configuring algorithm with user specified parameters ..." << std::endl; + + for (const auto& [key, val] : paramMap.items()) + { + const auto info = metaPropInterface->getPropertyInfo(key); + + if (info.IsNotNull()) + { + if (info->isWritable()) + { + std::cout << "Set meta property: " << key << " = " << val << std::endl; + ::map::core::MetaPropertyBase::Pointer prop = WrapIntoMetaProperty(info, val); + if (prop.IsNull()) + { + mitkThrow() << "Error. Cannot set specified meta property. Type conversion is not supported or value cannot be converted into type. Property name: " << info->getName() << "; property type: " << info->getTypeName(); + } + else + { + metaPropInterface->setProperty(key, prop); + } + } + else + { + mitkThrow() << "Cannot parametrize algorithm. A passed parameter is not writable for the algorithm. Violating parameter: \"" << key << "\"."; + } + } + else + { + auto knownProps = metaPropInterface->getPropertyInfos(); + std::ostringstream knownPropsNameString; + for (const auto& knownProp : knownProps) + { + knownPropsNameString << knownProp->getName() << "; "; + } + mitkThrow() << "Cannot parametrize algorithm. A parameter is unkown to algorithm. Unkown passed parameter: \"" << key << "\". Known parameters: " << knownPropsNameString.str(); + } + } + } + } + std::cout << "Load moving data..." << std::endl; auto movingImage = mitk::IOUtil::Load(settings.movingFileName); if (movingImage.IsNull()) { MITK_ERROR << "Cannot load moving image."; return EXIT_FAILURE; } if (movingImage->GetTimeSteps() > 1) { movingImage = mitk::SelectImageByTimeStep(movingImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Moving image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Load target data..." << std::endl; auto targetImage = mitk::IOUtil::Load(settings.targetFileName); if (targetImage.IsNull()) { MITK_ERROR << "Cannot load target image."; return EXIT_FAILURE; } if (targetImage->GetTimeSteps() > 1) { targetImage = mitk::SelectImageByTimeStep(targetImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Target image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Start registration...." << std::endl; mitk::MAPAlgorithmHelper helper(algorithm); helper.SetData(movingImage, targetImage); ::itk::StdStreamLogOutput::Pointer spStreamLogOutput = ::itk::StdStreamLogOutput::New(); spStreamLogOutput->SetStream(std::cout); map::core::Logbook::addAdditionalLogOutput(spStreamLogOutput); auto registration = helper.GetRegistration(); // wrap the registration in a data node if (registration.IsNull()) { MITK_ERROR << "No valid registration generated"; return EXIT_FAILURE; } auto regWrapper = mitk::MAPRegistrationWrapper::New(registration); std::cout << "Store registration...." << std::endl; mitk::IOUtil::Save(regWrapper, settings.outFileName); } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } return EXIT_SUCCESS; } diff --git a/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/StitchImages.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/StitchImages.cpp diff --git a/Modules/ModelFit/include/mitkScalarListLookupTable.h b/Modules/ModelFit/include/mitkScalarListLookupTable.h index 92c1541736..b7bed70910 100644 --- a/Modules/ModelFit/include/mitkScalarListLookupTable.h +++ b/Modules/ModelFit/include/mitkScalarListLookupTable.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 mitkScalarListLookupTable_h #define mitkScalarListLookupTable_h #include #include #include +#include #include "MitkModelFitExports.h" namespace mitk { /** * @brief Data class for modelfit properties that store a map of lists (e.g. static * parameters). */ class MITKMODELFIT_EXPORT ScalarListLookupTable { public: typedef std::string KeyType; typedef std::vector ValueType; typedef std::map LookupTableType; typedef std::pair EntryType; ScalarListLookupTable() {} virtual ~ScalarListLookupTable() {} virtual const char* GetNameOfClass() const; /** * @brief Sets the list at the given map key to the given value. * @param key The name of the list (i.e. map key). * @param value The list to be added (i.e. map value). */ void SetTableValue(const KeyType& key, const ValueType& value); /** * @brief Returns true if the list with the given name exists. * @param key The name of the desired list (i.e. map key) * @return true if the list exists or false otherwise. */ bool ValueExists(const KeyType& key) const; /** * @brief Returns the list with the given name. * @param key The name (i.e. map key) of the desired list. * @return The list with the given name. * @throw std::range_error If the list doesn't exist. */ const ValueType& GetTableValue(const KeyType& key) const; /** * @brief Returns the map of lists. * @return The map of lists. */ const LookupTableType& GetLookupTable() const; void SetLookupTable(const LookupTableType& table); bool operator==(const ScalarListLookupTable& lookupTable) const; bool operator!=(const ScalarListLookupTable& lookupTable) const; virtual ScalarListLookupTable& operator=(const ScalarListLookupTable& other); protected: LookupTableType m_LookupTable; }; /** * @brief Adds the string representation of the given ScalarListLookupTable to the * given stream. * @param stream The stream to which the map's string representation should be added. * @param l The map whose string representation should be added to the stream. * @return The given stream with the added string representation of the map. */ MITKMODELFIT_EXPORT std::ostream& operator<<(std::ostream& stream, const ScalarListLookupTable& l); + + MITKMODELFIT_EXPORT void to_json(nlohmann::json& j, const ScalarListLookupTable& lut); + MITKMODELFIT_EXPORT void from_json(const nlohmann::json& j, ScalarListLookupTable& lut); } #endif diff --git a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp index ab6f4e85e8..e833d4d10a 100644 --- a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp +++ b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp @@ -1,288 +1,287 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include "mitkFormulaParser.h" #include "mitkFresnel.h" namespace qi = boost::spirit::qi; namespace ascii = boost::spirit::ascii; namespace phx = boost::phoenix; typedef std::string::const_iterator Iter; typedef ascii::space_type Skipper; namespace qi = boost::spirit::qi; namespace mitk { /*! * @brief Transforms the given number from degrees to radians and returns it. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] deg A scalar value in degrees. * @return The given value in radians. */ template inline T deg2rad(const T deg) { return deg * boost::math::constants::pi() / static_cast(180); } /*! * @brief Returns the cosine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose cosine should be returned. * @return The cosine of the given degree scalar. */ template inline T cosd(const T t) { return std::cos(deg2rad(t)); } /*! * @brief Returns the sine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose sine should be returned. * @return The sine of the given degree scalar. */ template inline T sind(const T t) { return std::sin(deg2rad(t)); } /*! * @brief Returns the tangent of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose tangent should be returned. * @return The tangent of the given degree scalar. */ template inline T tand(const T t) { return std::tan(deg2rad(t)); } /*! * @brief Returns the fresnel integral sine at the given x-coordinate. * @details Code for "fresnel_s()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral sine should be returned. * @return The fresnel integral sine at the given x-coordinate. */ template T fresnelS(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_s(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief Returns the fresnel integral cosine at the given x-coordinate. * @details Code for "fresnel_c()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral cosine should be returned. * @return The fresnel integral cosine at the given x-coordinate. */ template T fresnelC(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_c(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief The grammar that defines the language (i.e. what is allowed) for the parser. */ class Grammar : public qi::grammar { /*! * @brief Helper structure that makes it easier to dynamically call any * one-parameter-function by overloading the @c () operator. */ struct func1_ { // Required for Phoenix 3+ template struct result; /*! * @brief Helper structure that is needed for compatibility with * @c boost::phoenix. * @tparam Functor Type of the functor (this struct). * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * */ template struct result { /*! @brief The result structure always needs this typedef */ typedef Arg1 type; }; /*! * @brief Calls the function @b f with the argument @b a1 and returns the * result. * The result always has the same type as the argument. * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * @param[in] f The function that should be called. * @param[in] a1 The argument the function should be called with. * @return The result of the called function. */ template Arg1 operator()(const Function f, const Arg1 a1) const { return f(a1); } }; /*! * @brief Helper structure that maps strings to function calls so that parsing e.g. * @c "cos(0)" actually calls the @c std::cos function with parameter @c 1 so it * returns @c 0. */ class unaryFunction_ : public qi::symbols::value_type, FormulaParser::ValueType(*)(FormulaParser::ValueType)> { public: /*! * @brief Constructs the structure, this is where the mapping takes place. */ unaryFunction_() { this->add ("abs", static_cast(&std::abs)) ("exp", static_cast(&std::exp)) // @TODO: exp ignores division by zero ("sin", static_cast(&std::sin)) ("cos", static_cast(&std::cos)) ("tan", static_cast(&std::tan)) ("sind", &sind) ("cosd", &cosd) ("tand", &tand) ("fresnelS", &fresnelS) ("fresnelC", &fresnelC); } } unaryFunction; public: /*! * @brief Constructs the grammar with the given formula parser. * @param[in, out] formulaParser The formula parser this grammar is for - so it can * access its variable map. */ Grammar(FormulaParser& formulaParser) : Grammar::base_type(start) { using qi::_val; using qi::_1; using qi::_2; using qi::char_; using qi::alpha; using qi::alnum; using qi::double_; using qi::as_string; phx::function func1; start = expression > qi::eoi; expression = term[_val = _1] >> *(('+' >> term[_val += _1]) | ('-' >> term[_val -= _1])); term = factor[_val = _1] >> *(('*' >> factor[_val *= _1]) | ('/' >> factor[_val /= _1])); - factor = primary[_val = _1]; - /*! @TODO: Repair exponentiation */ - //>> *('^' >> factor[phx::bind(std::pow, _val, _1)]); + factor = primary[_val = _1] + >> *('^' >> primary[_val = phx::bind(std::pow, _val, _1)]); variable = as_string[alpha >> *(alnum | char_('_'))] [_val = phx::bind(&FormulaParser::lookupVariable, &formulaParser, _1)]; primary = double_[_val = _1] | '(' >> expression[_val = _1] >> ')' | ('-' >> primary[_val = -_1]) | ('+' >> primary[_val = _1]) | (unaryFunction >> '(' >> expression >> ')')[_val = func1(_1, _2)] | variable[_val = _1]; } /*! the rules of the grammar. */ qi::rule start; qi::rule expression; qi::rule term; qi::rule factor; qi::rule variable; qi::rule primary; }; FormulaParser::FormulaParser(const VariableMapType* variables) : m_Variables(variables) {} FormulaParser::ValueType FormulaParser::parse(const std::string& input) { std::string::const_iterator iter = input.begin(); std::string::const_iterator end = input.end(); FormulaParser::ValueType result = static_cast(0); try { if (!qi::phrase_parse(iter, end, Grammar(*this), ascii::space, result)) { mitkThrowException(FormulaParserException) << "Could not parse '" << input << "': Grammar could not be applied to the input " << "at all."; } } catch (qi::expectation_failure& e) { std::string parsed = ""; for (Iter i = input.begin(); i != e.first; i++) { parsed += *i; } mitkThrowException(FormulaParserException) << "Error while parsing '" << input << "': Unexpected character '" << *e.first << "' after '" << parsed << "'"; } return result; }; FormulaParser::ValueType FormulaParser::lookupVariable(const std::string var) { if (m_Variables == nullptr) { mitkThrowException(FormulaParserException) << "Map of variables is empty"; } try { return m_Variables->at(var); } catch (std::out_of_range&) { mitkThrowException(FormulaParserException) << "No variable '" << var << "' defined in lookup"; } }; } diff --git a/Modules/ModelFit/src/Common/mitkScalarListLookupTable.cpp b/Modules/ModelFit/src/Common/mitkScalarListLookupTable.cpp index 35d1c336ac..fa8c3b5a3b 100644 --- a/Modules/ModelFit/src/Common/mitkScalarListLookupTable.cpp +++ b/Modules/ModelFit/src/Common/mitkScalarListLookupTable.cpp @@ -1,116 +1,131 @@ /*============================================================================ 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 "mitkScalarListLookupTable.h" #include #include +#include + +namespace mitk +{ + void to_json(nlohmann::json& j, const ScalarListLookupTable& lut) + { + j = lut.GetLookupTable(); + } + + void from_json(const nlohmann::json& j, ScalarListLookupTable& lut) + { + lut.SetLookupTable(j.get()); + } +} + const char* mitk::ScalarListLookupTable::GetNameOfClass() const { return "ScalarListLookupTable"; } void mitk::ScalarListLookupTable::SetTableValue(const KeyType& key, const ValueType& value) { m_LookupTable[key] = value; } bool mitk::ScalarListLookupTable::ValueExists(const KeyType& key) const { LookupTableType::const_iterator it = m_LookupTable.find(key); return (it != m_LookupTable.end()); } const mitk::ScalarListLookupTable::ValueType& mitk::ScalarListLookupTable::GetTableValue(const KeyType& key) const { LookupTableType::const_iterator it = m_LookupTable.find(key); if (it != m_LookupTable.end()) { return it->second; } else { throw std::range_error("id does not exist in the lookup table"); } } const mitk::ScalarListLookupTable::LookupTableType& mitk::ScalarListLookupTable::GetLookupTable() const { return m_LookupTable; } void mitk::ScalarListLookupTable::SetLookupTable(const LookupTableType& table) { m_LookupTable = table; }; bool mitk::ScalarListLookupTable::operator==(const mitk::ScalarListLookupTable& lookupTable) const { return (m_LookupTable == lookupTable.m_LookupTable); } bool mitk::ScalarListLookupTable::operator!=(const mitk::ScalarListLookupTable& lookupTable) const { return !(m_LookupTable == lookupTable.m_LookupTable); } mitk::ScalarListLookupTable& mitk::ScalarListLookupTable::operator=(const ScalarListLookupTable& other) { if (this == &other) { return *this; } else { m_LookupTable = other.m_LookupTable; return *this; } } std::ostream& mitk::operator<<(std::ostream& stream, const ScalarListLookupTable& l) { typedef ScalarListLookupTable::LookupTableType::const_iterator MapIterType; typedef ScalarListLookupTable::ValueType::const_iterator VectorIterType; MapIterType mapStart = l.GetLookupTable().begin(); MapIterType mapEnd = l.GetLookupTable().end(); stream << "["; for (MapIterType i = mapStart; i != mapEnd; ++i) { if (i != mapStart) { stream << ", "; } stream << i->first << " -> ["; VectorIterType vectorStart = i->second.begin(); VectorIterType vectorEnd = i->second.end(); for (VectorIterType j = vectorStart; j != vectorEnd; ++j) { if (j != vectorStart) { stream << ", "; } stream << *j; } stream << "]"; } return stream << "]"; }; diff --git a/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp b/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp index 3f821e1357..404182ea47 100644 --- a/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp +++ b/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp @@ -1,45 +1,47 @@ /*============================================================================ 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 "mitkGenericParamModelFactory.h" #include "mitkGenericParamModelParameterizer.h" mitk::GenericParamModelFactory::GenericParamModelFactory() = default; mitk::GenericParamModelFactory::~GenericParamModelFactory() = default; mitk::ModelParameterizerBase::ParametersType mitk::GenericParamModelFactory::GetDefaultInitialParameterization() const { return GenericParamModelParameterizer::New()->GetDefaultInitialParameterization(); }; mitk::ModelParameterizerBase::Pointer mitk::GenericParamModelFactory::DoCreateParameterizer( const mitk::modelFit::ModelFitInfo* fit) const { mitk::ModelParameterizerBase::Pointer result; GenericParamModelParameterizer::Pointer modelParameterizer = GenericParamModelParameterizer::New(); auto paramCount = fit->staticParamMap.Get( GenericParamModel::NAME_STATIC_PARAMETER_number); modelParameterizer->SetNumberOfParameters(paramCount[0]); + modelParameterizer->SetFunctionString(fit->function); + result = modelParameterizer.GetPointer(); return result; }; diff --git a/Modules/ModelFit/test/mitkFormulaParserTest.cpp b/Modules/ModelFit/test/mitkFormulaParserTest.cpp index 1b91a8a70f..f372b79303 100644 --- a/Modules/ModelFit/test/mitkFormulaParserTest.cpp +++ b/Modules/ModelFit/test/mitkFormulaParserTest.cpp @@ -1,225 +1,224 @@ /*============================================================================ 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 "mitkFormulaParser.h" using namespace mitk; #define TEST_NOTHROW(expression, MSG) \ do \ { \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ bool test_caught = false; \ try \ { \ expression; \ } \ catch(...) \ { \ test_caught = true; \ MITK_TEST_FAILED_MSG(<< "An unwanted exception was thrown"); \ } \ if(!test_caught) \ { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } \ } while(0) /*! * @author Sascha Diatschuk */ class FormulaParserTests { public: static void TestConstructor() { std::map varMap; FormulaParser *nullParser = nullptr, *parser = nullptr; TEST_NOTHROW(nullParser = new FormulaParser(nullptr), "Testing constructor with NULL argument"); TEST_NOTHROW(parser = new FormulaParser(&varMap), "Testing constructor with valid argument"); delete nullParser; delete parser; } static void TestLookupVariable() { // variable map is NULL FormulaParser *nullParser = new FormulaParser(nullptr); MITK_TEST_FOR_EXCEPTION(FormulaParserException, nullParser->lookupVariable("test")); delete nullParser; // variable map is empty std::map varMap; FormulaParser *parser = new FormulaParser(&varMap); MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->lookupVariable("test")); // lookup should succeed double var; varMap["test"] = 17; TEST_NOTHROW(var = parser->lookupVariable("test"), "Testing if lookupVariable throws unwanted exceptions"); MITK_TEST_CONDITION_REQUIRED(var == 17, "Testing if lookupVariable returns the correct value"); delete parser; } static void TestParse() { std::map varMap; varMap["test"] = 17; FormulaParser *parser = new FormulaParser(&varMap); // empty string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("")); // grammar can't process string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("_")); // unexpected character MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("5=")); // unknown variable MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("a")); double d; // addition TEST_NOTHROW(d = parser->parse("1+2"), "Testing if addition throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 3, "Testing if addition produces the correct result"); // subtraction TEST_NOTHROW(d = parser->parse("5-1"), "Testing if subtraction throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 4, "Testing if subtraction produces the correct result"); // multiplication TEST_NOTHROW(d = parser->parse("3*4"), "Testing if multiplication throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 12, "Testing if multiplication produces the correct result"); // division TEST_NOTHROW(d = parser->parse("28/4"), "Testing if division throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 7, "Testing if division produces the correct result"); - /*! @TODO: Reactivate as soon as exponentiation works again */ // exponentiation - //TEST_NOTHROW(d = parser->parse("2^3"), - // "Testing if exponentiation throws an unwanted exception"); - //MITK_TEST_CONDITION_REQUIRED(d == 8, - // "Testing if exponentiation produces the correct result"); + TEST_NOTHROW(d = parser->parse("2^3"), + "Testing if exponentiation throws an unwanted exception"); + MITK_TEST_CONDITION_REQUIRED(d == 8, + "Testing if exponentiation produces the correct result"); // algebraic signs TEST_NOTHROW(d = parser->parse("-7 + +1 - -1"), "Testing if algebraic signs throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == -5, "Testing if algebraic signs produce the correct result"); // parentheses TEST_NOTHROW(d = parser->parse("(1+2)*(4-2)"), "Testing if parentheses throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 6, "Testing if parentheses produce the correct result"); // variables TEST_NOTHROW(d = parser->parse("2*test-test"), "Testing if variables throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 17, "Testing if variables produce the correct result"); // abs TEST_NOTHROW(d = parser->parse("abs(-5)"), "Testing if abs throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 5, "Testing if abs produces the correct result"); const double eps = 0.0001; // exp TEST_NOTHROW(d = parser->parse("exp(1)"), "Testing if exp throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 2.71828182846) < eps, "Testing if exp produces the correct result"); // sin TEST_NOTHROW(d = parser->parse("sin(1.57079632679)"), "Testing if sin throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 1) < eps, "Testing if sin produces the correct result"); // cos TEST_NOTHROW(d = parser->parse("cos(3.14159265359)"), "Testing if cos throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d + 1) < eps, "Testing if cos produces the correct result"); // tan TEST_NOTHROW(d = parser->parse("tan(0.46364760899)"), "Testing if tan throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.5) < eps, "Testing if tan produces the correct result"); // sind TEST_NOTHROW(d = parser->parse("sind(145)"), "Testing if sind throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.57357643635) < eps, "Testing if sind produces the correct result"); // cosd TEST_NOTHROW(d = parser->parse("cosd(90)"), "Testing if cosd throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d < eps, "Testing if cosd produces the correct result"); // tand TEST_NOTHROW(d = parser->parse("tand(15)"), "Testing if tand throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.26794919243) < eps, "Testing if tand produces the correct result"); // fresnelS TEST_NOTHROW(d = parser->parse("fresnelS(1)"), "Testing if fresnelS throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.310268) < eps, "Testing if fresnelS produces the correct result"); TEST_NOTHROW(d = parser->parse("fresnelC(1)"), "Testing if fresnelC throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.904524) < eps, "Testing if fresnelC produces the correct result"); delete parser; } }; int mitkFormulaParserTest(int, char *[]) { MITK_TEST_BEGIN("FormulaParser Test"); FormulaParserTests::TestConstructor(); FormulaParserTests::TestLookupVariable(); FormulaParserTests::TestParse(); MITK_TEST_END(); } diff --git a/Modules/ModelFit/test/mitkModelFitInfoTest.cpp b/Modules/ModelFit/test/mitkModelFitInfoTest.cpp index 4793618497..692892d094 100644 --- a/Modules/ModelFit/test/mitkModelFitInfoTest.cpp +++ b/Modules/ModelFit/test/mitkModelFitInfoTest.cpp @@ -1,424 +1,425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include "mitkProperties.h" #include "mitkStandaloneDataStorage.h" #include "mitkModelFitInfo.h" #include "mitkModelFitConstants.h" #include "mitkModelFitException.h" #include "mitkModelFitResultRelationRule.h" #include #include +#include mitk::modelFit::ModelFitInfo::UIDType ensureModelFitUID(mitk::BaseData * data) { mitk::BaseProperty::Pointer uidProp = data->GetProperty(mitk::ModelFitConstants::LEGACY_UID_PROPERTY_NAME().c_str()); std::string propUID = ""; if (uidProp.IsNotNull()) { propUID = uidProp->GetValueAsString(); } else { mitk::UIDGenerator generator; propUID = generator.GetUID(); data->SetProperty(mitk::ModelFitConstants::LEGACY_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New(propUID)); } return propUID; }; mitk::DataNode::Pointer generateModelFitTestNode() { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetName("Param1"); auto testImage = mitk::Image::New(); node->SetData(testImage); testImage->SetProperty("modelfit.testEmpty", mitk::StringProperty::New("")); testImage->SetProperty("modelfit.testValid", mitk::StringProperty::New("test")); ensureModelFitUID(testImage); testImage->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("FitLegacy")); testImage->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName")); testImage->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); testImage->SetProperty(mitk::ModelFitConstants::LEGACY_FIT_INPUT_IMAGEUID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("input UID")); testImage->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); testImage->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_1")); testImage->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); testImage->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass")); testImage->SetProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME().c_str(), mitk::StringProperty::New("myX")); testImage->SetProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT().c_str())); testImage->SetProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("h")); testImage->SetProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT().c_str())); testImage->SetProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("kg")); return node; } class mitkModelFitInfoTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkModelFitInfoTestSuite); MITK_TEST(CheckModelFitInfo); MITK_TEST(CheckGetMandatoryProperty); MITK_TEST(CheckCreateFitInfoFromNode_Legacy); MITK_TEST(CheckCreateFitInfoFromNode); MITK_TEST(CheckGetNodesOfFit); MITK_TEST(CheckGetFitUIDsOfNode); CPPUNIT_TEST_SUITE_END(); mitk::StandaloneDataStorage::Pointer m_Storage; mitk::Image::Pointer m_ParamImage; mitk::Image::Pointer m_ParamImage2; mitk::Image::Pointer m_ParamImage_legacy; mitk::Image::Pointer m_ParamImage2_legacy; mitk::DataNode::Pointer m_ParamImageNode; mitk::DataNode::Pointer m_ParamImageNode2; mitk::DataNode::Pointer m_ParamImageNode_legacy; mitk::DataNode::Pointer m_ParamImageNode2_legacy; public: void setUp() override { m_Storage = mitk::StandaloneDataStorage::New(); //create input node mitk::DataNode::Pointer inputNode = mitk::DataNode::New(); inputNode->SetName("Input"); mitk::Image::Pointer image = mitk::Image::New(); inputNode->SetData(image); mitk::modelFit::ModelFitInfo::UIDType inputUID = ensureModelFitUID(image); m_Storage->Add(inputNode); mitk::DataStorage::SetOfObjects::Pointer parents = mitk::DataStorage::SetOfObjects::New(); parents->push_back(inputNode); ///////////////////////////////////////////////////// //Create nodes for a fit (new style using rules) ///////////////////////////////////////////////////// auto rule = mitk::ModelFitResultRelationRule::New(); //create first result for FitLegacy m_ParamImageNode = mitk::DataNode::New(); m_ParamImage = mitk::Image::New(); m_ParamImageNode->SetData(m_ParamImage); m_ParamImageNode->SetName("Param1"); m_ParamImage->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Fit")); m_ParamImage->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName1")); m_ParamImage->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); m_ParamImage->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); m_ParamImage->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_1")); m_ParamImage->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); m_ParamImage->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass")); m_ParamImage->SetProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME().c_str(), mitk::StringProperty::New("myX")); m_ParamImage->SetProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage->SetProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("h")); m_ParamImage->SetProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage->SetProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("kg")); m_ParamImage->SetProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Param1")); m_ParamImage->SetProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("b")); m_ParamImage->SetProperty(mitk::ModelFitConstants::PARAMETER_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_PARAMETER().c_str())); rule->Connect(m_ParamImage, image); m_Storage->Add(m_ParamImageNode, parents); //create second result for Fit m_ParamImageNode2 = mitk::DataNode::New(); m_ParamImageNode2->SetName("Param2"); m_ParamImage2 = mitk::Image::New(); m_ParamImageNode2->SetData(m_ParamImage2); m_ParamImage2->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Fit")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName1")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); m_ParamImage2->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_1")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME().c_str(), mitk::StringProperty::New("myX")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage2->SetProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("h")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage2->SetProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("kg")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Param2")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("a")); m_ParamImage2->SetProperty(mitk::ModelFitConstants::PARAMETER_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_DERIVED_PARAMETER().c_str())); rule->Connect(m_ParamImage2, image); m_Storage->Add(m_ParamImageNode2, parents); ///////////////////////////////////////////////////// //Create nodes for a fit in legacy mode (old fit uid) ///////////////////////////////////////////////////// //create first result for FitLegacy m_ParamImageNode_legacy = mitk::DataNode::New(); m_ParamImage_legacy = mitk::Image::New(); m_ParamImageNode_legacy->SetData(m_ParamImage_legacy); m_ParamImageNode_legacy->SetName("Param1_legacy"); ensureModelFitUID(m_ParamImage_legacy); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("FitLegacy")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName1")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::LEGACY_FIT_INPUT_IMAGEUID_PROPERTY_NAME().c_str(), mitk::StringProperty::New(inputUID.c_str())); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_1")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME().c_str(), mitk::StringProperty::New("myX")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("h")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("kg")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Param1_legacy")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("b")); m_ParamImage_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_PARAMETER().c_str())); m_Storage->Add(m_ParamImageNode_legacy, parents); //create second result for FitLegacy m_ParamImageNode2_legacy = mitk::DataNode::New(); m_ParamImageNode2_legacy->SetName("Param2_legacy"); m_ParamImage2_legacy = mitk::Image::New(); m_ParamImageNode2_legacy->SetData(m_ParamImage2_legacy); ensureModelFitUID(m_ParamImage2_legacy); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("FitLegacy")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName1")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::LEGACY_FIT_INPUT_IMAGEUID_PROPERTY_NAME().c_str(), mitk::StringProperty::New(inputUID.c_str())); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_1")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::MODEL_X_PROPERTY_NAME().c_str(), mitk::StringProperty::New("myX")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::XAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::XAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("h")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::YAXIS_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT().c_str())); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::YAXIS_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("kg")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Param2_legacy")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("a")); m_ParamImage2_legacy->SetProperty(mitk::ModelFitConstants::PARAMETER_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_DERIVED_PARAMETER().c_str())); m_Storage->Add(m_ParamImageNode2_legacy, parents); ///////////////////////////////////////////////////// //Create nodes for a fit on other input auto anotherInputNode = mitk::DataNode::New(); anotherInputNode->SetName("AnotherInput"); auto anotherImage = mitk::Image::New(); anotherInputNode->SetData(anotherImage); m_Storage->Add(anotherInputNode); parents = mitk::DataStorage::SetOfObjects::New(); parents->push_back(anotherInputNode); mitk::DataNode::Pointer node3 = mitk::DataNode::New(); node3->SetName("Param_Other"); mitk::Image::Pointer paramImage3 = mitk::Image::New(); node3->SetData(paramImage3); paramImage3->SetProperty(mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Fit2")); paramImage3->SetProperty(mitk::ModelFitConstants::FIT_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("MyName2")); paramImage3->SetProperty(mitk::ModelFitConstants::FIT_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New(mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED().c_str())); paramImage3->SetProperty(mitk::ModelFitConstants::MODEL_TYPE_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModels")); paramImage3->SetProperty(mitk::ModelFitConstants::MODEL_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("TestModel_2")); paramImage3->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_PROPERTY_NAME().c_str(), mitk::StringProperty::New("")); paramImage3->SetProperty(mitk::ModelFitConstants::MODEL_FUNCTION_CLASS_PROPERTY_NAME().c_str(), mitk::StringProperty::New("ModelClass_B")); paramImage3->SetProperty(mitk::ModelFitConstants::PARAMETER_NAME_PROPERTY_NAME().c_str(), mitk::StringProperty::New("Param_Other")); paramImage3->SetProperty(mitk::ModelFitConstants::PARAMETER_UNIT_PROPERTY_NAME().c_str(), mitk::StringProperty::New("a")); rule->Connect(paramImage3, anotherImage); m_Storage->Add(node3, parents); } void tearDown() override { } void CheckModelFitInfo() { mitk::modelFit::Parameter::Pointer p = mitk::modelFit::Parameter::New(); p->name = "p"; mitk::modelFit::ModelFitInfo::Pointer fit = mitk::modelFit::ModelFitInfo::New(); fit->AddParameter(p); CPPUNIT_ASSERT_MESSAGE("AddParameter unsuccessfully adds a parameter.", fit->GetParameters().size() == 1); mitk::modelFit::Parameter::ConstPointer resultParam = fit->GetParameter("test", mitk::modelFit::Parameter::ParameterType); CPPUNIT_ASSERT_MESSAGE("Testing if GetParameter returns NULL for wrong parameter.", resultParam.IsNull()); resultParam = fit->GetParameter("p", mitk::modelFit::Parameter::ParameterType); CPPUNIT_ASSERT_MESSAGE("Testing if GetParameter returns the correct parameter.", resultParam == p); p->type = mitk::modelFit::Parameter::CriterionType; resultParam = fit->GetParameter("p", mitk::modelFit::Parameter::CriterionType); CPPUNIT_ASSERT_MESSAGE("Testing if GetParameter returns the correct parameter with a non-default type.", resultParam == p); fit->DeleteParameter("test", mitk::modelFit::Parameter::CriterionType); CPPUNIT_ASSERT_MESSAGE("Testing if DeleteParameter fails for wrong parameter.", fit->GetParameters().size() == 1); fit->DeleteParameter("p", mitk::modelFit::Parameter::CriterionType); CPPUNIT_ASSERT_MESSAGE("Testing if DeleteParameter successfully removes a parameter.", fit->GetParameters().size() == 0); } void CheckGetMandatoryProperty() { mitk::DataNode::Pointer node = generateModelFitTestNode(); mitk::DataNode::Pointer invalideNode = mitk::DataNode::New(); CPPUNIT_ASSERT_THROW(mitk::modelFit::GetMandatoryProperty(node.GetPointer(), "modelfit.testInvalid"), mitk::modelFit::ModelFitException); CPPUNIT_ASSERT_THROW(mitk::modelFit::GetMandatoryProperty(node.GetPointer(), "modelfit.testEmpty"), mitk::modelFit::ModelFitException); CPPUNIT_ASSERT_MESSAGE("Testing if GetMandatoryProperty returns the correct value.", mitk::modelFit::GetMandatoryProperty(node.GetPointer(), "modelfit.testValid") == "test"); } void CheckCreateFitInfoFromNode_Legacy() { CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns NULL for invalid node.", mitk::modelFit::CreateFitInfoFromNode("FitLegacy", nullptr).IsNull()); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns NULL for node with missing properties.", mitk::modelFit::CreateFitInfoFromNode("invalide_UID", m_Storage).IsNull()); mitk::modelFit::ModelFitInfo::Pointer resultFit = mitk::modelFit::CreateFitInfoFromNode("FitLegacy", m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns a valid model fit info.", resultFit.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode creates a fit with correct attributes.", resultFit->fitType == mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED() && resultFit->uid == "FitLegacy" && resultFit->fitName == "MyName1" && resultFit->modelType == "TestModels" && resultFit->modelName == "TestModel_1" && resultFit->function == "" && resultFit->functionClassID == "ModelClass" && resultFit->x == "myX" && resultFit->xAxisName == mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT() && resultFit->xAxisUnit == "h" && resultFit->yAxisName == mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT() && resultFit->yAxisUnit == "kg" && resultFit->GetParameters().size() == 2); mitk::modelFit::Parameter::ConstPointer param = resultFit->GetParameter("Param1_legacy", mitk::modelFit::Parameter::ParameterType); CPPUNIT_ASSERT_MESSAGE("Testing if param 1 exists.", param.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if param 1 is configured correctly.", param->name == "Param1_legacy" && param->unit == "b" && param->image == m_ParamImage_legacy); mitk::modelFit::Parameter::ConstPointer param2 = resultFit->GetParameter("Param2_legacy", mitk::modelFit::Parameter::DerivedType); CPPUNIT_ASSERT_MESSAGE("Testing if param 2 exists.", param2.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if param 2 is configured correctly.", param2->name == "Param2_legacy" && param2->unit == "a" && param2->image == m_ParamImage2_legacy); } void CheckCreateFitInfoFromNode() { CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns NULL for invalid node.", mitk::modelFit::CreateFitInfoFromNode("Fit", nullptr).IsNull()); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns NULL for node with missing properties.", mitk::modelFit::CreateFitInfoFromNode("invalide_UID", m_Storage).IsNull()); mitk::modelFit::ModelFitInfo::Pointer resultFit = mitk::modelFit::CreateFitInfoFromNode("Fit", m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode returns a valid model fit info.", resultFit.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if CreateFitInfoFromNode creates a fit with correct attributes.", resultFit->fitType == mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED() && resultFit->uid == "Fit" && resultFit->fitName == "MyName1" && resultFit->modelType == "TestModels" && resultFit->modelName == "TestModel_1" && resultFit->function == "" && resultFit->functionClassID == "ModelClass" && resultFit->x == "myX" && resultFit->xAxisName == mitk::ModelFitConstants::XAXIS_NAME_VALUE_DEFAULT() && resultFit->xAxisUnit == "h" && resultFit->yAxisName == mitk::ModelFitConstants::YAXIS_NAME_VALUE_DEFAULT() && resultFit->yAxisUnit == "kg" && resultFit->GetParameters().size() == 2); mitk::modelFit::Parameter::ConstPointer param = resultFit->GetParameter("Param1", mitk::modelFit::Parameter::ParameterType); CPPUNIT_ASSERT_MESSAGE("Testing if param 1 exists.", param.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if param 1 is configured correctly.", param->name == "Param1" && param->unit == "b" && param->image == m_ParamImage); mitk::modelFit::Parameter::ConstPointer param2 = resultFit->GetParameter("Param2", mitk::modelFit::Parameter::DerivedType); CPPUNIT_ASSERT_MESSAGE("Testing if param 2 exists.", param2.IsNotNull()); CPPUNIT_ASSERT_MESSAGE("Testing if param 2 is configured correctly.", param2->name == "Param2" && param2->unit == "a" && param2->image == m_ParamImage2); } void CheckGetNodesOfFit() { auto nodes = mitk::modelFit::GetNodesOfFit("Fit", m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if GetNodesOfFit works correctly for Fit", nodes->Size() == 2); CPPUNIT_ASSERT(std::find(nodes->begin(), nodes->end(), m_ParamImageNode.GetPointer()) != nodes->end()); CPPUNIT_ASSERT(std::find(nodes->begin(), nodes->end(), m_ParamImageNode2.GetPointer()) != nodes->end()); nodes = mitk::modelFit::GetNodesOfFit("FitLegacy", m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if GetNodesOfFit works correctly for FitLegacy", nodes->Size() == 2); CPPUNIT_ASSERT(std::find(nodes->begin(), nodes->end(), m_ParamImageNode_legacy.GetPointer()) != nodes->end()); CPPUNIT_ASSERT(std::find(nodes->begin(), nodes->end(), m_ParamImageNode2_legacy.GetPointer()) != nodes->end()); CPPUNIT_ASSERT_MESSAGE("Testing if GetNodesOfFit works correctly for Fit2", mitk::modelFit::GetNodesOfFit("Fit2", m_Storage)->Size() == 1); CPPUNIT_ASSERT_MESSAGE("Testing if GetNodesOfFit works correctly for unkown fits.", mitk::modelFit::GetNodesOfFit("unkown_fit", m_Storage)->Size() == 0); CPPUNIT_ASSERT_MESSAGE("Testing if GetNodesOfFit works correctly for illegal calls.", mitk::modelFit::GetNodesOfFit("unkown_fit", nullptr).IsNull()); } void CheckGetFitUIDsOfNode() { auto testNode = m_Storage->GetNamedNode("Input"); mitk::modelFit::NodeUIDSetType uidSet = mitk::modelFit::GetFitUIDsOfNode(testNode, m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if GetFitUIDsOfNode works correctly.", uidSet.size() == 2 && uidSet.find("Fit") != uidSet.end() && uidSet.find("FitLegacy") != uidSet.end()); testNode = m_Storage->GetNamedNode("AnotherInput"); uidSet = mitk::modelFit::GetFitUIDsOfNode(testNode, m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if GetFitUIDsOfNode works correctly.", uidSet.size() == 1 && uidSet.find("Fit2") != uidSet.end()); uidSet = mitk::modelFit::GetFitUIDsOfNode(nullptr, m_Storage); CPPUNIT_ASSERT_MESSAGE("Testing if GetFitUIDsOfNode works correctly with invalid node.", uidSet.size() == 0); uidSet = mitk::modelFit::GetFitUIDsOfNode(testNode, nullptr); CPPUNIT_ASSERT_MESSAGE("Testing if GetFitUIDsOfNode works correctly with invalid storage.", uidSet.size() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkModelFitInfo) \ No newline at end of file diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 89d0588465..b027952284 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,77 +1,78 @@ # 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 Segmentation QtWidgets QtWidgetsExt ImageStatisticsUI SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification OpenIGTLink IGTBase IGT CameraCalibration OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI US USUI DICOMUI Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI RT RTUI IOExt XNAT TubeGraph BoundingShape RenderWindowManagerUI SemanticRelations SemanticRelationsUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI DICOMPM REST RESTService DICOMweb + ROI ) diff --git a/Modules/Multilabel/mitkLabel.h b/Modules/Multilabel/mitkLabel.h index 0d64a124df..968452be5d 100644 --- a/Modules/Multilabel/mitkLabel.h +++ b/Modules/Multilabel/mitkLabel.h @@ -1,106 +1,107 @@ /*============================================================================ 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 mitkLabel_h #define mitkLabel_h #include "MitkMultilabelExports.h" #include #include +#include #include namespace mitk { //## //##Documentation //## @brief A data structure describing a label. //## @ingroup Data //## class MITKMULTILABEL_EXPORT Label : public PropertyList { public: mitkClassMacro(Label, mitk::PropertyList); typedef unsigned short PixelType; itkNewMacro(Self); mitkNewMacro2Param(Self, PixelType, const std::string&); /// The maximum value a label can get: Since the value is of type unsigned short MAX_LABEL_VALUE = 65535 static const PixelType MAX_LABEL_VALUE; void SetLocked(bool locked); bool GetLocked() const; void SetVisible(bool visible); bool GetVisible() const; void SetOpacity(float opacity); float GetOpacity() const; void SetName(const std::string &name); std::string GetName() const; void SetCenterOfMassIndex(const mitk::Point3D ¢er); mitk::Point3D GetCenterOfMassIndex() const; void SetCenterOfMassCoordinates(const mitk::Point3D ¢er); mitk::Point3D GetCenterOfMassCoordinates() const; void SetColor(const mitk::Color &); const mitk::Color &GetColor() const; void SetValue(PixelType pixelValue); PixelType GetValue() const; void SetLayer(unsigned int layer); unsigned int GetLayer() const; void SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; using itk::Object::Modified; void Modified() { Superclass::Modified(); } Label(); Label(PixelType value, const std::string& name); ~Label() override; protected: void PrintSelf(std::ostream &os, itk::Indent indent) const override; Label(const Label &other); private: itk::LightObject::Pointer InternalClone() const override; }; /** * @brief Equal A function comparing two labels for beeing equal in data * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - Lebel equality via Equal-PropetyList * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::Label &leftHandSide, const mitk::Label &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 48cfd421c1..02b28d5cb8 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1562 +1,1564 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } +const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UnlabeledValue = 0; + mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } this->ReinitMaps(); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledValue); } ls->SetLayer(newLabelSetId); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetActiveLayer() != groupID ? this->GetLayerImage(groupID) : this; if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) { this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 //bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const //{ // auto finding = m_LabelToGroupMap.find(value); // if (m_LabelToGroupMap.end() != finding) // { // return finding->second == groupIndex; // } // return false; //} // //bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const //{ // return index < m_LabelSetContainer.size(); //} bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LabelSetContainer.size(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UnlabeledValue) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; mitk::LabelSetImage::ConstLabelVectorType result; const auto labelValues = m_GroupToLabelMap.find(index)->second; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; mitk::LabelSetImage::LabelVectorType result; const auto labelValues = m_GroupToLabelMap[index]; for (const auto& labelValue : labelValues) { auto* label = this->GetLabel(labelValue); if (label != nullptr) result.emplace_back(label); } return result; } void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); if (labelSet->GetNumberOfLabels() != 0) { for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { if (iter->first != UnlabeledValue) this->AddLabelToMap(iter->first, iter->second, layerID); } } else { m_GroupToLabelMap[layerID] = {}; } } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index bb8cd3a0ac..709bb63434 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,672 +1,672 @@ /*============================================================================ 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 mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; - const static LabelValueType UnlabeledValue = 0; + const static LabelValueType UnlabeledValue; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; using LabelValueVectorType = std::vector; /** * @brief Removes the label with the given value. * The label is removed from the labelset and * the pixel with the value of the label are set to UnlabeledValue. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @brief Removes a whole group including all its labels. * @remark with removing a group all groups with greater index will be reindexed to * close the gap. Hence externaly stored spatial group indices may become invalid. * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre group index must be valid. */ void RemoveGroup(GroupIndexType group); //future declaration (T28524) currently conflicted with old declaration ///** // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ //bool ExistLabel(LabelValueType value) const; ///** // * @brief Checks if a label belongs in a certain spatial group // * @param value the label value // * @param groupIndex Indexp of the spacial group which should be checked for the label // * @return true if the label exists otherwise false // */ //bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; /** * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. * * @param index Group index of the group that should be checked for existance. */ bool ExistGroup(GroupIndexType index) const; bool IsLabelInGroup(LabelValueType value) const; bool IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ GroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the mitk::Label if available otherwise nullptr */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); /** * @brief Returns a vector of all labels located on the specified group. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @return the respective vector of labels. * @pre group index must exist. */ const ConstLabelVectorType GetLabelsInGroup(GroupIndexType index) const; const LabelVectorType GetLabelsInGroup(GroupIndexType index); itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelEventType = Message1; using LabelsEventType = Message1; using GroupEventType = Message1; /** * \brief LabelAdded is emitted whenever a new label has been added. * * Observers should register to this event by calling this->AddLabelAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the added label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelAdded, LabelValueType); /** * \brief LabelModified is emitted whenever a label has been modified. * * A label is modified if either its pixel content was changed, its spatial group or the label instance * information. * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the * case that at least one label was modified (e.g. to avoid getting a signal for each label * individually), use LabelsChanged instead. * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the modified label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelModified, LabelValueType); /** * \brief LabelRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the removed label.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelRemoved, LabelValueType); /** * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). * * In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will * only be sent once (compared to LabelRemoved or LabelModified). * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, * MyObject::MyMethod). * The registered method will be called with the vector of label values of the modified labels.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); /** * \brief GroupAdded is emitted whenever a new group has been added. * * Observers should register to this event by calling this->AddGroupAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupAdded, GroupIndexType); /** * \brief GroupModified is emitted whenever a group has been modified. * * A group is modified if the set of labels associated with it are changed or the group's meta data. * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupModified, GroupIndexType); /** * \brief GroupRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the removed group.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupRemoved, GroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(GroupIndexType groupIndex); void OnGroupModified(GroupIndexType groupIndex); void OnGroupRemoved(GroupIndexType groupIndex); /** Reeinitalizes the internal maps based on the current layer/label content * of the instance. */ void ReinitMaps(); using LabelMapType = std::map; LabelMapType m_LabelMap; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::map; GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; LabelToGroupMapType m_LabelToGroupMap; private: /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(const std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue) const); /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ //[[deprecated("Will be removed with T28524")]] DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ //[[deprecated ("Will be removed with T28524")]] DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a cloned LabelSet to an existing layer * * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership * of the new LabelSet addition. * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values in all layers until the refactoring is done. returns a sorted list of all labels.*/ LabelValueVectorType GetUsedLabelValues() const; //helper function that ensures void RegisterLabelSet(mitk::LabelSet* ls); void ReleaseLabelSet(mitk::LabelSet* ls); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a label value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelSet must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/OpenIGTLink/mitkIGTLDeviceSource.cpp b/Modules/OpenIGTLink/mitkIGTLDeviceSource.cpp index f6e48f7dcc..40ad9545b1 100644 --- a/Modules/OpenIGTLink/mitkIGTLDeviceSource.cpp +++ b/Modules/OpenIGTLink/mitkIGTLDeviceSource.cpp @@ -1,308 +1,307 @@ /*============================================================================ 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 "mitkIGTLDeviceSource.h" #include "mitkIGTLDevice.h" #include "mitkIGTLMessage.h" -//#include "mitkIGTTimeStamp.h" -//#include "mitkIGTException.h" +#include //Microservices #include #include #include #include //itk #include const std::string mitk::IGTLDeviceSource::US_PROPKEY_IGTLDEVICENAME = mitk::IGTLMessageSource::US_INTERFACE_NAME + ".igtldevicename"; mitk::IGTLDeviceSource::IGTLDeviceSource() : mitk::IGTLMessageSource(), m_IGTLDevice(nullptr) { this->SetName("IGTLDeviceSource (no defined type)"); } mitk::IGTLDeviceSource::~IGTLDeviceSource() { if (m_IGTLDevice.IsNotNull()) { if (m_IGTLDevice->GetState() == mitk::IGTLDevice::Running) { this->StopCommunication(); } if (m_IGTLDevice->GetState() == mitk::IGTLDevice::Ready) { this->Disconnect(); } this->RemoveObservers(); m_IGTLDevice = nullptr; } } void mitk::IGTLDeviceSource::GenerateData() { if (m_IGTLDevice.IsNull()) return; /* update output with message from the device */ IGTLMessage* msgOut = this->GetOutput(); assert(msgOut); igtl::MessageBase::Pointer msgIn = dynamic_cast(m_IGTLDevice->GetNextImage2dMessage().GetPointer()); if (msgIn.IsNotNull()) { assert(msgIn); msgOut->SetMessage(msgIn); msgOut->SetName(msgIn->GetDeviceName()); } // else // { // MITK_ERROR("IGTLDeviceSource") << "Could not get the latest message."; // } } void mitk::IGTLDeviceSource::RemoveObservers() { if (this->m_IGTLDevice.IsNotNull()) { this->m_IGTLDevice->RemoveObserver(m_IncomingMessageObserverTag); this->m_IGTLDevice->RemoveObserver(m_IncomingCommandObserverTag); this->m_IGTLDevice->RemoveObserver(m_LostConnectionObserverTag); } } void mitk::IGTLDeviceSource::SetIGTLDevice(mitk::IGTLDevice* igtlDevice) { MITK_DEBUG << "Setting IGTLDevice to " << igtlDevice; if (this->m_IGTLDevice.GetPointer() != igtlDevice) { //check if we want to override the device if (this->m_IGTLDevice.IsNotNull()) { //the device was set previously => we need to reset the observers this->RemoveObservers(); } //set the device this->m_IGTLDevice = igtlDevice; this->CreateOutputs(); std::stringstream name; // create a human readable name for the source name << "OIGTL Device Source ( " << igtlDevice->GetName() << " )"; this->SetName(name.str()); //setup a observer that listens to new messages and new commands typedef itk::SimpleMemberCommand DeviceSrcCommand; DeviceSrcCommand::Pointer msgReceivedCommand = DeviceSrcCommand::New(); msgReceivedCommand->SetCallbackFunction(this, &IGTLDeviceSource::OnIncomingMessage); this->m_IncomingMessageObserverTag = this->m_IGTLDevice->AddObserver(mitk::MessageReceivedEvent(), msgReceivedCommand); DeviceSrcCommand::Pointer cmdReceivedCommand = DeviceSrcCommand::New(); cmdReceivedCommand->SetCallbackFunction(this, &IGTLDeviceSource::OnIncomingCommand); this->m_IncomingCommandObserverTag = this->m_IGTLDevice->AddObserver(mitk::CommandReceivedEvent(), cmdReceivedCommand); DeviceSrcCommand::Pointer connectionLostCommand = DeviceSrcCommand::New(); connectionLostCommand->SetCallbackFunction(this, &IGTLDeviceSource::OnLostConnection); this->m_LostConnectionObserverTag = this->m_IGTLDevice->AddObserver(mitk::LostConnectionEvent(), connectionLostCommand); } } void mitk::IGTLDeviceSource::CreateOutputs() { //if outputs are set then delete them if (this->GetNumberOfOutputs() > 0) { for (int numOP = this->GetNumberOfOutputs() - 1; numOP >= 0; numOP--) this->RemoveOutput(numOP); this->Modified(); } //fill the outputs if a valid OpenIGTLink device is set if (m_IGTLDevice.IsNull()) return; this->SetNumberOfIndexedOutputs(1); if (this->GetOutput(0) == nullptr) { DataObjectPointer newOutput = this->MakeOutput(0); this->SetNthOutput(0, newOutput); this->Modified(); } } void mitk::IGTLDeviceSource::Connect() { if (m_IGTLDevice.IsNull()) { throw std::invalid_argument("mitk::IGTLDeviceSource: " "No OpenIGTLink device set"); } if (this->IsConnected()) { return; } try { m_IGTLDevice->OpenConnection(); } catch (mitk::Exception &e) { throw std::runtime_error(std::string("mitk::IGTLDeviceSource: Could not open" "connection to OpenIGTLink device. Error: ") + e.GetDescription()); } } void mitk::IGTLDeviceSource::StartCommunication() { if (m_IGTLDevice.IsNull()) throw std::invalid_argument("mitk::IGTLDeviceSource: " "No OpenIGTLink device set"); if (m_IGTLDevice->GetState() == mitk::IGTLDevice::Running) return; if (m_IGTLDevice->StartCommunication() == false) throw std::runtime_error("mitk::IGTLDeviceSource: " "Could not start communication"); } void mitk::IGTLDeviceSource::Disconnect() { if (m_IGTLDevice.IsNull()) throw std::invalid_argument("mitk::IGTLDeviceSource: " "No OpenIGTLink device set"); if (m_IGTLDevice->CloseConnection() == false) throw std::runtime_error("mitk::IGTLDeviceSource: Could not close connection" " to OpenIGTLink device"); } void mitk::IGTLDeviceSource::StopCommunication() { if (m_IGTLDevice.IsNull()) throw std::invalid_argument("mitk::IGTLDeviceSource: " "No OpenIGTLink device set"); if (m_IGTLDevice->StopCommunication() == false) throw std::runtime_error("mitk::IGTLDeviceSource: " "Could not stop communicating"); } void mitk::IGTLDeviceSource::UpdateOutputInformation() { this->Modified(); // make sure that we need to be updated Superclass::UpdateOutputInformation(); } void mitk::IGTLDeviceSource::SetInput(unsigned int idx, const IGTLMessage* msg) { if (msg == nullptr) // if an input is set to nullptr, remove it { this->RemoveInput(idx); } else { // ProcessObject is not const-correct so a const_cast is required here this->ProcessObject::SetNthInput(idx, const_cast(msg)); } // this->CreateOutputsForAllInputs(); } bool mitk::IGTLDeviceSource::IsConnected() { if (m_IGTLDevice.IsNull()) return false; return (m_IGTLDevice->GetState() == mitk::IGTLDevice::Ready) || (m_IGTLDevice->GetState() == mitk::IGTLDevice::Running); } bool mitk::IGTLDeviceSource::IsCommunicating() { if (m_IGTLDevice.IsNull()) return false; return m_IGTLDevice->GetState() == mitk::IGTLDevice::Running; } void mitk::IGTLDeviceSource::RegisterAsMicroservice() { // Get Context us::ModuleContext* context = us::GetModuleContext(); // Define ServiceProps us::ServiceProperties props; mitk::UIDGenerator uidGen = mitk::UIDGenerator("org.mitk.services.IGTLDeviceSource.id_"); props[US_PROPKEY_ID] = uidGen.GetUID(); props[US_PROPKEY_DEVICENAME] = this->GetName(); props[US_PROPKEY_IGTLDEVICENAME] = m_Name; props[US_PROPKEY_DEVICETYPE] = m_Type; m_ServiceRegistration = context->RegisterService(this, props); MITK_INFO << "Registered new DeviceSource as microservice: " << uidGen.GetUID(); } void mitk::IGTLDeviceSource::OnIncomingMessage() { } void mitk::IGTLDeviceSource::OnIncomingCommand() { } void mitk::IGTLDeviceSource::OnLostConnection() { } const mitk::IGTLMessage* mitk::IGTLDeviceSource::GetInput(void) const { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(0)); } const mitk::IGTLMessage* mitk::IGTLDeviceSource::GetInput(unsigned int idx) const { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(idx)); } const mitk::IGTLMessage* mitk::IGTLDeviceSource::GetInput(std::string msgName) const { const DataObjectPointerArray& inputs = const_cast(this)->GetInputs(); for (DataObjectPointerArray::const_iterator it = inputs.begin(); it != inputs.end(); ++it) if (std::string(msgName) == (static_cast(it->GetPointer()))->GetName()) return static_cast(it->GetPointer()); return nullptr; } itk::ProcessObject::DataObjectPointerArraySizeType mitk::IGTLDeviceSource::GetInputIndex(std::string msgName) { DataObjectPointerArray outputs = this->GetInputs(); for (DataObjectPointerArray::size_type i = 0; i < outputs.size(); ++i) if (msgName == (static_cast(outputs.at(i).GetPointer()))->GetName()) return i; throw std::invalid_argument("output name does not exist"); } diff --git a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp index 66828da564..918f310434 100644 --- a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp @@ -1,305 +1,286 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include std::string inFilename; std::string outFileName; mitk::Image::Pointer image; bool verbose(false); bool t1_absolute(false); bool t1_relative(false); -bool t1_flash(false); bool t2(false); float k(1.0); float te(0); float rec_time(0); float relaxivity(0); float rel_time(0); void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("MR Signal to Concentration Converter"); parser.setDescription("MiniApp that allows to convert a T1 or T2 signal image into a concentration image for perfusion analysis."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "where to save the output concentration image.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Conversion parameters"); parser.addArgument( "t1-absolute", "", mitkCommandLineParser::Bool, "T1 absolute signal enhancement", "Activate conversion for T1 absolute signal enhancement."); parser.addArgument( "t1-relative", "", mitkCommandLineParser::Bool, "T1 relative signal enhancement", "Activate conversion for T1 relative signal enhancement."); - parser.addArgument( - "t1-flash", "", mitkCommandLineParser::Bool, "T1 turbo flash", "Activate specific conversion for T1 turbo flash sequences."); parser.addArgument( "t2", "", mitkCommandLineParser::Bool, "T2 signal conversion", "Activate conversion for T2 signal enhancement to concentration."); parser.addArgument( "k", "k", mitkCommandLineParser::Float, "Conversion factor k", "Needed for the following conversion modes: T1-absolute, T1-relative, T2. Default value is 1.", us::Any(1)); parser.addArgument( "recovery-time", "", mitkCommandLineParser::Float, "Recovery time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxivity", "", mitkCommandLineParser::Float, "Relaxivity", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxation-time", "", mitkCommandLineParser::Float, "Relaxation time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "te", "", mitkCommandLineParser::Float, "Echo time TE", "Needed for the following conversion modes: T2.", us::Any(1)); parser.beginGroup("Optional parameters"); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } t1_absolute = false; if (parsedArgs.count("t1-absolute")) { t1_absolute = us::any_cast(parsedArgs["t1-absolute"]); } t1_relative = false; if (parsedArgs.count("t1-relative")) { t1_relative = us::any_cast(parsedArgs["t1-relative"]); } - t1_flash = false; - if (parsedArgs.count("t1-flash")) - { - t1_flash = us::any_cast(parsedArgs["t1-flash"]); - } t2 = false; if (parsedArgs.count("t2")) { t2 = us::any_cast(parsedArgs["t2"]); } k = 0.0; if (parsedArgs.count("k")) { k = us::any_cast(parsedArgs["k"]); } relaxivity = 0.0; if (parsedArgs.count("relaxivity")) { relaxivity = us::any_cast(parsedArgs["relaxivity"]); } rec_time = 0.0; if (parsedArgs.count("recovery-time")) { rec_time = us::any_cast(parsedArgs["recovery-time"]); } rel_time = 0.0; if (parsedArgs.count("relaxation-time")) { rel_time = us::any_cast(parsedArgs["relaxation-time"]); } te = 0.0; if (parsedArgs.count("te")) { te = us::any_cast(parsedArgs["te"]); } //consistency checks int modeCount = 0; if (t1_absolute) ++modeCount; - if (t1_flash) ++modeCount; if (t1_relative) ++modeCount; if (t2) ++modeCount; if (modeCount==0) { mitkThrow() << "Invalid program call. Please select the type of conversion."; } if (modeCount >1) { mitkThrow() << "Invalid program call. Please select only ONE type of conversion."; } if (!k && (t2 || t1_absolute || t1_relative)) { mitkThrow() << "Invalid program call. Please set 'k', if you use t1-absolute, t1-relative or t2."; } if (!te && t2) { mitkThrow() << "Invalid program call. Please set 'te', if you use t2 mode."; } - if ((!rec_time||!rel_time||!relaxivity) && t1_flash) - { - mitkThrow() << "Invalid program call. Please set 'recovery-time', 'relaxation-time' and 'relaxivity', if you use t1-flash mode."; - } return true; } void doConversion() { mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(image); - concentrationGen->SetisTurboFlashSequence(t1_flash); + //concentrationGen->SetisTurboFlashSequence(t1_flash); concentrationGen->SetAbsoluteSignalEnhancement(t1_absolute); concentrationGen->SetRelativeSignalEnhancement(t1_relative); concentrationGen->SetisT2weightedImage(t2); - if (t1_flash) - { - concentrationGen->SetRecoveryTime(rec_time); - concentrationGen->SetRelaxationTime(rel_time); - concentrationGen->SetRelaxivity(relaxivity); - } - else if (t2) + if (t2) { concentrationGen->SetT2Factor(k); concentrationGen->SetT2EchoTime(te); } else { concentrationGen->SetFactor(k); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); mitk::IOUtil::Save(concentrationImage, outFileName); std::cout << "Store result: " << outFileName << std::endl; } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); try { if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; } } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered when parsing the CLI arguments."; return EXIT_FAILURE; } mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; doConversion(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp b/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp index 4805e6b3b6..a3767335f4 100644 --- a/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp +++ b/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp @@ -1,510 +1,509 @@ /*============================================================================ 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 "mitkPlanarAngle.h" #include "mitkPlanarCircle.h" #include "mitkPlanarCross.h" #include "mitkPlanarFourPointAngle.h" #include "mitkPlanarLine.h" #include "mitkPlanarPolygon.h" #include "mitkPlanarRectangle.h" #include "mitkPlanarSubdivisionPolygon.h" #include "mitkPlaneGeometry.h" #include "mitkGeometry3D.h" #include "mitkAbstractFileIO.h" #include "mitkFileReaderRegistry.h" #include "mitkFileWriterRegistry.h" #include "mitkIOUtil.h" #include /** \brief Helper class for testing PlanarFigure reader and writer classes. */ class PlanarFigureIOTestClass { public: typedef std::map PlanarFigureMap; typedef std::map PlanarFigureToStreamMap; static PlanarFigureMap CreatePlanarFigures() { PlanarFigureMap planarFigures; // Create PlaneGeometry on which to place the PlanarFigures mitk::PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->InitializeStandardPlane(100.0, 100.0); // Create a few sample points for PlanarFigure placement mitk::Point2D p0; p0[0] = 20.0; p0[1] = 20.0; mitk::Point2D p1; p1[0] = 80.0; p1[1] = 80.0; mitk::Point2D p2; p2[0] = 90.0; p2[1] = 10.0; mitk::Point2D p3; p3[0] = 10.0; p3[1] = 90.0; // Create PlanarAngle mitk::PlanarAngle::Pointer planarAngle = mitk::PlanarAngle::New(); planarAngle->SetPlaneGeometry(planeGeometry); planarAngle->PlaceFigure(p0); planarAngle->SetCurrentControlPoint(p1); planarAngle->AddControlPoint(p2); planarAngle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarAngle",planarAngle.GetPointer()); // Create PlanarCircle mitk::PlanarCircle::Pointer planarCircle = mitk::PlanarCircle::New(); planarCircle->SetPlaneGeometry(planeGeometry); planarCircle->PlaceFigure(p0); planarCircle->SetCurrentControlPoint(p1); planarCircle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCircle",planarCircle.GetPointer()); // Create PlanarCross mitk::PlanarCross::Pointer planarCross = mitk::PlanarCross::New(); planarCross->SetSingleLineMode(false); planarCross->SetPlaneGeometry(planeGeometry); planarCross->PlaceFigure(p0); planarCross->SetCurrentControlPoint(p1); planarCross->AddControlPoint(p2); planarCross->AddControlPoint(p3); planarCross->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCross",planarCross.GetPointer()); // Create PlanarFourPointAngle mitk::PlanarFourPointAngle::Pointer planarFourPointAngle = mitk::PlanarFourPointAngle::New(); planarFourPointAngle->SetPlaneGeometry(planeGeometry); planarFourPointAngle->PlaceFigure(p0); planarFourPointAngle->SetCurrentControlPoint(p1); planarFourPointAngle->AddControlPoint(p2); planarFourPointAngle->AddControlPoint(p3); planarFourPointAngle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarFourPointAngle",planarFourPointAngle.GetPointer()); // Create PlanarLine mitk::PlanarLine::Pointer planarLine = mitk::PlanarLine::New(); planarLine->SetPlaneGeometry(planeGeometry); planarLine->PlaceFigure(p0); planarLine->SetCurrentControlPoint(p1); planarLine->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarLine",planarLine.GetPointer()); // Create PlanarPolygon mitk::PlanarPolygon::Pointer planarPolygon = mitk::PlanarPolygon::New(); planarPolygon->SetClosed(false); planarPolygon->SetPlaneGeometry(planeGeometry); planarPolygon->PlaceFigure(p0); planarPolygon->SetCurrentControlPoint(p1); planarPolygon->AddControlPoint(p2); planarPolygon->AddControlPoint(p3); planarPolygon->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarPolygon",planarPolygon.GetPointer()); // Create PlanarSubdivisionPolygon mitk::PlanarSubdivisionPolygon::Pointer planarSubdivisionPolygon = mitk::PlanarSubdivisionPolygon::New(); planarSubdivisionPolygon->SetClosed(false); planarSubdivisionPolygon->SetPlaneGeometry(planeGeometry); planarSubdivisionPolygon->PlaceFigure(p0); planarSubdivisionPolygon->SetCurrentControlPoint(p1); planarSubdivisionPolygon->AddControlPoint(p2); planarSubdivisionPolygon->AddControlPoint(p3); planarSubdivisionPolygon->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarSubdivisionPolygon",planarSubdivisionPolygon.GetPointer()); // Create PlanarRectangle mitk::PlanarRectangle::Pointer planarRectangle = mitk::PlanarRectangle::New(); planarRectangle->SetPlaneGeometry(planeGeometry); planarRectangle->PlaceFigure(p0); planarRectangle->SetCurrentControlPoint(p1); planarRectangle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarRectangle",planarRectangle.GetPointer()); // create preciseGeometry which is using float coordinates mitk::PlaneGeometry::Pointer preciseGeometry = mitk::PlaneGeometry::New(); mitk::Vector3D right; right[0] = 0.0; right[1] = 1.23456; right[2] = 0.0; mitk::Vector3D down; down[0] = 1.23456; down[1] = 0.0; down[2] = 0.0; mitk::Vector3D spacing; spacing[0] = 0.0123456; spacing[1] = 0.0123456; spacing[2] = 1.123456; preciseGeometry->InitializeStandardPlane(right, down, &spacing); // convert points into the precise coordinates mitk::Point2D p0precise; p0precise[0] = p0[0] * spacing[0]; p0precise[1] = p0[1] * spacing[1]; mitk::Point2D p1precise; p1precise[0] = p1[0] * spacing[0]; p1precise[1] = p1[1] * spacing[1]; mitk::Point2D p2precise; p2precise[0] = p2[0] * spacing[0]; p2precise[1] = p2[1] * spacing[1]; mitk::Point2D p3precise; p3precise[0] = p3[0] * spacing[0]; p3precise[1] = p3[1] * spacing[1]; // Now all PlanarFigures are create using the precise Geometry // Create PlanarCross mitk::PlanarCross::Pointer nochncross = mitk::PlanarCross::New(); nochncross->SetSingleLineMode(false); nochncross->SetPlaneGeometry(preciseGeometry); nochncross->PlaceFigure(p0precise); nochncross->SetCurrentControlPoint(p1precise); nochncross->AddControlPoint(p2precise); nochncross->AddControlPoint(p3precise); nochncross->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("nochncross", nochncross.GetPointer()); // Create PlanarAngle mitk::PlanarAngle::Pointer planarAnglePrecise = mitk::PlanarAngle::New(); planarAnglePrecise->SetPlaneGeometry(preciseGeometry); planarAnglePrecise->PlaceFigure(p0precise); planarAnglePrecise->SetCurrentControlPoint(p1precise); planarAnglePrecise->AddControlPoint(p2precise); planarAnglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarAnglePrecise",planarAnglePrecise.GetPointer()); // Create PlanarCircle mitk::PlanarCircle::Pointer planarCirclePrecise = mitk::PlanarCircle::New(); planarCirclePrecise->SetPlaneGeometry(preciseGeometry); planarCirclePrecise->PlaceFigure(p0precise); planarCirclePrecise->SetCurrentControlPoint(p1precise); planarCirclePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCirclePrecise",planarCirclePrecise.GetPointer()); // Create PlanarFourPointAngle mitk::PlanarFourPointAngle::Pointer planarFourPointAnglePrecise = mitk::PlanarFourPointAngle::New(); planarFourPointAnglePrecise->SetPlaneGeometry(preciseGeometry); planarFourPointAnglePrecise->PlaceFigure(p0precise); planarFourPointAnglePrecise->SetCurrentControlPoint(p1precise); planarFourPointAnglePrecise->AddControlPoint(p2precise); planarFourPointAnglePrecise->AddControlPoint(p3precise); planarFourPointAnglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarFourPointAnglePrecise",planarFourPointAnglePrecise.GetPointer()); // Create PlanarLine mitk::PlanarLine::Pointer planarLinePrecise = mitk::PlanarLine::New(); planarLinePrecise->SetPlaneGeometry(preciseGeometry); planarLinePrecise->PlaceFigure(p0precise); planarLinePrecise->SetCurrentControlPoint(p1precise); planarLinePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarLinePrecise",planarLinePrecise.GetPointer()); // Create PlanarPolygon mitk::PlanarPolygon::Pointer planarPolygonPrecise = mitk::PlanarPolygon::New(); planarPolygonPrecise->SetClosed(false); planarPolygonPrecise->SetPlaneGeometry(preciseGeometry); planarPolygonPrecise->PlaceFigure(p0precise); planarPolygonPrecise->SetCurrentControlPoint(p1precise); planarPolygonPrecise->AddControlPoint(p2precise); planarPolygonPrecise->AddControlPoint(p3precise); planarPolygonPrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarPolygonPrecise",planarPolygonPrecise.GetPointer()); // Create PlanarSubdivisionPolygon mitk::PlanarSubdivisionPolygon::Pointer planarSubdivisionPolygonPrecise = mitk::PlanarSubdivisionPolygon::New(); planarSubdivisionPolygonPrecise->SetClosed(false); planarSubdivisionPolygonPrecise->SetPlaneGeometry(preciseGeometry); planarSubdivisionPolygonPrecise->PlaceFigure(p0precise); planarSubdivisionPolygonPrecise->SetCurrentControlPoint(p1precise); planarSubdivisionPolygonPrecise->AddControlPoint(p2precise); planarSubdivisionPolygonPrecise->AddControlPoint(p3precise); planarSubdivisionPolygonPrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarSubdivisionPolygonPrecise",planarSubdivisionPolygonPrecise.GetPointer()); // Create PlanarRectangle mitk::PlanarRectangle::Pointer planarRectanglePrecise = mitk::PlanarRectangle::New(); planarRectanglePrecise->SetPlaneGeometry(preciseGeometry); planarRectanglePrecise->PlaceFigure(p0precise); planarRectanglePrecise->SetCurrentControlPoint(p1precise); planarRectanglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarRectanglePrecise",planarRectanglePrecise.GetPointer()); return planarFigures; } static PlanarFigureMap CreateClonedPlanarFigures(PlanarFigureMap original) { PlanarFigureMap copiedPlanarFigures; for (const auto& pf : original) { mitk::PlanarFigure::Pointer copiedFigure = pf.second->Clone(); copiedPlanarFigures[pf.first] = copiedFigure; } return copiedPlanarFigures; } static void VerifyPlanarFigures(PlanarFigureMap &referencePfs, PlanarFigureMap &testPfs) { PlanarFigureMap::iterator it1, it2; int i = 0; for (it1 = referencePfs.begin(); it1 != referencePfs.end(); ++it1) { bool planarFigureFound = false; - int j = 0; + for (it2 = testPfs.begin(); it2 != testPfs.end(); ++it2) { // Compare PlanarFigures (returns false if different types) if (ComparePlanarFigures(it1->second, it2->second)) { planarFigureFound = true; } - ++j; } // Test if (at least) on PlanarFigure of the first type was found in the second list MITK_TEST_CONDITION_REQUIRED(planarFigureFound, "Testing if " << it1->second->GetNameOfClass() << " has a counterpart " << i); ++i; } } static bool ComparePlanarFigures(const mitk::PlanarFigure *referencePf, const mitk::PlanarFigure *testPf) { // Test if PlanarFigures are of same type; otherwise return if (strcmp(referencePf->GetNameOfClass(), testPf->GetNameOfClass()) != 0) { return false; } if (strcmp(referencePf->GetNameOfClass(), "PlanarCross") == 0) { std::cout << "Planar Cross Found" << std::endl; } // Test for equal number of control points if (referencePf->GetNumberOfControlPoints() != testPf->GetNumberOfControlPoints()) { return false; } // Test if all control points are equal for (unsigned int i = 0; i < referencePf->GetNumberOfControlPoints(); ++i) { mitk::Point2D point1 = referencePf->GetControlPoint(i); mitk::Point2D point2 = testPf->GetControlPoint(i); if (point1.EuclideanDistanceTo(point2) >= mitk::eps) { return false; } } // Test for equal number of properties typedef mitk::PropertyList::PropertyMap PropertyMap; const PropertyMap *refProperties = referencePf->GetPropertyList()->GetMap(); const PropertyMap *testProperties = testPf->GetPropertyList()->GetMap(); MITK_INFO << "List 1:"; for (auto i1 = refProperties->begin(); i1 != refProperties->end(); ++i1) { std::cout << i1->first << std::endl; } MITK_INFO << "List 2:"; for (auto i2 = testProperties->begin(); i2 != testProperties->end(); ++i2) { std::cout << i2->first << std::endl; } MITK_INFO << "-------"; //remark test planar figures may have additional properties //(e.g. reader meta information), but they are not relevant //for the test. Only check of all properties of the reference //are present and correct. for (const auto& prop : *refProperties) { auto finding = testProperties->find(prop.first); if (finding == testProperties->end()) { return false; } MITK_INFO << "Comparing " << prop.first << "(" << prop.second->GetValueAsString() << ") and " << finding->first << "(" << finding->second->GetValueAsString() << ")"; // Compare property objects contained in the map entries (see mitk::PropertyList) if (!(*(prop.second) == *(finding->second))) return false; } // Test if Geometry is equal const auto *planeGeometry1 = dynamic_cast(referencePf->GetPlaneGeometry()); const auto *planeGeometry2 = dynamic_cast(testPf->GetPlaneGeometry()); // Test Geometry transform parameters typedef mitk::Geometry3D::TransformType TransformType; const TransformType *affineGeometry1 = planeGeometry1->GetIndexToWorldTransform(); const TransformType::ParametersType ¶meters1 = affineGeometry1->GetParameters(); const TransformType::ParametersType ¶meters2 = planeGeometry2->GetIndexToWorldTransform()->GetParameters(); for (unsigned int i = 0; i < affineGeometry1->GetNumberOfParameters(); ++i) { if (fabs(parameters1.GetElement(i) - parameters2.GetElement(i)) >= mitk::eps) { return false; } } // Test Geometry bounds typedef mitk::Geometry3D::BoundsArrayType BoundsArrayType; const BoundsArrayType &bounds1 = planeGeometry1->GetBounds(); const BoundsArrayType &bounds2 = planeGeometry2->GetBounds(); for (unsigned int i = 0; i < 6; ++i) { if (fabs(bounds1.GetElement(i) - bounds2.GetElement(i)) >= mitk::eps) { return false; }; } // Test Geometry spacing and origin mitk::Vector3D spacing1 = planeGeometry1->GetSpacing(); mitk::Vector3D spacing2 = planeGeometry2->GetSpacing(); if ((spacing1 - spacing2).GetNorm() >= mitk::eps) { return false; } mitk::Point3D origin1 = planeGeometry1->GetOrigin(); mitk::Point3D origin2 = planeGeometry2->GetOrigin(); if (origin1.EuclideanDistanceTo(origin2) >= mitk::eps) { return false; } return true; } static PlanarFigureToStreamMap SerializePlanarFiguresToMemoryBuffers(PlanarFigureMap &planarFigures) { PlanarFigureToStreamMap pfMemoryStreams; for (const auto& pf : planarFigures) { mitk::FileWriterRegistry writerRegistry; auto writers = writerRegistry.GetWriters(pf.second.GetPointer(), ""); std::ostringstream stream; writers[0]->SetOutputStream("",&stream); writers[0]->SetInput(pf.second); writers[0]->Write(); pfMemoryStreams.emplace(pf.first, stream.str()); } return pfMemoryStreams; } static PlanarFigureMap DeserializePlanarFiguresFromMemoryBuffers(PlanarFigureToStreamMap pfMemoryStreams) { // Store them in the list and return it PlanarFigureMap planarFigures; mitk::FileReaderRegistry readerRegistry; std::vector readers = readerRegistry.GetReaders(mitk::FileReaderRegistry::GetMimeTypeForFile("pf")); for (const auto& pfStream : pfMemoryStreams) { std::istringstream stream; stream.str(pfStream.second); readers[0]->SetInput("", &stream); auto pfRead = readers[0]->Read(); MITK_TEST_CONDITION(pfRead.size() == 1, "One planar figure should be read from stream."); auto pf = dynamic_cast(pfRead.front().GetPointer()); MITK_TEST_CONDITION(pf != nullptr, "Loaded data should be a planar figure."); planarFigures.emplace(pfStream.first, pf); } return planarFigures; } }; // end test helper class /** \brief Test for PlanarFigure reader and writer classes. * * The test works as follows: * * First, a number of PlanarFigure objects of different types are created and placed with * various control points. These objects are the serialized to file, read again from file, and * the retrieved objects are compared with their control points, properties, and geometry * information to the original PlanarFigure objects. */ int mitkPlanarFigureIOTest(int /* argc */, char * /*argv*/ []) { MITK_TEST_BEGIN("PlanarFigureIO"); // Create a number of PlanarFigure objects PlanarFigureIOTestClass::PlanarFigureMap originalPlanarFigures = PlanarFigureIOTestClass::CreatePlanarFigures(); // Create a number of cloned planar figures to test the Clone function PlanarFigureIOTestClass::PlanarFigureMap clonedPlanarFigures = PlanarFigureIOTestClass::CreateClonedPlanarFigures(originalPlanarFigures); PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, clonedPlanarFigures); std::map pfFileNameMap; for (const auto& pf : originalPlanarFigures) { std::string filename = mitk::IOUtil::CreateTemporaryFile(pf.first+"_XXXXXX.pf", itksys::SystemTools::GetCurrentWorkingDirectory()); mitk::IOUtil::Save(pf.second, filename); pfFileNameMap.emplace(pf.first, filename); } // Write PlanarFigure objects to memory buffers PlanarFigureIOTestClass::PlanarFigureToStreamMap writersStreams = PlanarFigureIOTestClass::SerializePlanarFiguresToMemoryBuffers(originalPlanarFigures); // Read PlanarFigure objects from temp file PlanarFigureIOTestClass::PlanarFigureMap retrievedPlanarFigures; for (const auto& files : pfFileNameMap) { auto pf = mitk::IOUtil::Load(files.second); retrievedPlanarFigures.emplace(files.first, pf); } // Read PlanarFigure objects from memory buffers PlanarFigureIOTestClass::PlanarFigureMap retrievedPlanarFiguresFromMemory = PlanarFigureIOTestClass::DeserializePlanarFiguresFromMemoryBuffers(writersStreams); // Test if original and retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, retrievedPlanarFigures); // Test if original and memory retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, retrievedPlanarFiguresFromMemory); // empty the originalPlanarFigures originalPlanarFigures.clear(); // Test if cloned and retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(clonedPlanarFigures, retrievedPlanarFigures); MITK_TEST_END() } diff --git a/Modules/Python/autoload/PythonService/CMakeLists.txt b/Modules/Python/autoload/PythonService/CMakeLists.txt index c0b5d4f8f6..15f1384ec9 100644 --- a/Modules/Python/autoload/PythonService/CMakeLists.txt +++ b/Modules/Python/autoload/PythonService/CMakeLists.txt @@ -1,15 +1,17 @@ mitkFunctionCheckCompilerFlags("-Wno-cpp" CMAKE_CXX_FLAGS) -mitk_create_module(PythonService - INCLUDE_DIRS - PRIVATE src/PythonService - DEPENDS PUBLIC MitkPython - PACKAGE_DEPENDS - PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets - PRIVATE Python3|NumPy - AUTOLOAD_WITH MitkPython -) +if(CTKScriptingPythonCore_INCLUDE_DIRS AND CTKScriptingPythonWidgets_INCLUDE_DIRS) + mitk_create_module(PythonService + INCLUDE_DIRS + PRIVATE src/PythonService + DEPENDS PUBLIC MitkPython + PACKAGE_DEPENDS + PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets + PRIVATE Python3|NumPy + AUTOLOAD_WITH MitkPython + ) +endif() if(TARGET ${MODULE_TARGET}) configure_file(PythonPath.h.in "${CMAKE_CURRENT_BINARY_DIR}/PythonPath.h" @ONLY) endif() diff --git a/Modules/Python/autoload/PythonService/mitkPythonActivator.cpp b/Modules/Python/autoload/PythonService/mitkPythonActivator.cpp index d58ba5cf27..f14ab932c2 100644 --- a/Modules/Python/autoload/PythonService/mitkPythonActivator.cpp +++ b/Modules/Python/autoload/PythonService/mitkPythonActivator.cpp @@ -1,63 +1,63 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPythonActivator_h #define mitkPythonActivator_h // Microservices +#include "mitkPythonService.h" #include #include "usModuleContext.h" -#include "mitkPythonService.h" #include namespace mitk { /// /// installs the PythonService /// runs all initial commands (setting env paths etc) /// class PythonActivator : public us::ModuleActivator { public: void Load(us::ModuleContext* context) override { MITK_DEBUG << "PythonActivator::Load"; // Registering PythonService as MicroService m_PythonService = itk::SmartPointer(new PythonService()); us::ServiceProperties _PythonServiceProps; _PythonServiceProps["Name"] = std::string("PythonService"); m_PythonServiceRegistration = context->RegisterService(m_PythonService.GetPointer(), _PythonServiceProps); } void Unload(us::ModuleContext*) override { MITK_DEBUG("PythonActivator") << "PythonActivator::Unload"; MITK_DEBUG("PythonActivator") << "m_PythonService GetReferenceCount " << m_PythonService->GetReferenceCount(); m_PythonServiceRegistration.Unregister(); m_PythonService->Delete(); MITK_DEBUG("PythonActivator") << "m_PythonService GetReferenceCount " << m_PythonService->GetReferenceCount(); } ~PythonActivator() override { } private: itk::SmartPointer m_PythonService; us::ServiceRegistration m_PythonServiceRegistration; }; } US_EXPORT_MODULE_ACTIVATOR(mitk::PythonActivator) #endif diff --git a/Modules/Python/autoload/PythonService/mitkPythonService.h b/Modules/Python/autoload/PythonService/mitkPythonService.h index 31338dd8e1..c98f71a64c 100644 --- a/Modules/Python/autoload/PythonService/mitkPythonService.h +++ b/Modules/Python/autoload/PythonService/mitkPythonService.h @@ -1,107 +1,111 @@ /*============================================================================ 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 mitkPythonService_h #define mitkPythonService_h #include -#include "mitkIPythonService.h" +#ifdef snprintf +#undef snprintf +#endif + +#include +#include #include -#include "mitkSurface.h" namespace mitk { /// /// implementation of the IPythonService using ctkabstractpythonmanager /// \see IPythonService class PythonService: public itk::LightObject, public mitk::IPythonService { public: /// /// instantiate python manager here PythonService(); /// /// empty implementation... ~PythonService() override; /// /// \see IPythonService::Execute() std::string Execute( const std::string& pythonCommand, int commandType = SINGLE_LINE_COMMAND ) override; /// /// \see IPythonService::ExecuteScript() void ExecuteScript(const std::string &pathToPythonScript) override; /// /// \see IPythonService::PythonErrorOccured() bool PythonErrorOccured() const override; /// /// \see IPythonService::GetVariableStack() std::vector GetVariableStack() const override; /// /// \see IPythonService::DoesVariableExist() bool DoesVariableExist(const std::string& name) const override; /// /// \see IPythonService::GetVariable() std::string GetVariable(const std::string& name) const override; /// /// \see IPythonService::AddPythonCommandObserver() void AddPythonCommandObserver( PythonCommandObserver* observer ) override; /// /// \see IPythonService::RemovePythonCommandObserver() void RemovePythonCommandObserver( PythonCommandObserver* observer ) override; /// /// \see IPythonService::NotifyObserver() void NotifyObserver( const std::string& command ) override; /// /// \see IPythonService::IsItkPythonWrappingAvailable() bool IsSimpleItkPythonWrappingAvailable() override; /// /// \see IPythonService::CopyToPythonAsItkImage() bool CopyToPythonAsSimpleItkImage( mitk::Image* image, const std::string& varName ) override; /// /// \see IPythonService::CopyItkImageFromPython() mitk::Image::Pointer CopySimpleItkImageFromPython( const std::string& varName ) override; /// /// \see IPythonService::IsOpenCvPythonWrappingAvailable() bool IsOpenCvPythonWrappingAvailable() override; /// /// \see IPythonService::CopyToPythonAsCvImage() bool CopyToPythonAsCvImage( mitk::Image* image, const std::string& varName ) override; /// /// \see IPythonService::CopyCvImageFromPython() mitk::Image::Pointer CopyCvImageFromPython( const std::string& varName ) override; /// /// \see IPythonService::IsVtkPythonWrappingAvailable() bool IsVtkPythonWrappingAvailable() override; /// /// \see IPythonService::CopyToPythonAsVtkPolyData() bool CopyToPythonAsVtkPolyData( mitk::Surface* surface, const std::string& varName ) override; /// /// \see IPythonService::CopyVtkPolyDataFromPython() mitk::Surface::Pointer CopyVtkPolyDataFromPython( const std::string& varName ) override; /// /// \return the ctk abstract python manager instance ctkAbstractPythonManager* GetPythonManager() override; void AddRelativeSearchDirs(std::vector< std::string > dirs) override; void AddAbsoluteSearchDirs(std::vector< std::string > dirs) override; protected: private: QList m_Observer; ctkAbstractPythonManager m_PythonManager; bool m_ItkWrappingAvailable; bool m_OpenCVWrappingAvailable; bool m_VtkWrappingAvailable; bool m_ErrorOccured; }; } #endif diff --git a/Modules/Python/mitkIPythonService.h b/Modules/Python/mitkIPythonService.h index eb9c4d1dad..e53457d24a 100644 --- a/Modules/Python/mitkIPythonService.h +++ b/Modules/Python/mitkIPythonService.h @@ -1,153 +1,155 @@ /*============================================================================ 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 mitkIPythonService_h #define mitkIPythonService_h // mitk +#include +#include #include -#include "mitkImage.h" + //for microservices #include -#include "mitkSurface.h" + #include class ctkAbstractPythonManager; namespace mitk { /// /// describes a python variable (data container) /// \see IPythonService::GetVariableStack() /// struct PythonVariable { std::string m_Name; std::string m_Type; std::string m_Value; }; /// /// a PythonCommandObserver gets informed as soon as a python command was issued /// \see IPythonService::AddPythonCommandObserver() /// class PythonCommandObserver { public: virtual void CommandExecuted(const std::string& pythonCommand) = 0; }; /// /// The central service for issuing Python Code /// The class also enables to transfer mitk images to python as itk::Image and vice versa /// \see IPythonService::GetVariableStack() /// class MITKPYTHON_EXPORT IPythonService { public: /// /// Constant representing a single line command /// \see IPythonService::Execute() static const int SINGLE_LINE_COMMAND = 0; /// /// Constant representing a command in which the commands are seperated by new lines, i.e. "\\n" /// \see IPythonService::Execute() static const int MULTI_LINE_COMMAND = 1; /// /// Constant representing a single line command x which is run as "eval(x)" /// \see IPythonService::Execute() static const int EVAL_COMMAND = 2; /// /// Executes a python command. /// \return A variant containing the return value as string of the python code (if any) virtual std::string Execute( const std::string& pythonCommand, int commandType = SINGLE_LINE_COMMAND ) = 0; /// /// Executes a python script. virtual void ExecuteScript( const std::string& pathToPythonScript ) = 0; /// /// \return true if the last call to Execute...() resulted in an error, false otherwise virtual bool PythonErrorOccured() const = 0; /// /// \return The list of variables in the __main__ namespace virtual std::vector GetVariableStack() const = 0; /// /// \return true if a variable with this name is defined in the __main__ namespace, false otherwise virtual bool DoesVariableExist(const std::string& name) const = 0; /// /// \return value of variable with this name as string, empty string if variable does not exist virtual std::string GetVariable(const std::string& name) const = 0; /// /// adds a command observer which is informed after a command was issued with "Execute" virtual void AddPythonCommandObserver( PythonCommandObserver* observer ) = 0; /// /// removes a specific command observer virtual void RemovePythonCommandObserver( PythonCommandObserver* observer ) = 0; /// /// notify all observer. this should only be used if it can be garantueed that the /// current python interpreter instance got another command from anywhere else /// the the Execute() method of this service, e.g. the shell widget uses this function /// since it does not use Execute() virtual void NotifyObserver( const std::string& command ) = 0; /// /// \return true, if itk wrapping is available, false otherwise virtual bool IsSimpleItkPythonWrappingAvailable() = 0; /// /// copies an mitk image as itk image into the python interpreter process /// the image will be available as "varName" in python if everythin worked /// \return true if image was copied, else false virtual bool CopyToPythonAsSimpleItkImage( mitk::Image* image, const std::string& varName ) = 0; /// /// copies an itk image from the python process that is named "varName" /// \return the image or 0 if copying was not possible virtual mitk::Image::Pointer CopySimpleItkImageFromPython( const std::string& varName ) = 0; /// /// \return true, if OpenCv wrapping is available, false otherwise virtual bool IsOpenCvPythonWrappingAvailable() = 0; /// /// \see CopyToPythonAsItkImage() virtual bool CopyToPythonAsCvImage( mitk::Image* image, const std::string& varName ) = 0; /// /// \see CopyCvImageFromPython() virtual mitk::Image::Pointer CopyCvImageFromPython( const std::string& varName ) = 0; /// /// \return true, if vtk wrapping is available, false otherwise virtual bool IsVtkPythonWrappingAvailable() = 0; /// /// \see CopyToPythonAsItkImage() virtual bool CopyToPythonAsVtkPolyData( mitk::Surface* surface, const std::string& varName ) = 0; /// /// \see CopyCvImageFromPython() virtual mitk::Surface::Pointer CopyVtkPolyDataFromPython( const std::string& varName ) = 0; /// \return the ctk abstract python manager instance virtual ctkAbstractPythonManager* GetPythonManager() = 0; /// /// nothing to do here virtual ~IPythonService(); // leer in mitkIPythonService.cpp implementieren // force us module loading by linking static std::string ForceLoadModule(); virtual void AddRelativeSearchDirs(std::vector< std::string > dirs) = 0; virtual void AddAbsoluteSearchDirs(std::vector< std::string > dirs) = 0; }; } MITK_DECLARE_SERVICE_INTERFACE(mitk::IPythonService, "org.mitk.services.IPythonService") #endif diff --git a/Modules/QtPython/CMakeLists.txt b/Modules/QtPython/CMakeLists.txt index 433b7ae4f4..c22ed5a8db 100644 --- a/Modules/QtPython/CMakeLists.txt +++ b/Modules/QtPython/CMakeLists.txt @@ -1,13 +1,15 @@ if(MITK_USE_Python3) mitkFunctionCheckCompilerFlags("/wd4273" CMAKE_CXX_FLAGS) - mitk_create_module( - DEPENDS MitkCore MitkQtWidgets MitkPython - PACKAGE_DEPENDS - PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets + if(CTKScriptingPythonCore_INCLUDE_DIRS AND CTKScriptingPythonWidgets_INCLUDE_DIRS) + mitk_create_module( + DEPENDS MitkCore MitkQtWidgets MitkPython + PACKAGE_DEPENDS + PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets ) + endif() - if(BUILD_TESTING) + if(BUILD_TESTING AND TARGET ${MODULE_TARGET}) add_subdirectory(Testing) endif() endif() diff --git a/Modules/QtPython/QmitkCtkPythonShell.cpp b/Modules/QtPython/QmitkCtkPythonShell.cpp index ebdbbf36d8..dec9e9168a 100644 --- a/Modules/QtPython/QmitkCtkPythonShell.cpp +++ b/Modules/QtPython/QmitkCtkPythonShell.cpp @@ -1,94 +1,98 @@ /*============================================================================ 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 "QmitkCtkPythonShell.h" #include +#ifdef snprintf +#undef snprintf +#endif + +#include #include #include #include #include -#include "mitkIPythonService.h" #include #include #include struct QmitkCtkPythonShellData { mitk::IPythonService* m_PythonService; us::ServiceReference m_PythonServiceRef; }; QmitkCtkPythonShell::QmitkCtkPythonShell(QWidget* parent) : ctkPythonConsole(parent), d( new QmitkCtkPythonShellData ) { this->setWelcomeTextColor(Qt::green); this->setPromptColor(Qt::gray); this->setStdinTextColor(Qt::white); this->setCommandTextColor(Qt::white); this->setOutputTextColor(Qt::white); MITK_DEBUG("QmitkCtkPythonShell") << "retrieving IPythonService"; us::ModuleContext* context = us::GetModuleContext(); d->m_PythonServiceRef = context->GetServiceReference(); d->m_PythonService = dynamic_cast ( context->GetService(d->m_PythonServiceRef) ); MITK_DEBUG("QmitkCtkPythonShell") << "checking IPythonService"; Q_ASSERT( d->m_PythonService ); MITK_DEBUG("QmitkCtkPythonShell") << "initialize m_PythonService"; this->initialize( d->m_PythonService->GetPythonManager() ); MITK_DEBUG("QmitkCtkPythonShell") << "m_PythonService initialized"; mitk::IPythonService::ForceLoadModule(); } QmitkCtkPythonShell::~QmitkCtkPythonShell() { us::ModuleContext* context = us::GetModuleContext(); context->UngetService( d->m_PythonServiceRef ); delete d; } void QmitkCtkPythonShell::dragEnterEvent(QDragEnterEvent *event) { event->accept(); } void QmitkCtkPythonShell::dropEvent(QDropEvent *event) { QList urls = event->mimeData()->urls(); for(int i = 0; i < urls.size(); i++) { d->m_PythonService->Execute( urls[i].toString().toStdString(), mitk::IPythonService::SINGLE_LINE_COMMAND ); } } bool QmitkCtkPythonShell::canInsertFromMimeData(const QMimeData *) const { return true; } void QmitkCtkPythonShell::executeCommand(const QString& command) { MITK_DEBUG("QmitkCtkPythonShell") << "executing command " << command.toStdString(); d->m_PythonService->Execute(command.toStdString(),mitk::IPythonService::MULTI_LINE_COMMAND); d->m_PythonService->NotifyObserver(command.toStdString()); } void QmitkCtkPythonShell::Paste(const QString &command) { if( this->isVisible() ) { this->exec( command ); //this->executeCommand( command ); } } diff --git a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h index c2034d4f64..f5966a2b41 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h +++ b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h @@ -1,148 +1,147 @@ /*============================================================================ 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 QmitkDataStorageComboBoxWithSelectNone_h #define QmitkDataStorageComboBoxWithSelectNone_h #include #include "QmitkDataStorageComboBox.h" #include "QmitkCustomVariants.h" #include "mitkDataNode.h" /** * \class QmitkDataStorageComboBoxWithSelectNone * \brief Displays all or a subset (defined by a predicate) of nodes of the Data Storage, * and additionally, index 0 is always "please select", indicating no selection, and will * hence always return a nullptr mitk::DataNode* if asked for the node at index 0. * * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal * \sa QmitkDataStorageComboBox */ class MITKQTWIDGETS_EXPORT QmitkDataStorageComboBoxWithSelectNone : public QmitkDataStorageComboBox { Q_OBJECT Q_PROPERTY(mitkDataNodePtr SelectedNode READ GetSelectedNode WRITE SetSelectedNode) Q_PROPERTY(QString currentValue READ currentValue WRITE setCurrentValue) public: /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ QmitkDataStorageComboBoxWithSelectNone(QWidget* parent = nullptr, bool autoSelectNewNodes=false); /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ QmitkDataStorageComboBoxWithSelectNone( mitk::DataStorage* dataStorage, const mitk::NodePredicateBase* predicate, QWidget* parent = nullptr, bool autoSelectNewNodes = false); /** * \brief Nothing to do. * \see QmitkDataStorageComboBox */ ~QmitkDataStorageComboBoxWithSelectNone() override; /** * \brief Stores the string that will be present on index 0, currently equal to "please select". */ static const QString ZERO_ENTRY_STRING; /** * \brief Searches for a given node, returning the index if found. * \param dataNode an mitk::DataNode, can be nullptr. * \return int -1 if not found, and compared to base class, will add 1 onto the retrieved index. */ int Find(const mitk::DataNode* dataNode) const override; /** * \brief Retrieves the node at a given index, where if index is zero, will always return nullptr. * \param index An integer between 0 and n = number of nodes. * \return mitk::DataNode::Pointer nullptr or a data node pointer. */ mitk::DataNode::Pointer GetNode(int index) const override; /** * \brief Returns the selected DataNode or nullptr if there is none, or the current index is zero. */ mitk::DataNode::Pointer GetSelectedNode() const override; /** * \brief Sets the combo box to the index that contains the specified node, or 0 if the node cannot be found. */ virtual void SetSelectedNode(const mitk::DataNode::Pointer& node); using QmitkDataStorageComboBox::RemoveNode; /** * \brief Removes a node from the ComboBox at a specified index (if the index exists). * Gets called when a DataStorage Remove Event was thrown. */ void RemoveNode(int index) override; using QmitkDataStorageComboBox::SetNode; /** * \brief Set a DataNode in the ComboBox at the specified index (if the index exists). * Internally the method just calls InsertNode(unsigned int) */ void SetNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Get the current file path. */ virtual QString currentValue() const; /** * \brief Set the current file path. */ virtual void setCurrentValue(const QString& path); /** * \brief Set the string that will be present on index 0. */ void SetZeroEntryText(const QString& zeroEntryString); protected: /** * \brief Checks if the given index is within range. */ bool HasIndex(unsigned int index) const; /** * \brief Inserts a new node at the given index, unless index is 0, which is silently ignored. */ void InsertNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Reset function whenever datastorage or predicate changes. */ void Reset() override; private: /** * \brief This should store the current file path of the current image. * * * The reason is so that we can store and retrieve a temporary file name. */ QString m_CurrentPath; }; #endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h index ddafc6b443..104403f81f 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h @@ -1,101 +1,103 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSynchronizedNodeSelectionWidget_h #define QmitkSynchronizedNodeSelectionWidget_h #include #include "ui_QmitkSynchronizedNodeSelectionWidget.h" // mitk core #include // qt widgets module #include #include /* * @brief The 'QmitkSynchronizedNodeSelectionWidget' implements the 'QmitkAbstractNodeSelectionWidget' * by providing a table view, using a 'QmitkRenderWindowDataNodeTableModel' and extending it * with base renderer-specific functionality. * * Given a base renderer, the selection widget is able to display and access render window specific properties * of the selected nodes, making it possible to switch between a "synchronized" and "desynchronized" selection * state. * The widget can be used to decide if all data nodes of the data storage should be selected or * only an individually selected set of nodes, defined by a 'QmitkNodeSelectionDialog'. * If individual nodes are selected / removed from the selection, the widget can inform other * 'QmitkSynchronizedNodeSelectionWidget' about the current selection, if desired. * Additionally the widget allows to reinitialize the corresponding base renderer with a specific * data node geometry. */ class MITKQTWIDGETS_EXPORT QmitkSynchronizedNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: QmitkSynchronizedNodeSelectionWidget(QWidget* parent); ~QmitkSynchronizedNodeSelectionWidget(); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; void SetBaseRenderer(mitk::BaseRenderer* baseRenderer); void SetSelectAll(bool selectAll); bool GetSelectAll() const; + void SelectAll(); void SetSynchronized(bool synchronize); bool IsSynchronized() const; Q_SIGNALS: void SelectionModeChanged(bool selectAll); + void DeregisterSynchronization(); private Q_SLOTS: void OnModelUpdated(); void OnSelectionModeChanged(bool selectAll); void OnEditSelection(); void OnTableClicked(const QModelIndex& index); protected: void SetUpConnections(); void Initialize(); void UpdateInfo() override; void OnDataStorageChanged() override; void OnNodePredicateChanged() override; void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; void OnInternalSelectionChanged() override; bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; void OnNodeModified(const itk::Object* caller, const itk::EventObject& event) override; private: void ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReinitNode(const mitk::DataNode* dataNode); void RemoveFromInternalSelection(mitk::DataNode* dataNode); bool IsParentNodeSelected(const mitk::DataNode* dataNode) const; void DeselectNode(mitk::DataNode* dataNode); Ui::QmitkSynchronizedNodeSelectionWidget m_Controls; mitk::WeakPointer m_BaseRenderer; std::unique_ptr m_StorageModel; }; #endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h index ad672d5223..8975764392 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h @@ -1,150 +1,160 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSynchronizedWidgetConnector_h #define QmitkSynchronizedWidgetConnector_h #include // mitk core #include // mitk qt widgets #include // qt #include /* * @brief This class connects different 'QmitkSynchronizedNodeSelectionWidget', such that * they can synchronize their current node selection and their current selection mode. * * In order to synchronize a new node selection widget with other already connected * node selection widgets, 'ConnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. * In order to desynchronize a node selection widget, * 'DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. * If a new node selection has been connected / synchronized, * 'SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget*' can be used to initialy set * the current selection and the current selection mode. * For this, both values are stored in this class internally. */ class MITKQTWIDGETS_EXPORT QmitkSynchronizedWidgetConnector : public QObject { Q_OBJECT public: - using NodeList = QList; + using NodeList = QmitkSynchronizedNodeSelectionWidget::NodeList; QmitkSynchronizedWidgetConnector(); /* * @brief This function connects the different signals and slots of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode can be forwarded or received. * The connections are as follows: * - QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelection * - QmitkSynchronizedWidgetConnector::NodeSelectionChanged * -> QmitkAbstractNodeSelectionWidget::SetCurrentSelection * - QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelectionMode * - QmitkSynchronizedWidgetConnector::SelectionModeChanged * -> QmitkSynchronizedNodeSelectionWidget::SetSelectAll * * @param nodeSelectionWidget The synchronized node selection widget to be connected / synchronized. */ - void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function disconnects the different signals and slot of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode cannot be forwarded or received anymore. * * @param nodeSelectionWidget The synchronized node selection widget to be disconnected / desynchronized. */ - void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function sets the current selection and the selection mode of the given node selection widget * to the values of this instance. The required values are stored in this class internally. * It can be used to newly initialize the given node selection widget. * * @param nodeSelectionWidget The synchronized node selection widget for which the * current selection and the selection mode should be set. */ void SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; /* * @brief Get the current internal node selection. * * @return NodeList The current internal node selection stored as a member variable. */ NodeList GetNodeSelection() const; /* * @brief Get the current internal selection mode. * * @return The current internal selection mode stored as a member variable. */ bool GetSelectionMode() const; Q_SIGNALS: /* * @brief A signal that will be emitted by the 'ChangeSelection'-slot. * This happens if a new selection / list of nodes is set from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetCurrentSelection'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the new selection. * * @param nodes A list of data nodes that are newly selected. */ void NodeSelectionChanged(NodeList nodes); /* * @brief A signal that will be emitted by the 'ChangeSelectionMode'-slot. * This happens if the selection mode is change from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetSelectAll'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void SelectionModeChanged(bool selectAll); public Q_SLOTS: /* * @brief Set a new internal selection and send this new selection to connected * QmitkSynchronizedNodeSelectionWidgets using the 'NodeSelectionChanged'-signal. * * This slot itself is connected to the 'CurrentSelectionChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection. * * @param nodes A list of data nodes that are newly selected. */ void ChangeSelection(NodeList nodes); /* * @brief Set a new selection mode and send this new selection mode to connected * QmitkSynchronizedNodeSelectionWidgets using the 'SelectionModeChanged'-signal. * * This slot itself is connected to the 'SelectionModeChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void ChangeSelectionMode(bool selectAll); + /* + * @brief Decrease the internal counter of connections to keep track of how many + * QmitkSynchronizedNodeSelectionWidgets are synchronized. + * + * This slot itself is connected to the 'DeregisterSynchronization'-signal of each + * QmitkSynchronizedNodeSelectionWidget to get notified when a synchronized + * widget is deleted. + */ + void DeregisterWidget(); private: NodeList m_InternalSelection; bool m_SelectAll; + unsigned int m_ConnectionCounter; }; #endif diff --git a/Modules/QtWidgets/resource/GeometryDataIcon.svg b/Modules/QtWidgets/resource/GeometryDataIcon.svg new file mode 100644 index 0000000000..090de6c5ef --- /dev/null +++ b/Modules/QtWidgets/resource/GeometryDataIcon.svg @@ -0,0 +1,19 @@ + + + + + +image/svg+xml + + + + + + + + + + + + + diff --git a/Modules/QtWidgetsExt/resource/PlanarAngle_48.png b/Modules/QtWidgets/resource/PlanarAngle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarAngle_48.png rename to Modules/QtWidgets/resource/PlanarAngle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarBezierCurve_48.png b/Modules/QtWidgets/resource/PlanarBezierCurve_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarBezierCurve_48.png rename to Modules/QtWidgets/resource/PlanarBezierCurve_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarCircle_48.png b/Modules/QtWidgets/resource/PlanarCircle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarCircle_48.png rename to Modules/QtWidgets/resource/PlanarCircle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarDoubleEllipse_48.png b/Modules/QtWidgets/resource/PlanarDoubleEllipse_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarDoubleEllipse_48.png rename to Modules/QtWidgets/resource/PlanarDoubleEllipse_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarEllipse_48.png b/Modules/QtWidgets/resource/PlanarEllipse_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarEllipse_48.png rename to Modules/QtWidgets/resource/PlanarEllipse_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarFourPointAngle_48.png b/Modules/QtWidgets/resource/PlanarFourPointAngle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarFourPointAngle_48.png rename to Modules/QtWidgets/resource/PlanarFourPointAngle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarLine_48.png b/Modules/QtWidgets/resource/PlanarLine_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarLine_48.png rename to Modules/QtWidgets/resource/PlanarLine_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarPath_48.png b/Modules/QtWidgets/resource/PlanarPath_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarPath_48.png rename to Modules/QtWidgets/resource/PlanarPath_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarPolygon_48.png b/Modules/QtWidgets/resource/PlanarPolygon_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarPolygon_48.png rename to Modules/QtWidgets/resource/PlanarPolygon_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarRectangle_48.png b/Modules/QtWidgets/resource/PlanarRectangle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarRectangle_48.png rename to Modules/QtWidgets/resource/PlanarRectangle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarSubdivisionPolygon_48.png b/Modules/QtWidgets/resource/PlanarSubdivisionPolygon_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarSubdivisionPolygon_48.png rename to Modules/QtWidgets/resource/PlanarSubdivisionPolygon_48.png diff --git a/Modules/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index 377d75c396..81f4df1b5e 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,34 +1,47 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg hourglass-half-solid.svg times.svg reset.svg lock.svg unlock.svg invisible.svg visible.svg SegmentationTaskListIcon.svg + ROIIcon.svg + GeometryDataIcon.svg + PlanarAngle_48.png + PlanarBezierCurve_48.png + PlanarCircle_48.png + PlanarDoubleEllipse_48.png + PlanarEllipse_48.png + PlanarFourPointAngle_48.png + PlanarLine_48.png + PlanarPath_48.png + PlanarPolygon_48.png + PlanarRectangle_48.png + PlanarSubdivisionPolygon_48.png diff --git a/Modules/QtWidgets/resource/ROIIcon.svg b/Modules/QtWidgets/resource/ROIIcon.svg new file mode 100644 index 0000000000..dc1421047b --- /dev/null +++ b/Modules/QtWidgets/resource/ROIIcon.svg @@ -0,0 +1,15 @@ + + + + + +image/svg+xml + + + + + + + + + diff --git a/Modules/QtWidgets/src/QmitkIOUtil.cpp b/Modules/QtWidgets/src/QmitkIOUtil.cpp index abda3c7626..053a231c77 100644 --- a/Modules/QtWidgets/src/QmitkIOUtil.cpp +++ b/Modules/QtWidgets/src/QmitkIOUtil.cpp @@ -1,586 +1,586 @@ /*============================================================================ 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 "QmitkIOUtil.h" #include "mitkCoreServices.h" #include "mitkCustomMimeType.h" #include "mitkFileReaderRegistry.h" #include "mitkFileWriterRegistry.h" #include "mitkIMimeTypeProvider.h" #include "mitkMimeType.h" #include #include #include "QmitkFileReaderOptionsDialog.h" #include "QmitkFileWriterOptionsDialog.h" // QT #include #include #include #include #include // ITK #include #include struct QmitkIOUtil::Impl { struct ReaderOptionsDialogFunctor : public ReaderOptionsFunctorBase { bool operator()(LoadInfo &loadInfo) const override { QmitkFileReaderOptionsDialog dialog(loadInfo); if (dialog.exec() == QDialog::Accepted) { return !dialog.ReuseOptions(); } else { loadInfo.m_Cancel = true; return true; } } }; struct WriterOptionsDialogFunctor : public WriterOptionsFunctorBase { bool operator()(SaveInfo &saveInfo) const override { QmitkFileWriterOptionsDialog dialog(saveInfo); if (dialog.exec() == QDialog::Accepted) { return !dialog.ReuseOptions(); } else { saveInfo.m_Cancel = true; return true; } } }; //! Filename characters that are not valid - depending on the platform (Windows, Posix) static QString s_InvalidFilenameCharacters; //! Return 'true' when: //! - the specified filename contains characters not accepted by the file system (see s_InvalidFilenameCharacters) //! - filename starts or ends in space characters //! //! This test is not exhaustive but just excluding the most common problems. static bool IsIllegalFilename(const QString &fullFilename) { QFileInfo fi(fullFilename); auto filename = fi.fileName(); for (const auto &ch : qAsConst(s_InvalidFilenameCharacters)) { if (filename.contains(ch)) { return true; } } - if (filename.startsWith(' ' || filename.endsWith(' '))) + if (filename.startsWith(' ') || filename.endsWith(' ')) { return true; } return false; } }; // Impl #if defined(_WIN32) || defined(_WIN64) QString QmitkIOUtil::Impl::s_InvalidFilenameCharacters = "<>:\"/\\|?*"; #else QString QmitkIOUtil::Impl::s_InvalidFilenameCharacters = "/"; #endif struct MimeTypeComparison { MimeTypeComparison(const std::string &mimeTypeName) : m_Name(mimeTypeName) {} bool operator()(const mitk::MimeType &mimeType) const { return mimeType.GetName() == m_Name; } const std::string m_Name; }; QString QmitkIOUtil::GetFileOpenFilterString() { QString filters; mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); std::vector categories = mimeTypeProvider->GetCategories(); for (std::vector::iterator cat = categories.begin(); cat != categories.end(); ++cat) { QSet filterExtensions; std::vector mimeTypes = mimeTypeProvider->GetMimeTypesForCategory(*cat); for (std::vector::iterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt) { std::vector extensions = mt->GetExtensions(); for (std::vector::iterator ext = extensions.begin(); ext != extensions.end(); ++ext) { filterExtensions << QString::fromStdString(*ext); } } QString filter = QString::fromStdString(*cat) + " ("; foreach (const QString &extension, filterExtensions) { filter += "*." + extension + " "; } filter = filter.replace(filter.size() - 1, 1, ')'); filters += ";;" + filter; } filters.prepend("All (*)"); return filters; } QList QmitkIOUtil::Load(const QStringList &paths, QWidget *parent) { std::vector loadInfos; foreach (const QString &file, paths) { loadInfos.push_back(LoadInfo(file.toLocal8Bit().constData())); } Impl::ReaderOptionsDialogFunctor optionsCallback; std::string errMsg = Load(loadInfos, nullptr, nullptr, &optionsCallback); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg)); mitkThrow() << errMsg; } QList qResult; for (std::vector::const_iterator iter = loadInfos.begin(), iterEnd = loadInfos.end(); iter != iterEnd; ++iter) { for (const auto &elem : iter->m_Output) { qResult << elem; } } return qResult; } mitk::DataStorage::SetOfObjects::Pointer QmitkIOUtil::Load(const QStringList &paths, mitk::DataStorage &storage, QWidget *parent) { std::vector loadInfos; foreach (const QString &file, paths) { loadInfos.push_back(LoadInfo(file.toLocal8Bit().constData())); } mitk::DataStorage::SetOfObjects::Pointer nodeResult = mitk::DataStorage::SetOfObjects::New(); Impl::ReaderOptionsDialogFunctor optionsCallback; std::string errMsg = Load(loadInfos, nodeResult, &storage, &optionsCallback); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg)); } return nodeResult; } QList QmitkIOUtil::Load(const QString &path, QWidget *parent) { QStringList paths; paths << path; return Load(paths, parent); } mitk::DataStorage::SetOfObjects::Pointer QmitkIOUtil::Load(const QString &path, mitk::DataStorage &storage, QWidget *parent) { QStringList paths; paths << path; return Load(paths, storage, parent); } QString QmitkIOUtil::Save(const mitk::BaseData *data, const QString &defaultBaseName, const QString &defaultPath, QWidget *parent, bool setPathProperty) { std::vector dataVector; dataVector.push_back(data); QStringList defaultBaseNames; defaultBaseNames.push_back(defaultBaseName); return Save(dataVector, defaultBaseNames, defaultPath, parent, setPathProperty).back(); } QStringList QmitkIOUtil::Save(const std::vector &data, const QStringList &defaultBaseNames, const QString &defaultPath, QWidget *parent, bool setPathProperty) { QStringList fileNames; QString currentPath = defaultPath; std::vector saveInfos; int counter = 0; for (std::vector::const_iterator dataIter = data.begin(), dataIterEnd = data.end(); dataIter != dataIterEnd; ++dataIter, ++counter) { SaveInfo saveInfo(*dataIter, mitk::MimeType(), std::string()); SaveFilter filters(saveInfo); // If there is only the "__all__" filter string, it means there is no writer for this base data if (filters.Size() < 2) { QMessageBox::warning( parent, "Saving not possible", QString("No writer available for type \"%1\"").arg(QString::fromStdString((*dataIter)->GetNameOfClass()))); continue; } // Construct a default path and file name QString filterString = filters.ToString(); QString selectedFilter = filters.GetDefaultFilter(); QString fileName = currentPath; QString dialogTitle = "Save " + QString::fromStdString((*dataIter)->GetNameOfClass()); if (counter < defaultBaseNames.size()) { dialogTitle += " \"" + defaultBaseNames[counter] + "\""; fileName += QDir::separator() + defaultBaseNames[counter]; // We do not append an extension to the file name by default. The extension // is chosen by the user by either selecting a filter or writing the // extension in the file name himself (in the file save dialog). /* QString defaultExt = filters.GetDefaultExtension(); if (!defaultExt.isEmpty()) { fileName += "." + defaultExt; } */ } // Ask the user for a file name QString nextName = QFileDialog::getSaveFileName(parent, dialogTitle, fileName, filterString, &selectedFilter); if (Impl::IsIllegalFilename(nextName)) { QMessageBox::warning( parent, "Saving not possible", QString("File \"%2\" contains invalid characters.\n\nPlease avoid any of \"%1\"") .arg(Impl::s_InvalidFilenameCharacters.split("", QString::SkipEmptyParts).join(" ")) .arg(nextName)); continue; } if (nextName.isEmpty()) { // We stop asking for further file names, but we still save the // data where the user already confirmed the save dialog. break; } fileName = nextName; std::string stdFileName = fileName.toLocal8Bit().constData(); QFileInfo fileInfo(fileName); currentPath = fileInfo.absolutePath(); QString suffix = fileInfo.completeSuffix(); mitk::MimeType filterMimeType = filters.GetMimeTypeForFilter(selectedFilter); mitk::MimeType selectedMimeType; if (fileInfo.exists() && !fileInfo.isFile()) { QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName)); continue; } // Theoretically, the user could have entered an extension that does not match the selected filter // The extension then has prioritry over the filter // Check if one of the available mime-types match the filename std::vector filterMimeTypes = filters.GetMimeTypes(); for (std::vector::const_iterator mimeTypeIter = filterMimeTypes.begin(), mimeTypeIterEnd = filterMimeTypes.end(); mimeTypeIter != mimeTypeIterEnd; ++mimeTypeIter) { if (mimeTypeIter->MatchesExtension(stdFileName)) { selectedMimeType = *mimeTypeIter; break; } } if (!selectedMimeType.IsValid()) { // The file name either does not contain an extension or the // extension is unknown. // If the file already exists, we stop here because we are unable // to (over)write the file without adding a custom suffix. If the file // does not exist, we add the default extension from the currently // selected filter. If the "All" filter was selected, we only add the // default extensions if the file name itself does not already contain // an extension. if (!fileInfo.exists()) { if (filterMimeType == SaveFilter::ALL_MIMETYPE()) { if (suffix.isEmpty()) { // Use the highest ranked mime-type from the list selectedMimeType = filters.GetDefaultMimeType(); } } else { selectedMimeType = filterMimeType; } if (selectedMimeType.IsValid()) { suffix = QString::fromStdString(selectedMimeType.GetExtensions().front()); fileName += "." + suffix; stdFileName = fileName.toLocal8Bit().constData(); // We changed the file name (added a suffix) so ask in case // the file aready exists. fileInfo = QFileInfo(fileName); if (fileInfo.exists()) { if (!fileInfo.isFile()) { QMessageBox::warning( parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName)); continue; } if (QMessageBox::question( parent, "Replace File", QString("A file named \"%1\" already exists. Do you want to replace it?").arg(fileName)) == QMessageBox::No) { continue; } } } } } if (!selectedMimeType.IsValid()) { // The extension/filename is not valid (no mime-type found), bail out QMessageBox::warning( parent, "Saving not possible", QString("No mime-type available which can handle \"%1\".").arg(fileName)); continue; } if (!QFileInfo(fileInfo.absolutePath()).isWritable()) { QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not writable").arg(fileName)); continue; } fileNames.push_back(fileName); saveInfo.m_Path = stdFileName; saveInfo.m_MimeType = selectedMimeType; // pre-select the best writer for the chosen mime-type saveInfo.m_WriterSelector.Select(selectedMimeType.GetName()); saveInfos.push_back(saveInfo); } if (!saveInfos.empty()) { Impl::WriterOptionsDialogFunctor optionsCallback; std::string errMsg = Save(saveInfos, &optionsCallback, setPathProperty); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error writing files", QString::fromStdString(errMsg)); mitkThrow() << errMsg; } } return fileNames; } void QmitkIOUtil::SaveBaseDataWithDialog(mitk::BaseData *data, std::string fileName, QWidget * /*parent*/) { Save(data, fileName); } void QmitkIOUtil::SaveSurfaceWithDialog(mitk::Surface::Pointer surface, std::string fileName, QWidget * /*parent*/) { Save(surface, fileName); } void QmitkIOUtil::SaveImageWithDialog(mitk::Image::Pointer image, std::string fileName, QWidget * /*parent*/) { Save(image, fileName); } void QmitkIOUtil::SavePointSetWithDialog(mitk::PointSet::Pointer pointset, std::string fileName, QWidget * /*parent*/) { Save(pointset, fileName); } struct QmitkIOUtil::SaveFilter::Impl { Impl(const mitk::IOUtil::SaveInfo &saveInfo) : m_SaveInfo(saveInfo) { // Add an artifical filter for "All" m_MimeTypes.push_back(ALL_MIMETYPE()); m_FilterStrings.push_back("All (*.*)"); // Get all writers and their mime types for the given base data type // (this is sorted already) std::vector mimeTypes = saveInfo.m_WriterSelector.GetMimeTypes(); for (std::vector::const_reverse_iterator iter = mimeTypes.rbegin(), iterEnd = mimeTypes.rend(); iter != iterEnd; ++iter) { QList filterExtensions; mitk::MimeType mimeType = *iter; std::vector extensions = mimeType.GetExtensions(); for (auto &extension : extensions) { filterExtensions << QString::fromStdString(extension); } if (m_DefaultExtension.isEmpty()) { m_DefaultExtension = QString::fromStdString(extensions.front()); } QString filter = QString::fromStdString(mimeType.GetComment()) + " ("; foreach (const QString &extension, filterExtensions) { filter += "*." + extension + " "; } filter = filter.replace(filter.size() - 1, 1, ')'); m_MimeTypes.push_back(mimeType); m_FilterStrings.push_back(filter); } } const mitk::IOUtil::SaveInfo m_SaveInfo; std::vector m_MimeTypes; QStringList m_FilterStrings; QString m_DefaultExtension; }; mitk::MimeType QmitkIOUtil::SaveFilter::ALL_MIMETYPE() { static mitk::CustomMimeType allMimeType(std::string("__all__")); return mitk::MimeType(allMimeType, -1, -1); } QmitkIOUtil::SaveFilter::SaveFilter(const QmitkIOUtil::SaveFilter &other) : d(new Impl(*other.d)) { } QmitkIOUtil::SaveFilter::SaveFilter(const SaveInfo &saveInfo) : d(new Impl(saveInfo)) { } QmitkIOUtil::SaveFilter &QmitkIOUtil::SaveFilter::operator=(const QmitkIOUtil::SaveFilter &other) { d.reset(new Impl(*other.d)); return *this; } std::vector QmitkIOUtil::SaveFilter::GetMimeTypes() const { return d->m_MimeTypes; } QString QmitkIOUtil::SaveFilter::GetFilterForMimeType(const std::string &mimeType) const { std::vector::const_iterator iter = std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType)); if (iter == d->m_MimeTypes.end()) { return QString(); } int index = static_cast(iter - d->m_MimeTypes.begin()); if (index < 0 || index >= d->m_FilterStrings.size()) { return QString(); } return d->m_FilterStrings[index]; } mitk::MimeType QmitkIOUtil::SaveFilter::GetMimeTypeForFilter(const QString &filter) const { int index = d->m_FilterStrings.indexOf(filter); if (index < 0) { return mitk::MimeType(); } return d->m_MimeTypes[index]; } QString QmitkIOUtil::SaveFilter::GetDefaultFilter() const { if (d->m_FilterStrings.size() > 1) { return d->m_FilterStrings.at(1); } else if (d->m_FilterStrings.size() > 0) { return d->m_FilterStrings.front(); } return QString(); } QString QmitkIOUtil::SaveFilter::GetDefaultExtension() const { return d->m_DefaultExtension; } mitk::MimeType QmitkIOUtil::SaveFilter::GetDefaultMimeType() const { if (d->m_MimeTypes.size() > 1) { return d->m_MimeTypes.at(1); } else if (d->m_MimeTypes.size() > 0) { return d->m_MimeTypes.front(); } return mitk::MimeType(); } QString QmitkIOUtil::SaveFilter::ToString() const { return d->m_FilterStrings.join(";;"); } int QmitkIOUtil::SaveFilter::Size() const { return d->m_FilterStrings.size(); } bool QmitkIOUtil::SaveFilter::IsEmpty() const { return d->m_FilterStrings.isEmpty(); } bool QmitkIOUtil::SaveFilter::ContainsMimeType(const std::string &mimeType) { return std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType)) != d->m_MimeTypes.end(); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp index 04045da5b0..96b441e343 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp @@ -1,541 +1,541 @@ /*============================================================================ 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 "QmitkMultiWidgetLayoutManager.h" #include #include #include #include // qt #include #include QmitkMultiWidgetLayoutManager::QmitkMultiWidgetLayoutManager(QmitkAbstractMultiWidget* multiwidget) : QObject(multiwidget) , m_MultiWidget(multiwidget) , m_CurrentRenderWindowWidget(nullptr) { // nothing here } void QmitkMultiWidgetLayoutManager::SetLayoutDesign(LayoutDesign layoutDesign) { if (nullptr == m_MultiWidget) { return; } // retrieve the render window name from the sending render window auto renderWindow = dynamic_cast(QObject::sender()); m_CurrentRenderWindowWidget = m_MultiWidget->GetRenderWindowWidget(renderWindow).get(); switch (layoutDesign) { case LayoutDesign::DEFAULT: { SetDefaultLayout(); break; } case LayoutDesign::ALL_2D_TOP_3D_BOTTOM: { SetAll2DTop3DBottomLayout(); break; } case LayoutDesign::ALL_2D_LEFT_3D_RIGHT: { SetAll2DLeft3DRightLayout(); break; } case LayoutDesign::ONE_BIG: { SetOneBigLayout(); break; } case LayoutDesign::ONLY_2D_HORIZONTAL: { SetOnly2DHorizontalLayout(); break; } case LayoutDesign::ONLY_2D_VERTICAL: { SetOnly2DVerticalLayout(); break; } case LayoutDesign::ONE_TOP_3D_BOTTOM: { SetOneTop3DBottomLayout(); break; } case LayoutDesign::ONE_LEFT_3D_RIGHT: { SetOneLeft3DRightLayout(); break; } case LayoutDesign::ALL_HORIZONTAL: { SetAllHorizontalLayout(); break; } case LayoutDesign::ALL_VERTICAL: { SetAllVerticalLayout(); break; } case LayoutDesign::REMOVE_ONE: { RemoveOneLayout(); break; } case LayoutDesign::NONE: { break; } }; } void QmitkMultiWidgetLayoutManager::SetCurrentRenderWindowWidget(QmitkRenderWindowWidget* renderWindowWidget) { m_CurrentRenderWindowWidget = renderWindowWidget; } void QmitkMultiWidgetLayoutManager::SetDefaultLayout() { - MITK_INFO << "Set default layout" << std::endl; + MITK_DEBUG << "Set default layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSizeRow; for (int row = 0; row < m_MultiWidget->GetRowCount(); ++row) { splitterSizeRow.push_back(1000); QList splitterSizeColumn; auto splitter = new QSplitter(mainSplit); for (int column = 0; column < m_MultiWidget->GetColumnCount(); ++column) { splitterSizeColumn.push_back(1000); auto renderWindowWidget = m_MultiWidget->GetRenderWindowWidget(row, column); splitter->addWidget(renderWindowWidget.get()); renderWindowWidget->show(); } splitter->setSizes(splitterSizeColumn); } mainSplit->setSizes(splitterSizeRow); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::DEFAULT); } } void QmitkMultiWidgetLayoutManager::SetAll2DTop3DBottomLayout() { - MITK_INFO << "Set all 2D top and 3D bottom layout" << std::endl; + MITK_DEBUG << "Set all 2D top and 3D bottom layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); auto subSplit2D = new QSplitter(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { subSplit2D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit2D->setSizes(splitterSize); auto subSplit3D = new QSplitter(mainSplit); splitterSize.clear(); auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(600); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_2D_TOP_3D_BOTTOM); } } void QmitkMultiWidgetLayoutManager::SetAll2DLeft3DRightLayout() { - MITK_INFO << "Set all 2D left and 3D right layout" << std::endl; + MITK_DEBUG << "Set all 2D left and 3D right layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); auto subSplit2D = new QSplitter(Qt::Vertical, mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { subSplit2D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit2D->setSizes(splitterSize); auto subSplit3D = new QSplitter(mainSplit); splitterSize.clear(); auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(600); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_2D_LEFT_3D_RIGHT); } } void QmitkMultiWidgetLayoutManager::SetOneBigLayout() { - MITK_INFO << "Set single 2D layout" << std::endl; + MITK_DEBUG << "Set single 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_BIG); } } void QmitkMultiWidgetLayoutManager::SetOnly2DHorizontalLayout() { - MITK_INFO << "Set only 2D layout" << std::endl; + MITK_DEBUG << "Set only 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONLY_2D_HORIZONTAL); } } void QmitkMultiWidgetLayoutManager::SetOnly2DVerticalLayout() { - MITK_INFO << "Set only 2D layout" << std::endl; + MITK_DEBUG << "Set only 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONLY_2D_VERTICAL); } } void QmitkMultiWidgetLayoutManager::SetOneTop3DBottomLayout() { - MITK_INFO << "Set one top and all 3D bottom layout" << std::endl; + MITK_DEBUG << "Set one top and all 3D bottom layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); auto subSplit3D = new QSplitter(mainSplit); QList splitterSize; auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(1000); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_TOP_3D_BOTTOM); } } void QmitkMultiWidgetLayoutManager::SetOneLeft3DRightLayout() { - MITK_INFO << "Set one left and all 3D right layout" << std::endl; + MITK_DEBUG << "Set one left and all 3D right layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); auto subSplit3D = new QSplitter(Qt::Vertical, mainSplit); QList splitterSize; auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(1000); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_LEFT_3D_RIGHT); } } void QmitkMultiWidgetLayoutManager::SetAllHorizontalLayout() { - MITK_INFO << "Set default layout" << std::endl; + MITK_DEBUG << "Set all horizontal layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto allRenderWindowWidgets = m_MultiWidget->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : allRenderWindowWidgets) { if (nullptr != renderWindowWidget.second) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } } // set size for main splitter mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_HORIZONTAL); } } void QmitkMultiWidgetLayoutManager::SetAllVerticalLayout() { - MITK_INFO << "Set all vertical" << std::endl; + MITK_DEBUG << "Set all vertical layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto allRenderWindowWidgets = m_MultiWidget->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : allRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } // set size for splitter mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_VERTICAL); } } void QmitkMultiWidgetLayoutManager::RemoveOneLayout() { - MITK_INFO << "Remove single render window" << std::endl; + MITK_DEBUG << "Remove single render window"; m_MultiWidget->ActivateMenuWidget(false); m_CurrentRenderWindowWidget->hide(); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::NONE); } } diff --git a/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp b/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp index bd279a298a..62f41a8e2b 100644 --- a/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp +++ b/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp @@ -1,175 +1,218 @@ /*============================================================================ 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 "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include +#include QmitkNodeDescriptorManager* QmitkNodeDescriptorManager::GetInstance() { static QmitkNodeDescriptorManager instance; return &instance; } void QmitkNodeDescriptorManager::Initialize() { auto isImage = mitk::NodePredicateDataType::New("Image"); - AddDescriptor(new QmitkNodeDescriptor(tr("Image"), QString(":/Qmitk/Images_48.png"), isImage, this)); + AddDescriptor(new QmitkNodeDescriptor("Image", ":/Qmitk/Images_48.png", isImage, this)); auto isMultiComponentImage = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateProperty::New("Image.Displayed Component")); - AddDescriptor(new QmitkNodeDescriptor(tr("MultiComponentImage"), QString(": / Qmitk / Images_48.png"), isMultiComponentImage, this)); + AddDescriptor(new QmitkNodeDescriptor("MultiComponentImage", ":/Qmitk/Images_48.png", isMultiComponentImage, this)); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isBinaryImage = mitk::NodePredicateAnd::New(isBinary, isImage); - AddDescriptor(new QmitkNodeDescriptor(tr("ImageMask"), QString(":/Qmitk/Binaerbilder_48.png"), isBinaryImage, this)); + AddDescriptor(new QmitkNodeDescriptor("ImageMask", ":/Qmitk/Binaerbilder_48.png", isBinaryImage, this)); auto isLabelSetImage = mitk::NodePredicateDataType::New("LabelSetImage"); - AddDescriptor(new QmitkNodeDescriptor(tr("LabelSetImage"), QString(":/Qmitk/LabelSetImage_48.png"), isLabelSetImage, this)); + AddDescriptor(new QmitkNodeDescriptor("LabelSetImage", ":/Qmitk/LabelSetImage_48.png", isLabelSetImage, this)); auto segmentationTaskListIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/SegmentationTaskListIcon.svg")); auto isSegmentationTaskList = mitk::NodePredicateDataType::New("SegmentationTaskList"); AddDescriptor(new QmitkNodeDescriptor("SegmentationTaskList", segmentationTaskListIcon, isSegmentationTaskList, this)); + auto roiIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/ROIIcon.svg")); + auto isROI = mitk::NodePredicateDataType::New("ROI"); + AddDescriptor(new QmitkNodeDescriptor("ROI", roiIcon, isROI, this)); + + auto geometryDataIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/GeometryDataIcon.svg")); + auto isGeometryData = mitk::NodePredicateDataType::New("GeometryData"); + AddDescriptor(new QmitkNodeDescriptor("GeometryData", geometryDataIcon, isGeometryData, this)); + auto isPointSet = mitk::NodePredicateDataType::New("PointSet"); - AddDescriptor(new QmitkNodeDescriptor(tr("PointSet"), QString(":/Qmitk/PointSet_48.png"), isPointSet, this)); + AddDescriptor(new QmitkNodeDescriptor("PointSet", ":/Qmitk/PointSet_48.png", isPointSet, this)); auto isSurface = mitk::NodePredicateDataType::New("Surface"); - AddDescriptor(new QmitkNodeDescriptor(tr("Surface"), QString(":/Qmitk/Surface_48.png"), isSurface, this)); + AddDescriptor(new QmitkNodeDescriptor("Surface", ":/Qmitk/Surface_48.png", isSurface, this)); auto isNotBinary = mitk::NodePredicateNot::New(isBinary); auto isNoneBinaryImage = mitk::NodePredicateAnd::New(isImage, isNotBinary); - AddDescriptor(new QmitkNodeDescriptor(tr("NoneBinaryImage"), QString(":/Qmitk/Images_48.png"), isNoneBinaryImage, this)); + AddDescriptor(new QmitkNodeDescriptor("NoneBinaryImage", ":/Qmitk/Images_48.png", isNoneBinaryImage, this)); + + auto isPlanarLine = mitk::NodePredicateDataType::New("PlanarLine"); + AddDescriptor(new QmitkNodeDescriptor("PlanarLine", ":/Qmitk/PlanarLine_48.png", isPlanarLine, this)); + + auto isPlanarCircle = mitk::NodePredicateDataType::New("PlanarCircle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarCircle", ":/Qmitk/PlanarCircle_48.png", isPlanarCircle, this)); + + auto isPlanarEllipse = mitk::NodePredicateDataType::New("PlanarEllipse"); + AddDescriptor(new QmitkNodeDescriptor("PlanarEllipse", ":/Qmitk/PlanarEllipse_48.png", isPlanarEllipse, this)); + + auto isPlanarAngle = mitk::NodePredicateDataType::New("PlanarAngle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarAngle", ":/Qmitk/PlanarAngle_48.png", isPlanarAngle, this)); + + auto isPlanarFourPointAngle = mitk::NodePredicateDataType::New("PlanarFourPointAngle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarFourPointAngle", ":/Qmitk/PlanarFourPointAngle_48.png", isPlanarFourPointAngle, this)); + + auto isPlanarRectangle = mitk::NodePredicateDataType::New("PlanarRectangle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarRectangle", ":/Qmitk/PlanarRectangle_48.png", isPlanarRectangle, this)); + + auto isPlanarPolygon = mitk::NodePredicateDataType::New("PlanarPolygon"); + AddDescriptor(new QmitkNodeDescriptor("PlanarPolygon", ":/Qmitk/PlanarPolygon_48.png", isPlanarPolygon, this)); + + auto isNotClosedPolygon = mitk::NodePredicateProperty::New("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); + auto isPlanarPath = mitk::NodePredicateAnd::New(isNotClosedPolygon, isPlanarPolygon); + AddDescriptor(new QmitkNodeDescriptor("PlanarPath", ":/Qmitk/PlanarPath_48.png", isPlanarPath, this)); + + auto isPlanarDoubleEllipse = mitk::NodePredicateDataType::New("PlanarDoubleEllipse"); + AddDescriptor(new QmitkNodeDescriptor("PlanarDoubleEllipse", ":/Qmitk/PlanarDoubleEllipse_48.png", isPlanarDoubleEllipse, this)); + + auto isPlanarBezierCurve = mitk::NodePredicateDataType::New("PlanarBezierCurve"); + AddDescriptor(new QmitkNodeDescriptor("PlanarBezierCurve", ":/Qmitk/PlanarBezierCurve_48.png", isPlanarBezierCurve, this)); + + auto isPlanarSubdivisionPolygon = mitk::NodePredicateDataType::New("PlanarSubdivisionPolygon"); + AddDescriptor(new QmitkNodeDescriptor("PlanarSubdivisionPolygon", ":/Qmitk/PlanarSubdivisionPolygon_48.png", isPlanarSubdivisionPolygon, this)); } void QmitkNodeDescriptorManager::AddDescriptor(QmitkNodeDescriptor* descriptor) { descriptor->setParent(this); m_NodeDescriptors.push_back(descriptor); } void QmitkNodeDescriptorManager::RemoveDescriptor(QmitkNodeDescriptor* descriptor) { int index = m_NodeDescriptors.indexOf(descriptor); if (index != -1) { m_NodeDescriptors.removeAt(index); descriptor->setParent(nullptr); delete descriptor; } } QmitkNodeDescriptor* QmitkNodeDescriptorManager::GetDescriptor(const mitk::DataNode* node) const { QmitkNodeDescriptor* descriptor = m_UnknownDataNodeDescriptor; for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) descriptor = *it; } return descriptor; } QmitkNodeDescriptor* QmitkNodeDescriptorManager::GetDescriptor(const QString& className) const { QmitkNodeDescriptor* descriptor = nullptr; if (className == "Unknown") { return m_UnknownDataNodeDescriptor; } else { for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->GetNameOfClass() == className) descriptor = *it; } } return descriptor; } QList QmitkNodeDescriptorManager::GetActions(const mitk::DataNode* node) const { QList actions = m_UnknownDataNodeDescriptor->GetBatchActions(); actions.append(m_UnknownDataNodeDescriptor->GetActions()); QmitkNodeDescriptor* lastDescriptor = m_UnknownDataNodeDescriptor; for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) { actions.append(lastDescriptor->GetSeparator()); lastDescriptor = *it; actions.append(lastDescriptor->GetBatchActions()); actions.append(lastDescriptor->GetActions()); } } return actions; } QList QmitkNodeDescriptorManager::GetActions(const QList& nodes) const { QList actions = m_UnknownDataNodeDescriptor->GetBatchActions(); QmitkNodeDescriptor* lastDescriptor = m_UnknownDataNodeDescriptor; // find all descriptors for the nodes (unique) QSet nodeDescriptors; for (const auto& node : nodes) { for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) { nodeDescriptors.insert(*it); } } } // add all actions for the found descriptors for (const auto& nodeDescriptor : nodeDescriptors) { actions.append(lastDescriptor->GetSeparator()); lastDescriptor = nodeDescriptor; actions.append(lastDescriptor->GetBatchActions()); } return actions; } QmitkNodeDescriptorManager::QmitkNodeDescriptorManager() : m_UnknownDataNodeDescriptor(new QmitkNodeDescriptor("Unknown", QString(":/Qmitk/DataTypeUnknown_48.png"), nullptr, this)) { Initialize(); } QmitkNodeDescriptorManager::~QmitkNodeDescriptorManager() { // delete m_UnknownDataNodeDescriptor; // qDeleteAll(m_NodeDescriptors); } QmitkNodeDescriptor *QmitkNodeDescriptorManager::GetUnknownDataNodeDescriptor() const { return m_UnknownDataNodeDescriptor; } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp index 83ba291fa5..4a950fb941 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp @@ -1,183 +1,185 @@ /*============================================================================ 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 "QmitkRenderWindowUtilityWidget.h" #include // mitk core #include #include #include #include // mitk qt widgets #include #include // itk #include QmitkRenderWindowUtilityWidget::QmitkRenderWindowUtilityWidget( QWidget* parent/* = nullptr */, QmitkRenderWindow* renderWindow/* = nullptr */, mitk::DataStorage* dataStorage/* = nullptr */) : m_NodeSelectionWidget(nullptr) , m_SliceNavigationWidget(nullptr) , m_StepperAdapter(nullptr) , m_ViewDirectionSelector(nullptr) { this->setParent(parent); auto layout = new QHBoxLayout(this); layout->setMargin(0); mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_BaseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow()); m_NodeSelectionWidget = new QmitkSynchronizedNodeSelectionWidget(parent); m_NodeSelectionWidget->SetBaseRenderer(m_BaseRenderer); m_NodeSelectionWidget->SetDataStorage(dataStorage); m_NodeSelectionWidget->SetNodePredicate(noHelperObjects); auto menuBar = new QMenuBar(this); menuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + menuBar->setNativeMenuBar(false); auto dataMenu = menuBar->addMenu("Data"); QWidgetAction* dataAction = new QWidgetAction(dataMenu); dataAction->setDefaultWidget(m_NodeSelectionWidget); dataMenu->addAction(dataAction); layout->addWidget(menuBar); auto* synchPushButton = new QPushButton(this); auto* synchIcon = new QIcon(); auto synchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto desynchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); synchIcon->addPixmap(synchronizeSvg.pixmap(64), QIcon::Normal, QIcon::On); synchIcon->addPixmap(desynchronizeSvg.pixmap(64), QIcon::Normal, QIcon::Off); synchPushButton->setIcon(*synchIcon); synchPushButton->setToolTip("Synchronize / desynchronize data management"); synchPushButton->setCheckable(true); synchPushButton->setChecked(true); connect(synchPushButton, &QPushButton::clicked, this, &QmitkRenderWindowUtilityWidget::ToggleSynchronization); layout->addWidget(synchPushButton); auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); m_SliceNavigationWidget = new QmitkSliceNavigationWidget(this); m_StepperAdapter = new QmitkStepperAdapter(m_SliceNavigationWidget, sliceNavigationController->GetStepper()); layout->addWidget(m_SliceNavigationWidget); mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ m_BaseRenderer }; m_RenderWindowViewDirectionController = std::make_unique(); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); m_ViewDirectionSelector = new QComboBox(this); QStringList viewDirections{ "axial", "coronal", "sagittal"}; m_ViewDirectionSelector->insertItems(0, viewDirections); + m_ViewDirectionSelector->setMinimumContentsLength(12); connect(m_ViewDirectionSelector, &QComboBox::currentTextChanged, this, &QmitkRenderWindowUtilityWidget::ChangeViewDirection); UpdateViewPlaneSelection(); layout->addWidget(m_ViewDirectionSelector); // finally add observer, after all relevant objects have been created / initialized sliceNavigationController->ConnectGeometrySendEvent(this); } QmitkRenderWindowUtilityWidget::~QmitkRenderWindowUtilityWidget() { } void QmitkRenderWindowUtilityWidget::ToggleSynchronization(bool synchronized) { m_NodeSelectionWidget->SetSynchronized(synchronized); emit SynchronizationToggled(m_NodeSelectionWidget); } void QmitkRenderWindowUtilityWidget::SetGeometry(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) { return; } const auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); auto viewDirection = sliceNavigationController->GetViewDirection(); unsigned int axis = 0; switch (viewDirection) { case mitk::AnatomicalPlane::Original: return; case mitk::AnatomicalPlane::Axial: { axis = 2; break; } case mitk::AnatomicalPlane::Coronal: { axis = 1; break; } case mitk::AnatomicalPlane::Sagittal: { axis = 0; break; } } const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); const mitk::BaseGeometry* rendererGeometry = m_BaseRenderer->GetCurrentWorldGeometry(); mitk::TimeStepType timeStep = sliceNavigationController->GetStepper()->GetPos(); mitk::BaseGeometry::ConstPointer geometry = inputTimeGeometry->GetGeometryForTimeStep(timeStep); if (geometry == nullptr) return; mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); matrix.GetVnlMatrix().normalize_columns(); mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); int dominantAxis = itk::Function::Max3(inverseMatrix[0][axis], inverseMatrix[1][axis], inverseMatrix[2][axis]); bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][axis] < 0; bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[axis] < 0; m_SliceNavigationWidget->SetInverseDirection(referenceGeometryAxisInverted != rendererZAxisInverted); } void QmitkRenderWindowUtilityWidget::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString()); } void QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection() { const auto sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); const auto viewDirection = sliceNavigationController->GetDefaultViewDirection(); switch (viewDirection) { case mitk::AnatomicalPlane::Axial: m_ViewDirectionSelector->setCurrentIndex(0); break; case mitk::AnatomicalPlane::Coronal: m_ViewDirectionSelector->setCurrentIndex(1); break; case mitk::AnatomicalPlane::Sagittal: m_ViewDirectionSelector->setCurrentIndex(2); break; default: break; } } diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp index ac64be0664..aae35e9ac6 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -1,701 +1,709 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // mitk qt widgets module #include #include #include #include // mitk core module #include #include #include #include QmitkSynchronizedNodeSelectionWidget::QmitkSynchronizedNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_StorageModel = std::make_unique(this); m_Controls.tableView->setModel(m_StorageModel.get()); m_Controls.tableView->horizontalHeader()->setVisible(false); m_Controls.tableView->verticalHeader()->setVisible(false); m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); this->SetUpConnections(); this->Initialize(); } QmitkSynchronizedNodeSelectionWidget::~QmitkSynchronizedNodeSelectionWidget() { + bool isSynchronized = this->IsSynchronized(); + if (isSynchronized) + { + emit DeregisterSynchronization(); + return; + } + auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } - bool isSynchronized = this->IsSynchronized(); - if (!isSynchronized) + // If the model is not synchronized, + // we know that renderer-specific properties exist for all nodes. + // These properties need to be removed from the nodes. + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) { - // If the model is not synchronizes, - // we know that renderer-specific properties exist for all nodes. - // These properties need to be removed from the nodes. - auto allNodes = dataStorage->GetAll(); - for (auto& node : *allNodes) - { - // Delete the relevant renderer-specific properties for the node using the current base renderer. - mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); - } + // Delete the relevant renderer-specific properties for the node using the current base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } void QmitkSynchronizedNodeSelectionWidget::SetBaseRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { // no need to do something return; } if (nullptr == baseRenderer) { return; } auto oldBaseRenderer = m_BaseRenderer.Lock(); m_BaseRenderer = baseRenderer; auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { // If the model is synchronized, // all nodes use global / default properties. // No renderer-specific property lists should exist // so there is no need to transfer any property values. } else { // If the model is not synchronized, // we know that renderer-specific properties exist for all nodes. // These properties need to be removed from the nodes and // we need to transfer their values to new renderer-specific properties. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // Set the relevant renderer-specific properties for the node using the new base renderer. // By transferring the values from the old property list, // the same property-state is kept when switching to another base renderer. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, oldBaseRenderer); // Delete the relevant renderer-specific properties for the node using the old base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, oldBaseRenderer); } } this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::SetSelectAll(bool selectAll) { if (selectAll == m_Controls.selectionModeCheckBox->isChecked()) { // no need to do something return; } m_Controls.selectionModeCheckBox->setChecked(selectAll); } bool QmitkSynchronizedNodeSelectionWidget::GetSelectAll() const { return m_Controls.selectionModeCheckBox->isChecked(); } void QmitkSynchronizedNodeSelectionWidget::SetSynchronized(bool synchronize) { if (synchronize == this->IsSynchronized()) { // no need to do something return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } if (synchronize) { // set the base renderer of the model to nullptr, such that global properties are used m_StorageModel->SetCurrentRenderer(nullptr); // If the model is synchronized, // we know that the model was not synchronized before. // That means that all nodes use renderer-specific properties, // but now all nodes need global properties. // Thus we need to remove the renderer-specific properties of all nodes of the // datastorage. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // For helper / hidden nodes: // If the node predicate does not match, do not remove the renderer-specific property // This is relevant for the crosshair data nodes, which are only visible inside their // corresponding render window. if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { // Delete the relevant renderer-specific properties for the node using the current base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } } else { // set the base renderer of the model to current base renderer, such that renderer-specific properties are used m_StorageModel->SetCurrentRenderer(baseRenderer); // If the model is not synchronized anymore, // we know that the model was synchronized before. // That means that all nodes use global / default properties, // but now all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of all nodes of the // datastorage: // - hide those nodes, which are not part of the newly selected nodes. // - keep the property values of those nodes, which are part of the new selection AND // have been selected before auto currentNodeSelection = this->GetCurrentInternalSelection(); auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // check if the node is part of the current selection auto finding = std::find(std::begin(currentNodeSelection), std::end(currentNodeSelection), node); if (finding != std::end(currentNodeSelection)) // node found / part of the current selection { // Set the relevant renderer-specific properties for the node using the curent base renderer. // By transferring the values from the global / default property list, // the same property-state is kept when switching to non-synchronized mode. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, nullptr); } else { // If the node is not part of the selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); } } } // Since the synchronization might lead to a different node order depending on the layer properties, the render window // needs to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } bool QmitkSynchronizedNodeSelectionWidget::IsSynchronized() const { return m_StorageModel->GetCurrentRenderer().IsNull(); } void QmitkSynchronizedNodeSelectionWidget::OnModelUpdated() { m_Controls.tableView->resizeRowsToContents(); m_Controls.tableView->resizeColumnsToContents(); } void QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged(bool selectAll) { emit SelectionModeChanged(selectAll); if (selectAll) { - auto dataStorage = m_DataStorage.Lock(); - if (dataStorage.IsNull()) - { - return; - } - - auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); - NodeList currentSelection; - for (auto& node : *allNodes) - { - currentSelection.append(node); - } - - this->HandleChangeOfInternalSelection(currentSelection); + this->SelectAll(); } } void QmitkSynchronizedNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(m_StorageModel->GetCurrentSelection()); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); m_Controls.changeSelectionButton->setChecked(true); if (dialog->exec()) { m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); auto selectedNodes = dialog->GetSelectedNodes(); this->HandleChangeOfInternalSelection(selectedNodes); } m_Controls.changeSelectionButton->setChecked(false); delete dialog; } void QmitkSynchronizedNodeSelectionWidget::OnTableClicked(const QModelIndex& index) { if (!index.isValid() || m_StorageModel.get() != index.model()) { return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } QVariant dataNodeVariant = index.data(QmitkDataNodeRole); auto dataNode = dataNodeVariant.value(); if (index.column() == 1) // node visibility column { bool visibiliy = index.data(Qt::EditRole).toBool(); m_StorageModel->setData(index, QVariant(!visibiliy), Qt::EditRole); return; } if (index.column() == 2) // reinit node column { this->ReinitNode(dataNode); return; } if (index.column() == 3) // remove node column { this->RemoveFromInternalSelection(dataNode); return; } } void QmitkSynchronizedNodeSelectionWidget::SetUpConnections() { connect(m_StorageModel.get(), &QmitkRenderWindowDataNodeTableModel::ModelUpdated, this, &QmitkSynchronizedNodeSelectionWidget::OnModelUpdated); connect(m_Controls.selectionModeCheckBox, &QCheckBox::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged); connect(m_Controls.changeSelectionButton, &QPushButton::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnEditSelection); connect(m_Controls.tableView, &QTableView::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnTableClicked); } void QmitkSynchronizedNodeSelectionWidget::Initialize() { auto baseRenderer = m_BaseRenderer.Lock(); auto dataStorage = m_DataStorage.Lock(); m_StorageModel->SetDataStorage(dataStorage); m_StorageModel->SetCurrentRenderer(baseRenderer); if (baseRenderer.IsNull() || dataStorage.IsNull()) { m_Controls.selectionModeCheckBox->setEnabled(false); m_Controls.changeSelectionButton->setEnabled(false); // reset the model if no data storage is defined m_StorageModel->removeRows(0, m_StorageModel->rowCount()); return; } // Use the new data storage / node predicate to correctly set the list of // currently selected data nodes for the model. // If a new data storage or node predicate has been defined, // we switch to the "selectAll" mode and synchronize the selection for simplicity. // enable UI m_Controls.selectionModeCheckBox->setEnabled(true); m_Controls.changeSelectionButton->setEnabled(true); m_Controls.selectionModeCheckBox->setChecked(true); // set the base renderer of the model to nullptr, such that global properties are used (synchronized mode) m_StorageModel->SetCurrentRenderer(nullptr); } void QmitkSynchronizedNodeSelectionWidget::UpdateInfo() { } void QmitkSynchronizedNodeSelectionWidget::OnDataStorageChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::OnNodePredicateChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { this->ReviseSynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } else { this->ReviseDesynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } // Since a new selection might have a different rendering tree the render windows // need to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } void QmitkSynchronizedNodeSelectionWidget::OnInternalSelectionChanged() { m_StorageModel->SetCurrentSelection(this->GetCurrentInternalSelection()); } bool QmitkSynchronizedNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const { return this->IsSynchronized(); } void QmitkSynchronizedNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* node) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // For helper / hidden nodes if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { // If the node predicate does not match, do not add the node to the current selection. // Leave the visibility as it is. return; } // The selection mode determines if we want to show all nodes from the data storage // or use a local selected list of nodes. // We need to hide each new incoming data node, if we use a local selection, // since we do not want to show / select newly added nodes immediately. // We need to add the incoming node to our selection, if the selection mode check box // is checked. // We want to add the incoming node to our selection, if the node is a child node // of an already selected node. // Nodes added to the selection will be made visible. if (m_Controls.selectionModeCheckBox->isChecked() || this->IsParentNodeSelected(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // Check if the nodes is already part of the internal selection. // That can happen if another render window already added the new node and sent out the new, updated // selection to be synchronized. auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding != std::end(currentSelection)) // node found { // node already part of the selection return; } currentSelection.append(const_cast(node)); // This function will call 'QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged' // which will take care of the visibility-property for newly added node. this->HandleChangeOfInternalSelection(currentSelection); } else { // If the widget is in "local-selection" state (selectionModeCheckBox unchecked), // the new incoming node needs to be hid. // Here it depends on the synchronization-state which properties need // to be modified. if (this->IsSynchronized()) { // If the node will not be part of the new selection, hide the node. const_cast(node)->SetVisibility(false); } else { // If the widget is not synchronized, all nodes use renderer-specific properties. // Thus we need to modify the renderer-specific properties of the node: // - hide the node, which is not part of the selection this->DeselectNode(const_cast(node)); } } } void QmitkSynchronizedNodeSelectionWidget::OnNodeModified(const itk::Object* caller, const itk::EventObject& event) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (!itk::ModifiedEvent().CheckEvent(&event)) { return; } auto node = dynamic_cast(caller); if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // check if the node to be modified is part of the current selection auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding == std::end(currentSelection)) // node not found { // node not part of the selection return; } // We know that the node is relevant, but we don't know if the node modification was relevant // for the rendering. We just request an update here. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); m_StorageModel->UpdateModelData(); } } void QmitkSynchronizedNodeSelectionWidget::ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global properties of the selection: // - a) show those nodes, which are part of the new selection AND have not been // selected before // - b) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - c) hide those nodes, which are part of the old selection AND // have not been newly selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" property for the global / default property list // and show the node for this renderer. node->SetVisibility(true); // item a) } // else: item b): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, hide the node. node->SetVisibility(false); // item c) } // else: item b): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // If the model is not synchronized, all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of the selection: // - a) set the renderer-specific properties of those nodes, which are part of the new selection AND // have not been selected before (see 'SelectNode') // - b) show those nodes, which are part of the new selection AND have not been // selected before // - c) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - d) hide those nodes, which are part of the old selection AND // have not been newly selected // - e) set the renderer-specific properties of those nodes, which are part of the old selection AND // have not been newly selected, to denote which nodes are selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list // such that the global / default property list values are overwritten mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(node, baseRenderer); // item a) // Explicitly set the visibility to true for selected nodes to show them in the render window. node->SetVisibility(true, baseRenderer); // item b) } // else: item c): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized global property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); // item d) and e) } // else: item c): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReinitNode(const mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto selectedImage = dynamic_cast(dataNode->GetData()); if (nullptr == selectedImage) { return; } auto boundingBoxPredicate = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); if (!boundingBoxPredicate->CheckNode(dataNode)) { return; } mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), selectedImage->GetTimeGeometry()); } void QmitkSynchronizedNodeSelectionWidget::RemoveFromInternalSelection(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (this->IsSynchronized()) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global property of the node. // Explicitly set the visibility to false for unselected nodes to hide them in the render window. dataNode->SetVisibility(false); } m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); this->RemoveNodeFromSelection(dataNode); } bool QmitkSynchronizedNodeSelectionWidget::IsParentNodeSelected(const mitk::DataNode* dataNode) const { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return false; } auto currentSelection = this->GetCurrentInternalSelection(); auto parentNodes = dataStorage->GetSources(dataNode, m_NodePredicate, false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { const mitk::DataNode* parentNode = it->Value(); auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), parentNode); if (finding != std::end(currentSelection)) // parent node found { // at least one parent node is part of the selection return true; } } return false; } void QmitkSynchronizedNodeSelectionWidget::DeselectNode(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (nullptr == dataNode) { return; } if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) { // If the node should not be part of the selection, set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list, // such that the global / default property list values are overwritten. mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, baseRenderer); // Explicitly set the visibility to false for the node to hide them in the render window. dataNode->SetVisibility(false, baseRenderer); } } + +void QmitkSynchronizedNodeSelectionWidget::SelectAll() +{ + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); + NodeList currentSelection; + for (auto& node : *allNodes) + { + currentSelection.append(node); + } + + this->HandleChangeOfInternalSelection(currentSelection); +} diff --git a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp index 8091ac673f..52949b0cea 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp @@ -1,101 +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. ============================================================================*/ // mitk gui qt common plugin #include "QmitkSynchronizedWidgetConnector.h" bool NodeListsEqual(const QmitkSynchronizedWidgetConnector::NodeList& selection1, const QmitkSynchronizedWidgetConnector::NodeList& selection2) { if (selection1.size() != selection2.size()) { return false; } // lambda to compare node pointer inside both lists auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs) { return lhs == rhs; }; return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); } QmitkSynchronizedWidgetConnector::QmitkSynchronizedWidgetConnector() : m_SelectAll(true) + , m_ConnectionCounter(0) { } -void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { connect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelection); connect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); connect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); + + connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + m_ConnectionCounter++; } -void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { disconnect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelection); disconnect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); disconnect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); + + disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + this->DeregisterWidget(); } void QmitkSynchronizedWidgetConnector::SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const { - // widget is newly synchronized / connected so an initial setup needs to be made - nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + // We need to explicitly differentiate when "Select All" is active, since the internal selection + // might not contain all nodes. When no selection widget is synchronized, the m_InternalSelection + // won't be updated. + if (m_SelectAll) + { + nodeSelectionWidget->SelectAll(); + } + else + { + nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + } + nodeSelectionWidget->SetSelectAll(m_SelectAll); } QmitkSynchronizedWidgetConnector::NodeList QmitkSynchronizedWidgetConnector::GetNodeSelection() const { return m_InternalSelection; } bool QmitkSynchronizedWidgetConnector::GetSelectionMode() const { return m_SelectAll; } void QmitkSynchronizedWidgetConnector::ChangeSelection(NodeList nodes) { if (!NodeListsEqual(m_InternalSelection, nodes)) { m_InternalSelection = nodes; emit NodeSelectionChanged(m_InternalSelection); } } void QmitkSynchronizedWidgetConnector::ChangeSelectionMode(bool selectAll) { if (m_SelectAll!= selectAll) { m_SelectAll = selectAll; emit SelectionModeChanged(m_SelectAll); } } + +void QmitkSynchronizedWidgetConnector::DeregisterWidget() +{ + m_ConnectionCounter--; + + // When no more widgets are synchronized anymore, turn on SelectAll to avoid losing + // nodes that are added until synchronization of a widget is turned on again. + if (m_ConnectionCounter == 0) + { + m_SelectAll = true; + } +} diff --git a/Modules/QtWidgetsExt/CMakeLists.txt b/Modules/QtWidgetsExt/CMakeLists.txt index 85fdc156a1..9609183729 100644 --- a/Modules/QtWidgetsExt/CMakeLists.txt +++ b/Modules/QtWidgetsExt/CMakeLists.txt @@ -1,6 +1,18 @@ MITK_CREATE_MODULE( DEPENDS MitkAlgorithmsExt MitkQtWidgets PRIVATE MitkSceneSerializationBase PACKAGE_DEPENDS PUBLIC Qwt CTK|CTKWidgets PRIVATE Qt5|Concurrent+Svg+Xml VTK|IOImage ) + +if (TARGET ${MODULE_TARGET} AND MSVC) + #[[ Compiler warnings/errors because of QList, resp. Qwt 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() diff --git a/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc b/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc index 2e92201b32..32f836a0e5 100644 --- a/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc +++ b/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc @@ -1,41 +1,30 @@ btnAddPointSet.png eraser.svg btnCube.xpm btnCylinder.xpm arrow-down.svg btnEllipsoid.xpm folder-open.svg btnMoveDown.png btnMoveUp.png btnPyramid.xpm btnRemovePoint.png save.svg btnSetPoints.png plus.svg plus-xyz.svg btnSetSeedPoint.xpm btnSwapSets.png arrow-up.svg cross.png icon_seedpoint.png defaultWatermarkSmall.png logo_mint-medical.png ModuleView.png QmitkStandardViewsDialogBar.xpm - PlanarAngle_48.png - PlanarBezierCurve_48.png - PlanarCircle_48.png - PlanarDoubleEllipse_48.png - PlanarEllipse_48.png - PlanarFourPointAngle_48.png - PlanarLine_48.png - PlanarPath_48.png - PlanarPolygon_48.png - PlanarRectangle_48.png - PlanarSubdivisionPolygon_48.png play.xpm stop.xpm diff --git a/Modules/REST/CMakeLists.txt b/Modules/REST/CMakeLists.txt index 09e301a39e..61b6633c19 100644 --- a/Modules/REST/CMakeLists.txt +++ b/Modules/REST/CMakeLists.txt @@ -1,14 +1,26 @@ 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/ROI/CMakeLists.txt b/Modules/ROI/CMakeLists.txt new file mode 100644 index 0000000000..e4ab9d0abe --- /dev/null +++ b/Modules/ROI/CMakeLists.txt @@ -0,0 +1,6 @@ +mitk_create_module( + DEPENDS MitkCore + PACKAGE_DEPENDS VTK|RenderingAnnotation +) + +add_subdirectory(autoload/IO) diff --git a/Modules/ROI/autoload/IO/CMakeLists.txt b/Modules/ROI/autoload/IO/CMakeLists.txt new file mode 100644 index 0000000000..984bfe1eaa --- /dev/null +++ b/Modules/ROI/autoload/IO/CMakeLists.txt @@ -0,0 +1,5 @@ +mitk_create_module(ROIIO + DEPENDS PUBLIC MitkROI MitkSceneSerialization + PACKAGE_DEPENDS PRIVATE nlohmann_json + AUTOLOAD_WITH MitkCore +) diff --git a/Modules/ROI/autoload/IO/files.cmake b/Modules/ROI/autoload/IO/files.cmake new file mode 100644 index 0000000000..cd11c12ddf --- /dev/null +++ b/Modules/ROI/autoload/IO/files.cmake @@ -0,0 +1,13 @@ +set(H_FILES + include/mitkROIIOMimeTypes.h + src/mitkROIIO.h + src/mitkROIIOModuleActivator.h + src/mitkROISerializer.h +) + +set(CPP_FILES + mitkROIIO.cpp + mitkROIIOMimeTypes.cpp + mitkROIIOModuleActivator.cpp + mitkROISerializer.cpp +) diff --git a/Modules/ROI/autoload/IO/include/mitkROIIOMimeTypes.h b/Modules/ROI/autoload/IO/include/mitkROIIOMimeTypes.h new file mode 100644 index 0000000000..75bdad237e --- /dev/null +++ b/Modules/ROI/autoload/IO/include/mitkROIIOMimeTypes.h @@ -0,0 +1,38 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkROIIOMimeTypes_h +#define mitkROIIOMimeTypes_h + +#include +#include + +namespace mitk +{ + namespace MitkROIIOMimeTypes + { + class MITKROIIO_EXPORT MitkROIMimeType : public CustomMimeType + { + public: + MitkROIMimeType(); + + bool AppliesTo(const std::string& path) const override; + MitkROIMimeType* Clone() const override; + }; + + MITKROIIO_EXPORT MitkROIMimeType ROI_MIMETYPE(); + MITKROIIO_EXPORT std::string ROI_MIMETYPE_NAME(); + MITKROIIO_EXPORT std::vector Get(); + } +} + +#endif diff --git a/Modules/ROI/autoload/IO/src/mitkROIIO.cpp b/Modules/ROI/autoload/IO/src/mitkROIIO.cpp new file mode 100644 index 0000000000..ade9c8016d --- /dev/null +++ b/Modules/ROI/autoload/IO/src/mitkROIIO.cpp @@ -0,0 +1,213 @@ +/*============================================================================ + +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 "mitkROIIO.h" +#include +#include +#include + +#include +#include + +namespace +{ + int CheckFileFormat(const nlohmann::json& json) + { + if ("MITK ROI" != json["FileFormat"].get()) + mitkThrow() << "Unknown file format (expected \"MITK ROI\")!"; + + auto version = json["Version"].get(); + + if (1 != version) + mitkThrow() << "Unknown file format version (expected version 1)!"; + + return version; + } + + mitk::Vector3D GetSize(const mitk::BaseGeometry* geometry) + { + auto bounds = geometry->GetBounds(); + + mitk::Vector3D result; + result[0] = bounds[1]; + result[1] = bounds[3]; + result[2] = bounds[5]; + + return result; + } + + void SetSize(mitk::BaseGeometry* geometry, const mitk::Vector3D& size) + { + mitk::BaseGeometry::BoundsArrayType bounds({ 0.0, size[0], 0.0, size[1], 0.0, size[2] }); + geometry->SetBounds(bounds); + } + + mitk::TimeGeometry::Pointer ReadGeometry(const nlohmann::json& jGeometry) + { + auto geometry = mitk::Geometry3D::New(); + geometry->ImageGeometryOn(); + + if (!jGeometry.is_object()) + mitkThrow() << "Geometry is expected to be a JSON object."; + + if (jGeometry.contains("Origin")) + geometry->SetOrigin(jGeometry["Origin"].get()); + + if (jGeometry.contains("Spacing")) + geometry->SetSpacing(jGeometry["Spacing"].get()); + + if (jGeometry.contains("Size")) + SetSize(geometry, jGeometry["Size"].get()); + + auto timeSteps = jGeometry.contains("TimeSteps") + ? jGeometry["TimeSteps"].get() + : 1; + + auto result = mitk::ProportionalTimeGeometry::New(); + result->Initialize(geometry, timeSteps); + + return result; + } + + nlohmann::json WriteGeometry(const mitk::TimeGeometry* timeGeometry) + { + auto geometry = timeGeometry->GetGeometryForTimeStep(0); + + nlohmann::json result = { + { "Origin", geometry->GetOrigin() }, + { "Spacing", geometry->GetSpacing() }, + { "Size", GetSize(geometry) } + }; + + auto timeSteps = timeGeometry->CountTimeSteps(); + + if (timeSteps > 1) + result["TimeSteps"] = timeSteps; + + return result; + } +} + +mitk::ROIIO::ROIIO() + : AbstractFileIO(ROI::GetStaticNameOfClass(), MitkROIIOMimeTypes::ROI_MIMETYPE(), "MITK ROI") +{ + this->RegisterService(); +} + +std::vector mitk::ROIIO::DoRead() +{ + auto *stream = this->GetInputStream(); + std::ifstream fileStream; + + if (nullptr == stream) + { + auto filename = this->GetInputLocation(); + + if (filename.empty() || !std::filesystem::exists(filename)) + mitkThrow() << "Invalid or nonexistent filename: \"" << filename << "\"!"; + + fileStream.open(filename); + + if (!fileStream.is_open()) + mitkThrow() << "Could not open file \"" << filename << "\" for reading!"; + + stream = &fileStream; + } + + auto result = ROI::New(); + + try + { + auto j = nlohmann::json::parse(*stream); + + CheckFileFormat(j); + + auto geometry = ReadGeometry(j["Geometry"]); + result->SetTimeGeometry(geometry); + + if (j.contains("Name")) + result->SetProperty("name", mitk::StringProperty::New(j["Name"].get())); + + if (j.contains("Caption")) + result->SetProperty("caption", mitk::StringProperty::New(j["Caption"].get())); + + for (const auto& roi : j["ROIs"]) + result->AddElement(roi.get()); + } + catch (const nlohmann::json::exception &e) + { + mitkThrow() << e.what(); + } + + return { result }; +} + +void mitk::ROIIO::Write() +{ + auto input = dynamic_cast(this->GetInput()); + + if (input == nullptr) + mitkThrow() << "Invalid input for writing!"; + + if (input->GetNumberOfElements() == 0) + mitkThrow() << "No ROIs found!"; + + auto* stream = this->GetOutputStream(); + std::ofstream fileStream; + + if (stream == nullptr) + { + auto filename = this->GetOutputLocation(); + + if (filename.empty()) + mitkThrow() << "Neither an output stream nor an output filename was specified!"; + + fileStream.open(filename); + + if (!fileStream.is_open()) + mitkThrow() << "Could not open file \"" << filename << "\" for writing!"; + + stream = &fileStream; + } + + if (!stream->good()) + mitkThrow() << "Stream for writing is not good!"; + + nlohmann::ordered_json j = { + { "FileFormat", "MITK ROI" }, + { "Version", 1 } + }; + + if (auto name = input->GetProperty("name"); name.IsNotNull()) + j["Name"] = name->GetValueAsString(); + + j["Geometry"] = WriteGeometry(input->GetTimeGeometry()); + + auto caption = input->GetConstProperty("caption"); + + if (caption.IsNotNull()) + j["Caption"] = caption->GetValueAsString(); + + nlohmann::json rois; + + for (const auto& roi : *input) + rois.push_back(roi.second); + + j["ROIs"] = rois; + + *stream << std::setw(2) << j << std::endl; +} + +mitk::ROIIO* mitk::ROIIO::IOClone() const +{ + return new ROIIO(*this); +} diff --git a/Modules/Core/src/DataManagement/mitkLine.cpp b/Modules/ROI/autoload/IO/src/mitkROIIO.h similarity index 52% rename from Modules/Core/src/DataManagement/mitkLine.cpp rename to Modules/ROI/autoload/IO/src/mitkROIIO.h index 4d6ae904bc..dee6434802 100644 --- a/Modules/Core/src/DataManagement/mitkLine.cpp +++ b/Modules/ROI/autoload/IO/src/mitkROIIO.h @@ -1,13 +1,36 @@ /*============================================================================ 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 "mitkLine.h" +#ifndef mitkROIIO_h +#define mitkROIIO_h + +#include + +namespace mitk +{ + class ROIIO : public AbstractFileIO + { + public: + ROIIO(); + + using AbstractFileReader::Read; + void Write() override; + + protected: + std::vector DoRead() override; + + private: + ROIIO* IOClone() const override; + }; +} + +#endif diff --git a/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp b/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp new file mode 100644 index 0000000000..91b8032fe5 --- /dev/null +++ b/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp @@ -0,0 +1,75 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include + +#include + +mitk::MitkROIIOMimeTypes::MitkROIMimeType::MitkROIMimeType() + : CustomMimeType(ROI_MIMETYPE_NAME()) +{ + this->AddExtension("json"); + this->SetCategory("MITK ROI"); + this->SetComment("MITK ROI"); +} + +bool mitk::MitkROIIOMimeTypes::MitkROIMimeType::AppliesTo(const std::string& path) const +{ + bool result = CustomMimeType::AppliesTo(path); + + if (!std::filesystem::exists(path)) // T18572 + return result; + + std::ifstream file(path); + + if (!file.is_open()) + return false; + + auto json = nlohmann::json::parse(file, nullptr, false); + + if (json.is_discarded() || !json.is_object()) + return false; + + if ("MITK ROI" != json.value("FileFormat", "")) + return false; + + if (1 != json.value("Version", 0)) + return false; + + return true; +} + +mitk::MitkROIIOMimeTypes::MitkROIMimeType* mitk::MitkROIIOMimeTypes::MitkROIMimeType::Clone() const +{ + return new MitkROIMimeType(*this); +} + +mitk::MitkROIIOMimeTypes::MitkROIMimeType mitk::MitkROIIOMimeTypes::ROI_MIMETYPE() +{ + return MitkROIMimeType(); +} + +std::string mitk::MitkROIIOMimeTypes::ROI_MIMETYPE_NAME() +{ + return IOMimeTypes::DEFAULT_BASE_NAME() + ".roi"; +} + +std::vector mitk::MitkROIIOMimeTypes::Get() +{ + std::vector mimeTypes; + mimeTypes.push_back(ROI_MIMETYPE().Clone()); + return mimeTypes; +} diff --git a/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.cpp b/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.cpp new file mode 100644 index 0000000000..2c9304f004 --- /dev/null +++ b/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.cpp @@ -0,0 +1,37 @@ +/*============================================================================ + +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 "mitkROIIOModuleActivator.h" + +#include +#include "mitkROIIO.h" + +#include + +US_EXPORT_MODULE_ACTIVATOR(mitk::ROIIOModuleActivator) + +void mitk::ROIIOModuleActivator::Load(us::ModuleContext* context) +{ + auto mimeTypes = MitkROIIOMimeTypes::Get(); + + us::ServiceProperties props; + props[us::ServiceConstants::SERVICE_RANKING()] = 10; + + for (auto mimeType : mimeTypes) + context->RegisterService(mimeType, props); + + m_FileIOs.push_back(std::make_shared()); +} + +void mitk::ROIIOModuleActivator::Unload(us::ModuleContext*) +{ +} diff --git a/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.h b/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.h new file mode 100644 index 0000000000..d7c3378a01 --- /dev/null +++ b/Modules/ROI/autoload/IO/src/mitkROIIOModuleActivator.h @@ -0,0 +1,37 @@ +/*============================================================================ + +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 + +namespace mitk +{ + class AbstractFileIO; + + class ROIIOModuleActivator : public us::ModuleActivator + { + public: + ROIIOModuleActivator() = default; + ~ROIIOModuleActivator() override = default; + + ROIIOModuleActivator(const ROIIOModuleActivator&) = delete; + ROIIOModuleActivator& operator=(const ROIIOModuleActivator&) = delete; + + void Load(us::ModuleContext* context) override; + void Unload(us::ModuleContext*) override; + + private: + std::vector> m_FileIOs; + }; +} diff --git a/Modules/ROI/autoload/IO/src/mitkROISerializer.cpp b/Modules/ROI/autoload/IO/src/mitkROISerializer.cpp new file mode 100644 index 0000000000..6148f54324 --- /dev/null +++ b/Modules/ROI/autoload/IO/src/mitkROISerializer.cpp @@ -0,0 +1,55 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkROISerializer.h" + +#include +#include + +MITK_REGISTER_SERIALIZER(ROISerializer) + +mitk::ROISerializer::ROISerializer() +{ +} + +mitk::ROISerializer::~ROISerializer() +{ +} + +std::string mitk::ROISerializer::Serialize() +{ + auto roi = dynamic_cast(m_Data.GetPointer()); + + if (nullptr == roi) + { + MITK_ERROR << "Object at " << (const void*)this->m_Data << " is not an mitk::ROI. Cannot serialize as MITK ROI."; + return ""; + } + + auto filename = this->GetUniqueFilenameInWorkingDirectory(); + filename += "_" + m_FilenameHint + ".json"; + + std::string path = m_WorkingDirectory; + path += IOUtil::GetDirectorySeparator() + filename; + + try + { + IOUtil::Save(roi, path); + } + catch (std::exception& e) + { + MITK_ERROR << "Error serializing object at " << (const void*)this->m_Data << " to " << path << ": " << e.what(); + return ""; + } + + return filename; +} diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/mitkPluginActivator.cpp b/Modules/ROI/autoload/IO/src/mitkROISerializer.h similarity index 51% copy from Plugins/org.mitk.gui.qt.imagecropper/src/internal/mitkPluginActivator.cpp copy to Modules/ROI/autoload/IO/src/mitkROISerializer.h index 0902061f1c..0daf876a01 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/mitkPluginActivator.cpp +++ b/Modules/ROI/autoload/IO/src/mitkROISerializer.h @@ -1,26 +1,34 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#include +#ifndef mitkROISerializer_h +#define mitkROISerializer_h -#include "mitkPluginActivator.h" -#include "QmitkImageCropperView.h" +#include -void mitk::mitkPluginActivator::start(ctkPluginContext* context) +namespace mitk { - RegisterBoundingShapeObjectFactory(); - BERRY_REGISTER_EXTENSION_CLASS(QmitkImageCropperView, context) + class ROISerializer : public BaseDataSerializer + { + public: + mitkClassMacro(ROISerializer, BaseDataSerializer) + itkFactorylessNewMacro(Self) + + std::string Serialize() override; + + protected: + ROISerializer(); + ~ROISerializer() override; + }; } -void mitk::mitkPluginActivator::stop(ctkPluginContext*) -{ -} +#endif diff --git a/Modules/ROI/files.cmake b/Modules/ROI/files.cmake new file mode 100644 index 0000000000..1de29e9b16 --- /dev/null +++ b/Modules/ROI/files.cmake @@ -0,0 +1,17 @@ +set(H_FILES + include/mitkROI.h + include/mitkROIMapper2D.h + include/mitkROIMapper3D.h + include/mitkROIMapperLocalStorage.h + src/mitkROIMapperHelper.h + src/mitkROIObjectFactory.h +) + +set(CPP_FILES + mitkROI.cpp + mitkROIMapper2D.cpp + mitkROIMapper3D.cpp + mitkROIMapperHelper.cpp + mitkROIMapperLocalStorage.cpp + mitkROIObjectFactory.cpp +) diff --git a/Modules/ROI/include/mitkROI.h b/Modules/ROI/include/mitkROI.h new file mode 100644 index 0000000000..25fb2ad7bc --- /dev/null +++ b/Modules/ROI/include/mitkROI.h @@ -0,0 +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 mitkROI_h +#define mitkROI_h + +#include +#include + +namespace mitk +{ + /** \brief A collection of region of interests (ROIs). + * + * \note This class is considered experimental and subject to substational change. We mean it. + * + * ROIs, essentially defined by the minimum and maximum index coordinates of an axis-aligned box, are + * represented by the nested ROI::Element class. These index coordinates are relative to a common + * TimeGeometry. + * + * All ROIs are required to have a unique ID by which they can be accessed. + * + * ROIs can optionally have properties (PropertyList), also called default properties. In case of + * time-resolved ROIs, each time step can optionally have properties, too. These properties have + * precedence over the default properties. In other words, the default properties may contain + * fallback properties which are queried when a property is not defined at a certain time step. + * This allows for an opt-in dynamic appearance of ROIs over time, for example by changing + * color or opacity. + * + * ROIs are rendered both in 3-d and 2-d views as cubes, resp. cutting slices of these cubes. + * They support the following ROI::Element properties: + * + * - \c color (ColorProperty): Color of the cube + * - \c opacity (FloatProperty): Opacity of the cube + * - \c lineWidth (FloatProperty): %Line width of the cube edges + * + * ROIs display a customizable caption at their bottom-left corner. It is defined by the base data + * property \c caption (StringProperty). By default it is set to "{name} ({ID})". Braces + * are used to define placeholders which are replaced by their corresponding ROI::Element properties. + * {ID} is a special placeholder which will be replaced by the ID of the ROI::Element instead. + * The caption is allowed to include line breaks. Rendering of captions can be customized through the + * following data node properties: + * + * - \c font.size (IntProperty) Font size in points + * - \c font.bold (BoolProperty) Bold font style + * - \c font.italic (BoolProperty) Italic font style + * + * See \ref MITKROIPage for details on the JSON-based MITK %ROI file format. + */ + class MITKROI_EXPORT ROI : public BaseData + { + public: + /** \brief Encapsulates a single (possibly time-resolved) %ROI. + * + * \sa ROI + */ + class MITKROI_EXPORT Element : public IPropertyOwner + { + public: + using PointsType = std::map; + using PropertyListsType = std::map; + + Element(); + explicit Element(unsigned int id); + ~Element() = default; + + /** \brief Get a const property. + * + * \note A time step can be specified as context. Use \c std::to_string() to convert a time step to a context name. + * An empty context name addresses the default properties. + */ + BaseProperty::ConstPointer GetConstProperty(const std::string& propertyKey, const std::string& contextName = "", bool fallBackOnDefaultContext = true) const override; + BaseProperty::ConstPointer GetConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext = true) const; + + /** \brief Get all property keys. + * + * \note A time step can be specified as context. Use \c std::to_string() to convert a time step to a context name. + * An empty context name addresses the default properties. + */ + std::vector GetPropertyKeys(const std::string& contextName = "", bool includeDefaultContext = false) const override; + std::vector GetPropertyKeys(TimeStepType t, bool includeDefaultContext = false) const; + + /** \brief Get all property context names (stringified time steps). + */ + std::vector GetPropertyContextNames() const override; + + /** \brief Get a property. + * + * \note A time step can be specified as context. Use \c std::to_string() to convert a time step to a context name. + * An empty context name addresses the default properties. + */ + BaseProperty* GetNonConstProperty(const std::string& propertyKey, const std::string& contextName = "", bool fallBackOnDefaultContext = true) override; + BaseProperty* GetNonConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext = true); + + /** \brief Set a property. + * + * \note A time step can be specified as context. Use \c std::to_string() to convert a time step to a context name. + * An empty context name addresses the default properties. + */ + void SetProperty(const std::string& propertyKey, BaseProperty* property, const std::string& contextName = "", bool fallBackOnDefaultContext = false) override; + void SetProperty(const std::string& propertyKey, BaseProperty* property, TimeStepType t, bool fallBackOnDefaultContext = false); + + /** \brief Remove a property. + * + * \note A time step can be specified as context. Use \c std::to_string() to convert a time step to a context name. + * An empty context name addresses the default properties. + */ + void RemoveProperty(const std::string& propertyKey, const std::string& contextName = "", bool fallBackOnDefaultContext = false) override; + void RemoveProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext = false); + + unsigned int GetID() const; + void SetID(unsigned int id); + + /** \brief Check if the %ROI is defined for a certain time step. + */ + bool HasTimeStep(TimeStepType t) const; + + /** \brief Check if the %ROI can be considered time-resolved. + */ + bool HasTimeSteps() const; + + /** \brief Get all valid time steps that have a minimum point and a maximum point. + */ + std::vector GetTimeSteps() const; + + Point3D GetMin(TimeStepType t = 0) const; + void SetMin(const Point3D& min, TimeStepType t = 0); + + Point3D GetMax(TimeStepType t = 0) const; + void SetMax(const Point3D& max, TimeStepType t = 0); + + PropertyList* GetDefaultProperties() const; + void SetDefaultProperties(PropertyList* properties); + + /** \brief Get properties for a certain time step or \c nullptr if absent. + */ + PropertyList* GetProperties(TimeStepType t = 0) const; + + void SetProperties(PropertyList* properties, TimeStepType t = 0); + + private: + unsigned int m_ID; + PointsType m_Min; + PointsType m_Max; + PropertyList::Pointer m_DefaultProperties; + PropertyListsType m_Properties; + }; + + mitkClassMacro(ROI, BaseData) + itkFactorylessNewMacro(Self) + itkCloneMacro(Self) + + using ElementsType = std::map; + using Iterator = ElementsType::iterator; + using ConstIterator = ElementsType::const_iterator; + + size_t GetNumberOfElements() const; + + /** \brief Add a ROI::Element to the collection. + * + * \note The ID of the ROI::Element must be set to a unique number in advance. + */ + void AddElement(const Element& element); + + const Element& GetElement(unsigned int id) const; + Element& GetElement(unsigned int id); + + ConstIterator begin() const; + ConstIterator end() const; + + Iterator begin(); + Iterator end(); + + void SetRequestedRegionToLargestPossibleRegion() override; + bool RequestedRegionIsOutsideOfTheBufferedRegion() override; + bool VerifyRequestedRegion() override; + void SetRequestedRegion(const itk::DataObject* data) override; + + protected: + mitkCloneMacro(Self) + + ROI(); + ROI(const Self& other); + ~ROI() override; + + private: + ElementsType m_Elements; + }; + + MITKROI_EXPORT void to_json(nlohmann::json& j, const ROI::Element& roi); + MITKROI_EXPORT void from_json(const nlohmann::json& j, ROI::Element& roi); +} + +#endif diff --git a/Modules/ROI/include/mitkROIMapper2D.h b/Modules/ROI/include/mitkROIMapper2D.h new file mode 100644 index 0000000000..173915aaec --- /dev/null +++ b/Modules/ROI/include/mitkROIMapper2D.h @@ -0,0 +1,58 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkROIMapper2D_h +#define mitkROIMapper2D_h + +#include +#include +#include +#include + +namespace mitk +{ + class MITKROI_EXPORT ROIMapper2D : public VtkMapper + { + class LocalStorage : public ROIMapperLocalStorage + { + public: + LocalStorage(); + ~LocalStorage() override; + + const PlaneGeometry* GetLastPlaneGeometry() const; + void SetLastPlaneGeometry(const PlaneGeometry* planeGeometry); + + protected: + PlaneGeometry::ConstPointer m_LastPlaneGeometry; + }; + + public: + static void SetDefaultProperties(DataNode* node, BaseRenderer* renderer = nullptr, bool override = false); + + mitkClassMacro(ROIMapper2D, VtkMapper) + itkFactorylessNewMacro(Self) + + vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; + + protected: + ROIMapper2D(); + ~ROIMapper2D() override; + + void GenerateDataForRenderer(BaseRenderer* renderer) override; + void ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) override; + + private: + LocalStorageHandler m_LocalStorageHandler; + }; +} + +#endif diff --git a/Modules/ROI/include/mitkROIMapper3D.h b/Modules/ROI/include/mitkROIMapper3D.h new file mode 100644 index 0000000000..e57d3b148a --- /dev/null +++ b/Modules/ROI/include/mitkROIMapper3D.h @@ -0,0 +1,52 @@ +/*============================================================================ + +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 mitkROIMapper3D_h +#define mitkROIMapper3D_h + +#include +#include +#include +#include + +namespace mitk +{ + class MITKROI_EXPORT ROIMapper3D : public VtkMapper + { + class LocalStorage : public ROIMapperLocalStorage + { + public: + LocalStorage(); + ~LocalStorage() override; + }; + + public: + static void SetDefaultProperties(DataNode* node, BaseRenderer* renderer = nullptr, bool override = false); + + mitkClassMacro(ROIMapper3D, VtkMapper) + itkFactorylessNewMacro(Self) + + vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; + + protected: + ROIMapper3D(); + ~ROIMapper3D() override; + + void GenerateDataForRenderer(BaseRenderer* renderer) override; + void ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) override; + + private: + LocalStorageHandler m_LocalStorageHandler; + }; +} + +#endif diff --git a/Modules/ROI/include/mitkROIMapperLocalStorage.h b/Modules/ROI/include/mitkROIMapperLocalStorage.h new file mode 100644 index 0000000000..567d1eca8e --- /dev/null +++ b/Modules/ROI/include/mitkROIMapperLocalStorage.h @@ -0,0 +1,45 @@ +/*============================================================================ + +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 mitkROIMapperLocalStorage_h +#define mitkROIMapperLocalStorage_h + +#include + +template +class vtkSmartPointer; + +class vtkPropAssembly; + +namespace mitk +{ + /** \brief Common base class for both 2-d and 3-d %ROI mapper local storages. + */ + class ROIMapperLocalStorage : public Mapper::BaseLocalStorage + { + public: + ROIMapperLocalStorage(); + ~ROIMapperLocalStorage() override; + + vtkPropAssembly* GetPropAssembly() const; + void SetPropAssembly(vtkPropAssembly* propAssembly); + + TimePointType GetLastTimePoint() const; + void SetLastTimePoint(TimePointType timePoint); + + protected: + vtkSmartPointer m_PropAssembly; + TimePointType m_LastTimePoint; + }; +} + +#endif diff --git a/Modules/ROI/src/mitkROI.cpp b/Modules/ROI/src/mitkROI.cpp new file mode 100644 index 0000000000..6f5e614b1a --- /dev/null +++ b/Modules/ROI/src/mitkROI.cpp @@ -0,0 +1,399 @@ +/*============================================================================ + +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 + +void mitk::to_json(nlohmann::json& j, const ROI::Element& roi) +{ + j["ID"] = roi.GetID(); + + if (roi.HasTimeSteps()) + { + auto timeSteps = roi.GetTimeSteps(); + + for (const auto t : timeSteps) + { + nlohmann::json jTimeStep = { + { "t", t }, + { "Min", roi.GetMin(t) }, + { "Max", roi.GetMax(t) } + }; + + if (auto* properties = roi.GetProperties(t); properties != nullptr && !properties->IsEmpty()) + properties->ToJSON(jTimeStep["Properties"]); + + j["TimeSteps"].push_back(jTimeStep); + } + } + else + { + j["Min"] = roi.GetMin(); + j["Max"] = roi.GetMax(); + } + + if (auto* properties = roi.GetDefaultProperties(); properties != nullptr && !properties->IsEmpty()) + properties->ToJSON(j["Properties"]); +} + +void mitk::from_json(const nlohmann::json& j, ROI::Element& roi) +{ + auto id = j["ID"].get(); + roi.SetID(id); + + if (j.contains("TimeSteps")) + { + for (const auto& jTimeStep : j["TimeSteps"]) + { + auto t = jTimeStep["t"].get(); + + roi.SetMin(jTimeStep["Min"].get(), t); + roi.SetMax(jTimeStep["Max"].get(), t); + + if (jTimeStep.contains("Properties")) + { + auto properties = mitk::PropertyList::New(); + properties->FromJSON(jTimeStep["Properties"]); + roi.SetProperties(properties, t); + } + } + } + else + { + roi.SetMin(j["Min"].get()); + roi.SetMax(j["Max"].get()); + } + + if (j.contains("Properties")) + { + auto properties = mitk::PropertyList::New(); + properties->FromJSON(j["Properties"]); + roi.SetDefaultProperties(properties); + } +} + +mitk::ROI::Element::Element() + : Element(0) +{ +} + +mitk::ROI::Element::Element(unsigned int id) + : m_ID(id), + m_DefaultProperties(PropertyList::New()) +{ +} + +unsigned int mitk::ROI::Element::GetID() const +{ + return m_ID; +} + +void mitk::ROI::Element::SetID(unsigned int id) +{ + m_ID = id; +} + +bool mitk::ROI::Element::HasTimeStep(TimeStepType t) const +{ + return m_Min.count(t) != 0 && m_Max.count(t) != 0; +} + +bool mitk::ROI::Element::HasTimeSteps() const +{ + // Check for multiple time steps. + if (m_Min.size() > 1 && m_Max.size() > 1) + return true; + + // Check for single time step that is not 0. + if (m_Min.size() >= 1 && m_Max.size() >= 1) + return m_Min.count(0) == 0 && m_Max.count(0) == 0; + + // Single time step 0. + return false; +} + +std::vector mitk::ROI::Element::GetTimeSteps() const +{ + std::vector result; + result.reserve(m_Min.size()); + + for (const auto& [t, min] : m_Min) + { + if (m_Max.count(t) != 0) + result.push_back(t); + } + + return result; +} + +mitk::Point3D mitk::ROI::Element::GetMin(TimeStepType t) const +{ + return m_Min.at(t); +} + +void mitk::ROI::Element::SetMin(const Point3D& min, TimeStepType t) +{ + m_Min[t] = min; +} + +mitk::Point3D mitk::ROI::Element::GetMax(TimeStepType t) const +{ + return m_Max.at(t); +} + +void mitk::ROI::Element::SetMax(const Point3D& max, TimeStepType t) +{ + m_Max[t] = max; +} + +mitk::PropertyList* mitk::ROI::Element::GetDefaultProperties() const +{ + return m_DefaultProperties; +} + +void mitk::ROI::Element::SetDefaultProperties(PropertyList* properties) +{ + m_DefaultProperties = properties; +} + +mitk::PropertyList* mitk::ROI::Element::GetProperties(TimeStepType t) const +{ + if (m_Properties.count(t) != 0) + return m_Properties.at(t); + + return nullptr; +} + +void mitk::ROI::Element::SetProperties(PropertyList* properties, TimeStepType t) +{ + m_Properties[t] = properties; +} + +mitk::BaseProperty::ConstPointer mitk::ROI::Element::GetConstProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) const +{ + return !contextName.empty() + ? this->GetConstProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext) + : m_DefaultProperties->GetConstProperty(propertyKey); +} + +mitk::BaseProperty::ConstPointer mitk::ROI::Element::GetConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) const +{ + auto it = m_Properties.find(t); + + if (it != m_Properties.end() && it->second.IsNotNull()) + { + auto property = it->second->GetConstProperty(propertyKey); + + if (property.IsNotNull()) + return property; + } + + if (!fallBackOnDefaultContext) + return nullptr; + + return m_DefaultProperties->GetConstProperty(propertyKey); +} + +std::vector mitk::ROI::Element::GetPropertyKeys(const std::string& contextName, bool includeDefaultContext) const +{ + return !contextName.empty() + ? this->GetPropertyKeys(std::stoul(contextName), includeDefaultContext) + : m_DefaultProperties->GetPropertyKeys(); +} + +std::vector mitk::ROI::Element::GetPropertyKeys(TimeStepType t, bool includeDefaultContext) const +{ + auto it = m_Properties.find(t); + + std::vector result; + + if (it != m_Properties.end() && it->second.IsNotNull()) + result = it->second->GetPropertyKeys(); + + if (includeDefaultContext) + { + auto keys = m_DefaultProperties->GetPropertyKeys(); + auto end = result.cend(); + + std::remove_copy_if(keys.cbegin(), keys.cend(), std::back_inserter(result), [&, result, end](const std::string& key) { + return end != std::find(result.cbegin(), end, key); + }); + } + + return result; +} + +std::vector mitk::ROI::Element::GetPropertyContextNames() const +{ + std::vector result; + result.reserve(m_Properties.size()); + + std::transform(m_Properties.cbegin(), m_Properties.cend(), std::back_inserter(result), [](const PropertyListsType::value_type& property) { + return std::to_string(property.first); + }); + + return result; +} + +mitk::BaseProperty* mitk::ROI::Element::GetNonConstProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) +{ + return !contextName.empty() + ? this->GetNonConstProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext) + : m_DefaultProperties->GetNonConstProperty(propertyKey); +} + +mitk::BaseProperty* mitk::ROI::Element::GetNonConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) +{ + auto it = m_Properties.find(t); + + if (it != m_Properties.end() && it->second.IsNotNull()) + { + auto* property = it->second->GetNonConstProperty(propertyKey); + + if (property != nullptr) + return property; + } + + if (!fallBackOnDefaultContext) + return nullptr; + + return m_DefaultProperties->GetNonConstProperty(propertyKey); +} + +void mitk::ROI::Element::SetProperty(const std::string& propertyKey, BaseProperty* property, const std::string& contextName, bool fallBackOnDefaultContext) +{ + if (!contextName.empty()) + { + this->SetProperty(propertyKey, property, std::stoul(contextName), fallBackOnDefaultContext); + return; + } + + m_DefaultProperties->SetProperty(propertyKey, property); +} + +void mitk::ROI::Element::SetProperty(const std::string& propertyKey, BaseProperty* property, TimeStepType t, bool fallBackOnDefaultContext) +{ + auto it = m_Properties.find(t); + + if (it != m_Properties.end() && it->second.IsNotNull()) + { + it->second->SetProperty(propertyKey, property); + return; + } + + if (!fallBackOnDefaultContext) + mitkThrow() << "Time step " << t << " does not exist!"; + + m_DefaultProperties->SetProperty(propertyKey, property); +} + +void mitk::ROI::Element::RemoveProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) +{ + if (!contextName.empty()) + { + this->RemoveProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext); + return; + } + + m_DefaultProperties->RemoveProperty(propertyKey); +} + +void mitk::ROI::Element::RemoveProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) +{ + auto it = m_Properties.find(t); + + if (it != m_Properties.end() && it->second.IsNotNull()) + { + it->second->RemoveProperty(propertyKey); + return; + } + + if (!fallBackOnDefaultContext) + mitkThrow() << "Time step " << t << " does not exist!"; + + m_DefaultProperties->RemoveProperty(propertyKey); +} + +mitk::ROI::ROI() +{ +} + +mitk::ROI::ROI(const Self& other) + : BaseData(other) +{ +} + +mitk::ROI::~ROI() +{ +} + +size_t mitk::ROI::GetNumberOfElements() const +{ + return m_Elements.size(); +} + +void mitk::ROI::AddElement(const Element& element) +{ + const auto id = element.GetID(); + + if (m_Elements.count(id) != 0) + mitkThrow() << "ROI already contains an element with ID " << std::to_string(id) << '.'; + + m_Elements[id] = element; +} + +const mitk::ROI::Element& mitk::ROI::GetElement(unsigned int id) const +{ + return m_Elements.at(id); +} + +mitk::ROI::Element& mitk::ROI::GetElement(unsigned int id) +{ + return m_Elements.at(id); +} + +mitk::ROI::ConstIterator mitk::ROI::begin() const +{ + return m_Elements.begin(); +} + +mitk::ROI::ConstIterator mitk::ROI::end() const +{ + return m_Elements.end(); +} + +mitk::ROI::Iterator mitk::ROI::begin() +{ + return m_Elements.begin(); +} + +mitk::ROI::Iterator mitk::ROI::end() +{ + return m_Elements.end(); +} + +void mitk::ROI::SetRequestedRegionToLargestPossibleRegion() +{ +} + +bool mitk::ROI::RequestedRegionIsOutsideOfTheBufferedRegion() +{ + return false; +} + +bool mitk::ROI::VerifyRequestedRegion() +{ + return true; +} + +void mitk::ROI::SetRequestedRegion(const itk::DataObject*) +{ +} diff --git a/Modules/ROI/src/mitkROIMapper2D.cpp b/Modules/ROI/src/mitkROIMapper2D.cpp new file mode 100644 index 0000000000..1435d4c6d7 --- /dev/null +++ b/Modules/ROI/src/mitkROIMapper2D.cpp @@ -0,0 +1,187 @@ +/*============================================================================ + +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 "mitkROIMapperHelper.h" + +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + mitk::Point3D GetBottomLeftPoint(vtkPoints* points, mitk::BaseRenderer* renderer) + { + mitk::Point3D point = points->GetPoint(0); + mitk::Point2D bottomLeftDisplayPoint; + renderer->WorldToDisplay(point, bottomLeftDisplayPoint); + + auto numPoints = points->GetNumberOfPoints(); + mitk::Point2D displayPoint; + + for (decltype(numPoints) i = 1; i < numPoints; ++i) + { + point.FillPoint(points->GetPoint(i)); + renderer->WorldToDisplay(point, displayPoint); + + bottomLeftDisplayPoint[0] = std::min(bottomLeftDisplayPoint[0], displayPoint[0]); + bottomLeftDisplayPoint[1] = std::min(bottomLeftDisplayPoint[1], displayPoint[1]); + } + + renderer->DisplayToWorld(bottomLeftDisplayPoint, point); + + return point; + } +} + +mitk::ROIMapper2D::LocalStorage::LocalStorage() +{ +} + +mitk::ROIMapper2D::LocalStorage::~LocalStorage() +{ +} + +const mitk::PlaneGeometry* mitk::ROIMapper2D::LocalStorage::GetLastPlaneGeometry() const +{ + return m_LastPlaneGeometry; +} + +void mitk::ROIMapper2D::LocalStorage::SetLastPlaneGeometry(const PlaneGeometry* planeGeometry) +{ + m_LastPlaneGeometry = planeGeometry; +} + +void mitk::ROIMapper2D::SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override) +{ + Superclass::SetDefaultProperties(node, renderer, override); + ROIMapperHelper::SetDefaultProperties(node, renderer, override); +} + +mitk::ROIMapper2D::ROIMapper2D() +{ +} + +mitk::ROIMapper2D::~ROIMapper2D() +{ +} + +void mitk::ROIMapper2D::GenerateDataForRenderer(BaseRenderer* renderer) +{ + const auto timePoint = renderer->GetWorldTimeGeometry()->TimeStepToTimePoint(renderer->GetTimeStep()); + const auto* planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); + auto* localStorage = m_LocalStorageHandler.GetLocalStorage(renderer); + const auto* dataNode = this->GetDataNode(); + + if (localStorage->GetLastPlaneGeometry() != nullptr && localStorage->GetLastPlaneGeometry()->IsOnPlane(planeGeometry) && + localStorage->GetLastGenerateDataTime() >= dataNode->GetMTime() && + localStorage->GetLastTimePoint() == timePoint) + { + return; + } + + localStorage->SetLastPlaneGeometry(planeGeometry->Clone()); + localStorage->SetLastTimePoint(timePoint); + + auto data = static_cast(this->GetData()); + + if (!data->GetTimeGeometry()->IsValidTimePoint(timePoint)) + return; + + const auto t = data->GetTimeGeometry()->TimePointToTimeStep(timePoint); + auto propAssembly = vtkSmartPointer::New(); + + if (dataNode->IsVisible(renderer)) + { + const auto* geometry = data->GetGeometry(t); + const auto halfSpacing = geometry->GetSpacing() * 0.5f; + + auto plane = vtkSmartPointer::New(); + plane->SetOrigin(planeGeometry->GetOrigin().data()); + plane->SetNormal(planeGeometry->GetNormal().data()); + + for (const auto& [id, roi] : *data) + { + if (!roi.HasTimeStep(t)) + continue; + + Point3D min; + geometry->IndexToWorld(roi.GetMin(t), min); + min -= halfSpacing; + + Point3D max; + geometry->IndexToWorld(roi.GetMax(t), max); + max += halfSpacing; + + auto cube = vtkSmartPointer::New(); + cube->SetBounds(min[0], max[0], min[1], max[1], min[2], max[2]); + + auto cutter = vtkSmartPointer::New(); + cutter->SetInputConnection(cube->GetOutputPort()); + cutter->SetPlane(plane); + cutter->Update(); + + auto* slicePolyData = cutter->GetOutput(); + + if (slicePolyData->GetNumberOfLines() == 0) + continue; + + auto mapper = vtkSmartPointer::New(); + mapper->SetInputConnection(cutter->GetOutputPort()); + + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + this->ApplyColorAndOpacityProperties(renderer, actor); + ROIMapperHelper::ApplyIndividualProperties(roi, t, actor); + + propAssembly->AddPart(actor); + + if (std::string caption; dataNode->GetStringProperty("caption", caption, renderer)) + { + caption = ROIMapperHelper::ParseCaption(caption, roi, t); + + if (!caption.empty()) + { + auto bottomLeftPoint = GetBottomLeftPoint(slicePolyData->GetPoints(), renderer); + auto captionActor = ROIMapperHelper::CreateCaptionActor(caption, bottomLeftPoint, actor->GetProperty(), dataNode, renderer); + + propAssembly->AddPart(captionActor); + } + } + } + } + + localStorage->SetPropAssembly(propAssembly); + localStorage->UpdateGenerateDataTime(); +} + +void mitk::ROIMapper2D::ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) +{ + auto* property = actor->GetProperty(); + + float opacity = 1.0f; + this->GetDataNode()->GetOpacity(opacity, renderer); + property->SetOpacity(opacity); +} + +vtkProp* mitk::ROIMapper2D::GetVtkProp(BaseRenderer* renderer) +{ + return m_LocalStorageHandler.GetLocalStorage(renderer)->GetPropAssembly(); +} diff --git a/Modules/ROI/src/mitkROIMapper3D.cpp b/Modules/ROI/src/mitkROIMapper3D.cpp new file mode 100644 index 0000000000..e8120de0e3 --- /dev/null +++ b/Modules/ROI/src/mitkROIMapper3D.cpp @@ -0,0 +1,118 @@ +/*============================================================================ + +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 "mitkROIMapperHelper.h" + +#include +#include +#include +#include + +mitk::ROIMapper3D::LocalStorage::LocalStorage() +{ +} + +mitk::ROIMapper3D::LocalStorage::~LocalStorage() +{ +} + +void mitk::ROIMapper3D::SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override) +{ + Superclass::SetDefaultProperties(node, renderer, override); + ROIMapperHelper::SetDefaultProperties(node, renderer, override); +} + +mitk::ROIMapper3D::ROIMapper3D() +{ +} + +mitk::ROIMapper3D::~ROIMapper3D() +{ +} + +void mitk::ROIMapper3D::GenerateDataForRenderer(BaseRenderer* renderer) +{ + const auto timePoint = renderer->GetWorldTimeGeometry()->TimeStepToTimePoint(renderer->GetTimeStep()); + auto* localStorage = m_LocalStorageHandler.GetLocalStorage(renderer); + const auto* dataNode = this->GetDataNode(); + + if (localStorage->GetLastGenerateDataTime() >= dataNode->GetMTime() && + localStorage->GetLastTimePoint() == timePoint) + { + return; + } + + localStorage->SetLastTimePoint(timePoint); + + auto data = static_cast(this->GetData()); + + if (!data->GetTimeGeometry()->IsValidTimePoint(timePoint)) + return; + + const auto t = data->GetTimeGeometry()->TimePointToTimeStep(timePoint); + auto propAssembly = vtkSmartPointer::New(); + + if (dataNode->IsVisible(renderer)) + { + const auto* geometry = data->GetGeometry(); + const auto halfSpacing = geometry->GetSpacing() * 0.5f; + + for (const auto& [id, roi] : *data) + { + if (!roi.HasTimeStep(t)) + continue; + + Point3D min; + geometry->IndexToWorld(roi.GetMin(t), min); + min -= halfSpacing; + + Point3D max; + geometry->IndexToWorld(roi.GetMax(t), max); + max += halfSpacing; + + auto cube = vtkSmartPointer::New(); + cube->SetBounds(min[0], max[0], min[1], max[1], min[2], max[2]); + cube->Update(); + + auto mapper = vtkSmartPointer::New(); + mapper->SetInputConnection(cube->GetOutputPort()); + + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + this->ApplyColorAndOpacityProperties(renderer, actor); + ROIMapperHelper::ApplyIndividualProperties(roi, t, actor); + + propAssembly->AddPart(actor); + } + } + + localStorage->SetPropAssembly(propAssembly); + localStorage->UpdateGenerateDataTime(); +} + +void mitk::ROIMapper3D::ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) +{ + auto* property = actor->GetProperty(); + + float opacity = 1.0f; + this->GetDataNode()->GetOpacity(opacity, renderer); + property->SetOpacity(opacity); +} + +vtkProp* mitk::ROIMapper3D::GetVtkProp(BaseRenderer* renderer) +{ + return m_LocalStorageHandler.GetLocalStorage(renderer)->GetPropAssembly(); +} diff --git a/Modules/ROI/src/mitkROIMapperHelper.cpp b/Modules/ROI/src/mitkROIMapperHelper.cpp new file mode 100644 index 0000000000..68dc54c545 --- /dev/null +++ b/Modules/ROI/src/mitkROIMapperHelper.cpp @@ -0,0 +1,130 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkROIMapperHelper.h" + +#include +#include + +#include + +#include + +void mitk::ROIMapperHelper::ApplyIndividualProperties(const ROI::Element& roi, TimeStepType t, vtkActor* actor) +{ + auto* property = actor->GetProperty(); + + property->SetRepresentationToWireframe(); + property->LightingOff(); + + if (auto colorProperty = GetConstProperty("color", roi, t); colorProperty != nullptr) + { + const auto color = colorProperty->GetColor(); + property->SetColor(color[0], color[1], color[2]); + } + + if (auto opacityProperty = GetConstProperty("opacity", roi, t); opacityProperty != nullptr) + { + const auto opacity = opacityProperty->GetValue(); + property->SetOpacity(property->GetOpacity() * opacity); + } + + if (auto lineWidthProperty = GetConstProperty("lineWidth", roi, t); lineWidthProperty != nullptr) + { + const auto lineWidth = lineWidthProperty->GetValue(); + property->SetLineWidth(lineWidth); + } +} + +vtkSmartPointer mitk::ROIMapperHelper::CreateCaptionActor(const std::string& caption, const Point3D& attachmentPoint, vtkProperty* property, const DataNode* dataNode, const BaseRenderer* renderer) +{ + auto captionActor = vtkSmartPointer::New(); + captionActor->SetPosition(property->GetLineWidth() * 0.5, property->GetLineWidth() * 0.5); + captionActor->GetTextActor()->SetTextScaleModeToNone(); + captionActor->SetAttachmentPoint(attachmentPoint[0], attachmentPoint[1], attachmentPoint[2]); + captionActor->SetCaption(caption.c_str()); + captionActor->BorderOff(); + captionActor->LeaderOff(); + + auto* textProperty = captionActor->GetCaptionTextProperty(); + textProperty->SetColor(property->GetColor()); + textProperty->SetOpacity(property->GetOpacity()); + textProperty->ShadowOff(); + + int fontSize = 16; + dataNode->GetIntProperty("font.size", fontSize, renderer); + textProperty->SetFontSize(fontSize); + + bool bold = false; + dataNode->GetBoolProperty("font.bold", bold, renderer); + textProperty->SetBold(bold); + + bool italic = false; + dataNode->GetBoolProperty("font.italic", italic, renderer); + textProperty->SetItalic(italic); + + return captionActor; +} + +std::string mitk::ROIMapperHelper::ParseCaption(const std::string& captionTemplate, const ROI::Element& roi, TimeStepType t) +{ + std::regex regex(R"(\{([^}]*)\})"); // Anything between curly braces (considered as placeholder). + + auto start = captionTemplate.cbegin(); + bool hasPlaceholders = false; + std::string caption; + std::smatch match; + + // Iterate through the caption template and substitute all + // placeholders with corresponding data or property values. + + while (std::regex_search(start, captionTemplate.cend(), match, regex)) + { + hasPlaceholders = true; + + caption.append(match.prefix().first, match.prefix().second); + + if (match[1] == "ID") + { + caption.append(std::to_string(roi.GetID())); + } + else + { + auto property = roi.GetConstProperty(match[1], t); + + if (property.IsNotNull()) + caption.append(property->GetValueAsString()); + } + + start = match.suffix().first; + } + + if (match.suffix().matched) + caption.append(match.suffix().first, match.suffix().second); + + if (hasPlaceholders) + { + boost::trim(caption); + return caption; + } + + return captionTemplate; +} + +void mitk::ROIMapperHelper::SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override) +{ + node->AddProperty("opacity", FloatProperty::New(1.0f), renderer, override); + node->AddProperty("font.bold", BoolProperty::New(false), renderer, override); + node->AddProperty("font.italic", BoolProperty::New(false), renderer, override); + node->AddProperty("font.size", IntProperty::New(16), renderer, override); + node->AddProperty("caption", StringProperty::New("{ID}\n{name}"), renderer, override); +} diff --git a/Modules/ROI/src/mitkROIMapperHelper.h b/Modules/ROI/src/mitkROIMapperHelper.h new file mode 100644 index 0000000000..edab2ad05d --- /dev/null +++ b/Modules/ROI/src/mitkROIMapperHelper.h @@ -0,0 +1,60 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkROIMapperHelper_h +#define mitkROIMapperHelper_h + +#include +#include + +#include +#include +#include + +namespace mitk +{ + namespace ROIMapperHelper + { + /** \brief Apply %ROI properties at a certain time step to the given actor. + */ + void ApplyIndividualProperties(const ROI::Element& roi, TimeStepType t, vtkActor* actor); + + /** \brief Create an actor for the %ROI caption located at a certain attachment point considering several properties. + */ + vtkSmartPointer CreateCaptionActor(const std::string& caption, const Point3D& attachmentPoint, vtkProperty* property, const DataNode* dataNode, const BaseRenderer* renderer); + + /** \brief Substitute all placeholders in a caption with corresponding property values. + * + * \sa ROI + */ + std::string ParseCaption(const std::string& captionTemplate, const ROI::Element& roi, TimeStepType t = 0); + + /** \brief Set common default properties for both 2-d and 3-d %ROI mappers. + */ + void SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override); + + /** \brief Syntactic sugar for getting %ROI properties. + */ + template + const T* GetConstProperty(const std::string& propertyKey, const ROI::Element& roi, TimeStepType t) + { + auto property = roi.GetConstProperty(propertyKey, t); + + if (property.IsNotNull()) + return dynamic_cast(property.GetPointer()); + + return nullptr; + } + } +} + +#endif diff --git a/Modules/ROI/src/mitkROIMapperLocalStorage.cpp b/Modules/ROI/src/mitkROIMapperLocalStorage.cpp new file mode 100644 index 0000000000..4c57abe8df --- /dev/null +++ b/Modules/ROI/src/mitkROIMapperLocalStorage.cpp @@ -0,0 +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. + +============================================================================*/ + +#include + +#include +#include + +mitk::ROIMapperLocalStorage::ROIMapperLocalStorage() + : m_PropAssembly(vtkSmartPointer::New()), + m_LastTimePoint(0.0) +{ +} + +mitk::ROIMapperLocalStorage::~ROIMapperLocalStorage() +{ +} + +vtkPropAssembly* mitk::ROIMapperLocalStorage::GetPropAssembly() const +{ + return m_PropAssembly; +} + +void mitk::ROIMapperLocalStorage::SetPropAssembly(vtkPropAssembly* propAssembly) +{ + m_PropAssembly = propAssembly; +} + +mitk::TimePointType mitk::ROIMapperLocalStorage::GetLastTimePoint() const +{ + return m_LastTimePoint; +} + +void mitk::ROIMapperLocalStorage::SetLastTimePoint(TimePointType timePoint) +{ + m_LastTimePoint = timePoint; +} diff --git a/Modules/ROI/src/mitkROIObjectFactory.cpp b/Modules/ROI/src/mitkROIObjectFactory.cpp new file mode 100644 index 0000000000..cffe1fca6d --- /dev/null +++ b/Modules/ROI/src/mitkROIObjectFactory.cpp @@ -0,0 +1,108 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkROIObjectFactory.h" +#include +#include +#include +#include + +mitk::ROIObjectFactory::ROIObjectFactory() +{ +} + +mitk::ROIObjectFactory::~ROIObjectFactory() +{ +} + +mitk::Mapper::Pointer mitk::ROIObjectFactory::CreateMapper(DataNode* node, MapperSlotId slotId) +{ + Mapper::Pointer mapper; + + if (node != nullptr) + { + auto* roi = dynamic_cast(node->GetData()); + + if (roi != nullptr) + { + if (slotId == BaseRenderer::Standard2D) + { + mapper = ROIMapper2D::New(); + } + else if (slotId == BaseRenderer::Standard3D) + { + mapper = ROIMapper3D::New(); + } + + if (mapper.IsNotNull()) + mapper->SetDataNode(node); + } + } + + return mapper; +} + +void mitk::ROIObjectFactory::SetDefaultProperties(DataNode* node) +{ + if (node == nullptr) + return; + + auto* roi = dynamic_cast(node->GetData()); + + if (roi == nullptr) + return; + + ROIMapper2D::SetDefaultProperties(node); + ROIMapper3D::SetDefaultProperties(node); +} + +std::string mitk::ROIObjectFactory::GetFileExtensions() +{ + return ""; +} + +mitk::ROIObjectFactory::MultimapType mitk::ROIObjectFactory::GetFileExtensionsMap() +{ + return {}; +} + +std::string mitk::ROIObjectFactory::GetSaveFileExtensions() +{ + return ""; +} + +mitk::ROIObjectFactory::MultimapType mitk::ROIObjectFactory::GetSaveFileExtensionsMap() +{ + return {}; +} + +namespace mitk +{ + class RegisterROIObjectFactory + { + public: + RegisterROIObjectFactory() + : m_Factory(ROIObjectFactory::New()) + { + CoreObjectFactory::GetInstance()->RegisterExtraFactory(m_Factory); + } + + ~RegisterROIObjectFactory() + { + } + + private: + ROIObjectFactory::Pointer m_Factory; + }; +} + +static mitk::RegisterROIObjectFactory registerROIObjectFactory; diff --git a/Modules/ROI/src/mitkROIObjectFactory.h b/Modules/ROI/src/mitkROIObjectFactory.h new file mode 100644 index 0000000000..75ac43fc92 --- /dev/null +++ b/Modules/ROI/src/mitkROIObjectFactory.h @@ -0,0 +1,39 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkROIObjectFactory_h +#define mitkROIObjectFactory_h + +#include + +namespace mitk +{ + class ROIObjectFactory : public CoreObjectFactoryBase + { + public: + mitkClassMacro(ROIObjectFactory, CoreObjectFactoryBase) + itkFactorylessNewMacro(Self) + + Mapper::Pointer CreateMapper(DataNode* node, MapperSlotId slotId) override; + void SetDefaultProperties(DataNode *node) override; + std::string GetFileExtensions() override; + MultimapType GetFileExtensionsMap() override; + std::string GetSaveFileExtensions() override; + MultimapType GetSaveFileExtensionsMap() override; + + protected: + ROIObjectFactory(); + ~ROIObjectFactory() override; + }; +} + +#endif diff --git a/Modules/RT/include/mitkIsoDoseLevelSetProperty.h b/Modules/RT/include/mitkIsoDoseLevelSetProperty.h index 32aa056beb..84bce165a9 100644 --- a/Modules/RT/include/mitkIsoDoseLevelSetProperty.h +++ b/Modules/RT/include/mitkIsoDoseLevelSetProperty.h @@ -1,73 +1,76 @@ /*============================================================================ 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 mitkIsoDoseLevelSetProperty_h #define mitkIsoDoseLevelSetProperty_h #include "mitkBaseProperty.h" #include "mitkIsoDoseLevelCollections.h" #include "MitkRTExports.h" namespace mitk { /** \brief Property class for dose iso level sets. */ class MITKRT_EXPORT IsoDoseLevelSetProperty : public BaseProperty { protected: IsoDoseLevelSet::Pointer m_IsoLevelSet; IsoDoseLevelSetProperty(); explicit IsoDoseLevelSetProperty(const IsoDoseLevelSetProperty& other); explicit IsoDoseLevelSetProperty(IsoDoseLevelSet* levelSet); public: mitkClassMacro(IsoDoseLevelSetProperty, BaseProperty); itkNewMacro(IsoDoseLevelSetProperty); mitkNewMacro1Param(IsoDoseLevelSetProperty, IsoDoseLevelSet*); typedef IsoDoseLevelSet ValueType; ~IsoDoseLevelSetProperty() override; const IsoDoseLevelSet * GetIsoDoseLevelSet() const; const IsoDoseLevelSet * GetValue() const; IsoDoseLevelSet * GetIsoDoseLevelSet(); IsoDoseLevelSet * GetValue(); void SetIsoDoseLevelSet(IsoDoseLevelSet* levelSet); void SetValue(IsoDoseLevelSet* levelSet); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; private: itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty& property) const override; bool Assign(const BaseProperty& property) override; }; } // namespace mitk #endif diff --git a/Modules/RT/include/mitkIsoDoseLevelVectorProperty.h b/Modules/RT/include/mitkIsoDoseLevelVectorProperty.h index eadc300ac4..5887234c37 100644 --- a/Modules/RT/include/mitkIsoDoseLevelVectorProperty.h +++ b/Modules/RT/include/mitkIsoDoseLevelVectorProperty.h @@ -1,74 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkIsoDoseLevelVectorProperty_h #define mitkIsoDoseLevelVectorProperty_h #include "mitkBaseProperty.h" #include "mitkIsoDoseLevelCollections.h" #include "MitkRTExports.h" namespace mitk { /** \brief Property class for dose iso level vector. */ class MITKRT_EXPORT IsoDoseLevelVectorProperty : public BaseProperty { protected: IsoDoseLevelVector::Pointer m_IsoLevelVector; IsoDoseLevelVectorProperty(); explicit IsoDoseLevelVectorProperty(const IsoDoseLevelVectorProperty& other); explicit IsoDoseLevelVectorProperty(IsoDoseLevelVector* levelVector); public: mitkClassMacro(IsoDoseLevelVectorProperty, BaseProperty); itkNewMacro(IsoDoseLevelVectorProperty); mitkNewMacro1Param(IsoDoseLevelVectorProperty, IsoDoseLevelVector*); typedef IsoDoseLevelVector ValueType; ~IsoDoseLevelVectorProperty() override; const IsoDoseLevelVector * GetIsoDoseLevelVector() const; const IsoDoseLevelVector * GetValue() const; IsoDoseLevelVector * GetIsoDoseLevelVector(); IsoDoseLevelVector * GetValue(); void SetIsoDoseLevelVector(IsoDoseLevelVector* levelVector); void SetValue(IsoDoseLevelVector* levelVector); std::string GetValueAsString() const override; + bool ToJSON(nlohmann::json& j) const override; + bool FromJSON(const nlohmann::json& j) override; + using BaseProperty::operator=; private: itk::LightObject::Pointer InternalClone() const override; bool IsEqual(const BaseProperty& property) const override; bool Assign(const BaseProperty& property) override; }; } // namespace mitk #endif diff --git a/Modules/RT/src/mitkIsoDoseLevelSetProperty.cpp b/Modules/RT/src/mitkIsoDoseLevelSetProperty.cpp index aa15834838..35b321b3ab 100644 --- a/Modules/RT/src/mitkIsoDoseLevelSetProperty.cpp +++ b/Modules/RT/src/mitkIsoDoseLevelSetProperty.cpp @@ -1,103 +1,113 @@ /*============================================================================ 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 "mitkIsoDoseLevelSetProperty.h" mitk::IsoDoseLevelSetProperty::IsoDoseLevelSetProperty() { } mitk::IsoDoseLevelSetProperty::IsoDoseLevelSetProperty(const mitk::IsoDoseLevelSetProperty& other) : BaseProperty(other) , m_IsoLevelSet(other.m_IsoLevelSet) { } mitk::IsoDoseLevelSetProperty::IsoDoseLevelSetProperty(IsoDoseLevelSet* levelSet) : m_IsoLevelSet(levelSet) { } mitk::IsoDoseLevelSetProperty::~IsoDoseLevelSetProperty() { } bool mitk::IsoDoseLevelSetProperty::IsEqual(const BaseProperty& property) const { return this->m_IsoLevelSet == static_cast(property).m_IsoLevelSet; } bool mitk::IsoDoseLevelSetProperty::Assign(const BaseProperty& property) { this->m_IsoLevelSet = static_cast(property).m_IsoLevelSet; return true; } const mitk::IsoDoseLevelSet * mitk::IsoDoseLevelSetProperty::GetIsoDoseLevelSet() const { return m_IsoLevelSet; } const mitk::IsoDoseLevelSet * mitk::IsoDoseLevelSetProperty::GetValue() const { return GetIsoDoseLevelSet(); } mitk::IsoDoseLevelSet * mitk::IsoDoseLevelSetProperty::GetIsoDoseLevelSet() { return m_IsoLevelSet; } mitk::IsoDoseLevelSet * mitk::IsoDoseLevelSetProperty::GetValue() { return GetIsoDoseLevelSet(); } void mitk::IsoDoseLevelSetProperty::SetIsoDoseLevelSet(IsoDoseLevelSet* levelSet) { if(m_IsoLevelSet != levelSet) { m_IsoLevelSet = levelSet; Modified(); } } void mitk::IsoDoseLevelSetProperty::SetValue(IsoDoseLevelSet* levelSet) { SetIsoDoseLevelSet(levelSet); } std::string mitk::IsoDoseLevelSetProperty::GetValueAsString() const { std::stringstream myStr; myStr << "IsoDoseLevels: "; if (m_IsoLevelSet.IsNotNull()) { myStr << m_IsoLevelSet->Size() << std::endl; for (IsoDoseLevelSet::ConstIterator pos = m_IsoLevelSet->Begin(); pos != m_IsoLevelSet->End(); ++pos) { myStr << " " << 100*(pos->GetDoseValue()) << "% : ("<GetColor()<< "); iso line: " << pos->GetVisibleIsoLine() << "; color wash: "<GetVisibleColorWash() << std::endl; } } return myStr.str(); } +bool mitk::IsoDoseLevelSetProperty::ToJSON(nlohmann::json&) const +{ + return false; // Not implemented +} + +bool mitk::IsoDoseLevelSetProperty::FromJSON(const nlohmann::json&) +{ + return false; // Not implemented +} + itk::LightObject::Pointer mitk::IsoDoseLevelSetProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); return result; } diff --git a/Modules/RT/src/mitkIsoDoseLevelVectorProperty.cpp b/Modules/RT/src/mitkIsoDoseLevelVectorProperty.cpp index 0182f7c135..808acfa397 100644 --- a/Modules/RT/src/mitkIsoDoseLevelVectorProperty.cpp +++ b/Modules/RT/src/mitkIsoDoseLevelVectorProperty.cpp @@ -1,103 +1,113 @@ /*============================================================================ 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 "mitkIsoDoseLevelVectorProperty.h" mitk::IsoDoseLevelVectorProperty::IsoDoseLevelVectorProperty() { } mitk::IsoDoseLevelVectorProperty::IsoDoseLevelVectorProperty(const mitk::IsoDoseLevelVectorProperty& other) : BaseProperty(other) , m_IsoLevelVector(other.m_IsoLevelVector) { } mitk::IsoDoseLevelVectorProperty::IsoDoseLevelVectorProperty(IsoDoseLevelVector* levelVector) : m_IsoLevelVector(levelVector) { } mitk::IsoDoseLevelVectorProperty::~IsoDoseLevelVectorProperty() { } bool mitk::IsoDoseLevelVectorProperty::IsEqual(const BaseProperty& property) const { return this->m_IsoLevelVector == static_cast(property).m_IsoLevelVector; } bool mitk::IsoDoseLevelVectorProperty::Assign(const BaseProperty& property) { this->m_IsoLevelVector = static_cast(property).m_IsoLevelVector; return true; } const mitk::IsoDoseLevelVector * mitk::IsoDoseLevelVectorProperty::GetIsoDoseLevelVector() const { return m_IsoLevelVector; } const mitk::IsoDoseLevelVector * mitk::IsoDoseLevelVectorProperty::GetValue() const { return GetIsoDoseLevelVector(); } mitk::IsoDoseLevelVector * mitk::IsoDoseLevelVectorProperty::GetIsoDoseLevelVector() { return m_IsoLevelVector; } mitk::IsoDoseLevelVector * mitk::IsoDoseLevelVectorProperty::GetValue() { return GetIsoDoseLevelVector(); } void mitk::IsoDoseLevelVectorProperty::SetIsoDoseLevelVector(IsoDoseLevelVector* levelVector) { if(m_IsoLevelVector != levelVector) { m_IsoLevelVector = levelVector; Modified(); } } void mitk::IsoDoseLevelVectorProperty::SetValue(IsoDoseLevelVector* levelVector) { SetIsoDoseLevelVector(levelVector); } std::string mitk::IsoDoseLevelVectorProperty::GetValueAsString() const { std::stringstream myStr; myStr << "IsoDoseLevels: "; if (m_IsoLevelVector.IsNotNull()) { myStr << m_IsoLevelVector->Size() << std::endl; for (IsoDoseLevelVector::ConstIterator pos = m_IsoLevelVector->Begin(); pos != m_IsoLevelVector->End(); ++pos) { myStr << " " << 100*(pos->Value()->GetDoseValue()) << "% : ("<Value()->GetColor()<< "); iso line: " << pos->Value()->GetVisibleIsoLine() << std::endl; } } return myStr.str(); } +bool mitk::IsoDoseLevelVectorProperty::ToJSON(nlohmann::json&) const +{ + return false; // Not implemented +} + +bool mitk::IsoDoseLevelVectorProperty::FromJSON(const nlohmann::json&) +{ + return false; // Not implemented +} + itk::LightObject::Pointer mitk::IsoDoseLevelVectorProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); return result; } diff --git a/Modules/SceneSerialization/src/mitkSceneIO.cpp b/Modules/SceneSerialization/src/mitkSceneIO.cpp index c96aa37a3e..6072a7bc3f 100644 --- a/Modules/SceneSerialization/src/mitkSceneIO.cpp +++ b/Modules/SceneSerialization/src/mitkSceneIO.cpp @@ -1,577 +1,578 @@ /*============================================================================ 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 "mitkBaseDataSerializer.h" #include "mitkPropertyListSerializer.h" #include "mitkSceneIO.h" #include "mitkSceneReader.h" #include "mitkBaseRenderer.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkStandaloneDataStorage.h" #include #include +#include #include #include #include #include #include "itksys/SystemTools.hxx" #include mitk::SceneIO::SceneIO() : m_WorkingDirectory(""), m_UnzipErrors(0) { } mitk::SceneIO::~SceneIO() { } std::string mitk::SceneIO::CreateEmptyTempDirectory() { mitk::UIDGenerator uidGen; // std::string returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + // Poco::Path::separator() + "SceneIOTemp" + uidGen.GetUID(); std::string returnValue = Poco::Path::temp() + "SceneIOTemp" + uidGen.GetUID(); std::string uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir(uniquename); try { bool existsNot = tempdir.createDirectory(); if (!existsNot) { MITK_ERROR << "Warning: Directory already exitsts: " << uniquename << " (choosing another)"; returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + Poco::Path::separator() + "SceneIOTempDirectory" + uidGen.GetUID(); uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir2(uniquename); if (!tempdir2.createDirectory()) { MITK_ERROR << "Warning: Second directory also already exitsts: " << uniquename; } } } catch (std::exception &e) { MITK_ERROR << "Could not create temporary directory " << uniquename << ":" << e.what(); return ""; } return returnValue; } mitk::DataStorage::Pointer mitk::SceneIO::LoadScene(const std::string &filename, DataStorage *pStorage, bool clearStorageFirst) { mitk::LocaleSwitch localeSwitch("C"); // prepare data storage DataStorage::Pointer storage = pStorage; if (storage.IsNull()) { storage = StandaloneDataStorage::New().GetPointer(); } // test input filename if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to load scene."; return storage; } // test if filename can be read std::ifstream file(filename.c_str(), std::ios::binary); if (!file.good()) { MITK_ERROR << "Cannot open '" << filename << "' for reading"; return storage; } // get new temporary directory m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot open scene files."; return storage; } // unzip all filenames contents to temp dir m_UnzipErrors = 0; Poco::Zip::Decompress unzipper(file, Poco::Path(m_WorkingDirectory)); unzipper.EError += Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk += Poco::Delegate>( this, &SceneIO::OnUnzipOk); unzipper.decompressAllFiles(); unzipper.EError -= Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk -= Poco::Delegate>( this, &SceneIO::OnUnzipOk); if (m_UnzipErrors) { MITK_ERROR << "There were " << m_UnzipErrors << " errors unzipping '" << filename << "'. Will attempt to read whatever could be unzipped."; } // transcode locale-dependent string m_WorkingDirectory = Poco::Path::transcode (m_WorkingDirectory); auto indexFile = m_WorkingDirectory + mitk::IOUtil::GetDirectorySeparator() + "index.xml"; storage = LoadSceneUnzipped(indexFile, storage, clearStorageFirst); // delete temp directory try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; } // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method) return storage; } mitk::DataStorage::Pointer mitk::SceneIO::LoadSceneUnzipped(const std::string &indexfilename, DataStorage *pStorage, bool clearStorageFirst) { mitk::LocaleSwitch localeSwitch("C"); // prepare data storage DataStorage::Pointer storage = pStorage; if (storage.IsNull()) { storage = StandaloneDataStorage::New().GetPointer(); } if (clearStorageFirst) { try { storage->Remove(storage->GetAll()); } catch (...) { MITK_ERROR << "DataStorage cannot be cleared properly."; } } // test input filename if (indexfilename.empty()) { MITK_ERROR << "No filename given. Not possible to load scene."; return storage; } // transcode locale-dependent string std::string tempfilename; std::string workingDir; itksys::SystemTools::SplitProgramPath(indexfilename, workingDir, tempfilename); // test if index.xml exists // parse index.xml with TinyXML tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS != document.LoadFile(indexfilename.c_str())) { MITK_ERROR << "Could not open/read/parse " << workingDir << mitk::IOUtil::GetDirectorySeparator() << "index.xml\nTinyXML reports: " << document.ErrorStr() << std::endl; return storage; } SceneReader::Pointer reader = SceneReader::New(); if (!reader->LoadScene(document, workingDir, storage)) { MITK_ERROR << "There were errors while loading scene file " << indexfilename << ". Your data may be corrupted"; } // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method) return storage; } bool mitk::SceneIO::SaveScene(DataStorage::SetOfObjects::ConstPointer sceneNodes, const DataStorage *storage, const std::string &filename) { if (!sceneNodes) { MITK_ERROR << "No set of nodes given. Not possible to save scene."; return false; } if (!storage) { MITK_ERROR << "No data storage given. Not possible to save scene."; // \TODO: Technically, it would be possible to // save the nodes without their relation return false; } if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to save scene."; return false; } mitk::LocaleSwitch localeSwitch("C"); try { m_FailedNodes = DataStorage::SetOfObjects::New(); m_FailedProperties = PropertyList::New(); // start XML DOM tinyxml2::XMLDocument document; document.InsertEndChild(document.NewDeclaration()); auto *version = document.NewElement("Version"); version->SetAttribute("Writer", __FILE__); version->SetAttribute("Revision", "$Revision: 17055 $"); version->SetAttribute("FileVersion", 1); document.InsertEndChild(version); // DataStorage::SetOfObjects::ConstPointer sceneNodes = storage->GetSubset( predicate ); if (sceneNodes.IsNull()) { MITK_WARN << "Saving empty scene to " << filename; } else { if (sceneNodes->size() == 0) { MITK_WARN << "Saving empty scene to " << filename; } MITK_INFO << "Storing scene with " << sceneNodes->size() << " objects to " << filename; m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot create scene files."; return false; } ProgressBar::GetInstance()->AddStepsToDo(sceneNodes->size()); // find out about dependencies typedef std::map UIDMapType; typedef std::map> SourcesMapType; UIDMapType nodeUIDs; // for dependencies: ID of each node SourcesMapType sourceUIDs; // for dependencies: IDs of a node's parent nodes UIDGenerator nodeUIDGen("OBJECT_"); for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (!node) continue; // unlikely event that we get a nullptr pointer as an object for saving. just ignore // generate UIDs for all source objects DataStorage::SetOfObjects::ConstPointer sourceObjects = storage->GetSources(node); for (auto sourceIter = sourceObjects->begin(); sourceIter != sourceObjects->end(); ++sourceIter) { if (std::find(sceneNodes->begin(), sceneNodes->end(), *sourceIter) == sceneNodes->end()) continue; // source is not saved, so don't generate a UID for this source // create a uid for the parent object if (nodeUIDs[*sourceIter].empty()) { nodeUIDs[*sourceIter] = nodeUIDGen.GetUID(); } // store this dependency for writing sourceUIDs[node].push_back(nodeUIDs[*sourceIter]); } if (nodeUIDs[node].empty()) { nodeUIDs[node] = nodeUIDGen.GetUID(); } } // write out objects, dependencies and properties for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (node) { auto *nodeElement = document.NewElement("node"); std::string filenameHint(node->GetName()); filenameHint = itksys::SystemTools::MakeCindentifier( filenameHint.c_str()); // escape filename <-- only allow [A-Za-z0-9_], replace everything else with _ // store dependencies auto searchUIDIter = nodeUIDs.find(node); if (searchUIDIter != nodeUIDs.end()) { // store this node's ID nodeElement->SetAttribute("UID", searchUIDIter->second.c_str()); } auto searchSourcesIter = sourceUIDs.find(node); if (searchSourcesIter != sourceUIDs.end()) { // store all source IDs for (auto sourceUIDIter = searchSourcesIter->second.begin(); sourceUIDIter != searchSourcesIter->second.end(); ++sourceUIDIter) { auto *uidElement = document.NewElement("source"); uidElement->SetAttribute("UID", sourceUIDIter->c_str()); nodeElement->InsertEndChild(uidElement); } } // store basedata if (BaseData *data = node->GetData()) { // std::string filenameHint( node->GetName() ); bool error(false); auto *dataElement = SaveBaseData(document, data, filenameHint, error); // returns a reference to a file if (error) { m_FailedNodes->push_back(node); } // store basedata properties PropertyList *propertyList = data->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { auto *baseDataPropertiesElement = SavePropertyList(document, propertyList, filenameHint + "-data"); // returns a reference to a file dataElement->InsertEndChild(baseDataPropertiesElement); } nodeElement->InsertEndChild(dataElement); } // store all renderwindow specific propertylists mitk::DataNode::PropertyListKeyNames propertyListKeys = node->GetPropertyListNames(); for (const auto &renderWindowName : propertyListKeys) { PropertyList *propertyList = node->GetPropertyList(renderWindowName); if (propertyList && !propertyList->IsEmpty()) { auto *renderWindowPropertiesElement = SavePropertyList(document, propertyList, filenameHint + "-" + renderWindowName); // returns a reference to a file renderWindowPropertiesElement->SetAttribute("renderwindow", renderWindowName.c_str()); nodeElement->InsertEndChild(renderWindowPropertiesElement); } } // don't forget the renderwindow independent list PropertyList *propertyList = node->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { auto *propertiesElement = SavePropertyList(document, propertyList, filenameHint + "-node"); // returns a reference to a file nodeElement->InsertEndChild(propertiesElement); } document.InsertEndChild(nodeElement); } else { MITK_WARN << "Ignoring nullptr node during scene serialization."; } ProgressBar::GetInstance()->Progress(); } // end for all nodes } // end if sceneNodes std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); auto xmlFilename = defaultLocale_WorkingDirectory + Poco::Path::separator() + "index.xml"; if (tinyxml2::XML_SUCCESS != document.SaveFile(xmlFilename.c_str())) { MITK_ERROR << "Could not write scene to " << defaultLocale_WorkingDirectory << Poco::Path::separator() << "index.xml" << "\nTinyXML reports '" << document.ErrorStr() << "'"; return false; } else { try { Poco::File deleteFile(filename.c_str()); if (deleteFile.exists()) { deleteFile.remove(); } // create zip at filename std::ofstream file(filename.c_str(), std::ios::binary | std::ios::out); if (!file.good()) { MITK_ERROR << "Could not open a zip file for writing: '" << filename << "'"; return false; } else { Poco::Zip::Compress zipper(file, true); Poco::Path tmpdir(m_WorkingDirectory); zipper.addRecursive(tmpdir); zipper.close(); } try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; return false; // ok? } } catch (std::exception &e) { MITK_ERROR << "Could not create ZIP file from " << m_WorkingDirectory << "\nReason: " << e.what(); return false; } return true; } } catch (std::exception &e) { MITK_ERROR << "Caught exception during saving temporary files to disk. Error description: '" << e.what() << "'"; return false; } } tinyxml2::XMLElement *mitk::SceneIO::SaveBaseData(tinyxml2::XMLDocument &doc, BaseData *data, const std::string &filenamehint, bool &error) { assert(data); error = true; // find correct serializer // the serializer must // - create a file containing all information to recreate the BaseData object --> needs to know where to put this // file (and a filename?) // - TODO what to do about writers that creates one file per timestep? auto *element = doc.NewElement("data"); element->SetAttribute("type", data->GetNameOfClass()); // construct name of serializer class std::string serializername(data->GetNameOfClass()); serializername += "Serializer"; std::list thingsThatCanSerializeThis = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (thingsThatCanSerializeThis.size() < 1) { MITK_ERROR << "No serializer found for " << data->GetNameOfClass() << ". Skipping object"; } for (auto iter = thingsThatCanSerializeThis.begin(); iter != thingsThatCanSerializeThis.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetData(data); serializer->SetFilenameHint(filenamehint); std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename.c_str()); if (!writtenfilename.empty()) error = false; } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } element->SetAttribute("UID", data->GetUID().c_str()); return element; } tinyxml2::XMLElement *mitk::SceneIO::SavePropertyList(tinyxml2::XMLDocument &doc, PropertyList *propertyList, const std::string &filenamehint) { assert(propertyList); // - TODO what to do about shared properties (same object in two lists or behind several keys)? auto *element = doc.NewElement("properties"); // construct name of serializer class PropertyListSerializer::Pointer serializer = PropertyListSerializer::New(); serializer->SetPropertyList(propertyList); serializer->SetFilenameHint(filenamehint); std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename.c_str()); PropertyList::Pointer failedProperties = serializer->GetFailedProperties(); if (failedProperties.IsNotNull()) { // move failed properties to global list m_FailedProperties->ConcatenatePropertyList(failedProperties, true); } } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } return element; } const mitk::SceneIO::FailedBaseDataListType *mitk::SceneIO::GetFailedNodes() { return m_FailedNodes.GetPointer(); } const mitk::PropertyList *mitk::SceneIO::GetFailedProperties() { return m_FailedProperties; } void mitk::SceneIO::OnUnzipError(const void * /*pSender*/, std::pair &info) { ++m_UnzipErrors; MITK_ERROR << "Error while unzipping: " << info.second; } void mitk::SceneIO::OnUnzipOk(const void * /*pSender*/, std::pair & /*info*/) { // MITK_INFO << "Unzipped ok: " << info.second.toString(); } diff --git a/Modules/SceneSerialization/src/mitkSceneReaderV1.h b/Modules/SceneSerialization/src/mitkSceneReaderV1.h index ba69c6081e..2c370b1ad8 100644 --- a/Modules/SceneSerialization/src/mitkSceneReaderV1.h +++ b/Modules/SceneSerialization/src/mitkSceneReaderV1.h @@ -1,76 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSceneReaderV1_h #define mitkSceneReaderV1_h -#include "mitkSceneReader.h" +#include +#include namespace tinyxml2 { class XMLElement; } namespace mitk { class SceneReaderV1 : public SceneReader { public: mitkClassMacro(SceneReaderV1, SceneReader); itkFactorylessNewMacro(Self); itkCloneMacro(Self); bool LoadScene(tinyxml2::XMLDocument &document, const std::string &workingDirectory, DataStorage *storage) override; protected: /** \brief tries to create one DataNode from a given XML \ element */ DataNode::Pointer LoadBaseDataFromDataTag(const tinyxml2::XMLElement *dataElement, const PropertyList *properties, const std::string &workingDirectory, bool &error); /** \brief reads all the properties from the XML document and recreates them in node */ bool DecorateNodeWithProperties(DataNode *node, const tinyxml2::XMLElement *nodeElement, const std::string &workingDirectory); /** \brief Clear a default property list and handle some exceptions. Called after assigning a BaseData object to a fresh DataNode via SetData(). This call to SetData() would create default properties that have not been there when saving the scene. Since they can produce problems, we clear the list and use only those properties that we read from the scene file. This method also handles some exceptions for backwards compatibility. Those exceptions are documented directly in the code of the method. */ void ClearNodePropertyListWithExceptions(DataNode &node, PropertyList &propertyList); typedef std::pair> NodesAndParentsPair; typedef std::list OrderedNodesList; typedef std::map IDToNodeMappingType; typedef std::map NodeToIDMappingType; OrderedNodesList m_OrderedNodePairs; IDToNodeMappingType m_NodeForID; NodeToIDMappingType m_IDForNode; UIDGenerator m_UIDGen; }; } #endif diff --git a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h index 19eca9289d..0ac6ef7e12 100644 --- a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h +++ b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h @@ -1,151 +1,149 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkVectorPropertySerializer_h #define mitkVectorPropertySerializer_h #include "mitkBasePropertySerializer.h" #include "mitkVectorProperty.h" #include #include namespace mitk { /** \brief Serializes a VectorProperty Serializes an instance of VectorProperty into a XML structure like \verbatim \endverbatim This class is implemented as a template and makes use of std::stringstream for necessary conversions of specific data types to and from string. For numeric types, the class adds a precision token to stringstream that should usually suffice. */ template class MITKSCENESERIALIZATIONBASE_EXPORT VectorPropertySerializer : public BasePropertySerializer { public: // Expand manually most of mitkClassMacro: // mitkClassMacro(VectorProperty, mitk::BaseProperty); // This manual expansion is done to override explicitely // the GetNameOfClass methods typedef VectorProperty PropertyType; typedef VectorPropertySerializer Self; typedef BasePropertySerializer SuperClass; typedef itk::SmartPointer Pointer; typedef itk::SmartPointer ConstPointer; std::vector GetClassHierarchy() const override { return mitk::GetClassHierarchy(); } // This function must return different // strings in function of the template parameter! // Serialization depends on this feature. static const char *GetStaticNameOfClass() { // concatenate a prefix dependent on the template type and our own classname static std::string nameOfClass = std::string(VectorPropertyDataType::prefix()) + "VectorPropertySerializer"; return nameOfClass.c_str(); } const char *GetNameOfClass() const override { return this->GetStaticNameOfClass(); } itkFactorylessNewMacro(Self); itkCloneMacro(Self); //! Build an XML version of this property tinyxml2::XMLElement* Serialize(tinyxml2::XMLDocument& doc) override { auto *listElement = doc.NewElement("Values"); if (const PropertyType *prop = dynamic_cast(m_Property.GetPointer())) { typename PropertyType::VectorType elements = prop->GetValue(); unsigned int index(0); for (auto listEntry : elements) { std::stringstream indexS; indexS << index++; auto *entryElement = doc.NewElement("Value"); entryElement->SetAttribute("idx", indexS.str().c_str()); entryElement->SetAttribute("value", boost::lexical_cast(listEntry).c_str()); listElement->InsertEndChild(entryElement); } return listElement; } else { return nullptr; } } //! Construct a property from an XML serialization BaseProperty::Pointer Deserialize(const tinyxml2::XMLElement *listElement) override { typename PropertyType::VectorType datalist; if (listElement) { - unsigned int index(0); std::string valueString; DATATYPE value; for (auto *valueElement = listElement->FirstChildElement("Value"); valueElement; valueElement = valueElement->NextSiblingElement("Value")) { valueString = valueElement->Attribute("value"); if (valueString.empty()) { MITK_ERROR << "Missing value attribute in list"; return nullptr; } try { value = boost::lexical_cast(valueString); } catch (boost::bad_lexical_cast &e) { MITK_ERROR << "Could not parse '" << valueString << "' as number: " << e.what(); return nullptr; } datalist.push_back(value); - ++index; } typename PropertyType::Pointer property = PropertyType::New(); property->SetValue(datalist); return property.GetPointer(); } else { MITK_ERROR << "Missing tag."; } return nullptr; } }; typedef VectorPropertySerializer DoubleVectorPropertySerializer; typedef VectorPropertySerializer IntVectorPropertySerializer; } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp index 86e8a0b011..0aec6159c9 100644 --- a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp @@ -1,486 +1,484 @@ /*============================================================================ 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 "mitkCorrectorAlgorithm.h" #include "mitkContourUtils.h" #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageDataItem.h" #include #include "itkCastImageFilter.h" #include "itkImageDuplicator.h" #include "itkImageRegionIterator.h" mitk::CorrectorAlgorithm::CorrectorAlgorithm() : ImageToImageFilter(), m_FillColor(1), m_EraseColor(0) { } mitk::CorrectorAlgorithm::~CorrectorAlgorithm() { } template void ConvertBackToCorrectPixelType( itk::Image *, mitk::Image::Pointer target, itk::Image::Pointer segmentationPixelTypeImage) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::CastImageFilter CastImageFilterType; typename CastImageFilterType::Pointer castImageFilter = CastImageFilterType::New(); castImageFilter->SetInput(segmentationPixelTypeImage); castImageFilter->Update(); typename OutputImageType::Pointer tempItkImage = castImageFilter->GetOutput(); tempItkImage->DisconnectPipeline(); mitk::CastToMitkImage(tempItkImage, target); } void mitk::CorrectorAlgorithm::GenerateData() { Image::Pointer inputImage = ImageToImageFilter::GetInput(0); if (inputImage.IsNull() || inputImage->GetDimension() != 2) { itkExceptionMacro("CorrectorAlgorithm needs a 2D image as input."); } if (m_Contour.IsNull()) { itkExceptionMacro("CorrectorAlgorithm needs a Contour object as input."); } // copy the input (since m_WorkingImage will be changed later) m_WorkingImage = inputImage; TimeGeometry::Pointer originalGeometry = nullptr; if (inputImage->GetTimeGeometry()) { originalGeometry = inputImage->GetTimeGeometry()->Clone(); m_WorkingImage->SetTimeGeometry(originalGeometry); } else { itkExceptionMacro("Original image does not have a 'Time sliced geometry'! Cannot copy."); } Image::Pointer temporarySlice; // Convert to DefaultSegmentationDataType (because TobiasHeimannCorrectionAlgorithm relys on that data type) { itk::Image::Pointer correctPixelTypeImage; CastToItkImage(m_WorkingImage, correctPixelTypeImage); assert(correctPixelTypeImage.IsNotNull()); // possible bug in CastToItkImage ? // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479: // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 // && aSpacing[2]>0' failed // solution here: we overwrite it with an unity matrix itk::Image::DirectionType imageDirection; imageDirection.SetIdentity(); // correctPixelTypeImage->SetDirection(imageDirection); temporarySlice = this->GetOutput(); // temporarySlice = ImportItkImage( correctPixelTypeImage ); // m_FillColor = 1; m_EraseColor = 0; ImprovedHeimannCorrectionAlgorithm(correctPixelTypeImage); // this is suboptimal, needs to be kept synchronous to DefaultSegmentationDataType if (inputImage->GetChannelDescriptor().GetPixelType().GetComponentType() == itk::IOComponentEnum::USHORT) { // the cast at the beginning did not copy the data CastToMitkImage(correctPixelTypeImage, temporarySlice); } else { // it did copy the data and cast the pixel type AccessByItk_n(m_WorkingImage, ConvertBackToCorrectPixelType, (temporarySlice, correctPixelTypeImage)); } } temporarySlice->SetTimeGeometry(originalGeometry); } template itk::Index<2> mitk::CorrectorAlgorithm::ensureIndexInImage(ScalarType i0, ScalarType i1) { itk::Index<2> toReturn; itk::Size<5> size = m_WorkingImage->GetLargestPossibleRegion().GetSize(); toReturn[0] = std::min((ScalarType)(size[0] - 1), std::max((ScalarType)0.0, i0)); toReturn[1] = std::min((ScalarType)(size[1] - 1), std::max((ScalarType)0.0, i1)); return toReturn; } bool mitk::CorrectorAlgorithm::ImprovedHeimannCorrectionAlgorithm( itk::Image::Pointer pic) { /*! Some documentation (not by the original author) TobiasHeimannCorrectionAlgorithm will be called, when the user has finished drawing a freehand line. There should be different results, depending on the line's properties: 1. Without any prior segmentation, the start point and the end point of the drawn line will be connected to a contour and the area enclosed by the contour will be marked as segmentation. 2. When the whole line is inside a segmentation, start and end point will be connected to a contour and the area of this contour will be subtracted from the segmentation. 3. When the line starts inside a segmentation and ends outside with only a single transition from segmentation to no-segmentation, nothing will happen. 4. When there are multiple transitions between inside-segmentation and outside-segmentation, the line will be divided in so called segments. Each segment is either fully inside or fully outside a segmentation. When it is inside a segmentation, its enclosed area will be subtracted from the segmentation. When the segment is outside a segmentation, its enclosed area it will be added to the segmentation. The algorithm is described in full length in Tobias Heimann's diploma thesis (MBI Technical Report 145, p. 37 - 40). */ ContourModel::Pointer projectedContour = mitk::ContourModelUtils::ProjectContourTo2DSlice(m_WorkingImage, m_Contour); if (projectedContour.IsNull() || projectedContour->GetNumberOfVertices() < 2) return false; // Read the first point of the contour auto contourIter = projectedContour->Begin(); if (contourIter == projectedContour->End()) return false; itk::Index<2> previousIndex; previousIndex = ensureIndexInImage((*contourIter)->Coordinates[0], (*contourIter)->Coordinates[1]); ++contourIter; int currentColor = (pic->GetPixel(previousIndex) == m_FillColor); TSegData currentSegment; - int countOfSegments = 1; bool firstSegment = true; auto contourEnd = projectedContour->End(); for (; contourIter != contourEnd; ++contourIter) { // Get current point itk::Index<2> currentIndex; currentIndex = ensureIndexInImage((*contourIter)->Coordinates[0] + 0.5, (*contourIter)->Coordinates[1] + 0.5); // Calculate length and slope double slopeX = currentIndex[0] - previousIndex[0]; double slopeY = currentIndex[1] - previousIndex[1]; double length = std::sqrt(slopeX * slopeX + slopeY * slopeY); double deltaX = slopeX / length; double deltaY = slopeY / length; for (double i = 0; i <= length && length > 0; i += 1) { itk::Index<2> temporaryIndex; temporaryIndex = ensureIndexInImage(previousIndex[0] + deltaX * i, previousIndex[1] + deltaY * i); if (!pic->GetLargestPossibleRegion().IsInside(temporaryIndex)) continue; if ((pic->GetPixel(temporaryIndex) == m_FillColor) != currentColor) { currentSegment.points.push_back(temporaryIndex); if (!firstSegment) { ModifySegment(currentSegment, pic); } else { firstSegment = false; } currentSegment = TSegData(); - ++countOfSegments; currentColor = (pic->GetPixel(temporaryIndex) == m_FillColor); } currentSegment.points.push_back(temporaryIndex); } previousIndex = currentIndex; } return true; } void mitk::CorrectorAlgorithm::ColorSegment( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); int color = 0; if (colorMode) color = m_EraseColor; else color = m_FillColor; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); for (; indexIterator != indexEnd; ++indexIterator) { pic->SetPixel(*indexIterator, color); } } itk::Image::Pointer mitk::CorrectorAlgorithm::CloneImage( itk::Image::Pointer pic) { typedef itk::Image ItkImageType; typedef itk::ImageDuplicator DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(pic); duplicator->Update(); return duplicator->GetOutput(); } itk::Index<2> mitk::CorrectorAlgorithm::GetFirstPoint( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); itk::Index<2> index; for (; indexIterator != indexEnd; ++indexIterator) { for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); if ((pic->GetPixel(index) == m_FillColor) != colorMode) { return index; } } } } mitkThrow() << "No Starting point is found next to the curve."; } std::vector> mitk::CorrectorAlgorithm::FindSeedPoints( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; std::vector> seedPoints; try { itk::Index<2> firstPoint = GetFirstPoint(segment, pic); seedPoints.push_back(firstPoint); } catch (const mitk::Exception&) { return seedPoints; } if (segment.points.size() < 4) return seedPoints; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); ItkImagePointerType listOfPoints = CloneImage(pic); listOfPoints->FillBuffer(0); listOfPoints->SetPixel(seedPoints[0], 1); for (; indexIterator != indexEnd; ++indexIterator) { listOfPoints->SetPixel(*indexIterator, 2); } indexIterator = segment.points.begin(); indexIterator++; indexIterator++; indexEnd--; indexEnd--; for (; indexIterator != indexEnd; ++indexIterator) { bool pointFound = true; while (pointFound) { pointFound = false; itk::Index<2> index; itk::Index<2> index2; for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); index2 = index; if (listOfPoints->GetPixel(index2) > 0) continue; index[0] = index[0] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] - 1; index[1] = index[1] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[1] = index[1] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } } } } } return seedPoints; } int mitk::CorrectorAlgorithm::FillRegion( const std::vector> &seedPoints, itk::Image::Pointer pic) { int numberOfPixel = 0; int mode = (pic->GetPixel(seedPoints[0]) == m_FillColor); int drawColor = m_FillColor; if (mode) { drawColor = m_EraseColor; } std::vector> workPoints; workPoints = seedPoints; // workPoints.push_back(seedPoints[0]); while (workPoints.size() > 0) { itk::Index<2> currentIndex = workPoints.back(); workPoints.pop_back(); if ((pic->GetPixel(currentIndex) == m_FillColor) == mode) ++numberOfPixel; pic->SetPixel(currentIndex, drawColor); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] + 2, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1] - 1); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0], currentIndex[1] + 2); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); } return numberOfPixel; } void mitk::CorrectorAlgorithm::OverwriteImage( itk::Image::Pointer source, itk::Image::Pointer target) { typedef itk::Image ItkImageType; typedef itk::ImageRegionIterator ImageIteratorType; ImageIteratorType sourceIter(source, source->GetLargestPossibleRegion()); ImageIteratorType targetIter(target, target->GetLargestPossibleRegion()); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } bool mitk::CorrectorAlgorithm::ModifySegment(const TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; ItkImagePointerType firstSideImage = CloneImage(pic); ColorSegment(segment, firstSideImage); ItkImagePointerType secondSideImage = CloneImage(firstSideImage); std::vector> seedPoints = FindSeedPoints(segment, firstSideImage); if (seedPoints.size() < 1) return false; int firstSidePixel = FillRegion(seedPoints, firstSideImage); std::vector> secondSeedPoints = FindSeedPoints(segment, firstSideImage); if (secondSeedPoints.size() < 1) return false; int secondSidePixel = FillRegion(secondSeedPoints, secondSideImage); if (firstSidePixel < secondSidePixel) { OverwriteImage(firstSideImage, pic); } else { OverwriteImage(secondSideImage, pic); } return true; } diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp index 94b63e8320..967788e10a 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp @@ -1,411 +1,425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentAnythingTool.h" #include #include #include #include "mitkInteractionPositionEvent.h" #include "mitkPointSetShapeProperty.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include // us #include #include #include #include #include #include "mitkImageAccessByItk.h" using namespace std::chrono_literals; namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, SegmentAnythingTool, "SegmentAnythingTool"); } mitk::SegmentAnythingTool::SegmentAnythingTool() : SegWithPreviewTool(true, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); this->IsTimePointChangeAwareOff(); this->KeepActiveAfterAcceptOn(); } const char **mitk::SegmentAnythingTool::GetXPM() const { return nullptr; } const char *mitk::SegmentAnythingTool::GetName() const { return "Segment Anything"; } us::ModuleResource mitk::SegmentAnythingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } void mitk::SegmentAnythingTool::Activated() { Superclass::Activated(); m_PointSetPositive = mitk::PointSet::New(); m_PointSetNodePositive = mitk::DataNode::New(); m_PointSetNodePositive->SetData(m_PointSetPositive); m_PointSetNodePositive->SetName(std::string(this->GetName()) + "_PointSetPositive"); m_PointSetNodePositive->SetBoolProperty("helper object", true); m_PointSetNodePositive->SetColor(0.0, 1.0, 0.0); m_PointSetNodePositive->SetVisibility(true); m_PointSetNodePositive->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodePositive->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodePositive, this->GetToolManager()->GetWorkingData(0)); m_PointSetNegative = mitk::PointSet::New(); m_PointSetNodeNegative = mitk::DataNode::New(); m_PointSetNodeNegative->SetData(m_PointSetNegative); m_PointSetNodeNegative->SetName(std::string(this->GetName()) + "_PointSetNegative"); m_PointSetNodeNegative->SetBoolProperty("helper object", true); m_PointSetNodeNegative->SetColor(1.0, 0.0, 0.0); m_PointSetNodeNegative->SetVisibility(true); m_PointSetNodeNegative->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodeNegative->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodeNegative, this->GetToolManager()->GetWorkingData(0)); this->SetLabelTransferScope(LabelTransferScope::ActiveLabel); this->SetLabelTransferMode(LabelTransferMode::MapLabel); } void mitk::SegmentAnythingTool::Deactivated() { this->ClearSeeds(); GetDataStorage()->Remove(m_PointSetNodePositive); GetDataStorage()->Remove(m_PointSetNodeNegative); m_PointSetNodePositive = nullptr; m_PointSetNodeNegative = nullptr; m_PointSetPositive = nullptr; m_PointSetNegative = nullptr; m_PythonService.reset(); Superclass::Deactivated(); } void mitk::SegmentAnythingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddNegativePoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPositivePoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::SegmentAnythingTool::InitSAMPythonProcess() { if (nullptr != m_PythonService) { m_PythonService.reset(); } this->ClearPicks(); m_PythonService = std::make_unique( this->GetPythonPath(), this->GetModelType(), this->GetCheckpointPath(), this->GetGpuId()); m_PythonService->StartAsyncProcess(); } bool mitk::SegmentAnythingTool::IsPythonReady() const { return m_PythonService->CheckStatus(); } void mitk::SegmentAnythingTool::OnAddNegativePoint(StateMachineAction *, InteractionEvent *interactionEvent) { if (!this->GetIsReady() || m_PointSetPositive->GetSize() == 0 || nullptr == this->GetWorkingPlaneGeometry() || !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry()))) { return; } if (!this->IsUpdating() && m_PointSetNegative.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSetNegative->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); m_PointSetCount++; this->UpdatePreview(); } } } void mitk::SegmentAnythingTool::OnAddPositivePoint(StateMachineAction *, InteractionEvent *interactionEvent) { if (!this->GetIsReady()) { return; } m_IsGenerateEmbeddings = false; if ((nullptr == this->GetWorkingPlaneGeometry()) || !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry()))) { m_IsGenerateEmbeddings = true; this->ClearSeeds(); this->SetWorkingPlaneGeometry(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone()); } if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSetPositive->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); ++m_PointSetCount; this->UpdatePreview(); } } } void mitk::SegmentAnythingTool::OnDelete(StateMachineAction *, InteractionEvent *) { if (!this->IsUpdating() && m_PointSetPositive.IsNotNull() && m_PointSetNegative.IsNotNull()) { PointSet::Pointer removeSet = m_PointSetPositive; decltype(m_PointSetPositive->GetMaxId().Index()) maxId = 0; if (m_PointSetPositive->GetSize() > 0) { maxId = m_PointSetPositive->GetMaxId().Index(); } if (m_PointSetNegative->GetSize() > 0 && (maxId < m_PointSetNegative->GetMaxId().Index())) { removeSet = m_PointSetNegative; } removeSet->RemovePointAtEnd(0); --m_PointSetCount; this->UpdatePreview(); } } void mitk::SegmentAnythingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::SegmentAnythingTool::HasPicks() const { return this->m_PointSetPositive.IsNotNull() && this->m_PointSetPositive->GetSize() > 0; } void mitk::SegmentAnythingTool::ClearSeeds() { if (this->m_PointSetPositive.IsNotNull()) { m_PointSetCount -= m_PointSetPositive->GetSize(); this->m_PointSetPositive = mitk::PointSet::New(); // renew pointset this->m_PointSetNodePositive->SetData(this->m_PointSetPositive); } if (this->m_PointSetNegative.IsNotNull()) { m_PointSetCount -= m_PointSetNegative->GetSize(); this->m_PointSetNegative = mitk::PointSet::New(); // renew pointset this->m_PointSetNodeNegative->SetData(this->m_PointSetNegative); } } void mitk::SegmentAnythingTool::ConfirmCleanUp() { auto previewImage = this->GetPreviewSegmentation(); for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } this->ClearSeeds(); RenderingManager::GetInstance()->RequestUpdateAll(); } template void mitk::SegmentAnythingTool::ITKWindowing(const itk::Image *inputImage, Image *mitkImage, ScalarType min, ScalarType max) { typedef itk::Image ImageType; typedef itk::IntensityWindowingImageFilter IntensityFilterType; typename IntensityFilterType::Pointer filter = IntensityFilterType::New(); filter->SetInput(inputImage); filter->SetWindowMinimum(min); filter->SetWindowMaximum(max); filter->SetOutputMinimum(min); filter->SetOutputMaximum(max); filter->Update(); mitkImage->SetImportVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), 0, 0, Image::ManageMemory); filter->GetOutput()->GetPixelContainer()->ContainerManageMemoryOff(); } +namespace +{ + // Checks if the image has valid size across each dimension. The check is + // critical for 2D images since 2D image are not valid in Saggital and Coronal views. + bool IsImageAtTimeStepValid(const mitk::Image *inputAtTimeStep) + { + int total = 0; + total += (inputAtTimeStep->GetDimension(0) > 1); + total += (inputAtTimeStep->GetDimension(1) > 1); + total += (inputAtTimeStep->GetDimension(2) > 1); + return (total > 1); + } +} + void mitk::SegmentAnythingTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { - if (nullptr != previewImage && m_PointSetPositive.IsNotNull()) + if (nullptr != previewImage && m_PointSetPositive.IsNotNull() && ::IsImageAtTimeStepValid(inputAtTimeStep)) { if (this->HasPicks() && nullptr != m_PythonService) { mitk::LevelWindow levelWindow; this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); std::string uniquePlaneID = GetHashForCurrentPlane(levelWindow); m_ProgressCommand->SetProgress(20); try { std::stringstream csvStream; this->EmitSAMStatusMessageEvent("Prompting Segment Anything Model..."); m_ProgressCommand->SetProgress(50); if (inputAtTimeStep->GetPixelType().GetNumberOfComponents() < 2) { auto filteredImage = mitk::Image::New(); filteredImage->Initialize(inputAtTimeStep); AccessByItk_n(inputAtTimeStep, ITKWindowing, // apply level window filter (filteredImage, levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound())); m_PythonService->TransferImageToProcess(filteredImage, uniquePlaneID); csvStream = this->GetPointsAsCSVString(filteredImage->GetGeometry()); } else { m_PythonService->TransferImageToProcess(inputAtTimeStep, uniquePlaneID); csvStream = this->GetPointsAsCSVString(inputAtTimeStep->GetGeometry()); } m_ProgressCommand->SetProgress(100); m_PythonService->TransferPointsToProcess(csvStream); m_ProgressCommand->SetProgress(150); std::this_thread::sleep_for(100ms); mitk::LabelSetImage::Pointer outputBuffer = m_PythonService->RetrieveImageFromProcess(this->GetTimeOutLimit()); m_ProgressCommand->SetProgress(180); mitk::SegTool2D::WriteSliceToVolume(previewImage, this->GetWorkingPlaneGeometry(), outputBuffer.GetPointer(), timeStep, false); this->SetSelectedLabels({MASK_VALUE}); this->EmitSAMStatusMessageEvent("Successfully generated segmentation."); } catch (const mitk::Exception &e) { this->ClearPicks(); this->EmitSAMStatusMessageEvent(e.GetDescription()); mitkThrow() << e.GetDescription(); } } else if (nullptr != this->GetWorkingPlaneGeometry()) { this->ResetPreviewContentAtTimeStep(timeStep); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } } } std::string mitk::SegmentAnythingTool::GetHashForCurrentPlane(const mitk::LevelWindow &levelWindow) { mitk::Vector3D normal = this->GetWorkingPlaneGeometry()->GetNormal(); mitk::Point3D center = this->GetWorkingPlaneGeometry()->GetCenter(); std::stringstream hashstream; hashstream << std::setprecision(3) << std::fixed << normal[0]; //Consider only 3 digits after decimal hashstream << std::setprecision(3) << std::fixed << normal[1]; hashstream << std::setprecision(3) << std::fixed << normal[2]; hashstream << std::setprecision(3) << std::fixed << center[0]; hashstream << std::setprecision(3) << std::fixed << center[1]; hashstream << std::setprecision(3) << std::fixed << center[2]; hashstream << levelWindow.GetLowerWindowBound(); hashstream << levelWindow.GetUpperWindowBound(); size_t hashVal = std::hash{}(hashstream.str()); return std::to_string(hashVal); } std::stringstream mitk::SegmentAnythingTool::GetPointsAsCSVString(const mitk::BaseGeometry *baseGeometry) { MITK_INFO << "No.of points: " << m_PointSetPositive->GetSize(); std::stringstream pointsAndLabels; pointsAndLabels << "Point,Label\n"; mitk::PointSet::PointsConstIterator pointSetItPos = m_PointSetPositive->Begin(); mitk::PointSet::PointsConstIterator pointSetItNeg = m_PointSetNegative->Begin(); const char SPACE = ' '; while (pointSetItPos != m_PointSetPositive->End() || pointSetItNeg != m_PointSetNegative->End()) { if (pointSetItPos != m_PointSetPositive->End()) { mitk::Point3D point = pointSetItPos.Value(); if (baseGeometry->IsInside(point)) { Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); pointsAndLabels << static_cast(p2D[0]) << SPACE << static_cast(p2D[1]) << ",1" << std::endl; } ++pointSetItPos; } if (pointSetItNeg != m_PointSetNegative->End()) { mitk::Point3D point = pointSetItNeg.Value(); if (baseGeometry->IsInside(point)) { Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); pointsAndLabels << static_cast(p2D[0]) << SPACE << static_cast(p2D[1]) << ",0" << std::endl; } ++pointSetItNeg; } } return pointsAndLabels; } std::vector> mitk::SegmentAnythingTool::GetPointsAsVector( const mitk::BaseGeometry *baseGeometry) { std::vector> clickVec; clickVec.reserve(m_PointSetPositive->GetSize() + m_PointSetNegative->GetSize()); mitk::PointSet::PointsConstIterator pointSetItPos = m_PointSetPositive->Begin(); mitk::PointSet::PointsConstIterator pointSetItNeg = m_PointSetNegative->Begin(); while (pointSetItPos != m_PointSetPositive->End() || pointSetItNeg != m_PointSetNegative->End()) { if (pointSetItPos != m_PointSetPositive->End()) { mitk::Point3D point = pointSetItPos.Value(); if (baseGeometry->IsInside(point)) { Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); clickVec.push_back(std::pair(p2D, "1")); } ++pointSetItPos; } if (pointSetItNeg != m_PointSetNegative->End()) { mitk::Point3D point = pointSetItNeg.Value(); if (baseGeometry->IsInside(point)) { Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); clickVec.push_back(std::pair(p2D, "0")); } ++pointSetItNeg; } } return clickVec; } mitk::Point2D mitk::SegmentAnythingTool::Get2DIndicesfrom3DWorld(const mitk::BaseGeometry *baseGeometry, const mitk::Point3D &point3d) { mitk::Point3D index3D; baseGeometry->WorldToIndex(point3d, index3D); MITK_INFO << index3D[0] << " " << index3D[1] << " " << index3D[2]; // remove Point2D point2D; point2D.SetElement(0, index3D[0]); point2D.SetElement(1, index3D[1]); return point2D; } void mitk::SegmentAnythingTool::EmitSAMStatusMessageEvent(const std::string& status) { SAMStatusMessageEvent.Send(status); } diff --git a/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp b/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp index c4a9b811b1..6c45ebb90b 100644 --- a/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp +++ b/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp @@ -1,414 +1,414 @@ /*============================================================================ 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 "mitkMorphologicalOperations.h" #include #include #include #include #include #include #include #include #include #include #include void mitk::MorphologicalOperations::Closing(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Closing..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkClosing, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkClosing, image, factor, structuralElement); } MITK_INFO << "Finished Closing"; } void mitk::MorphologicalOperations::Erode(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Erode..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkErode, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkErode, image, factor, structuralElement); } MITK_INFO << "Finished Erode"; } void mitk::MorphologicalOperations::Dilate(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Dilate..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkDilate, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkDilate, image, factor, structuralElement); } MITK_INFO << "Finished Dilate"; } void mitk::MorphologicalOperations::Opening(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Opening..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkOpening, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkOpening, image, factor, structuralElement); } MITK_INFO << "Finished Opening"; } void mitk::MorphologicalOperations::FillHoles(mitk::Image::Pointer &image) { MITK_INFO << "Start FillHole..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_1(img3D, itkFillHoles, img3D); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_1(image, itkFillHoles, image); } MITK_INFO << "Finished FillHole"; } template void mitk::MorphologicalOperations::itkClosing( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryMorphologicalClosingImageFilter BallClosingFilterType; typedef typename itk::BinaryMorphologicalClosingImageFilter CrossClosingFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallClosingFilterType::Pointer closingFilter = BallClosingFilterType::New(); closingFilter->SetKernel(ball); closingFilter->SetInput(sourceImage); closingFilter->SetForegroundValue(1); closingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(closingFilter->GetOutput(), resultImage); + resultImage->SetVolume(closingFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossClosingFilterType::Pointer closingFilter = CrossClosingFilterType::New(); closingFilter->SetKernel(cross); closingFilter->SetInput(sourceImage); closingFilter->SetForegroundValue(1); closingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(closingFilter->GetOutput(), resultImage); + resultImage->SetVolume(closingFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkErode( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryErodeImageFilter BallErodeFilterType; typedef typename itk::BinaryErodeImageFilter CrossErodeFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallErodeFilterType::Pointer erodeFilter = BallErodeFilterType::New(); erodeFilter->SetKernel(ball); erodeFilter->SetInput(sourceImage); erodeFilter->SetErodeValue(1); erodeFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(erodeFilter->GetOutput(), resultImage); + resultImage->SetVolume(erodeFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossErodeFilterType::Pointer erodeFilter = CrossErodeFilterType::New(); erodeFilter->SetKernel(cross); erodeFilter->SetInput(sourceImage); erodeFilter->SetErodeValue(1); erodeFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(erodeFilter->GetOutput(), resultImage); + resultImage->SetVolume(erodeFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkDilate( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryDilateImageFilter BallDilateFilterType; typedef typename itk::BinaryDilateImageFilter CrossDilateFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallDilateFilterType::Pointer dilateFilter = BallDilateFilterType::New(); dilateFilter->SetKernel(ball); dilateFilter->SetInput(sourceImage); dilateFilter->SetDilateValue(1); dilateFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(dilateFilter->GetOutput(), resultImage); + resultImage->SetVolume(dilateFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossDilateFilterType::Pointer dilateFilter = CrossDilateFilterType::New(); dilateFilter->SetKernel(cross); dilateFilter->SetInput(sourceImage); dilateFilter->SetDilateValue(1); dilateFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(dilateFilter->GetOutput(), resultImage); + resultImage->SetVolume(dilateFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkOpening( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryMorphologicalOpeningImageFilter BallOpeningFiltertype; typedef typename itk::BinaryMorphologicalOpeningImageFilter CrossOpeningFiltertype; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallOpeningFiltertype::Pointer openingFilter = BallOpeningFiltertype::New(); openingFilter->SetKernel(ball); openingFilter->SetInput(sourceImage); openingFilter->SetForegroundValue(1); openingFilter->SetBackgroundValue(0); openingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(openingFilter->GetOutput(), resultImage); + resultImage->SetVolume(openingFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossOpeningFiltertype::Pointer openingFilter = CrossOpeningFiltertype::New(); openingFilter->SetKernel(cross); openingFilter->SetInput(sourceImage); openingFilter->SetForegroundValue(1); openingFilter->SetBackgroundValue(0); openingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(openingFilter->GetOutput(), resultImage); + resultImage->SetVolume(openingFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkFillHoles(itk::Image *sourceImage, mitk::Image::Pointer &resultImage) { typedef itk::Image ImageType; typedef typename itk::BinaryFillholeImageFilter FillHoleFilterType; typename FillHoleFilterType::Pointer fillHoleFilter = FillHoleFilterType::New(); fillHoleFilter->SetInput(sourceImage); fillHoleFilter->SetForegroundValue(1); fillHoleFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(fillHoleFilter->GetOutput(), resultImage); + resultImage->SetVolume(fillHoleFilter->GetOutput()->GetBufferPointer()); } template TStructuringElement mitk::MorphologicalOperations::CreateStructuringElement(StructuralElementType structuralElementFlag, int factor) { TStructuringElement strElem; typename TStructuringElement::SizeType size; size.Fill(0); switch (structuralElementFlag) { case Ball_Axial: case Cross_Axial: size.SetElement(0, factor); size.SetElement(1, factor); break; case Ball_Coronal: case Cross_Coronal: size.SetElement(0, factor); size.SetElement(2, factor); break; case Ball_Sagittal: case Cross_Sagittal: size.SetElement(1, factor); size.SetElement(2, factor); break; case Ball: case Cross: size.Fill(factor); break; } strElem.SetRadius(size); strElem.CreateStructuringElement(); return strElem; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 6311220ef5..2b2002ec07 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1131 +1,1139 @@ /*============================================================================ 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 #include #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; auto newLabel = group->AddLabel(templateLabel, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); auto group = m_Segmentation->GetLabelSet(containingGroup); m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal({ label->GetValue() }); } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal(relevantLabels); } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } + else + { + this->SetSelectedLabels({}); + } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } - auto question = QStringLiteral("Do you really want to delete the group of the selected label with all labels?"); - auto answer = QMessageBox::question(this, "Delete group", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + const auto* selectedLabel = this->GetFirstSelectedLabelObject(); - if (answer != QMessageBox::Yes) + if (selectedLabel == nullptr) return; - auto selectedLabel = this->GetFirstSelectedLabelObject(); const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); + auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); + auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (answer != QMessageBox::Yes) + return; + this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); - auto question = QStringLiteral("Do you really want to delete the current group with all labels?"); - auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); + auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything in group but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels, group](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); group->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 315e7952fb..bbf9702b83 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,999 +1,1021 @@ /*============================================================================ 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 "QmitkMultiLabelTreeModel.h" #include "mitkRenderingManager.h" #include "QmitkStyleManager.h" class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_parentItem(parentItem), m_ItemType(type), m_Label(label), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* ParentItem() const { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* NextSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row > 0) return m_parentItem->m_childItems[row-1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; /** returns a vector containing all label values of referenced by this item or its child items.*/ std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const { if (this->m_ItemType == ItemType::Instance) { return { this->GetLabelValue() }; } std::vector< mitk::LabelSetImage::LabelValueType> result; for (const auto child : this->m_childItems) { auto childresult = child->GetLabelsInSubTree(); result.reserve(result.size() + childresult.size()); result.insert(result.end(), childresult.begin(), childresult.end()); } return result; } std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) { QModelIndex parentIndex = QModelIndex(); if (nullptr != start->m_parentItem) { parentIndex = GetIndexByItem(start->m_parentItem, model); } else { return parentIndex; } return model->index(start->Row(), 0, parentIndex); } QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::GroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) { if (nullptr != root && groupIndex < root->m_childItems.size()) { return root->m_childItems[groupIndex]; } return nullptr; } QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) { QmitkMultiLabelSegTreeItem* result = nullptr; for (auto item : root->m_childItems) { result = GetInstanceItem(labelValue, item); if (nullptr != result) return result; } if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) { return root; } return nullptr; } const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) { const QmitkMultiLabelSegTreeItem* result = nullptr; if (nullptr != startItem) { if (startItem->HandleAsInstance()) { result = startItem; } else if (!startItem->m_childItems.empty()) { result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); } } return result; } QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) { if (nullptr != group) { auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); if (group->m_childItems.end() != finding) { return *finding; } } return nullptr; } QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) , m_Observed(false) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group %1").arg(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) name = name + QString(" (%1 instances)").arg(item->m_childItems.size()); return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" [%1]").arg(item->GetLabelValue())); } } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } } } else if (role == ItemModelRole::LabelDataRole) { auto label = item->GetLabel(); if (nullptr!=label) return QVariant::fromValue(label); } else if (role == ItemModelRole::LabelValueRole) { auto label = item->GetLabel(); if (nullptr != label) return QVariant(label->GetValue()); } else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } else if (role == ItemModelRole::GroupIDRole) { QVariant v; v.setValue(item->GetGroupID()); return v; } return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) return QModelIndex(); auto labelItem = relevantItem->ParentItem(); if (labelItem->m_childItems.size() == 1) { //was the only instance of the label, therefor return the label item instat. relevantItem = labelItem; } return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const { auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { - resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); - if (nullptr != resultItem) break; + const auto* sibling = searchItem; - //no next closest label instance on this level -> check for closest before - resultItem = GetFirstInstanceLikeItem(searchItem->PrevSibblingItem()); - if (nullptr != resultItem) break; + while (sibling != nullptr) + { + sibling = sibling->NextSibblingItem(); + resultItem = GetFirstInstanceLikeItem(sibling); + + if (nullptr != resultItem) + break; + } + + if (nullptr != resultItem) + break; + + // No next closest label instance on this level -> check for closest before + sibling = searchItem; + + while (sibling != nullptr) + { + sibling = sibling->PrevSibblingItem(); + resultItem = GetFirstInstanceLikeItem(sibling); + + if (nullptr != resultItem) + break; + } + + if (nullptr != resultItem) + break; - //no closest label instance before current on this level -> moeve one level up + // No closest label instance before current on this level -> moeve one level up searchItem = searchItem->ParentItem(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; return currentItem->GetLabelsInSubTree(); } std::vector QmitkMultiLabelTreeModel::GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (currentIndex.isValid()) { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Group == currentItem->m_ItemType) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Instance == currentItem->m_ItemType) currentItem = currentItem->ParentItem(); return currentItem->GetLabelsInSubTree(); } Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { this->RemoveObserver(); this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { auto labelSet = m_Segmentation->GetLabelSet(groupID); for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) { if (lIter->first== mitk::LabelSetImage::UnlabeledValue) continue; bool newItemCreated = false; AddLabelToGroupTree(lIter->second, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } void QmitkMultiLabelTreeModel::AddObserver() { if (this->m_Segmentation.IsNotNull()) { if (m_Observed) { MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); m_Observed = true; } } void QmitkMultiLabelTreeModel::RemoveObserver() { if (this->m_Segmentation.IsNotNull()) { this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); } m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { GroupIndexType groupIndex = 0; if (m_Segmentation->IsLabelInGroup(labelValue, groupIndex)) { auto label = m_Segmentation->GetLabel(labelValue); if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); bool newLabelCreated = false; auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); if (newLabelCreated) { if (groupItem->m_childItems.size() == 1) { //first label added auto groupIndex = GetIndexByItem(groupItem, this); emit dataChanged(groupIndex, groupIndex); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } else { //whole new label level added to group item auto groupIndex = GetIndexByItem(groupItem, this); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } } else { if (instanceItem->ParentItem()->m_childItems.size() < 3) { //second instance item was added, so label item will now able to colapse // -> the whole label node has to be updated. auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); emit dataChanged(labelIndex, labelIndex); this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); this->endInsertRows(); } else { // instance item was added to existing label item with multiple instances //-> just notify the row insertion auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); this->endInsertRows(); } } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } else if (labelItem->m_childItems.size() == 2) { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); emit dataChanged(labelIndex, labelIndex); } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::OnGroupAdded(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } void QmitkMultiLabelTreeModel::OnGroupModified(GroupIndexType /*groupIndex*/) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; } bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; } bool QmitkMultiLabelTreeModel::GetAllowLockModification() const { return m_AllowLockModification; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp index 2cd4e90488..326bbc522f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp @@ -1,183 +1,274 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file.s ============================================================================*/ #include "QmitkSetupVirtualEnvUtil.h" #include "mitkLog.h" #include #include +#include +#include +#include +#include +#include QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil() { m_BaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + qApp->organizationName() + QDir::separator(); } QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil(const QString &baseDir) { m_BaseDir = baseDir; } QString& QmitkSetupVirtualEnvUtil::GetBaseDir() { return m_BaseDir; } QString QmitkSetupVirtualEnvUtil::GetVirtualEnvPath() { return m_venvPath; } QString& QmitkSetupVirtualEnvUtil::GetSystemPythonPath() { return m_SysPythonPath; } QString& QmitkSetupVirtualEnvUtil::GetPythonPath() { return m_PythonPath; } QString& QmitkSetupVirtualEnvUtil::GetPipPath() { return m_PipPath; } void QmitkSetupVirtualEnvUtil::SetVirtualEnvPath(const QString &path) { m_venvPath = path; } void QmitkSetupVirtualEnvUtil::SetPipPath(const QString &path) { m_PipPath = path; } void QmitkSetupVirtualEnvUtil::SetPythonPath(const QString &path) { if (this->IsPythonPath(path)) { m_PythonPath = path; } else { MITK_INFO << "Python was not detected in " + path.toStdString(); } } void QmitkSetupVirtualEnvUtil::SetSystemPythonPath(const QString &path) { if (this->IsPythonPath(path)) { m_SysPythonPath = path; } else { MITK_INFO << "Python was not detected in " + path.toStdString(); } } void QmitkSetupVirtualEnvUtil::PrintProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void QmitkSetupVirtualEnvUtil::InstallPytorch(const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *)) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("pip"); args.push_back("install"); - args.push_back("light-the-torch"); + args.push_back("light-the-torch==0.7.5"); spExec->Execute(workingDir, "python", args); PipInstall("torch==2.0.0", workingDir, callback, "ltt"); PipInstall("torchvision==0.15.0", workingDir, callback, "ltt"); } void QmitkSetupVirtualEnvUtil::InstallPytorch() { this->InstallPytorch(GetPythonPath().toStdString(), &PrintProcessEvent); } void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("install"); args.push_back(library); spExec->Execute(workingDir, command, args); } void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, void (*callback)(itk::Object*, const itk::EventObject&, void*), const std::string& command) { this->PipInstall(library, this->GetPipPath().toStdString(), callback, command); } void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &pythonCode, const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-c"); args.push_back(pythonCode); spExec->Execute(workingDir, command, args); } void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &args, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { this->ExecutePython(args, this->GetPythonPath().toStdString(), callback, command); } bool QmitkSetupVirtualEnvUtil::IsPythonPath(const QString &pythonPath) { QString fullPath = pythonPath; bool isExists = #ifdef _WIN32 QFile::exists(fullPath + QDir::separator() + QString("python.exe")); #else QFile::exists(fullPath + QDir::separator() + QString("python3")); #endif return isExists; } + +namespace +{ + std::mutex mutex; + std::string pyVersionCaptured; + + void CapturePyVersion(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) + { + std::string testCOUT; + const auto *pEvent = dynamic_cast(&e); + if (pEvent) + { + pyVersionCaptured = pEvent->GetOutput(); + } + } + + std::vector SplitVersionString(const std::string& version) + { + std::vector splits; + std::string part; + std::istringstream tokenStream(version); + while (std::getline(tokenStream, part, '.')) + { + splits.push_back(std::stoi(part)); + } + return splits; + } + + bool IsSupported(const std::string& version, const std::string& low, const std::string& high) + { + std::vector inHandVersion = SplitVersionString(version); + std::vector targetLowVersion = SplitVersionString(low); + std::vector targetHighVersion = SplitVersionString(high); + if (inHandVersion.size() > 1 && targetLowVersion.size() > 1 && targetHighVersion.size() > 1) + { // comparing second part of the version + return (inHandVersion[1] > targetLowVersion[1] && inHandVersion[1] < targetHighVersion[1]); + } + return false; + } +} + +std::pair QmitkSetupVirtualEnvUtil::GetExactPythonPath(const QString &pyEnv) +{ + QString fullPath = pyEnv; + bool pythonDoesExist = false; + bool isSupportedVersion = false; +#ifdef _WIN32 + const std::string PYTHON_EXE = "python.exe"; + // check if python exist in given folder. + pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString::fromStdString(PYTHON_EXE)); + if (!pythonDoesExist && // check if in Scripts already, if not go there + !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + } +#else + const std::string PYTHON_EXE = "python3"; + pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString::fromStdString(PYTHON_EXE)); + if (!pythonDoesExist && + !(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + pythonDoesExist = QFile::exists(fullPath + QDir::separator() + QString("python3")); + } +#endif + std::pair pythonPath; + if (pythonDoesExist) + { + ::mutex.lock(); + std::regex sanitizer(R"([^3\.(\d+)])"); + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&::CapturePyVersion); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + args.push_back("--version"); + spExec->Execute(fullPath.toStdString(), PYTHON_EXE, args); + std::string pyVersionNumber = std::regex_replace(::pyVersionCaptured, sanitizer, ""); + isSupportedVersion = ::IsSupported(pyVersionNumber, "3.8", "3.13"); + pythonPath.second = QString::fromStdString(pyVersionNumber); + ::mutex.unlock(); + } + pythonPath.first = pythonDoesExist &&isSupportedVersion ? fullPath : ""; + return pythonPath; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h index 3f822b66dc..518fc5addc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.h @@ -1,195 +1,200 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file.s ============================================================================*/ #ifndef QmitkSetupVirtualEnvUtil_h_Included #define QmitkSetupVirtualEnvUtil_h_Included #include "mitkLog.h" #include "mitkProcessExecutor.h" #include #include -#include -#include /** * @brief Abstract Class to Setup a python virtual environment and pip install required packages. * Derive this class for creating installer for the respective tool. */ class MITKSEGMENTATIONUI_EXPORT QmitkSetupVirtualEnvUtil { public: QmitkSetupVirtualEnvUtil(const QString& baseDir); QmitkSetupVirtualEnvUtil(); /** * @brief Implement the method in child class * to setup the virtual environment. */ virtual bool SetupVirtualEnv(const QString& venvName) = 0; /** * @brief Get the Virtual Env Path object. Override this method in the respective * tool installer class. * * @return QString */ virtual QString GetVirtualEnvPath(); /** * @brief Function to Pip install a library package given the location of * pip3 executable. * Any callback function can be passed to process the output. * * @param library * @param workingDir * @param callback * @param command */ void PipInstall(const std::string &library, const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command = "pip3"); /** * @brief Overloaded function to Pip install a library function. * * @param library * @param callback * @param command */ void PipInstall(const std::string &library, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command = "pip3"); /** * @brief Function to execute any python code given a python path. * Any callback function can be passed to process the output. * * @param args * @param pythonPath * @param callback * @param command */ void ExecutePython(const std::string &args, const std::string &pythonPath, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command = "python"); /** * @brief Overloaded function to Execute Python code. * Any callback function can be passed to process the output. * * @param args * @param callback * @param command */ void ExecutePython(const std::string &args, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command = "python"); /** * @brief Installs pytorch using light-the-torch package, correctly identifying cuda version. * Requires location of pip3 executable. * Any callback function can be passed to process the output. * * @param workingDir * @param callback */ void InstallPytorch(const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *)); /** * @brief Overloaded function to install pytorch using light-the-torch package, correctly * identifying cuda version. */ void InstallPytorch(); /** * @brief Get the Base Dir object * * @return QString& */ QString& GetBaseDir(); /** * @brief Get the System Python Path object * * @return QString& */ QString& GetSystemPythonPath(); /** * @brief Get the Python Path object * * @return QString& */ QString& GetPythonPath(); /** * @brief Get the Pip Path object * * @return QString& */ QString& GetPipPath(); /** * @brief Set the System Python Path object * * @param path */ void SetSystemPythonPath(const QString& path); /** * @brief Set the Python Path object * * @param path */ void SetPythonPath(const QString& path); /** * @brief Set the Pip Path object * * @param path */ void SetPipPath(const QString& path); /** * @brief Set the Virtual Env Path object * * @param path */ void SetVirtualEnvPath(const QString &path); /** * @brief Check if the path provide has python executable or not. * * @param pythonPath * @return true * @return false */ bool IsPythonPath(const QString &pythonPath); /** * @brief Function can be used as callback to simply print out all the process execution output * parsed out from itk::EventObject. * */ static void PrintProcessEvent(itk::Object *, const itk::EventObject &e, void *); + /** + * @brief Get the exact Python path and version for any OS from the virtual environment path. + * @return A pair of the exact python path and its Python version or empty, if an supported + * version of Python could not be found. + */ + static std::pair GetExactPythonPath(const QString &pyEnv); + private: QString m_PythonPath; QString m_PipPath; QString m_BaseDir; QString m_venvPath; QString m_SysPythonPath; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 7016cc6971..96f5bf2fbd 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,622 +1,622 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(toolButtonClicked(int))); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; - QList::iterator it; + for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { layout->addWidget(gui); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); if (selectedToolButton) { selectedToolButton->setChecked(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::setEnabled(bool enable) { if (QWidget::isEnabled() == enable) return; QWidget::setEnabled(enable); if (enable) { m_ToolManager->RegisterClient(); auto id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } void QmitkToolSelectionBox::UpdateButtonsEnabledState() { auto buttons = m_ToolButtonGroup->buttons(); const auto refDataNode = m_ToolManager->GetReferenceData(0); const mitk::BaseData* refData = nullptr; if (nullptr != refDataNode) { refData = refDataNode->GetData(); } const auto workingDataNode = m_ToolManager->GetWorkingData(0); const mitk::BaseData* workingData = nullptr; if (nullptr != workingDataNode) { workingData = workingDataNode->GetData(); } for (const auto& button : qAsConst(buttons)) { const auto buttonID = m_ToolButtonGroup->id(button); const auto toolID = m_ToolIDForButtonID[buttonID]; const auto tool = m_ToolManager->GetToolById(toolID); button->setEnabled(tool->CanHandle(refData, workingData)); } } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); delete btn; } mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { button->setText(label); // a label button->setToolTip(tooltip); QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); this->UpdateButtonsEnabledState(); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegExp("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui index 4403408d4d..b4a1b287b6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui @@ -1,357 +1,357 @@ QmitkTotalSegmentatorToolGUIControls 0 0 699 352 0 0 100 0 100000 100000 QmitkTotalSegmentatorToolWidget 0 0 0 0 0 0 <html><head/><body><p>Welcome to TotalSegmentator v2 tool in MITK. [Experimental]</p><p>Please note that this is only an interface to TotalSegmentator. MITK does not ship with TotalSegmentator. Make sure to have a working internet connection to install TotalSegmentator. Or, choose an python environment with TotalSegmentator in it before inferencing. Currently, the tool is pretrained on CT images only. </p><p>Refer to <a href="https://github.com/wasserth/TotalSegmentator"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/wasserth/TotalSegmentator</span></a> to learn everything about the TotalSegmentator.</p><p><br/></p></body></html> Qt::RichText true 0 0 100000 16777215 Install TotalSegmentator 0 0 Install Options true 5 true 0 0 0 0 6 0 0 Use Custom Installation: false 0 0 100000 16777215 Clear Install 0 0 Custom Env. Path: 0 0 - System Python: + System Python (>=3.9): 0 0 Task: 0 0 Advanced true 5 true 0 0 0 0 6 0 0 Fast: true 0 0 GPU Id: 0 0 100000 16777215 Run TotalSegmentator 0 0 true ctkComboBox QComboBox
ctkComboBox.h
1
ctkCollapsibleGroupBox QWidget
ctkCollapsibleGroupBox.h
ctkCheckBox QWidget
ctkCheckBox.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp index 4bcd1f5f58..a78bef468a 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -1,501 +1,497 @@ +/*============================================================================ + +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 "QmitkTotalSegmentatorToolGUI.h" #include "mitkProcessExecutor.h" #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The TotalSegmentator tool can be very slow."; this->ShowErrorMessage(warning); } m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Controls.sysPythonComboBox->addItem("Select"); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->addItem("Select"); m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); m_Controls.pythonEnvComboBox->setDisabled(true); m_Controls.previewButton->setDisabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); m_Controls.subtaskComboBox->addItems(VALID_TASKS); QString welcomeText; this->SetGPUInfo(); if (m_GpuLoader.GetGPUCount() != 0) { welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; } else { welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; } connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); connect(m_Controls.pythonEnvComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); connect(m_Controls.sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") { m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); } const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); if (m_IsInstalled) { - m_PythonPath = GetExactPythonPath(storageDir); + m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(storageDir).first; m_Installer.SetVirtualEnvPath(m_PythonPath); this->EnableAll(m_IsInstalled); welcomeText += " TotalSegmentator is already found installed."; } else { welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; } this->WriteStatusMessage(welcomeText); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Controls.clearButton->setIcon(deleteIcon); m_Controls.previewButton->setIcon(arrowIcon); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } void QmitkTotalSegmentatorToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) { m_Controls.previewButton->setEnabled(isEnable); m_Controls.subtaskComboBox->setEnabled(isEnable); m_Controls.installButton->setEnabled((!isEnable)); } void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() { bool isInstalled = false; - QString systemPython = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); - if (systemPython.isEmpty()) + const auto [path, version] = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); + if (path.isEmpty()) + { + this->WriteErrorMessage("ERROR: Couldn't find compatible Python."); + return; + } + // check if python 3.12 and ask for confirmation + if (version.startsWith("3.12") && + QMessageBox::No == QMessageBox::question( + nullptr, + "Installing TotalSegmentator", + QString("WARNING: This is an unsupported version of Python that may not work. " + "We recommend using a supported Python version between 3.9 and 3.11.\n\n" + "Continue anyway?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No)) { - this->WriteErrorMessage("ERROR: Couldn't find Python."); + return; + } + this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); + m_Installer.SetSystemPythonPath(path); + isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); + if (isInstalled) + { + m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(m_Installer.GetVirtualEnvPath()).first; + this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); } else { - this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); - m_Installer.SetSystemPythonPath(systemPython); - isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); - if (isInstalled) - { - m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); - this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); - } - else - { - this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); - } + this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); } this->EnableAll(isInstalled); } void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } try { m_Controls.previewButton->setEnabled(false); qApp->processEvents(); if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); } bool isFast = m_Controls.fastBox->isChecked(); QString subTask = m_Controls.subtaskComboBox->currentText(); if (subTask != VALID_TASKS[0]) { isFast = true; } tool->SetPythonPath(m_PythonPath.toStdString()); tool->SetGpuId(FetchSelectedGPUFromUI()); tool->SetFast(isFast); tool->SetSubTask(subTask.toStdString()); this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); tool->UpdatePreview(); m_Controls.previewButton->setEnabled(true); m_FirstPreviewComputation = false; } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); return; } catch (...) { std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. { QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (lastSelectedPyEnv != pythonPathTextItem) { m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); } } } void QmitkTotalSegmentatorToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) { QString fullPath = pythonPath; bool isPythonExists = false, isExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator.exe")) && isPythonExists; #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; #endif return isExists; } void QmitkTotalSegmentatorToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.sysPythonComboBox->addItem("(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { - m_Controls.sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); + m_Controls.pythonEnvComboBox->addItem("(" + envName + "): " + subIt.filePath()); } } } } -QString QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) +std::pair QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) { - QString pyPath; + std::pair pyPath; if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.sysPythonComboBox->insertItem(0, path); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.sysPythonComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else { QString uiPyPath = this->GetPythonPathFromUI(pyEnv); - pyPath = this->GetExactPythonPath(uiPyPath); + pyPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath); } return pyPath; } void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } - else if (!this->IsTotalSegmentatorInstalled(pyEnv)) + else if (!this->IsTotalSegmentatorInstalled(this->GetPythonPathFromUI(pyEnv))) { this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); m_Controls.previewButton->setDisabled(true); } else {// Show positive status meeage m_Controls.previewButton->setDisabled(false); QString uiPyPath = this->GetPythonPathFromUI(pyEnv); - m_PythonPath = this->GetExactPythonPath(uiPyPath); + m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath).first; } } QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } -QString QmitkTotalSegmentatorToolGUI::GetExactPythonPath(const QString &pyEnv) const -{ - QString fullPath = pyEnv; - bool isPythonExists = false; -#ifdef _WIN32 - isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); - if (!isPythonExists && - !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) - { - fullPath += QDir::separator() + QString("Scripts"); - isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); - } -#else - isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); - if (!isPythonExists && !(fullPath.endsWith("bin", Qt::CaseInsensitive) || - fullPath.endsWith("bin/", Qt::CaseInsensitive))) - { - fullPath += QDir::separator() + QString("bin"); - isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); - } -#endif - if (!isPythonExists) - { - fullPath.clear(); - } - return fullPath; -} - void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) { bool isEnabled = false; if (state == Qt::Checked) { isEnabled = true; m_Controls.previewButton->setDisabled(true); m_PythonPath.clear(); } else { m_PythonPath.clear(); m_Controls.previewButton->setDisabled(true); if (m_IsInstalled) { const QString pythonPath = m_Installer.GetVirtualEnvPath(); - m_PythonPath = this->GetExactPythonPath(pythonPath); + auto pathObject = QmitkSetupVirtualEnvUtil::GetExactPythonPath(pythonPath); + m_PythonPath = pathObject.first; this->EnableAll(m_IsInstalled); } } m_Controls.pythonEnvComboBox->setEnabled(isEnabled); } void QmitkTotalSegmentatorToolGUI::OnClearInstall() { QDir folderPath(m_Installer.GetVirtualEnvPath()); if (folderPath.removeRecursively()) { m_Controls.installButton->setEnabled(true); m_IsInstalled = false; if (!m_Controls.overrideBox->isChecked()) { m_Controls.previewButton->setEnabled(false); } } else { MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; } } bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) { if (GetSystemPythonPath().isEmpty()) { return false; } QDir folderPath(GetBaseDir()); folderPath.mkdir(venvName); if (!folderPath.cd(venvName)) { return false; // Check if directory creation was successful. } mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&PrintProcessEvent); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("venv"); args.push_back(venvName.toStdString()); #ifdef _WIN32 QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; QString pythonExeFolder = "Scripts"; #else QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; QString pythonExeFolder = "bin"; #endif spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment if (folderPath.cd(pythonExeFolder)) { this->SetPythonPath(folderPath.absolutePath()); this->SetPipPath(folderPath.absolutePath()); this->InstallPytorch(); for (auto &package : PACKAGES) { this->PipInstall(package.toStdString(), &PrintProcessEvent); } std::string pythonCode; // python syntax to check if torch is installed with CUDA. pythonCode.append("import torch;"); pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " "installed WITHOUT CUDA');"); this->ExecutePython(pythonCode, &PrintProcessEvent); return true; } return false; } QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h index 6757be59db..ac0c787f4c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h @@ -1,175 +1,181 @@ +/*============================================================================ + +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.0.2"; + const QString TOTALSEGMENTATOR_VERSION = "2.0.5"; const std::vector PACKAGES = {QString("Totalsegmentator==") + TOTALSEGMENTATOR_VERSION}; 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 */ - QString OnSystemPythonChanged(const QString &); + 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 enviroments * 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 avaialble, 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 Get the Exact Python Path for any OS - * from the virtual environment path. - * @return QString - */ - QString GetExactPythonPath(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", "cerebral_bleed", "hip_implant", "coronary_arteries", "body", "lung_vessels", "pleural_pericard_effusion" }; QmitkTotalSegmentatorToolInstaller m_Installer; }; #endif diff --git a/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp b/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp index 16952fd4a7..f3d4924082 100644 --- a/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp @@ -1,617 +1,614 @@ /*============================================================================ 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 "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkImageCast.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkDoubleArray.h" #include "vtkPolyData.h" #include "vtkSmartPointer.h" #include "itkImageRegionIteratorWithIndex.h" #include "itkNeighborhoodIterator.h" #include void mitk::CreateDistanceImageFromSurfaceFilter::CreateEmptyDistanceImage() { // Determine the bounds of the input points in index- and world-coordinates DistanceImageType::PointType minPointInWorldCoordinates, maxPointInWorldCoordinates; DistanceImageType::IndexType minPointInIndexCoordinates, maxPointInIndexCoordinates; DetermineBounds( minPointInWorldCoordinates, maxPointInWorldCoordinates, minPointInIndexCoordinates, maxPointInIndexCoordinates); // Calculate the extent of the region that contains all given points in MM. // To do this, we take the difference between the maximal and minimal // index-coordinates (must not be less than 1) and multiply it with the // spacing of the reference-image. Vector3D extentMM; for (unsigned int dim = 0; dim < 3; ++dim) { extentMM[dim] = (std::abs(maxPointInIndexCoordinates[dim] - minPointInIndexCoordinates[dim])) * m_ReferenceImage->GetSpacing()[dim]; } /* * Now create an empty distance image. The created image will always have the same number of pixels, independent from * the original image (e.g. always consists of 500000 pixels) and will have an isotropic spacing. * The spacing is calculated like the following: * The image's volume = 500000 Pixels = extentX*spacing*extentY*spacing*extentZ*spacing * So the spacing is: spacing = ( extentX*extentY*extentZ / 500000 )^(1/3) */ double basis = (extentMM[0] * extentMM[1] * extentMM[2]) / m_DistanceImageVolume; double exponent = 1.0 / 3.0; m_DistanceImageSpacing = pow(basis, exponent); // calculate the number of pixels of the distance image for each direction unsigned int numberOfXPixel = extentMM[0] / m_DistanceImageSpacing; unsigned int numberOfYPixel = extentMM[1] / m_DistanceImageSpacing; unsigned int numberOfZPixel = extentMM[2] / m_DistanceImageSpacing; // We increase the sizeOfRegion by 4 as we decrease the origin by 2 later. // This expansion of the region is necessary to achieve a complete // interpolation. DistanceImageType::SizeType sizeOfRegion; sizeOfRegion[0] = numberOfXPixel + 8; sizeOfRegion[1] = numberOfYPixel + 8; sizeOfRegion[2] = numberOfZPixel + 8; // The region starts at index 0,0,0 DistanceImageType::IndexType initialOriginAsIndex; initialOriginAsIndex.Fill(0); DistanceImageType::PointType originAsWorld = minPointInWorldCoordinates; DistanceImageType::RegionType lpRegion; lpRegion.SetSize(sizeOfRegion); lpRegion.SetIndex(initialOriginAsIndex); // We initialize the itk::Image with // * origin and direction to have it correctly placed and rotated in the world // * the largest possible region to set the extent to be calculated // * the isotropic spacing that we have calculated above m_DistanceImageITK = DistanceImageType::New(); m_DistanceImageITK->SetOrigin(originAsWorld); m_DistanceImageITK->SetDirection(m_ReferenceImage->GetDirection()); m_DistanceImageITK->SetRegions(lpRegion); m_DistanceImageITK->SetSpacing(itk::Vector(m_DistanceImageSpacing)); m_DistanceImageITK->Allocate(); // First of all the image is initialized with the value 10*m_DistanceImageSpacing for each pixel m_DistanceImageDefaultBufferValue = 10 * m_DistanceImageSpacing; m_DistanceImageITK->FillBuffer(m_DistanceImageDefaultBufferValue); // Now we move the origin of the distanceImage 2 index-Coordinates // in all directions - DistanceImageType::IndexType originAsIndex; - m_DistanceImageITK->TransformPhysicalPointToIndex(originAsWorld, originAsIndex); + auto originAsIndex = m_DistanceImageITK->TransformPhysicalPointToIndex(originAsWorld); originAsIndex[0] -= 2; originAsIndex[1] -= 2; originAsIndex[2] -= 2; m_DistanceImageITK->TransformIndexToPhysicalPoint(originAsIndex, originAsWorld); m_DistanceImageITK->SetOrigin(originAsWorld); } mitk::CreateDistanceImageFromSurfaceFilter::CreateDistanceImageFromSurfaceFilter() : m_DistanceImageSpacing(0.0), m_DistanceImageDefaultBufferValue(0.0) { m_DistanceImageVolume = 50000; this->m_UseProgressBar = false; this->m_ProgressStepSize = 5; mitk::Image::Pointer output = mitk::Image::New(); this->SetNthOutput(0, output.GetPointer()); } mitk::CreateDistanceImageFromSurfaceFilter::~CreateDistanceImageFromSurfaceFilter() { } void mitk::CreateDistanceImageFromSurfaceFilter::GenerateData() { this->PreprocessContourPoints(); this->CreateEmptyDistanceImage(); // First of all we have to build the equation-system from the existing contour-edge-points this->CreateSolutionMatrixAndFunctionValues(); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(1); m_Weights = m_SolutionMatrix.partialPivLu().solve(m_FunctionValues); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(2); // The last step is to create the distance map with the interpolated distance function this->FillDistanceImage(); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(2); m_Centers.clear(); m_Normals.clear(); } void mitk::CreateDistanceImageFromSurfaceFilter::PreprocessContourPoints() { unsigned int numberOfInputs = this->GetNumberOfIndexedInputs(); if (numberOfInputs == 0) { MITK_ERROR << "mitk::CreateDistanceImageFromSurfaceFilter: No input available. Please set an input!" << std::endl; itkExceptionMacro("mitk::CreateDistanceImageFromSurfaceFilter: No input available. Please set an input!"); return; } // First of all we have to extract the nomals and the surface points. // Duplicated points can be eliminated vtkSmartPointer polyData; vtkSmartPointer currentCellNormals; vtkSmartPointer existingPolys; vtkSmartPointer existingPoints; double p[3]; PointType currentPoint; PointType normal; for (unsigned int i = 0; i < numberOfInputs; i++) { auto currentSurface = this->GetInput(i); polyData = currentSurface->GetVtkPolyData(); if (polyData->GetNumberOfPolys() == 0) { MITK_INFO << "mitk::CreateDistanceImageFromSurfaceFilter: No input-polygons available. Please be sure the input " "surface consists of polygons!" << std::endl; } currentCellNormals = vtkDoubleArray::SafeDownCast(polyData->GetCellData()->GetNormals()); existingPolys = polyData->GetPolys(); existingPoints = polyData->GetPoints(); existingPolys->InitTraversal(); const vtkIdType *cell(nullptr); vtkIdType cellSize(0); for (existingPolys->InitTraversal(); existingPolys->GetNextCell(cellSize, cell);) { for (vtkIdType j = 0; j < cellSize; j++) { existingPoints->GetPoint(cell[j], p); currentPoint.copy_in(p); int count = std::count(m_Centers.begin(), m_Centers.end(), currentPoint); if (count == 0) { double currentNormal[3]; currentCellNormals->GetTuple(cell[j], currentNormal); normal.copy_in(currentNormal); m_Normals.push_back(normal); m_Centers.push_back(currentPoint); } } // end for all points } // end for all cells } // end for all outputs } void mitk::CreateDistanceImageFromSurfaceFilter::CreateSolutionMatrixAndFunctionValues() { // For we can now calculate the exact size of the centers we initialize the data structures unsigned int numberOfCenters = m_Centers.size(); m_Centers.reserve(numberOfCenters * 3); m_FunctionValues.resize(numberOfCenters * 3); m_FunctionValues.fill(0); PointType currentPoint; PointType normal; // Create inner points for (unsigned int i = 0; i < numberOfCenters; i++) { currentPoint = m_Centers.at(i); normal = m_Normals.at(i); currentPoint[0] = currentPoint[0] - normal[0] * m_DistanceImageSpacing; currentPoint[1] = currentPoint[1] - normal[1] * m_DistanceImageSpacing; currentPoint[2] = currentPoint[2] - normal[2] * m_DistanceImageSpacing; m_Centers.push_back(currentPoint); m_FunctionValues[numberOfCenters + i] = -m_DistanceImageSpacing; } // Create outer points for (unsigned int i = 0; i < numberOfCenters; i++) { currentPoint = m_Centers.at(i); normal = m_Normals.at(i); currentPoint[0] = currentPoint[0] + normal[0] * m_DistanceImageSpacing; currentPoint[1] = currentPoint[1] + normal[1] * m_DistanceImageSpacing; currentPoint[2] = currentPoint[2] + normal[2] * m_DistanceImageSpacing; m_Centers.push_back(currentPoint); m_FunctionValues[numberOfCenters * 2 + i] = m_DistanceImageSpacing; } // Now we have created all centers and all function values. Next step is to create the solution matrix numberOfCenters = m_Centers.size(); m_SolutionMatrix.resize(numberOfCenters, numberOfCenters); m_Weights.resize(numberOfCenters); PointType p1; PointType p2; double norm; for (unsigned int i = 0; i < numberOfCenters; i++) { for (unsigned int j = 0; j < numberOfCenters; j++) { // Calculate the RBF value. Currently using Phi(r) = r with r is the euclidian distance between two points p1 = m_Centers.at(i); p2 = m_Centers.at(j); p1 = p1 - p2; norm = p1.two_norm(); m_SolutionMatrix(i, j) = norm; } } } void mitk::CreateDistanceImageFromSurfaceFilter::FillDistanceImage() { /* * Now we must calculate the distance for each pixel. But instead of calculating the distance value * for all of the image's pixels we proceed similar to the region growing algorithm: * * 1. Take the first pixel from the narrowband_point_list and calculate the distance for each neighbor (6er) * 2. If the current index's distance value is below a certain threshold push it into the list * 3. Next iteration take the next index from the list and originAsIndex with 1. again * * This is done until the narrowband_point_list is empty. */ typedef itk::ImageRegionIteratorWithIndex ImageIterator; typedef itk::NeighborhoodIterator NeighborhoodImageIterator; std::queue narrowbandPoints; PointType currentPoint = m_Centers.at(0); double distance = this->CalculateDistanceValue(currentPoint); // create itk::Point from vnl_vector DistanceImageType::PointType currentPointAsPoint; currentPointAsPoint[0] = currentPoint[0]; currentPointAsPoint[1] = currentPoint[1]; currentPointAsPoint[2] = currentPoint[2]; // Transform the input point in world-coordinates to index-coordinates - DistanceImageType::IndexType currentIndex; - m_DistanceImageITK->TransformPhysicalPointToIndex(currentPointAsPoint, currentIndex); + auto currentIndex = m_DistanceImageITK->TransformPhysicalPointToIndex(currentPointAsPoint); assert( m_DistanceImageITK->GetLargestPossibleRegion().IsInside(currentIndex)); // we are quite certain this should hold narrowbandPoints.push(currentIndex); m_DistanceImageITK->SetPixel(currentIndex, distance); NeighborhoodImageIterator::RadiusType radius; radius.Fill(1); NeighborhoodImageIterator nIt(radius, m_DistanceImageITK, m_DistanceImageITK->GetLargestPossibleRegion()); unsigned int relativeNbIdx[] = {4, 10, 12, 14, 16, 22}; bool isInBounds = false; while (!narrowbandPoints.empty()) { nIt.SetLocation(narrowbandPoints.front()); narrowbandPoints.pop(); unsigned int *relativeNb = &relativeNbIdx[0]; for (int i = 0; i < 6; i++) { nIt.GetPixel(*relativeNb, isInBounds); if (isInBounds && nIt.GetPixel(*relativeNb) == m_DistanceImageDefaultBufferValue) { currentIndex = nIt.GetIndex(*relativeNb); // Transform the currently checked point from index-coordinates to // world-coordinates m_DistanceImageITK->TransformIndexToPhysicalPoint(currentIndex, currentPointAsPoint); // create a vnl_vector currentPoint[0] = currentPointAsPoint[0]; currentPoint[1] = currentPointAsPoint[1]; currentPoint[2] = currentPointAsPoint[2]; // and check the distance distance = this->CalculateDistanceValue(currentPoint); if (std::fabs(distance) <= m_DistanceImageSpacing * 2) { nIt.SetPixel(*relativeNb, distance); narrowbandPoints.push(currentIndex); } } relativeNb++; } } ImageIterator imgRegionIterator(m_DistanceImageITK, m_DistanceImageITK->GetLargestPossibleRegion()); imgRegionIterator.GoToBegin(); double prevPixelVal = 1; DistanceImageType::IndexType _size; _size.Fill(-1); _size += m_DistanceImageITK->GetLargestPossibleRegion().GetSize(); // Set every pixel inside the surface to -m_DistanceImageDefaultBufferValue except the edge point (so that the // received surface is closed) while (!imgRegionIterator.IsAtEnd()) { if (imgRegionIterator.Get() == m_DistanceImageDefaultBufferValue && prevPixelVal < 0) { while (imgRegionIterator.Get() == m_DistanceImageDefaultBufferValue) { if (imgRegionIterator.GetIndex()[0] == _size[0] || imgRegionIterator.GetIndex()[1] == _size[1] || imgRegionIterator.GetIndex()[2] == _size[2] || imgRegionIterator.GetIndex()[0] == 0U || imgRegionIterator.GetIndex()[1] == 0U || imgRegionIterator.GetIndex()[2] == 0U) { imgRegionIterator.Set(m_DistanceImageDefaultBufferValue); prevPixelVal = m_DistanceImageDefaultBufferValue; ++imgRegionIterator; break; } else { imgRegionIterator.Set((-1) * m_DistanceImageDefaultBufferValue); ++imgRegionIterator; prevPixelVal = (-1) * m_DistanceImageDefaultBufferValue; } } } else if (imgRegionIterator.GetIndex()[0] == _size[0] || imgRegionIterator.GetIndex()[1] == _size[1] || imgRegionIterator.GetIndex()[2] == _size[2] || imgRegionIterator.GetIndex()[0] == 0U || imgRegionIterator.GetIndex()[1] == 0U || imgRegionIterator.GetIndex()[2] == 0U) { imgRegionIterator.Set(m_DistanceImageDefaultBufferValue); prevPixelVal = m_DistanceImageDefaultBufferValue; ++imgRegionIterator; } else { prevPixelVal = imgRegionIterator.Get(); ++imgRegionIterator; } } Image::Pointer resultImage = this->GetOutput(); // Cast the created distance-Image from itk::Image to the mitk::Image // that is our output. CastToMitkImage(m_DistanceImageITK, resultImage); } double mitk::CreateDistanceImageFromSurfaceFilter::CalculateDistanceValue(PointType p) { double distanceValue(0); PointType p1; PointType p2; double norm; CenterList::iterator centerIter; unsigned int count(0); for (centerIter = m_Centers.begin(); centerIter != m_Centers.end(); centerIter++) { p1 = *centerIter; p2 = p - p1; norm = p2.two_norm(); distanceValue = distanceValue + (norm * m_Weights[count]); ++count; } return distanceValue; } void mitk::CreateDistanceImageFromSurfaceFilter::GenerateOutputInformation() { } void mitk::CreateDistanceImageFromSurfaceFilter::PrintEquationSystem() { std::stringstream out; out << "Nummber of rows: " << m_SolutionMatrix.rows() << " ****** Number of columns: " << m_SolutionMatrix.cols() << endl; out << "[ "; for (int i = 0; i < m_SolutionMatrix.rows(); i++) { for (int j = 0; j < m_SolutionMatrix.cols(); j++) { out << m_SolutionMatrix(i, j) << " "; } out << ";" << endl; } out << " ]\n\n\n"; for (unsigned int i = 0; i < m_Centers.size(); i++) { out << m_Centers.at(i) << ";" << endl; } std::cout << "Equation system: \n\n\n" << out.str(); } void mitk::CreateDistanceImageFromSurfaceFilter::SetInput(const mitk::Surface *surface) { this->SetInput(0, surface); } void mitk::CreateDistanceImageFromSurfaceFilter::SetInput(unsigned int idx, const mitk::Surface *surface) { if (this->GetInput(idx) != surface) { this->SetNthInput(idx, const_cast(surface)); this->Modified(); } } const mitk::Surface *mitk::CreateDistanceImageFromSurfaceFilter::GetInput() { if (this->GetNumberOfIndexedInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(0)); } const mitk::Surface *mitk::CreateDistanceImageFromSurfaceFilter::GetInput(unsigned int idx) { if (this->GetNumberOfIndexedInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(idx)); } void mitk::CreateDistanceImageFromSurfaceFilter::RemoveInputs(mitk::Surface *input) { DataObjectPointerArraySizeType nb = this->GetNumberOfIndexedInputs(); for (DataObjectPointerArraySizeType i = 0; i < nb; i++) { if (this->GetInput(i) == input) { this->RemoveInput(i); return; } } } void mitk::CreateDistanceImageFromSurfaceFilter::Reset() { for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { this->PopBackInput(); } this->SetNumberOfIndexedInputs(0); this->SetNumberOfIndexedOutputs(1); mitk::Image::Pointer output = mitk::Image::New(); this->SetNthOutput(0, output.GetPointer()); } void mitk::CreateDistanceImageFromSurfaceFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::CreateDistanceImageFromSurfaceFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; } void mitk::CreateDistanceImageFromSurfaceFilter::SetReferenceImage(itk::ImageBase<3>::Pointer referenceImage) { m_ReferenceImage = referenceImage; } void mitk::CreateDistanceImageFromSurfaceFilter::DetermineBounds( DistanceImageType::PointType &minPointInWorldCoordinates, DistanceImageType::PointType &maxPointInWorldCoordinates, DistanceImageType::IndexType &minPointInIndexCoordinates, DistanceImageType::IndexType &maxPointInIndexCoordinates) { PointType firstCenter = m_Centers.at(0); DistanceImageType::PointType tmpPoint; tmpPoint[0] = firstCenter[0]; tmpPoint[1] = firstCenter[1]; tmpPoint[2] = firstCenter[2]; // transform the first point from world-coordinates to index-coordinates - itk::ContinuousIndex tmpIndex; - m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint, tmpIndex); + auto tmpIndex = m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint); // initialize the variables with this first point DistanceImageType::IndexValueType xmin = tmpIndex[0]; DistanceImageType::IndexValueType ymin = tmpIndex[1]; DistanceImageType::IndexValueType zmin = tmpIndex[2]; DistanceImageType::IndexValueType xmax = tmpIndex[0]; DistanceImageType::IndexValueType ymax = tmpIndex[1]; DistanceImageType::IndexValueType zmax = tmpIndex[2]; // iterate over the rest of the points auto centerIter = m_Centers.begin(); for (++centerIter; centerIter != m_Centers.end(); centerIter++) { tmpPoint[0] = (*centerIter)[0]; tmpPoint[1] = (*centerIter)[1]; tmpPoint[2] = (*centerIter)[2]; // transform each point from world-coordinates to index-coordinates - m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint, tmpIndex); + tmpIndex = m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint); // and set the variables accordingly to find the minimum // and maximum in all directions in index-coordinates if (xmin > tmpIndex[0]) { xmin = tmpIndex[0]; } if (ymin > tmpIndex[1]) { ymin = tmpIndex[1]; } if (zmin > tmpIndex[2]) { zmin = tmpIndex[2]; } if (xmax < tmpIndex[0]) { xmax = tmpIndex[0]; } if (ymax < tmpIndex[1]) { ymax = tmpIndex[1]; } if (zmax < tmpIndex[2]) { zmax = tmpIndex[2]; } } // put the found coordinates into Index-Points minPointInIndexCoordinates[0] = xmin; minPointInIndexCoordinates[1] = ymin; minPointInIndexCoordinates[2] = zmin; maxPointInIndexCoordinates[0] = xmax; maxPointInIndexCoordinates[1] = ymax; maxPointInIndexCoordinates[2] = zmax; // and transform them into world-coordinates m_ReferenceImage->TransformIndexToPhysicalPoint(minPointInIndexCoordinates, minPointInWorldCoordinates); m_ReferenceImage->TransformIndexToPhysicalPoint(maxPointInIndexCoordinates, maxPointInWorldCoordinates); } diff --git a/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp b/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp index 282ab45610..cd843e9717 100644 --- a/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp +++ b/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp @@ -1,386 +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 #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Test for the class "ToFDistanceImageToSurfaceFilter". */ typedef mitk::ToFProcessingCommon::ToFPoint2D ToFPoint2D; typedef mitk::ToFProcessingCommon::ToFPoint3D ToFPoint3D; typedef mitk::ToFProcessingCommon::ToFScalarType ToFScalarType; int mitkToFDistanceImageToSurfaceFilterTest(int /* argc */, char* /*argv*/[]) { MITK_TEST_BEGIN("ToFDistanceImageToSurfaceFilter"); mitk::ToFDistanceImageToSurfaceFilter::Pointer filter = mitk::ToFDistanceImageToSurfaceFilter::New(); // create test image unsigned int dimX =204; unsigned int dimY =204; mitk::Image::Pointer image = mitk::ImageGenerator::GenerateRandomImage(dimX,dimY); //initialize intrinsic parameters with some arbitrary values ToFScalarType focalLengthX = 295.78960; ToFScalarType focalLengthY = 296.348535; ToFPoint2D focalLengthXY; focalLengthXY[0]=focalLengthX; focalLengthXY[1]=focalLengthY; ToFScalarType k1=-0.36,k2=-0.14,p1=0.001,p2=-0.00; ToFPoint2D principalPoint; principalPoint[0] = 103.576546; principalPoint[1] = 100.1532; mitk::CameraIntrinsics::Pointer cameraIntrinsics = mitk::CameraIntrinsics::New(); cameraIntrinsics->SetFocalLength(focalLengthX,focalLengthY); cameraIntrinsics->SetPrincipalPoint(principalPoint[0],principalPoint[1]); cameraIntrinsics->SetDistorsionCoeffs(k1,k2,p1,p2); // test SetCameraIntrinsics() filter->SetCameraIntrinsics(cameraIntrinsics); MITK_TEST_CONDITION_REQUIRED((focalLengthX==filter->GetCameraIntrinsics()->GetFocalLengthX()),"Testing SetCameraIntrinsics with focalLength"); ToFPoint2D pp; pp[0] = filter->GetCameraIntrinsics()->GetPrincipalPointX(); pp[1] = filter->GetCameraIntrinsics()->GetPrincipalPointY(); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(principalPoint,pp),"Testing SetCameraIntrinsics with principalPoint()"); // test SetInterPixelDistance() ToFPoint2D interPixelDistance; interPixelDistance[0] = 0.04564; interPixelDistance[1] = 0.0451564; filter->SetInterPixelDistance(interPixelDistance); ToFPoint2D ipD = filter->GetInterPixelDistance(); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(ipD,interPixelDistance),"Testing Set/GetInterPixelDistance()"); // test SetReconstructionMode() filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance,"Testing Set/GetReconstructionMode()"); // test Set/GetInput() filter->SetInput(image); MITK_TEST_CONDITION_REQUIRED((image==filter->GetInput()),"Testing Set/GetInput()"); // test filter without subset (without interpixeldistance) MITK_INFO<<"Test filter with subset without interpixeldistance "; filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithOutInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithOutInterPixelDistance,"Testing Set/GetReconstructionMode()"); vtkSmartPointer expectedResult = vtkSmartPointer::New(); expectedResult->SetDataTypeToDouble(); - unsigned int counter = 0; double* point = new double[3]; // MITK_INFO<<"Test"; // MITK_INFO<<"focal: "< index = { { static_cast(i), static_cast< itk::IndexValueType >( j) } }; float distance = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distance = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } ToFPoint3D coordinate = mitk::ToFProcessingCommon::IndexToCartesianCoordinates(i,j,distance,focalLengthX,focalLengthY,principalPoint[0],principalPoint[1]); // if ((i==0)&&(j==0)) // { // MITK_INFO<<"Distance test: "<InsertPoint(pointID,point); } - counter++; } } filter->Update(); mitk::Surface::Pointer resultSurface = filter->GetOutput(); vtkSmartPointer result = vtkSmartPointer::New(); result->SetDataTypeToDouble(); result = resultSurface->GetVtkPolyData()->GetPoints(); MITK_TEST_CONDITION_REQUIRED((expectedResult->GetNumberOfPoints()==result->GetNumberOfPoints()),"Test if number of points in surface is equal"); bool pointSetsEqual = true; for (int i=0; iGetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; if (!mitk::Equal(expectedPoint,resultPoint)) { // MITK_INFO << i; pointSetsEqual = false; } } MITK_TEST_CONDITION_REQUIRED(pointSetsEqual,"Testing filter without subset"); // test filter without subset (with interpixeldistance) MITK_INFO<<"Test filter with subset with interpixeldistance "; filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance,"Testing Set/GetReconstructionMode()"); // calculate focal length considering inter pixel distance ToFScalarType focalLength = (focalLengthX*interPixelDistance[0]+focalLengthY*interPixelDistance[1])/2.0; expectedResult = vtkSmartPointer::New(); expectedResult->SetDataTypeToDouble(); - counter = 0; point = new double[3]; // MITK_INFO<<"Test"; // MITK_INFO<<"focal: "< index = {{ static_cast(i), static_cast< itk::IndexValueType >( j )}}; float distance = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distance = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } ToFPoint3D coordinate = mitk::ToFProcessingCommon::IndexToCartesianCoordinatesWithInterpixdist(i,j,distance,focalLength,interPixelDistance,principalPoint); // if ((i==0)&&(j==0)) // { // MITK_INFO<<"Distance test: "<InsertPoint(pointID,point); } - counter++; } } filter->Modified(); filter->Update(); resultSurface = filter->GetOutput(); result = vtkSmartPointer::New(); result->SetDataTypeToDouble(); result = resultSurface->GetVtkPolyData()->GetPoints(); MITK_TEST_CONDITION_REQUIRED((expectedResult->GetNumberOfPoints()==result->GetNumberOfPoints()),"Test if number of points in surface is equal"); pointSetsEqual = true; for (int i=0; iGetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; if (!mitk::Equal(expectedPoint,resultPoint)) { // MITK_INFO << i; MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D expectedPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(expectedPoint,focalLengthXY,principalPoint); ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(resultPoint,focalLengthXY,principalPoint); if (!mitk::Equal(expectedPointBackward,resultPointBackward)) { // MITK_INFO << i; // MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D expectedPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(expectedPoint,focalLength,interPixelDistance,principalPoint); ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(resultPoint,focalLength,interPixelDistance,principalPoint); if (!mitk::Equal(expectedPointBackward,resultPointBackward)) { // MITK_INFO << i; // MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* res = result->GetPoint(i); ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(resultPoint,focalLengthXY,principalPoint); itk::Index<2> index = {{ (int) (resultPointBackward[0]+0.5), (int) (resultPointBackward[1]+0.5) }}; float distanceBackward = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distanceBackward = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } if (!mitk::Equal(distanceBackward,(float) resultPointBackward[2])) { MITK_INFO<<"expected: " << resultPointBackward[2]; MITK_INFO<<"result: "<< distanceBackward; compareToInput = false; } } MITK_TEST_CONDITION_REQUIRED(compareToInput,"Testing backward transformation compared to original image without interpixeldistance"); //Backwardtransformation test compare to original input with interpixeldistance compareToInput = true; for ( int i=0; iGetNumberOfPoints(); i++) { double* res = result->GetPoint(i); ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(resultPoint,focalLength,interPixelDistance,principalPoint); itk::Index<2> pixelIndex = {{ (int) (resultPointBackward[0]+0.5), (int) (resultPointBackward[1]+0.5) }}; float distanceBackward = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distanceBackward = readAccess.GetPixelByIndex(pixelIndex); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } if (!mitk::Equal(distanceBackward, (float) resultPointBackward[2])) { compareToInput = false; } } MITK_TEST_CONDITION_REQUIRED(compareToInput,"Testing backward transformation compared to original image with interpixeldistance"); //clean up delete[] point; // expectedResult->Delete(); MITK_TEST_END(); } diff --git a/Modules/TubeGraph/include/mitkTubeGraphProperty.h b/Modules/TubeGraph/include/mitkTubeGraphProperty.h index 87afa04b89..95fc651efe 100644 --- a/Modules/TubeGraph/include/mitkTubeGraphProperty.h +++ b/Modules/TubeGraph/include/mitkTubeGraphProperty.h @@ -1,146 +1,149 @@ /*============================================================================ 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 mitkTubeGraphProperty_h #define mitkTubeGraphProperty_h #include #include "mitkTubeGraph.h" #include #include #include #include #include namespace mitk { /** * \brief Property for tube graphs */ class MITKTUBEGRAPH_EXPORT TubeGraphProperty : public BaseProperty { public: mitkClassMacro(TubeGraphProperty, BaseProperty); itkNewMacro(TubeGraphProperty); struct LabelGroup { struct Label { std::string labelName; bool isVisible; Color labelColor; }; std::string labelGroupName; std::vector