diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ae49aa27d..c6bc64a917 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1481 +1,1481 @@ set(MITK_CMAKE_MINIMUM_REQUIRED_VERSION 3.14.5) cmake_minimum_required(VERSION ${MITK_CMAKE_MINIMUM_REQUIRED_VERSION}) #----------------------------------------------------------------------------- # See https://cmake.org/cmake/help/v3.14/manual/cmake-policies.7.html for details #----------------------------------------------------------------------------- set(project_policies ) foreach(policy ${project_policies}) if(POLICY ${policy}) cmake_policy(SET ${policy} NEW) endif() endforeach() #----------------------------------------------------------------------------- # Superbuild Option - Enabled by default #----------------------------------------------------------------------------- option(MITK_USE_SUPERBUILD "Build MITK and the projects it depends on via SuperBuild.cmake." ON) if(MITK_USE_SUPERBUILD) project(MITK-superbuild) set(MITK_SOURCE_DIR ${PROJECT_SOURCE_DIR}) set(MITK_BINARY_DIR ${PROJECT_BINARY_DIR}) else() project(MITK VERSION 2018.04.99) include_directories(SYSTEM ${MITK_SUPERBUILD_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # MITK Extension Feature #----------------------------------------------------------------------------- set(MITK_EXTENSION_DIRS "" CACHE STRING "") set(MITK_DIR_PLUS_EXTENSION_DIRS ${MITK_SOURCE_DIR} ${MITK_EXTENSION_DIRS}) #----------------------------------------------------------------------------- # Update CMake module path #----------------------------------------------------------------------------- set(MITK_CMAKE_DIR ${MITK_SOURCE_DIR}/CMake) set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake) get_filename_component(MITK_CMAKE_EXTENSION_DIR ${MITK_CMAKE_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTENSION_DIR}) list(APPEND CMAKE_MODULE_PATH ${MITK_CMAKE_EXTENSION_DIR}) endif() endforeach() #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- # Standard CMake macros include(FeatureSummary) include(CTestUseLaunchers) include(CMakeParseArguments) include(FindPackageHandleStandardArgs) # MITK macros include(mitkFunctionGetGccVersion) include(mitkFunctionCheckCompilerFlags) include(mitkFunctionSuppressWarnings) # includes several functions include(mitkMacroEmptyExternalProject) include(mitkFunctionGenerateProjectXml) include(mitkFunctionEnableBuildConfiguration) include(mitkFunctionWhitelists) include(mitkFunctionAddExternalProject) include(mitkFunctionAddLibrarySearchPaths) SUPPRESS_VC_DEPRECATED_WARNINGS() #----------------------------------------------------------------------------- # Set a default build type if none was specified #----------------------------------------------------------------------------- if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified.") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() #----------------------------------------------------------------------------- # Check miminum macOS version #----------------------------------------------------------------------------- # The minimum supported macOS version is 10.13. If you use a version less than 10.13, there is no guarantee that the build still works. if(APPLE) exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE macos_version) if (macos_version VERSION_LESS "10.13") message(WARNING "Detected macOS version \"${macos_version}\" is not supported anymore. Minimum required macOS version is at least 10.13.") endif() if (CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.13) message(WARNING "Detected macOS deployment target \"${CMAKE_OSX_DEPLOYMENT_TARGET}\" is not supported anymore. Minimum required macOS version is at least 10.13.") endif() endif() #----------------------------------------------------------------------------- # Check miminum compiler versions #----------------------------------------------------------------------------- if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # require at least gcc 4.9 as provided by ppa:ubuntu-toolchain-r/test for Ubuntu 14.04 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) message(FATAL_ERROR "GCC version must be at least 4.9 If you are using Ubuntu 14.04, you can easily install gcc and g++ 4.9 (or any later version available) in addition to your version ${CMAKE_CXX_COMPILER_VERSION}: sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install gcc-4.9 g++-4.9 Make sure to explicitly specify these compilers when configuring MITK: CMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc-4.9 CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++-4.9 For more information on the proposed PPA see the Toolchain Updates section of https://wiki.ubuntu.com/ToolChain.") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # require at least clang 3.4 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4) message(FATAL_ERROR "Clang version must be at least 3.4") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") # require at least clang 5.0 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) message(FATAL_ERROR "Apple Clang version must be at least 5.0") endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # require at least Visual Studio 2017 if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10) message(FATAL_ERROR "Microsoft Visual Studio 2017 or newer required") endif() else() message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang (Linux or Apple), GCC and MSVC.") endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionGetGccVersion(${CMAKE_CXX_COMPILER} GCC_VERSION) else() set(GCC_VERSION 0) endif() set(MITK_CXX_STANDARD 14) set(CMAKE_CXX_EXTENSIONS 0) set(CMAKE_CXX_STANDARD ${MITK_CXX_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED 1) # This is necessary to avoid problems with compile feature checks. # CMAKE_CXX_STANDARD seems to only set the -std=c++14 flag for targets. # However, compile flag checks also need to be done with -std=c++14. # The MITK_CXX14_FLAG variable is also used for external projects # build during the MITK super-build. mitkFunctionCheckCompilerFlags("-std=c++14" MITK_CXX14_FLAG) #----------------------------------------------------------------------------- # Warn if source or build path is too long #----------------------------------------------------------------------------- if(WIN32) set(_src_dir_length_max 50) set(_bin_dir_length_max 50) if(MITK_USE_SUPERBUILD) set(_src_dir_length_max 34) # _src_dir_length_max - strlen(ep/src/ITK-build) set(_bin_dir_length_max 40) # _bin_dir_length_max - strlen(MITK-build) endif() string(LENGTH "${MITK_SOURCE_DIR}" _src_n) string(LENGTH "${MITK_BINARY_DIR}" _bin_n) # The warnings should be converted to errors if(_src_n GREATER _src_dir_length_max) message(WARNING "MITK source code directory path length is too long (${_src_n} > ${_src_dir_length_max})." "Please move the MITK source code directory to a directory with a shorter path." ) endif() if(_bin_n GREATER _bin_dir_length_max) message(WARNING "MITK build directory path length is too long (${_bin_n} > ${_bin_dir_length_max})." "Please move the MITK build directory to a directory with a shorter path." ) endif() endif() #----------------------------------------------------------------------------- # Additional MITK Options (also shown during superbuild) #----------------------------------------------------------------------------- # ----------------------------------------- # General build options option(BUILD_SHARED_LIBS "Build MITK with shared libraries" ON) option(WITH_COVERAGE "Enable/Disable coverage" OFF) option(BUILD_TESTING "Test the project" ON) option(MITK_FAST_TESTING "Disable long-running tests like packaging" OFF) option(MITK_XVFB_TESTING "Execute test drivers through xvfb-run" OFF) option(MITK_BUILD_ALL_APPS "Build all MITK applications" OFF) option(MITK_BUILD_EXAMPLES "Build the MITK Examples" OFF) option(MITK_ENABLE_PIC_READER "Enable support for reading the DKFZ pic file format." ON) mark_as_advanced( MITK_XVFB_TESTING MITK_FAST_TESTING MITK_BUILD_ALL_APPS MITK_ENABLE_PIC_READER ) #----------------------------------------------------------------------------- # 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_EXTENSION_DIRS}) set(MITK_CMAKE_EXTERNALS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMakeExternals) get_filename_component(MITK_CMAKE_EXTERNALS_EXTENSION_DIR ${MITK_CMAKE_EXTERNALS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake) include(${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake) endif() endforeach() # ----------------------------------------- # Other MITK_USE_* options not related to # external projects build via the # MITK superbuild option(MITK_USE_BLUEBERRY "Build the BlueBerry platform" ON) option(MITK_USE_OpenCL "Use OpenCL GPU-Computing library" OFF) option(MITK_USE_OpenMP "Use OpenMP" OFF) option(MITK_USE_Python3 "Use Python 3" OFF) #----------------------------------------------------------------------------- # Build configurations #----------------------------------------------------------------------------- set(_buildConfigs "Custom") file(GLOB _buildConfigFiles CMake/BuildConfigurations/*.cmake) foreach(_buildConfigFile ${_buildConfigFiles}) get_filename_component(_buildConfigFile ${_buildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_buildConfigFile}) endforeach() foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) file(GLOB _extBuildConfigFiles ${MITK_EXTENSION_DIR}/CMake/BuildConfigurations/*.cmake) foreach(_extBuildConfigFile ${_extBuildConfigFiles}) get_filename_component(_extBuildConfigFile ${_extBuildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_extBuildConfigFile}) endforeach() list(REMOVE_DUPLICATES _buildConfigs) endforeach() set(MITK_BUILD_CONFIGURATION "Custom" CACHE STRING "Use pre-defined MITK configurations") set_property(CACHE MITK_BUILD_CONFIGURATION PROPERTY STRINGS ${_buildConfigs}) mitkFunctionEnableBuildConfiguration() mitkFunctionCreateWhitelistPaths(MITK) mitkFunctionFindWhitelists(MITK) # ----------------------------------------- # Qt version related variables option(MITK_USE_Qt5 "Use Qt 5 library" ON) if(MITK_USE_Qt5) set(MITK_QT5_MINIMUM_VERSION 5.12) set(MITK_QT5_COMPONENTS Concurrent OpenGL PrintSupport Script Sql Svg Widgets Xml XmlPatterns WebEngineWidgets UiTools Help LinguistTools) if(APPLE) list(APPEND MITK_QT5_COMPONENTS DBus) elseif(UNIX) list(APPEND MITK_QT5_COMPONENTS X11Extras) endif() # Hint at default install locations of Qt if(NOT Qt5_DIR) if(MSVC) set(_dir_candidates "C:/Qt") if(CMAKE_GENERATOR MATCHES "^Visual Studio [0-9]+ ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") elseif(CMAKE_GENERATOR MATCHES "Ninja") include(mitkFunctionGetMSVCVersion) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_PRODUCT_NAME MATCHES "^Visual Studio ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") endif() endif() if(_compilers MATCHES "[0-9]+") if (CMAKE_MATCH_0 EQUAL 2019) list(APPEND _compilers "msvc2017") # Binary compatible to 2019 endif() endif() else() set(_dir_candidates ~/Qt) if(APPLE) set(_compilers clang) else() list(APPEND _dir_candidates /opt/Qt) set(_compilers gcc) endif() endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) foreach(_compiler ${_compilers}) list(APPEND _compilers64 "${_compiler}_64") endforeach() set(_compilers ${_compilers64}) endif() foreach(_dir_candidate ${_dir_candidates}) get_filename_component(_dir_candidate ${_dir_candidate} REALPATH) foreach(_compiler ${_compilers}) set(_glob_expression "${_dir_candidate}/5.*/${_compiler}") file(GLOB _hints ${_glob_expression}) list(SORT _hints) list(APPEND MITK_QT5_HINTS ${_hints}) endforeach() endforeach() endif() find_package(Qt5 ${MITK_QT5_MINIMUM_VERSION} COMPONENTS ${MITK_QT5_COMPONENTS} REQUIRED HINTS ${MITK_QT5_HINTS}) if(${Qt5_VERSION} VERSION_GREATER_EQUAL 5.13) - message(FATAL_ERROR "Qt version ${Qt5_VERSION_MAJOR}.${Qt5_VERSION_MINOR} is not yet supported. We recommend using the latest long-term support version 5.12.x.") + message(WARNING "Qt version ${Qt5_VERSION_MAJOR}.${Qt5_VERSION_MINOR} is not yet supported. We recommend using version 5.12.x.") endif() endif() # ----------------------------------------- # Custom dependency logic option(MITK_USE_SYSTEM_Boost "Use the system Boost" OFF) set(MITK_USE_Boost_LIBRARIES "" CACHE STRING "A semi-colon separated list of required Boost libraries") if(MITK_USE_cpprestsdk) find_package(OpenSSL QUIET) if(NOT OpenSSL_FOUND) set(openssl_message "Could not find OpenSSL (dependency of C++ REST SDK).\n") if(UNIX) if(APPLE) set(openssl_message "${openssl_message}Please install it using your favorite package management " "system (i.e. Homebrew or MacPorts).\n") else() set(openssl_message "${openssl_message}Please install the dev package of OpenSSL (i.e. libssl-dev).\n") endif() else() set(openssl_message "${openssl_message}Please install Win32 OpenSSL:\n" " https://slproweb.com/products/Win32OpenSSL.html\n") endif() set(openssl_message "${openssl_message}If it still cannot be found, you can hint CMake to find OpenSSL by " "adding/setting the OPENSSL_ROOT_DIR variable to the root directory of an " "OpenSSL installation. Make sure to clear variables of partly found " "versions of OpenSSL before, or they will be mixed up.") message(FATAL_ERROR ${openssl_message}) endif() list(APPEND MITK_USE_Boost_LIBRARIES date_time regex system) if(UNIX) list(APPEND MITK_USE_Boost_LIBRARIES atomic chrono filesystem random thread) endif() list(REMOVE_DUPLICATES MITK_USE_Boost_LIBRARIES) set(MITK_USE_Boost_LIBRARIES ${MITK_USE_Boost_LIBRARIES} CACHE STRING "A semi-colon separated list of required Boost libraries" FORCE) endif() if(MITK_USE_Python3) set(MITK_USE_ZLIB ON CACHE BOOL "" FORCE) if(APPLE AND CMAKE_FRAMEWORK_PATH AND CMAKE_FRAMEWORK_PATH MATCHES "python3\\.?([0-9]+)") find_package(Python3 3.${CMAKE_MATCH_1} EXACT REQUIRED COMPONENTS Interpreter Development NumPy) else() find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy) endif() if(WIN32) string(REPLACE "\\" "/" Python3_STDARCH "${Python3_STDARCH}") string(REPLACE "\\" "/" Python3_STDLIB "${Python3_STDLIB}") string(REPLACE "\\" "/" Python3_SITELIB "${Python3_SITELIB}") endif() endif() if(BUILD_TESTING AND NOT MITK_USE_CppUnit) message("> Forcing MITK_USE_CppUnit to ON because BUILD_TESTING=ON") set(MITK_USE_CppUnit ON CACHE BOOL "Use CppUnit for unit tests" FORCE) endif() if(MITK_USE_BLUEBERRY) option(MITK_BUILD_ALL_PLUGINS "Build all MITK plugins" OFF) mark_as_advanced(MITK_BUILD_ALL_PLUGINS) if(NOT MITK_USE_CTK) message("> Forcing MITK_USE_CTK to ON because of MITK_USE_BLUEBERRY") set(MITK_USE_CTK ON CACHE BOOL "Use CTK in MITK" FORCE) endif() endif() #----------------------------------------------------------------------------- # Pixel type multiplexing #----------------------------------------------------------------------------- # Customize the default pixel types for multiplex macros set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") mark_as_advanced(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES MITK_ACCESSBYITK_DIMENSIONS ) # consistency checks if(NOT MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES) set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES) set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES) set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) string(REPLACE "," ";" _integral_types ${MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES}) string(REPLACE "," ";" _floating_types ${MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES}) foreach(_scalar_type ${_integral_types} ${_floating_types}) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}itk::VariableLengthVector<${_scalar_type}>,") endforeach() string(LENGTH "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" _length) math(EXPR _length "${_length} - 1") string(SUBSTRING "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" 0 ${_length} MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES ${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES} CACHE STRING "List of vector pixel types used in AccessByItk and InstantiateAccessFunction macros for itk::VectorImage types" FORCE) endif() if(NOT MITK_ACCESSBYITK_DIMENSIONS) set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") endif() #----------------------------------------------------------------------------- # Project.xml #----------------------------------------------------------------------------- # A list of topologically ordered targets set(CTEST_PROJECT_SUBPROJECTS) list(APPEND CTEST_PROJECT_SUBPROJECTS MITK-Core MITK-CoreUI MITK-IGT MITK-ToF MITK-Modules # all modules not contained in a specific subproject MITK-Plugins # all plugins not contained in a specific subproject MITK-Examples Unlabeled # special "subproject" catching all unlabeled targets and tests ) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake) get_filename_component(MITK_CMAKE_EXTENSION_DIR ${MITK_CMAKE_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_CMAKE_EXTENSION_DIR}/CTestSubprojectList.cmake) set(MITK_CTEST_SUBPROJECTS "") include(${MITK_CMAKE_EXTENSION_DIR}/CTestSubprojectList.cmake) if(MITK_CTEST_SUBPROJECTS) list(APPEND CTEST_PROJECT_SUBPROJECTS ${MITK_CTEST_SUBPROJECTS}) endif() endif() endforeach() # Configure CTestConfigSubProject.cmake that could be used by CTest scripts configure_file(${MITK_SOURCE_DIR}/CTestConfigSubProject.cmake.in ${MITK_BINARY_DIR}/CTestConfigSubProject.cmake) if(CTEST_PROJECT_ADDITIONAL_TARGETS) # those targets will be executed at the end of the ctest driver script # and they also get their own subproject label set(subproject_list "${CTEST_PROJECT_SUBPROJECTS};${CTEST_PROJECT_ADDITIONAL_TARGETS}") else() set(subproject_list "${CTEST_PROJECT_SUBPROJECTS}") endif() # Generate Project.xml file expected by the CTest driver script mitkFunctionGenerateProjectXml(${MITK_BINARY_DIR} MITK "${subproject_list}" ${MITK_USE_SUPERBUILD}) #----------------------------------------------------------------------------- # Superbuild script #----------------------------------------------------------------------------- if(MITK_USE_SUPERBUILD) include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake") # Print configuration summary message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL) return() endif() #***************************************************************************** #**************************** END OF SUPERBUILD **************************** #***************************************************************************** #----------------------------------------------------------------------------- # Organize MITK targets in folders #----------------------------------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(MITK_ROOT_FOLDER "MITK" CACHE STRING "") mark_as_advanced(MITK_ROOT_FOLDER) #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- include(WriteBasicConfigVersionFile) include(CheckCXXSourceCompiles) include(GenerateExportHeader) include(mitkFunctionAddCustomModuleTest) include(mitkFunctionCheckModuleDependencies) include(mitkFunctionCompileSnippets) include(mitkFunctionConfigureVisualStudioUserProjectFile) include(mitkFunctionConvertXPSchema) include(mitkFunctionCreateBlueBerryApplication) include(mitkFunctionCreateCommandLineApp) include(mitkFunctionCreateModule) include(mitkFunctionCreatePlugin) include(mitkFunctionCreateProvisioningFile) include(mitkFunctionGetLibrarySearchPaths) include(mitkFunctionGetVersion) include(mitkFunctionGetVersionDescription) include(mitkFunctionInstallAutoLoadModules) include(mitkFunctionInstallCTKPlugin) include(mitkFunctionInstallProvisioningFiles) include(mitkFunctionInstallThirdPartyCTKPlugins) include(mitkFunctionOrganizeSources) include(mitkFunctionUseModules) if( ${MITK_USE_MatchPoint} ) include(mitkFunctionCreateMatchPointDeployedAlgorithm) endif() include(mitkMacroConfigureItkPixelTypes) include(mitkMacroCreateExecutable) include(mitkMacroCreateModuleTests) include(mitkMacroGenerateToolsLibrary) include(mitkMacroGetLinuxDistribution) include(mitkMacroGetPMDPlatformString) include(mitkMacroInstall) include(mitkMacroInstallHelperApp) include(mitkMacroInstallTargets) include(mitkMacroMultiplexPicType) # Deprecated include(mitkMacroCreateCTKPlugin) #----------------------------------------------------------------------------- # Global CMake variables #----------------------------------------------------------------------------- # Required and enabled C++14 features for all MITK code. # These are added as PUBLIC compile features to all MITK modules. set(MITK_CXX_FEATURES cxx_auto_type cxx_decltype cxx_enum_forward_declarations cxx_extended_friend_declarations cxx_extern_templates cxx_final cxx_lambdas cxx_local_type_template_args cxx_long_long_type cxx_nullptr cxx_override cxx_range_for cxx_right_angle_brackets cxx_rvalue_references cxx_static_assert cxx_strong_enums cxx_template_template_parameters cxx_trailing_return_types cxx_variadic_macros ) if(NOT DEFINED CMAKE_DEBUG_POSTFIX) # We can't do this yet because the CTK Plugin Framework # cannot cope with a postfix yet. #set(CMAKE_DEBUG_POSTFIX d) endif() #----------------------------------------------------------------------------- # Output directories. #----------------------------------------------------------------------------- set(_default_LIBRARY_output_dir lib) set(_default_RUNTIME_output_dir bin) set(_default_ARCHIVE_output_dir lib) foreach(type LIBRARY RUNTIME ARCHIVE) # Make sure the directory exists if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY AND NOT EXISTS ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) message("Creating directory MITK_CMAKE_${type}_OUTPUT_DIRECTORY: ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") file(MAKE_DIRECTORY "${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") endif() if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY) set(CMAKE_${type}_OUTPUT_DIRECTORY ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) else() set(CMAKE_${type}_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${_default_${type}_output_dir}) set(MITK_CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY}) endif() set(CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY} CACHE INTERNAL "Output directory for ${type} files.") mark_as_advanced(CMAKE_${type}_OUTPUT_DIRECTORY) endforeach() #----------------------------------------------------------------------------- # Set MITK specific options and variables (NOT available during superbuild) #----------------------------------------------------------------------------- # Look for optional Doxygen package find_package(Doxygen) option(BLUEBERRY_DEBUG_SMARTPOINTER "Enable code for debugging smart pointers" OFF) mark_as_advanced(BLUEBERRY_DEBUG_SMARTPOINTER) # Ask the user to show the console window for applications option(MITK_SHOW_CONSOLE_WINDOW "Use this to enable or disable the console window when starting MITK GUI Applications" ON) mark_as_advanced(MITK_SHOW_CONSOLE_WINDOW) # TODO: check if necessary option(USE_ITKZLIB "Use the ITK zlib for pic compression." ON) mark_as_advanced(USE_ITKZLIB) if(NOT MITK_FAST_TESTING) if(DEFINED MITK_CTEST_SCRIPT_MODE AND (MITK_CTEST_SCRIPT_MODE STREQUAL "continuous" OR MITK_CTEST_SCRIPT_MODE STREQUAL "experimental") ) set(MITK_FAST_TESTING ON) endif() endif() if(NOT UNIX) set(MITK_WIN32_FORCE_STATIC "STATIC" CACHE INTERNAL "Use this variable to always build static libraries on non-unix platforms") endif() if(MITK_BUILD_ALL_PLUGINS) set(MITK_BUILD_ALL_PLUGINS_OPTION "FORCE_BUILD_ALL") endif() # Configure pixel types used for ITK image access multiplexing mitkMacroConfigureItkPixelTypes() # Configure module naming conventions set(MITK_MODULE_NAME_REGEX_MATCH "^[A-Z].*$") set(MITK_MODULE_NAME_REGEX_NOT_MATCH "^[Mm][Ii][Tt][Kk].*$") set(MITK_DEFAULT_MODULE_NAME_PREFIX "Mitk") set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) set(MITK_MODULE_NAME_DEFAULTS_TO_DIRECTORY_NAME 1) #----------------------------------------------------------------------------- # Get MITK version info #----------------------------------------------------------------------------- mitkFunctionGetVersion(${MITK_SOURCE_DIR} MITK) mitkFunctionGetVersionDescription(${MITK_SOURCE_DIR} MITK) # MITK_VERSION set(MITK_VERSION_STRING "${MITK_VERSION_MAJOR}.${MITK_VERSION_MINOR}.${MITK_VERSION_PATCH}") if(MITK_VERSION_PATCH STREQUAL "99") set(MITK_VERSION_STRING "${MITK_VERSION_STRING}-${MITK_REVISION_SHORTID}") endif() #----------------------------------------------------------------------------- # Installation preparation # # These should be set before any MITK install macros are used #----------------------------------------------------------------------------- # on macOS all BlueBerry plugins get copied into every # application bundle (.app directory) specified here if(MITK_USE_BLUEBERRY AND APPLE) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 1 option_name) list(GET target_info_list 0 app_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) set(MACOSX_BUNDLE_NAMES ${MACOSX_BUNDLE_NAMES} Mitk${app_name}) endif() endforeach() endif() endforeach() endif() #----------------------------------------------------------------------------- # Set coverage Flags #----------------------------------------------------------------------------- if(WITH_COVERAGE) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(coverage_flags "-g -fprofile-arcs -ftest-coverage -O0 -DNDEBUG") set(COVERAGE_CXX_FLAGS ${coverage_flags}) set(COVERAGE_C_FLAGS ${coverage_flags}) endif() endif() #----------------------------------------------------------------------------- # MITK C/CXX Flags #----------------------------------------------------------------------------- set(MITK_C_FLAGS "${COVERAGE_C_FLAGS}") set(MITK_C_FLAGS_DEBUG ) set(MITK_C_FLAGS_RELEASE ) set(MITK_CXX_FLAGS "${COVERAGE_CXX_FLAGS} ${MITK_CXX14_FLAG}") set(MITK_CXX_FLAGS_DEBUG ) set(MITK_CXX_FLAGS_RELEASE ) set(MITK_EXE_LINKER_FLAGS ) set(MITK_SHARED_LINKER_FLAGS ) if(WIN32) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DPOCO_NO_UNWINDOWS -DWIN32_LEAN_AND_MEAN -DNOMINMAX") mitkFunctionCheckCompilerFlags("/wd4005" MITK_CXX_FLAGS) # warning C4005: macro redefinition mitkFunctionCheckCompilerFlags("/wd4231" MITK_CXX_FLAGS) # warning C4231: nonstandard extension used : 'extern' before template explicit instantiation # the following line should be removed after fixing bug 17637 mitkFunctionCheckCompilerFlags("/wd4316" MITK_CXX_FLAGS) # warning C4316: object alignment on heap mitkFunctionCheckCompilerFlags("/wd4180" MITK_CXX_FLAGS) # warning C4180: qualifier applied to function type has no meaning mitkFunctionCheckCompilerFlags("/wd4251" MITK_CXX_FLAGS) # warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' endif() if(APPLE) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DGL_SILENCE_DEPRECATION") # Apple deprecated OpenGL in macOS 10.14 endif() if(NOT MSVC_VERSION) foreach(_flag -Wall -Wextra -Wpointer-arith -Winvalid-pch -Wcast-align -Wwrite-strings -Wno-error=gnu -Wno-error=unknown-pragmas # The strict-overflow warning is generated by ITK template code -Wno-error=strict-overflow -Woverloaded-virtual -Wstrict-null-sentinel #-Wold-style-cast #-Wsign-promo -Wno-error=deprecated-copy -Wno-array-bounds -fdiagnostics-show-option ) mitkFunctionCheckCAndCXXCompilerFlags(${_flag} MITK_C_FLAGS MITK_CXX_FLAGS) endforeach() endif() if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE) mitkFunctionCheckCompilerFlags("-Wl,--no-undefined" MITK_SHARED_LINKER_FLAGS) mitkFunctionCheckCompilerFlags("-Wl,--as-needed" MITK_SHARED_LINKER_FLAGS) endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionCheckCAndCXXCompilerFlags("-fstack-protector-all" MITK_C_FLAGS MITK_CXX_FLAGS) set(MITK_CXX_FLAGS_RELEASE "-U_FORTIFY_SOURCES -D_FORTIFY_SOURCE=2 ${MITK_CXX_FLAGS_RELEASE}") endif() set(MITK_MODULE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) set(MITK_EXE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) #----------------------------------------------------------------------------- # MITK Packages #----------------------------------------------------------------------------- set(MITK_MODULES_PACKAGE_DEPENDS_DIR ${MITK_SOURCE_DIR}/CMake/PackageDepends) set(MODULES_PACKAGE_DEPENDS_DIRS ${MITK_MODULES_PACKAGE_DEPENDS_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_PACKAGE_DEPENDS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/CMake/PackageDepends) get_filename_component(MITK_PACKAGE_DEPENDS_EXTENSION_DIR ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}) list(APPEND MODULES_PACKAGE_DEPENDS_DIRS ${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}) endif() endforeach() if(NOT MITK_USE_SYSTEM_Boost) set(Boost_NO_SYSTEM_PATHS 1) endif() set(Boost_USE_MULTITHREADED 1) set(Boost_USE_STATIC_LIBS 0) set(Boost_USE_STATIC_RUNTIME 0) set(Boost_ADDITIONAL_VERSIONS 1.68 1.68.0) # We need this later for a DCMTK workaround set(_dcmtk_dir_orig ${DCMTK_DIR}) # This property is populated at the top half of this file get_property(MITK_EXTERNAL_PROJECTS GLOBAL PROPERTY MITK_EXTERNAL_PROJECTS) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(MITK_USE_${ep} AND _package) if(_components) find_package(${_package} COMPONENTS ${_components} REQUIRED CONFIG) else() # Prefer config mode first because it finds external # Config.cmake files pointed at by _DIR variables. # Otherwise, existing Find.cmake files could fail. if(DEFINED ${_package}_DIR) #we store the information because it will be overwritten by find_package #and would get lost for all EPs that use on Find.cmake instead of config #files. set(_temp_EP_${_package}_dir ${${_package}_DIR}) endif(DEFINED ${_package}_DIR) find_package(${_package} QUIET CONFIG) string(TOUPPER "${_package}" _package_uc) if(NOT (${_package}_FOUND OR ${_package_uc}_FOUND)) if(DEFINED _temp_EP_${_package}_dir) set(${_package}_DIR ${_temp_EP_${_package}_dir} CACHE PATH "externaly set dir of the package ${_package}" FORCE) endif(DEFINED _temp_EP_${_package}_dir) find_package(${_package} REQUIRED) endif() endif() endif() endforeach() # Ensure that the MITK CMake module path comes first set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR} ${CMAKE_MODULE_PATH} ) if(MITK_USE_DCMTK) # Due to the preferred CONFIG mode in find_package calls above, # the DCMTKConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMTK. if(${_dcmtk_dir_orig} MATCHES "${MITK_EXTERNAL_PROJECT_PREFIX}.*") # Help our FindDCMTK.cmake script find our super-build DCMTK set(DCMTK_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) else() # Use the original value set(DCMTK_DIR ${_dcmtk_dir_orig}) endif() find_package(DCMTK REQUIRED MODULE) endif() if(MITK_USE_DCMQI) # Due to the preferred CONFIG mode in find_package calls above, # the DCMQIConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMQI. # Help our FindDCMQI.cmake script find our super-build DCMQI set(DCMQI_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) find_package(DCMQI REQUIRED) endif() link_directories(${Boost_LIBRARY_DIRS}) if(MITK_USE_OpenIGTLink) link_directories(${OpenIGTLink_LIBRARY_DIRS}) endif() if(MITK_USE_OpenCL) find_package(OpenCL REQUIRED) endif() if(MITK_USE_OpenMP) find_package(OpenMP REQUIRED COMPONENTS CXX) else() find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) elseif(APPLE AND OpenMP_libomp_LIBRARY AND NOT OpenMP_CXX_LIB_NAMES) set(OpenMP_CXX_LIB_NAMES libomp CACHE STRING "" FORCE) get_filename_component(openmp_lib_dir "${OpenMP_libomp_LIBRARY}" DIRECTORY) set(openmp_include_dir "${openmp_lib_dir}/../include") if(EXISTS "${openmp_include_dir}") get_filename_component(openmp_include_dir "${openmp_include_dir}" REALPATH) set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${openmp_include_dir}" CACHE STRING "" FORCE) find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) endif() endif() endif() endif() # Qt support if(MITK_USE_Qt5) find_package(Qt5Core ${MITK_QT5_MINIMUM_VERSION} REQUIRED) # at least Core required get_target_property(_qmake_exec Qt5::qmake LOCATION) execute_process(COMMAND ${_qmake_exec} -query QT_INSTALL_BINS RESULT_VARIABLE _result OUTPUT_VARIABLE QT_BINARY_DIR ERROR_VARIABLE _error ) string(STRIP "${QT_BINARY_DIR}" QT_BINARY_DIR) if(_result OR NOT EXISTS "${QT_BINARY_DIR}") message(FATAL_ERROR "Could not determine Qt binary directory: ${_result} ${QT_BINARY_DIR} ${_error}") endif() find_program(QT_HELPGENERATOR_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5 qhelpgenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_COLLECTIONGENERATOR_EXECUTABLE NAMES qcollectiongenerator qcollectiongenerator-qt5 qcollectiongenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_ASSISTANT_EXECUTABLE NAMES assistant assistant-qt5 assistant5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_XMLPATTERNS_EXECUTABLE NAMES xmlpatterns PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) mark_as_advanced(QT_HELPGENERATOR_EXECUTABLE QT_COLLECTIONGENERATOR_EXECUTABLE QT_ASSISTANT_EXECUTABLE QT_XMLPATTERNS_EXECUTABLE ) if(MITK_USE_BLUEBERRY) option(BLUEBERRY_USE_QT_HELP "Enable support for integrating plugin documentation into Qt Help" ${DOXYGEN_FOUND}) mark_as_advanced(BLUEBERRY_USE_QT_HELP) # Sanity checks for in-application BlueBerry plug-in help generation if(BLUEBERRY_USE_QT_HELP) set(_force_blueberry_use_qt_help_to_off 0) if(NOT DOXYGEN_FOUND) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen was not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(DOXYGEN_FOUND AND DOXYGEN_VERSION VERSION_LESS 1.8.7) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen version 1.8.7 or newer not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_HELPGENERATOR_EXECUTABLE) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because QT_HELPGENERATOR_EXECUTABLE is empty.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT MITK_USE_Qt5) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because MITK_USE_Qt5 is OFF.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_XMLPATTERNS_EXECUTABLE) message("You have enabled Qt Help support, but QT_XMLPATTERNS_EXECUTABLE is empty") set(_force_blueberry_use_qt_help_to_off 1) endif() if(_force_blueberry_use_qt_help_to_off) set(BLUEBERRY_USE_QT_HELP OFF CACHE BOOL "Enable support for integrating plugin documentation into Qt Help" FORCE) endif() endif() if(BLUEBERRY_QT_HELP_REQUIRED AND NOT BLUEBERRY_USE_QT_HELP) message(FATAL_ERROR "BLUEBERRY_USE_QT_HELP is required to be set to ON") endif() endif() endif() #----------------------------------------------------------------------------- # Testing #----------------------------------------------------------------------------- if(BUILD_TESTING) enable_testing() include(CTest) mark_as_advanced(TCL_TCLSH DART_ROOT) # Setup file for setting custom ctest vars configure_file( CMake/CTestCustom.cmake.in ${MITK_BINARY_DIR}/CTestCustom.cmake @ONLY ) # Initial cache for ProjectTemplate and PluginGenerator tests configure_file( CMake/mitkTestInitialCache.txt.in ${MITK_BINARY_DIR}/mitkTestInitialCache.txt @ONLY ) # Configuration for the CMake-generated test driver set(CMAKE_TESTDRIVER_EXTRA_INCLUDES "#include ") set(CMAKE_TESTDRIVER_BEFORE_TESTMAIN " try {") set(CMAKE_TESTDRIVER_AFTER_TESTMAIN " } catch ( const std::exception & excp ) { fprintf(stderr,\"%s\\n\",excp.what()); return EXIT_FAILURE; } catch( ... ) { printf(\"Exception caught in the test driver\\n\"); return EXIT_FAILURE; } ") set(MITK_TEST_OUTPUT_DIR "${MITK_BINARY_DIR}/test_output") if(NOT EXISTS ${MITK_TEST_OUTPUT_DIR}) file(MAKE_DIRECTORY ${MITK_TEST_OUTPUT_DIR}) endif() # Test the package target include(mitkPackageTest) endif() configure_file(mitkTestingConfig.h.in ${MITK_BINARY_DIR}/mitkTestingConfig.h) #----------------------------------------------------------------------------- # MITK_SUPERBUILD_BINARY_DIR #----------------------------------------------------------------------------- # If MITK_SUPERBUILD_BINARY_DIR isn't defined, it means MITK is *NOT* build using Superbuild. # In that specific case, MITK_SUPERBUILD_BINARY_DIR should default to MITK_BINARY_DIR if(NOT DEFINED MITK_SUPERBUILD_BINARY_DIR) set(MITK_SUPERBUILD_BINARY_DIR ${MITK_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # Set C/CXX and linker flags for MITK code #----------------------------------------------------------------------------- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MITK_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MITK_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MITK_CXX_FLAGS_RELEASE}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MITK_C_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${MITK_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${MITK_C_FLAGS_RELEASE}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MITK_EXE_LINKER_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${MITK_SHARED_LINKER_FLAGS}") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${MITK_MODULE_LINKER_FLAGS}") #----------------------------------------------------------------------------- # Add custom targets representing CDash subprojects #----------------------------------------------------------------------------- foreach(subproject ${CTEST_PROJECT_SUBPROJECTS}) if(NOT TARGET ${subproject} AND NOT subproject MATCHES "Unlabeled") add_custom_target(${subproject}) set_property(TARGET ${subproject} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CTestSubprojects") endif() endforeach() #----------------------------------------------------------------------------- # Add subdirectories #----------------------------------------------------------------------------- add_subdirectory(Utilities) add_subdirectory(Modules) include("${CMAKE_CURRENT_SOURCE_DIR}/Modules/ModuleList.cmake") mitkFunctionWhitelistModules(MITK MITK_MODULES) set(MITK_ROOT_FOLDER_BACKUP "${MITK_ROOT_FOLDER}") foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) get_filename_component(MITK_ROOT_FOLDER ${MITK_EXTENSION_DIR} NAME) set(MITK_MODULES_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Modules) get_filename_component(MITK_MODULES_EXTENSION_DIR ${MITK_MODULES_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake) set(MITK_MODULES "") include(${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake) foreach(mitk_module ${MITK_MODULES}) add_subdirectory(${MITK_MODULES_EXTENSION_DIR}/${mitk_module} Modules/${mitk_module}) endforeach() endif() set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) endforeach() set(MITK_ROOT_FOLDER "${MITK_ROOT_FOLDER_BACKUP}") add_subdirectory(Wrapping) set(MITK_DOXYGEN_OUTPUT_DIR "${PROJECT_BINARY_DIR}/Documentation/Doxygen" CACHE PATH "Output directory for doxygen generated documentation.") if(MITK_USE_BLUEBERRY) set(BLUEBERRY_XPDOC_OUTPUT_DIR "${MITK_DOXYGEN_OUTPUT_DIR}/html/extension-points/html/") execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BLUEBERRY_XPDOC_OUTPUT_DIR}) include("${CMAKE_CURRENT_SOURCE_DIR}/Plugins/PluginList.cmake") mitkFunctionWhitelistPlugins(MITK MITK_PLUGINS) set(mitk_plugins_fullpath "") foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath Plugins/${mitk_plugin}) endforeach() set(MITK_PLUGIN_REGEX_LIST "") foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_PLUGINS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Plugins) get_filename_component(MITK_PLUGINS_EXTENSION_DIR ${MITK_PLUGINS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake) set(MITK_PLUGINS "") include(${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake) foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath ${MITK_PLUGINS_EXTENSION_DIR}/${mitk_plugin}) endforeach() endif() endforeach() if(EXISTS ${MITK_PRIVATE_MODULES}/PluginList.cmake) include(${MITK_PRIVATE_MODULES}/PluginList.cmake) foreach(mitk_plugin ${MITK_PRIVATE_PLUGINS}) list(APPEND mitk_plugins_fullpath ${MITK_PRIVATE_MODULES}/${mitk_plugin}) endforeach() endif() if(MITK_BUILD_EXAMPLES) include("${CMAKE_CURRENT_SOURCE_DIR}/Examples/Plugins/PluginList.cmake") set(mitk_example_plugins_fullpath ) foreach(mitk_example_plugin ${MITK_EXAMPLE_PLUGINS}) list(APPEND mitk_example_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) list(APPEND mitk_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) endforeach() endif() # Specify which plug-ins belong to this project macro(GetMyTargetLibraries all_target_libraries varname) set(re_ctkplugin_mitk "^org_mitk_[a-zA-Z0-9_]+$") set(re_ctkplugin_bb "^org_blueberry_[a-zA-Z0-9_]+$") set(_tmp_list) list(APPEND _tmp_list ${all_target_libraries}) ctkMacroListFilter(_tmp_list re_ctkplugin_mitk re_ctkplugin_bb MITK_PLUGIN_REGEX_LIST OUTPUT_VARIABLE ${varname}) endmacro() # Get infos about application directories and build options set(mitk_apps_fullpath "") foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) if(${option_name}) list(APPEND mitk_apps_fullpath "${MITK_APPLICATIONS_EXTENSION_DIR}/${directory_name}^^${option_name}") endif() endforeach() endif() endforeach() if (mitk_plugins_fullpath) ctkMacroSetupPlugins(${mitk_plugins_fullpath} BUILD_OPTION_PREFIX MITK_BUILD_ APPS ${mitk_apps_fullpath} BUILD_ALL ${MITK_BUILD_ALL_PLUGINS} COMPACT_OPTIONS) endif() set(MITK_PLUGIN_USE_FILE "${MITK_BINARY_DIR}/MitkPluginUseFile.cmake") if(${PROJECT_NAME}_PLUGIN_LIBRARIES) ctkFunctionGeneratePluginUseFile(${MITK_PLUGIN_USE_FILE}) else() file(REMOVE ${MITK_PLUGIN_USE_FILE}) set(MITK_PLUGIN_USE_FILE ) endif() endif() #----------------------------------------------------------------------------- # Documentation #----------------------------------------------------------------------------- if(DOXYGEN_FOUND) add_subdirectory(Documentation) endif() #----------------------------------------------------------------------------- # Installation #----------------------------------------------------------------------------- # set MITK cpack variables # These are the default variables, which can be overwritten ( see below ) include(mitkSetupCPack) set(use_default_config ON) set(ALL_MITK_APPS "") set(activated_apps_no 0) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) set(MITK_APPS "") include(${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake) foreach(mitk_app ${MITK_APPS}) string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) list(APPEND ALL_MITK_APPS "${MITK_EXTENSION_DIR}/Applications/${directory_name}^^${option_name}^^${executable_name}") if(${option_name} OR MITK_BUILD_ALL_APPS) MATH(EXPR activated_apps_no "${activated_apps_no} + 1") endif() endforeach() endif() endforeach() list(LENGTH ALL_MITK_APPS app_count) if(app_count EQUAL 1 AND (activated_apps_no EQUAL 1 OR MITK_BUILD_ALL_APPS)) # Corner case if there is only one app in total set(use_project_cpack ON) elseif(activated_apps_no EQUAL 1 AND NOT MITK_BUILD_ALL_APPS) # Only one app is enabled (no "build all" flag set) set(use_project_cpack ON) else() # Less or more then one app is enabled set(use_project_cpack OFF) endif() foreach(mitk_app ${ALL_MITK_APPS}) # extract target_dir and option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 target_dir) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) # check whether application specific configuration files will be used if(use_project_cpack) # use files if they exist if(EXISTS "${target_dir}/CPackOptions.cmake") include("${target_dir}/CPackOptions.cmake") endif() if(EXISTS "${target_dir}/CPackConfig.cmake.in") set(CPACK_PROJECT_CONFIG_FILE "${target_dir}/CPackConfig.cmake") configure_file(${target_dir}/CPackConfig.cmake.in ${CPACK_PROJECT_CONFIG_FILE} @ONLY) set(use_default_config OFF) endif() endif() # add link to the list list(APPEND CPACK_CREATE_DESKTOP_LINKS "${executable_name}") endif() endforeach() # if no application specific configuration file was used, use default if(use_default_config) configure_file(${MITK_SOURCE_DIR}/MITKCPackOptions.cmake.in ${MITK_BINARY_DIR}/MITKCPackOptions.cmake @ONLY) set(CPACK_PROJECT_CONFIG_FILE "${MITK_BINARY_DIR}/MITKCPackOptions.cmake") endif() # include CPack model once all variables are set include(CPack) # Additional installation rules include(mitkInstallRules) #----------------------------------------------------------------------------- # Last configuration steps #----------------------------------------------------------------------------- # ---------------- Export targets ----------------- set(MITK_EXPORTS_FILE "${MITK_BINARY_DIR}/MitkExports.cmake") file(REMOVE ${MITK_EXPORTS_FILE}) set(targets_to_export) get_property(module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) if(module_targets) list(APPEND targets_to_export ${module_targets}) endif() if(MITK_USE_BLUEBERRY) if(MITK_PLUGIN_LIBRARIES) list(APPEND targets_to_export ${MITK_PLUGIN_LIBRARIES}) endif() endif() export(TARGETS ${targets_to_export} APPEND FILE ${MITK_EXPORTS_FILE}) set(MITK_EXPORTED_TARGET_PROPERTIES ) foreach(target_to_export ${targets_to_export}) get_target_property(autoload_targets ${target_to_export} MITK_AUTOLOAD_TARGETS) if(autoload_targets) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_TARGETS \"${autoload_targets}\")") endif() get_target_property(autoload_dir ${target_to_export} MITK_AUTOLOAD_DIRECTORY) if(autoload_dir) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_DIRECTORY \"${autoload_dir}\")") endif() get_target_property(deprecated_module ${target_to_export} MITK_MODULE_DEPRECATED_SINCE) if(deprecated_module) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_MODULE_DEPRECATED_SINCE \"${deprecated_module}\")") endif() endforeach() # ---------------- External projects ----------------- get_property(MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS_CONFIG GLOBAL PROPERTY MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS) set(MITK_CONFIG_EXTERNAL_PROJECTS ) #string(REPLACE "^^" ";" _mitk_external_projects ${MITK_EXTERNAL_PROJECTS}) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} set(MITK_USE_${ep} ${MITK_USE_${ep}}) set(MITK_${ep}_DIR \"${${ep}_DIR}\") set(MITK_${ep}_COMPONENTS ${_components}) ") endforeach() foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(_components) set(_components_arg COMPONENTS \${_components}) else() set(_components_arg) endif() if(_package) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} if(MITK_USE_${ep}) set(${ep}_DIR \${MITK_${ep}_DIR}) if(MITK_${ep}_COMPONENTS) mitkMacroFindDependency(${_package} COMPONENTS \${MITK_${ep}_COMPONENTS}) else() mitkMacroFindDependency(${_package}) endif() endif()") endif() endforeach() # ---------------- Tools ----------------- configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactory.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactoryLoader.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactoryLoader.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolGUIExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolGUIExtensionITKFactory.cpp.in COPYONLY) # ---------------- Configure files ----------------- configure_file(mitkVersion.h.in ${MITK_BINARY_DIR}/mitkVersion.h) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) set(IPFUNC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ipFunc) set(UTILITIES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) configure_file(MITKConfig.cmake.in ${MITK_BINARY_DIR}/MITKConfig.cmake @ONLY) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake VERSION ${MITK_VERSION_STRING} COMPATIBILITY AnyNewerVersion) #----------------------------------------------------------------------------- # MITK Applications #----------------------------------------------------------------------------- # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Applications) if(MSVC AND TARGET MitkWorkbench) set_directory_properties(PROPERTIES VS_STARTUP_PROJECT MitkWorkbench) endif() foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_EXTENSION_DIR}/Applications) get_filename_component(MITK_APPLICATIONS_EXTENSION_DIR ${MITK_APPLICATIONS_EXTENSION_DIR} ABSOLUTE) if(EXISTS ${MITK_APPLICATIONS_EXTENSION_DIR}/CMakeLists.txt) add_subdirectory(${MITK_APPLICATIONS_EXTENSION_DIR} Applications) endif() endforeach() #----------------------------------------------------------------------------- # MITK Examples #----------------------------------------------------------------------------- if(MITK_BUILD_EXAMPLES) # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Examples) endif() #----------------------------------------------------------------------------- # Print configuration summary #----------------------------------------------------------------------------- message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL ) diff --git a/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.cpp b/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.cpp index 2f67220497..f51bbb69e1 100644 --- a/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.cpp +++ b/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.cpp @@ -1,191 +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. ============================================================================*/ #include "SimpleRenderWindowView.h" #include #include "org_mitk_example_gui_customviewer_views_Activator.h" #include #include #include #include +#include #include #include /** * \brief Helper class adapted from QmitkAbstractRenderEditor by defining the correct plugin context. * * This helper class adapted from QmitkAbstractRenderEditor provides the rendering manager interface. */ // //! [SimpleRenderWindowViewHelper] class AbstractRenderWindowViewPrivate { public: AbstractRenderWindowViewPrivate() : m_RenderingManagerInterface(mitk::MakeRenderingManagerInterface(mitk::RenderingManager::GetInstance())), m_PrefServiceTracker(org_mitk_example_gui_customviewer_views_Activator::GetPluginContext()) // //! [SimpleRenderWindowViewHelper] { m_PrefServiceTracker.open(); } ~AbstractRenderWindowViewPrivate() { delete m_RenderingManagerInterface; } mitk::IRenderingManager *m_RenderingManagerInterface; ctkServiceTracker m_PrefServiceTracker; berry::IBerryPreferences::Pointer m_Prefs; }; const std::string SimpleRenderWindowView::VIEW_ID = "org.mitk.customviewer.views.simplerenderwindowview"; SimpleRenderWindowView::SimpleRenderWindowView() : m_RenderWindow(nullptr), d(new AbstractRenderWindowViewPrivate) { } SimpleRenderWindowView::~SimpleRenderWindowView() { } QmitkRenderWindow *SimpleRenderWindowView::GetActiveQmitkRenderWindow() const { return m_RenderWindow; } QHash SimpleRenderWindowView::GetRenderWindows() const { QHash wnds; wnds.insert("transversal", m_RenderWindow); return wnds; } QHash SimpleRenderWindowView::GetQmitkRenderWindows() const { QHash wnds; wnds.insert("transversal", m_RenderWindow); return wnds; } QmitkRenderWindow *SimpleRenderWindowView::GetRenderWindow(const QString &id) const { if (id == "transversal") { return m_RenderWindow; } return nullptr; } QmitkRenderWindow *SimpleRenderWindowView::GetQmitkRenderWindow(const QString &id) const { if (id == "transversal") { return m_RenderWindow; } return nullptr; } QmitkRenderWindow *SimpleRenderWindowView::GetQmitkRenderWindow(const mitk::BaseRenderer::ViewDirection &viewDirection) const { if (viewDirection == mitk::BaseRenderer::ViewDirection::AXIAL) { return m_RenderWindow; } return 0; } -mitk::Point3D SimpleRenderWindowView::GetSelectedPosition(const QString & /*id*/) const -{ - const mitk::PlaneGeometry *pg = m_RenderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); - if (pg) - { - return pg->GetCenter(); - } - else - { - return mitk::Point3D(); - } -} - -void SimpleRenderWindowView::SetSelectedPosition(const mitk::Point3D &, const QString &) -{ -} - -void SimpleRenderWindowView::EnableDecorations(bool enable, const QStringList &decorations) -{ - if (decorations.isEmpty() || decorations.contains(DECORATION_MENU)) - { - m_RenderWindow->ActivateMenuWidget(enable); - } -} - -bool SimpleRenderWindowView::IsDecorationEnabled(const QString &decoration) const -{ - if (decoration == DECORATION_MENU) - { - return m_RenderWindow->GetActivateMenuWidgetFlag(); - } - return false; -} - -QStringList SimpleRenderWindowView::GetDecorations() const -{ - QStringList decorations; - decorations << DECORATION_MENU; - return decorations; -} - void SimpleRenderWindowView::SetFocus() { m_RenderWindow->setFocus(); } // //! [SimpleRenderWindowViewCreatePartControl] void SimpleRenderWindowView::CreateQtPartControl(QWidget *parent) { QVBoxLayout *layout = new QVBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); m_RenderWindow = new QmitkRenderWindow(parent); layout->addWidget(m_RenderWindow); mitk::DataStorage::Pointer ds = this->GetDataStorage(); m_RenderWindow->GetRenderer()->SetDataStorage(ds); this->RequestUpdate(); } // //! [SimpleRenderWindowViewCreatePartControl] mitk::IRenderingManager *SimpleRenderWindowView::GetRenderingManager() const { // we use the global rendering manager here. This should maybe replaced // by a local one, managing only the render windows specific for the view return d->m_RenderingManagerInterface; } void SimpleRenderWindowView::RequestUpdate(mitk::RenderingManager::RequestType requestType) { if (GetRenderingManager()) GetRenderingManager()->RequestUpdateAll(requestType); } void SimpleRenderWindowView::ForceImmediateUpdate(mitk::RenderingManager::RequestType requestType) { if (GetRenderingManager()) GetRenderingManager()->ForceImmediateUpdateAll(requestType); } mitk::SliceNavigationController *SimpleRenderWindowView::GetTimeNavigationController() const { if (GetRenderingManager()) return GetRenderingManager()->GetTimeNavigationController(); return nullptr; } + +mitk::Point3D SimpleRenderWindowView::GetSelectedPosition(const QString& /*id*/) const +{ + const mitk::PlaneGeometry* pg = m_RenderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); + if (pg) + { + return pg->GetCenter(); + } + else + { + return mitk::Point3D(); + } +} + +void SimpleRenderWindowView::SetSelectedPosition(const mitk::Point3D&, const QString&) +{ +} + +mitk::TimePointType SimpleRenderWindowView::GetSelectedTimePoint(const QString& /*id*/) const +{ + auto timeNavigator = this->GetTimeNavigationController(); + if (nullptr != timeNavigator) + { + return timeNavigator->GetSelectedTimePoint(); + } + return 0; +} + +void SimpleRenderWindowView::EnableDecorations(bool enable, const QStringList& decorations) +{ + if (decorations.isEmpty() || decorations.contains(DECORATION_MENU)) + { + m_RenderWindow->ActivateMenuWidget(enable); + } +} + +bool SimpleRenderWindowView::IsDecorationEnabled(const QString& decoration) const +{ + if (decoration == DECORATION_MENU) + { + return m_RenderWindow->GetActivateMenuWidgetFlag(); + } + return false; +} + +QStringList SimpleRenderWindowView::GetDecorations() const +{ + QStringList decorations; + decorations << DECORATION_MENU; + return decorations; +} diff --git a/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.h b/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.h index 917fa54e42..7ef01d3d9a 100644 --- a/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.h +++ b/Examples/Plugins/org.mitk.example.gui.customviewer.views/src/internal/SimpleRenderWindowView.h @@ -1,142 +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 SimpleRenderWindowView_H_ #define SimpleRenderWindowView_H_ #include #include #include class QmitkRenderWindow; class AbstractRenderWindowViewPrivate; /** * \brief A view class suited for the ViewerPerspective within the custom viewer plug-in. * * This view class contributes data node rendering functionality to the ViewerPerspective. * Being a subclass of QmitkAbstractView, this class yields access to the data storage and * thus is interconnected with the mitk::QmitkDataManagerView present in the same perspective. * As a subclass of mitk::IRenderWindowPart, this class provides an instance of QmitkRenderWindow. * A SimpleRenderWindowView instance is part of the ViewerPerspective for data visualization. */ // //! [SimpleRenderWindowViewDeclaration] class SimpleRenderWindowView : public QmitkAbstractView, public mitk::IRenderWindowPart // //! [SimpleRenderWindowViewDeclaration] { Q_OBJECT public: /** * Standard constructor. */ SimpleRenderWindowView(); ~SimpleRenderWindowView() override; /** * String based view identifier. */ static const std::string VIEW_ID; berryObjectMacro(SimpleRenderWindowView); // ------------------- mitk::IRenderWindowPart ---------------------- /** * \see mitk::IRenderWindowPart::GetActiveQmitkRenderWindow() */ QmitkRenderWindow *GetActiveQmitkRenderWindow() const override; QHash GetRenderWindows() const; /** * \see mitk::IRenderWindowPart::GetQmitkRenderWindows() */ QHash GetQmitkRenderWindows() const override; QmitkRenderWindow *GetRenderWindow(const QString &id) const; /** * \see mitk::IRenderWindowPart::GetQmitkRenderWindow(QString) */ QmitkRenderWindow *GetQmitkRenderWindow(const QString &id) const override; /** * \see mitk::IRenderWindowPart::GetQmitkRenderWindow(mitk::BaseRenderer::ViewDirection) */ QmitkRenderWindow *GetQmitkRenderWindow(const mitk::BaseRenderer::ViewDirection &viewDirection) const override; /** - * \see mitk::IRenderWindowPart::GetSelectionPosition() + * \see mitk::QmitkAbstractRenderEditor::GetRenderingManager() */ - mitk::Point3D GetSelectedPosition(const QString &id = QString()) const override; + mitk::IRenderingManager *GetRenderingManager() const override; /** - * \see mitk::IRenderWindowPart::SetSelectedPosition() + * \see mitk::QmitkAbstractRenderEditor::RequestUpdate() */ - void SetSelectedPosition(const mitk::Point3D &pos, const QString &id = QString()) override; + void RequestUpdate( + mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) override; /** - * \see mitk::IRenderWindowPart::EnableDecorations() + * \see mitk::QmitkAbstractRenderEditor::ForceImmediateUpdate() */ - void EnableDecorations(bool enable, const QStringList &decorations = QStringList()) override; + void ForceImmediateUpdate(mitk::RenderingManager::RequestType) override; /** - * \see mitk::IRenderWindowPart::IsDecorationEnabled() + * \see mitk::QmitkAbstractRenderEditor::GetTimeNavigationController() */ - bool IsDecorationEnabled(const QString &decoration) const override; + mitk::SliceNavigationController *GetTimeNavigationController() const override; /** - * \see mitk::IRenderWindowPart::GetDecorations() + * \see mitk::IRenderWindowPart::GetSelectionPosition() */ - QStringList GetDecorations() const override; + mitk::Point3D GetSelectedPosition(const QString& id = QString()) const override; /** - * \see mitk::QmitkAbstractRenderEditor::GetRenderingManager() + * \see mitk::IRenderWindowPart::SetSelectedPosition() */ - mitk::IRenderingManager *GetRenderingManager() const override; + void SetSelectedPosition(const mitk::Point3D& pos, const QString& id = QString()) override; /** - * \see mitk::QmitkAbstractRenderEditor::RequestUpdate() + * \see mitk::IRenderWindowPart::GetSelectedTimePoint() + */ + mitk::TimePointType GetSelectedTimePoint(const QString& id = QString()) const override; + + /** + * \see mitk::IRenderWindowPart::EnableDecorations() */ - void RequestUpdate( - mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) override; + void EnableDecorations(bool enable, const QStringList& decorations = QStringList()) override; /** - * \see mitk::QmitkAbstractRenderEditor::ForceImmediateUpdate() + * \see mitk::IRenderWindowPart::IsDecorationEnabled() */ - void ForceImmediateUpdate(mitk::RenderingManager::RequestType) override; + bool IsDecorationEnabled(const QString& decoration) const override; /** - * \see mitk::QmitkAbstractRenderEditor::GetTimeNavigationController() + * \see mitk::IRenderWindowPart::GetDecorations() */ - mitk::SliceNavigationController *GetTimeNavigationController() const override; + QStringList GetDecorations() const override; protected: void SetFocus() override; /** * Creates the QmitkRenderWindow whose renderer is being connected to the view's data storage. */ void CreateQtPartControl(QWidget *parent) override; private: /** * The view's render window. */ QmitkRenderWindow *m_RenderWindow; QScopedPointer d; }; #endif /*SimpleRenderWindowView_H_*/ diff --git a/Examples/Tutorial/Step8/Step8.cpp b/Examples/Tutorial/Step8/Step8.cpp index 696ba0b9c7..2363ed0a48 100644 --- a/Examples/Tutorial/Step8/Step8.cpp +++ b/Examples/Tutorial/Step8/Step8.cpp @@ -1,77 +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. ============================================================================*/ #include "Step8.h" #include "QmitkLevelWindowWidget.h" #include "QmitkRenderWindow.h" #include "QmitkStdMultiWidget.h" #include "mitkRenderingManager.h" #include #include //##Documentation //## @brief As Step6, but with QmitkStdMultiWidget as widget Step8::Step8(int argc, char *argv[], QWidget *parent) : Step6(argc, argv, parent) { } void Step8::SetupWidgets() { //************************************************************************* // Part I: Create windows and pass the tree to it //************************************************************************* // Create toplevel widget with vertical layout QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setMargin(0); vlayout->setSpacing(2); // Create viewParent widget with horizontal layout QWidget *viewParent = new QWidget(this); vlayout->addWidget(viewParent); QHBoxLayout *hlayout = new QHBoxLayout(viewParent); hlayout->setMargin(0); //************************************************************************* // Part Ia: create and initialize QmitkStdMultiWidget //************************************************************************* QmitkStdMultiWidget *multiWidget = new QmitkStdMultiWidget(viewParent); hlayout->addWidget(multiWidget); // Tell the multiWidget which DataStorage to render multiWidget->SetDataStorage(m_DataStorage); // Initialize the multiWidget with the render windows multiWidget->InitializeMultiWidget(); // Add the displayed views to the DataStorage to see their positions in 2D and 3D multiWidget->AddPlanesToDataStorage(); - multiWidget->SetWidgetPlanesVisibility(true); //************************************************************************* // Part Ib: create and initialize LevelWindowWidget //************************************************************************* QmitkLevelWindowWidget *levelWindowWidget = new QmitkLevelWindowWidget(viewParent); hlayout->addWidget(levelWindowWidget); // Tell the levelWindowWidget which DataStorage to access levelWindowWidget->SetDataStorage(m_DataStorage); } /** \example Step8.cpp */ diff --git a/Modules/CEST/CMakeLists.txt b/Modules/CEST/CMakeLists.txt index b48bd100e0..8aee176bf4 100644 --- a/Modules/CEST/CMakeLists.txt +++ b/Modules/CEST/CMakeLists.txt @@ -1,8 +1,9 @@ MITK_CREATE_MODULE( DEPENDS MitkCore + PRIVATE MitkDICOMReader PACKAGE_DEPENDS PRIVATE ITK|ITKIOImageBase+ITKIOGDCM Poco ) add_subdirectory(autoload/IO) add_subdirectory(test) diff --git a/Modules/CEST/autoload/IO/CMakeLists.txt b/Modules/CEST/autoload/IO/CMakeLists.txt index cb10fda5b5..0e9e46ddbb 100644 --- a/Modules/CEST/autoload/IO/CMakeLists.txt +++ b/Modules/CEST/autoload/IO/CMakeLists.txt @@ -1,6 +1,6 @@ MITK_CREATE_MODULE( CESTIO DEPENDS MitkCEST MitkDICOMReader PACKAGE_DEPENDS PRIVATE ITK|ITKIOGDCM - AUTOLOAD_WITH MitkCore + AUTOLOAD_WITH MitkDICOMReader ) diff --git a/Modules/CEST/autoload/IO/files.cmake b/Modules/CEST/autoload/IO/files.cmake index 8864bec575..d6dda67b3b 100644 --- a/Modules/CEST/autoload/IO/files.cmake +++ b/Modules/CEST/autoload/IO/files.cmake @@ -1,9 +1,10 @@ set(CPP_FILES mitkCESTDICOMReaderService.cpp + mitkCESTGenericDICOMReaderService.cpp mitkCESTIOMimeTypes.cpp mitkCESTIOActivator.cpp ) set(RESOURCE_FILES cest_DKFZ.xml ) diff --git a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp new file mode 100644 index 0000000000..0276670704 --- /dev/null +++ b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp @@ -0,0 +1,407 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +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 "mitkCESTGenericDICOMReaderService.h" + +#include "mitkIOMimeTypes.h" +#include +#include +#include +#include +#include +#include + +#include "mitkCESTImageNormalizationFilter.h" + +#include "itksys/SystemTools.hxx" + +#include +#include +#include + +#include +#include + +namespace +{ + std::string OPTION_NAME_B1() + { + return "B1 amplitude"; + } + + std::string OPTION_NAME_PULSE() + { + return "Pulse duration [us]"; + } + + std::string OPTION_NAME_DC() + { + return "Duty cycle [%]"; + } + + std::string OPTION_NAME_NORMALIZE() + { + return "Normalize data"; + } + + std::string OPTION_NAME_NORMALIZE_AUTOMATIC() + { + return "Automatic"; + } + + std::string OPTION_NAME_NORMALIZE_NO() + { + return "No"; + } + + std::string OPTION_NAME_MERGE() + { + return "Merge all series"; + } + + std::string OPTION_NAME_MERGE_YES() + { + return "Yes"; + } + + std::string OPTION_NAME_MERGE_NO() + { + return "No"; + } + + std::string META_FILE_OPTION_NAME_MERGE() + { + return "CEST.MergeAllSeries"; + } + +} + +namespace mitk +{ + DICOMTagPath DICOM_IMAGING_FREQUENCY_PATH() + { + return mitk::DICOMTagPath(0x0018, 0x0084); + } + + CESTDICOMManualReaderService::CESTDICOMManualReaderService(const CustomMimeType& mimeType, const std::string& description) + : BaseDICOMReaderService(mimeType, description) + { + IFileIO::Options options; + options[OPTION_NAME_B1()] = 0.0; + options[OPTION_NAME_PULSE()] = 0.0; + options[OPTION_NAME_DC()] = 0.0; + std::vector normalizationStrategy; + normalizationStrategy.push_back(OPTION_NAME_NORMALIZE_AUTOMATIC()); + normalizationStrategy.push_back(OPTION_NAME_NORMALIZE_NO()); + options[OPTION_NAME_NORMALIZE()] = normalizationStrategy; + std::vector mergeStrategy; + mergeStrategy.push_back(OPTION_NAME_MERGE_NO()); + mergeStrategy.push_back(OPTION_NAME_MERGE_YES()); + options[OPTION_NAME_MERGE()] = mergeStrategy; + this->SetDefaultOptions(options); + + this->RegisterService(); + } + + namespace + { + void ExtractOptionFromPropertyTree(const std::string& key, boost::property_tree::ptree& root, std::map& options) + { + auto finding = root.find(key); + if (finding != root.not_found()) + { + try + { + options[key] = finding->second.get_value(); + } + catch (const boost::property_tree::ptree_bad_data& /*e*/) + { + options[key] = finding->second.get_value(); + } + } + } + + IFileIO::Options ExtractOptionsFromFile(const std::string& file) + { + boost::property_tree::ptree root; + + if (itksys::SystemTools::FileExists(file)) + { + try + { + boost::property_tree::read_json(file, root, std::locale("C")); + } + catch (const boost::property_tree::json_parser_error & e) + { + MITK_WARN << "Could not parse CEST meta file. Fall back to default values. Error was:\n" << e.what(); + } + } + else + { + MITK_DEBUG << "CEST meta file does not exist. Fall back to default values. CEST meta file path: " << file; + } + + IFileIO::Options options; + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_B1Amplitude(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_PULSEDURATION(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_DutyCycle(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_OFFSETS(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_TREC(), root, options); + ExtractOptionFromPropertyTree(META_FILE_OPTION_NAME_MERGE(), root, options); + + return options; + } + + void TransferOption(const mitk::IFileIO::Options& sourceOptions, const std::string& sourceName, mitk::IFileIO::Options& options, const std::string& newName) + { + auto sourceFinding = sourceOptions.find(sourceName); + auto finding = options.find(newName); + + bool replaceValue = finding == options.end(); + if (!replaceValue) + { + replaceValue = us::any_cast(finding->second) == 0.; + } + + if (sourceFinding != sourceOptions.end() && us::any_cast(sourceFinding->second) != 0. && replaceValue) + { + options[newName] = sourceFinding->second; + } + } + + void TransferMergeOption(const mitk::IFileIO::Options& sourceOptions, const std::string& sourceName, mitk::IFileIO::Options& options, const std::string& newName) + { + auto sourceFinding = sourceOptions.find(sourceName); + auto finding = options.find(newName); + + bool replaceValue = finding == options.end(); + if (!replaceValue) + { + try + { + us::any_cast(finding->second); + } + catch (const us::BadAnyCastException& /*e*/) + { + replaceValue = true; + //if we cannot cast in string the user has not made a selection yet + } + } + + if (sourceFinding != sourceOptions.end() && us::any_cast(sourceFinding->second) != OPTION_NAME_MERGE_NO() && replaceValue) + { + options[newName] = sourceFinding->second; + } + } + } + + std::string CESTDICOMManualReaderService::GetCESTMetaFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "CEST_META.json"; + return metafile; + } + + std::string CESTDICOMManualReaderService::GetTRECFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "TREC.txt"; + return metafile; + } + + std::string CESTDICOMManualReaderService::GetLISTFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "LIST.txt"; + return metafile; + } + + + IFileIO::Options CESTDICOMManualReaderService::GetOptions() const + { + auto options = AbstractFileReader::GetOptions(); + if (!this->GetInputLocation().empty()) + { + auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); + + TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, OPTION_NAME_B1()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, OPTION_NAME_PULSE()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, OPTION_NAME_DC()); + TransferMergeOption(fileOptions, META_FILE_OPTION_NAME_MERGE(), options, OPTION_NAME_MERGE()); + } + return options; + } + + us::Any CESTDICOMManualReaderService::GetOption(const std::string& name) const + { + this->GetOptions(); //ensure (default) options are set. + return AbstractFileReader::GetOption(name); + } + + DICOMFileReader::Pointer CESTDICOMManualReaderService::GetReader(const mitk::StringList& relevantFiles) const + { + auto selector = mitk::DICOMFileReaderSelector::New(); + + const std::string mergeStrategy = this->GetOption(OPTION_NAME_MERGE()).ToString(); + + if (mergeStrategy == OPTION_NAME_MERGE_YES()) + { + auto r = ::us::GetModuleContext()->GetModule()->GetResource("cest_DKFZ.xml"); + selector->AddConfigFromResource(r); + } + + selector->LoadBuiltIn3DnTConfigs(); + selector->SetInputFiles(relevantFiles); + + mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); + if (reader.IsNotNull()) + { + //reset tag cache to ensure that additional tags of interest + //will be regarded by the reader if set later on. + reader->SetTagCache(nullptr); + } + + return reader; + } + + std::vector> CESTDICOMManualReaderService::Read() + { + const Options userOptions = this->GetOptions(); + + const std::string mergeStrategy = userOptions.find(OPTION_NAME_MERGE())->second.ToString(); + this->SetOnlyRegardOwnSeries(mergeStrategy != OPTION_NAME_MERGE_YES()); + + std::vector result; + std::vector dicomResult = BaseDICOMReaderService::Read(); + + const std::string normalizationStrategy = userOptions.find(OPTION_NAME_NORMALIZE())->second.ToString(); + + for (const auto &item : dicomResult) + { + auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); + IFileIO::Options options; + TransferOption(userOptions, OPTION_NAME_B1(), options, CEST_PROPERTY_NAME_B1Amplitude()); + TransferOption(userOptions, OPTION_NAME_PULSE(), options, CEST_PROPERTY_NAME_PULSEDURATION()); + TransferOption(userOptions, OPTION_NAME_DC(), options, CEST_PROPERTY_NAME_DutyCycle()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, CEST_PROPERTY_NAME_B1Amplitude()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, CEST_PROPERTY_NAME_PULSEDURATION()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, CEST_PROPERTY_NAME_DutyCycle()); + + TransferOption(fileOptions, CEST_PROPERTY_NAME_OFFSETS(), options, CEST_PROPERTY_NAME_OFFSETS()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_TREC(), options, CEST_PROPERTY_NAME_TREC()); + + auto trecValues = CustomTagParser::ReadListFromFile(this->GetTRECFilePath()); + auto offsetValues = CustomTagParser::ReadListFromFile(this->GetLISTFilePath()); + + bool isCEST = !offsetValues.empty(); + bool isT1 = !trecValues.empty(); + + if (!isCEST && !isT1) + {//check if there are settings in the metafile + auto finding = fileOptions.find(CEST_PROPERTY_NAME_OFFSETS()); + if (finding != fileOptions.end()) + { + isCEST = true; + offsetValues = finding->second.ToString(); + }; + + finding = fileOptions.find(CEST_PROPERTY_NAME_TREC()); + if (finding != fileOptions.end()) + { + isT1 = true; + trecValues = finding->second.ToString(); + }; + } + + if (isCEST) + { + MITK_INFO << "CEST image detected due to LIST.txt or offset property in CEST_META.json"; + options[CEST_PROPERTY_NAME_OFFSETS()] = offsetValues; + } + else if (isT1) + { + MITK_INFO << "T1 image detected due to TREC.txt or trec property in CEST_META.json"; + options[CEST_PROPERTY_NAME_TREC()] = trecValues; + } + else + { + mitkThrow() << "Cannot load CEST/T1 file. No CEST offsets or T1 trec values specified. LIST.txt/TREC.txt or information in CEST_META.json is missing."; + } + + for (const auto& option : options) + { + item->GetPropertyList()->SetStringProperty(option.first.c_str(), option.second.ToString().c_str()); + } + + auto freqProp = item->GetProperty(mitk::DICOMTagPathToPropertyName(DICOM_IMAGING_FREQUENCY_PATH()).c_str()); + + if (freqProp.IsNull()) + { + mitkThrow() << "Loaded image in invalid state. Does not contain the DICOM Imaging Frequency tag."; + } + SetCESTFrequencyMHz(item, mitk::ConvertDICOMStrToValue(freqProp->GetValueAsString())); + + auto image = dynamic_cast(item.GetPointer()); + + if (isCEST) + { + try + { + auto offsets = ExtractCESTOffset(image); + } + catch (...) + { + mitkThrow() << "Cannot load CEST file. Number of CEST offsets do not equal the number of image time steps. Image time steps: " << image->GetTimeSteps() << "; offset values: " << offsetValues; + } + } + else if (isT1) + { + try + { + auto t1s = ExtractCESTT1Time(image); + } + catch (...) + { + mitkThrow() << "Cannot load T1 file. Number of T1 times do not equal the number of image time steps. Image time steps: " << image->GetTimeSteps() << "; T1 values: " << trecValues; + } + } + + if (normalizationStrategy == OPTION_NAME_NORMALIZE_AUTOMATIC() && mitk::IsNotNormalizedCESTImage(image)) + { + MITK_INFO << "Unnormalized CEST image was loaded and will be normalized automatically."; + auto normalizationFilter = mitk::CESTImageNormalizationFilter::New(); + normalizationFilter->SetInput(image); + normalizationFilter->Update(); + auto normalizedImage = normalizationFilter->GetOutput(); + + auto nameProp = item->GetProperty("name"); + if (!nameProp) + { + mitkThrow() << "Cannot load CEST file. Property \"name\" is missing after BaseDICOMReaderService::Read()."; + } + normalizedImage->SetProperty("name", mitk::StringProperty::New(nameProp->GetValueAsString() + "_normalized")); + result.push_back(normalizedImage); + } + else + { + result.push_back(item); + } + } + + return result; + } + + CESTDICOMManualReaderService *CESTDICOMManualReaderService::Clone() const + { + return new CESTDICOMManualReaderService(*this); + } +} diff --git a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h new file mode 100644 index 0000000000..2918b325c8 --- /dev/null +++ b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h @@ -0,0 +1,54 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef MITKCESTGenericDICOMReaderService_H +#define MITKCESTGenericDICOMReaderService_H + +#include + +namespace mitk { + + /** + Service wrapper that auto selects (using the mitk::DICOMFileReaderSelector) the best DICOMFileReader from + the DICOMReader module and loads the CEST relevant meta data from a provided cest_meta.json file or + provided from the user as reader options. + */ + class CESTDICOMManualReaderService : public BaseDICOMReaderService + { + public: + CESTDICOMManualReaderService(const CustomMimeType& mimeType, const std::string& description); + + /** Uses the AbstractFileReader Read function and add extra steps for CEST meta data */ + using AbstractFileReader::Read; + std::vector > Read() override; + + Options GetOptions() const override; + us::Any GetOption(const std::string& name) const override; + + protected: + CESTDICOMManualReaderService(const CESTDICOMManualReaderService&) = default; + CESTDICOMManualReaderService& operator=(const CESTDICOMManualReaderService&) = default; + + std::string GetCESTMetaFilePath() const; + std::string GetTRECFilePath() const; + std::string GetLISTFilePath() const; + + mitk::DICOMFileReader::Pointer GetReader(const mitk::StringList& relevantFiles) const override; + + private: + CESTDICOMManualReaderService* Clone() const override; + }; + + DICOMTagPath DICOM_IMAGING_FREQUENCY_PATH(); +} + +#endif // MITKCESTGenericDICOMReaderService_H diff --git a/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp b/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp index 008c80bf88..c9be3c3167 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp +++ b/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp @@ -1,43 +1,102 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTIOActivator.h" #include "mitkCESTDICOMReaderService.h" +#include "mitkCESTGenericDICOMReaderService.h" #include +#include #include "mitkCESTIOMimeTypes.h" +#include + namespace mitk { void CESTIOActivator::Load(us::ModuleContext *context) { us::ServiceProperties props; props[us::ServiceConstants::SERVICE_RANKING()] = 10; m_MimeTypes = mitk::MitkCESTIOMimeTypes::Get(); - for (std::vector::const_iterator mimeTypeIter = m_MimeTypes.begin(), - iterEnd = m_MimeTypes.end(); - mimeTypeIter != iterEnd; - ++mimeTypeIter) + for (const auto& mimeType : m_MimeTypes) + { + if (mimeType->GetName() == mitk::MitkCESTIOMimeTypes::CEST_DICOM_WITHOUT_META_FILE_NAME()) + { // "w/o meta" mimetype should only registered with low priority. + context->RegisterService(mimeType); + } + else + { + context->RegisterService(mimeType, props); + } + } + + m_CESTDICOMReader = std::make_unique(); + m_CESTDICOMManualWithMetaFileReader = std::make_unique(MitkCESTIOMimeTypes::CEST_DICOM_WITH_META_FILE_MIMETYPE(), "CEST DICOM Manual Reader"); + m_CESTDICOMManualWithOutMetaFileReader = std::make_unique(MitkCESTIOMimeTypes::CEST_DICOM_WITHOUT_META_FILE_MIMETYPE(), "CEST DICOM Manual Reader"); + + m_Context = context; + { + std::lock_guard lock(m_Mutex); + // Listen for events pertaining to dictionary services. + m_Context->AddServiceListener(this, &CESTIOActivator::DICOMTagsOfInterestServiceChanged, + std::string("(&(") + us::ServiceConstants::OBJECTCLASS() + "=" + + us_service_interface_iid() + "))"); + // Query for any service references matching any language. + std::vector > refs = + context->GetServiceReferences(); + if (!refs.empty()) + { + for (const auto& ref : refs) + { + this->RegisterTagsOfInterest(m_Context->GetService(ref)); + m_Context->UngetService(ref); + } + } + } + } + + void CESTIOActivator::Unload(us::ModuleContext *) + { + for (auto& elem : m_MimeTypes) { - context->RegisterService(*mimeTypeIter, props); + delete elem; + elem = nullptr; } + } - m_CESTDICOMReader.reset(new CESTDICOMReaderService()); + void CESTIOActivator::RegisterTagsOfInterest(IDICOMTagsOfInterest* toiService) const + { + if (toiService != nullptr) + { + toiService->AddTagOfInterest(mitk::DICOM_IMAGING_FREQUENCY_PATH()); + } } - void CESTIOActivator::Unload(us::ModuleContext *) {} + void CESTIOActivator::DICOMTagsOfInterestServiceChanged(const us::ServiceEvent event) + { + std::lock_guard lock(m_Mutex); + // If a dictionary service was registered, see if we + // need one. If so, get a reference to it. + if (event.GetType() == us::ServiceEvent::REGISTERED) + { + // Get a reference to the service object. + us::ServiceReference ref = event.GetServiceReference(); + this->RegisterTagsOfInterest(m_Context->GetService(ref)); + m_Context->UngetService(ref); + } + } } US_EXPORT_MODULE_ACTIVATOR(mitk::CESTIOActivator) diff --git a/Modules/CEST/autoload/IO/mitkCESTIOActivator.h b/Modules/CEST/autoload/IO/mitkCESTIOActivator.h index 87f2aca5ff..5c19a699b0 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOActivator.h +++ b/Modules/CEST/autoload/IO/mitkCESTIOActivator.h @@ -1,40 +1,53 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 MITKCESTIOActivator_H #define MITKCESTIOActivator_H #include +#include #include #include #include +#include namespace mitk { struct IFileReader; class IDICOMTagsOfInterest; class CESTIOActivator : public us::ModuleActivator { public: void Load(us::ModuleContext *context) override; void Unload(us::ModuleContext *context) override; + private: + void RegisterTagsOfInterest(IDICOMTagsOfInterest* toiService) const; + void DICOMTagsOfInterestServiceChanged(const us::ServiceEvent event); + std::unique_ptr m_CESTDICOMReader; + std::unique_ptr m_CESTDICOMManualWithMetaFileReader; + std::unique_ptr m_CESTDICOMManualWithOutMetaFileReader; std::vector m_MimeTypes; + + // Module context + us::ModuleContext* m_Context; + /**mutex to guard the service listening */ + std::mutex m_Mutex; }; } #endif // MITKCESTIOActivator_H diff --git a/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.cpp b/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.cpp index 28b696d7ea..449b415072 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.cpp +++ b/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.cpp @@ -1,123 +1,229 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTIOMimeTypes.h" #include "mitkIOMimeTypes.h" #include #include #include #include #include #include namespace mitk { std::vector MitkCESTIOMimeTypes::Get() { std::vector mimeTypes; // order matters here (descending rank for mime types) + mimeTypes.push_back(CEST_DICOM_WITH_META_FILE_MIMETYPE().Clone()); mimeTypes.push_back(CEST_DICOM_MIMETYPE().Clone()); + mimeTypes.push_back(CEST_DICOM_WITHOUT_META_FILE_MIMETYPE().Clone()); return mimeTypes; } // Mime Types - MitkCESTIOMimeTypes::MitkCESTDicomMimeType::MitkCESTDicomMimeType() : CustomMimeType(CEST_DICOM_MIMETYPE_NAME()) + MitkCESTIOMimeTypes::MitkCESTDicomMimeType::MitkCESTDicomMimeType() : IOMimeTypes::BaseDicomMimeType(CEST_DICOM_MIMETYPE_NAME()) { - this->AddExtension("gdcm"); - this->AddExtension("dcm"); - this->AddExtension("DCM"); - this->AddExtension("dc3"); - this->AddExtension("DC3"); - this->AddExtension("ima"); - this->AddExtension("img"); - this->SetCategory(IOMimeTypes::CATEGORY_IMAGES()); this->SetComment("CEST DICOM"); } bool MitkCESTIOMimeTypes::MitkCESTDicomMimeType::AppliesTo(const std::string &path) const { - bool canRead(CustomMimeType::AppliesTo(path)); + bool canRead(IOMimeTypes::BaseDicomMimeType::AppliesTo(path)); // fix for bug 18572 // Currently this function is called for writing as well as reading, in that case // the image information can of course not be read // This is a bug, this function should only be called for reading. if (!itksys::SystemTools::FileExists(path.c_str())) { return canRead; } // end fix for bug 18572 - // Ask the GDCM ImageIO class directly - itk::GDCMImageIO::Pointer gdcmIO = itk::GDCMImageIO::New(); - canRead = gdcmIO->CanReadFile(path.c_str()); - if (!canRead) { return canRead; } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); mitk::DICOMTag siemensCESTprivateTag(0x0029, 0x1020); mitk::StringList relevantFiles; relevantFiles.push_back(path); scanner->AddTag(siemensCESTprivateTag); scanner->SetInputFiles(relevantFiles); scanner->Scan(); mitk::DICOMTagCache::Pointer tagCache = scanner->GetScanCache(); mitk::DICOMImageFrameList imageFrameList = mitk::ConvertToDICOMImageFrameList(tagCache->GetFrameInfoList()); - mitk::DICOMImageFrameInfo *firstFrame = imageFrameList.begin()->GetPointer(); - std::string byteString = tagCache->GetTagValue(firstFrame, siemensCESTprivateTag).value; + bool mapNotEmpty = false; - if (byteString.empty()) { - return false; - } - mitk::CustomTagParser tagParser(relevantFiles[0]); + if (!imageFrameList.empty()) + { + mitk::DICOMImageFrameInfo* firstFrame = imageFrameList.begin()->GetPointer(); + + std::string byteString = tagCache->GetTagValue(firstFrame, siemensCESTprivateTag).value; - auto parsedPropertyList = tagParser.ParseDicomPropertyString(byteString); + if (byteString.empty()) { + return false; + } + mitk::CustomTagParser tagParser(relevantFiles[0]); - bool mapNotEmpty = parsedPropertyList->GetMap()->size() > 0; + auto parsedPropertyList = tagParser.ParseDicomPropertyString(byteString); + + mapNotEmpty = !parsedPropertyList->GetMap()->empty(); + } return mapNotEmpty; } MitkCESTIOMimeTypes::MitkCESTDicomMimeType *MitkCESTIOMimeTypes::MitkCESTDicomMimeType::Clone() const { return new MitkCESTDicomMimeType(*this); } MitkCESTIOMimeTypes::MitkCESTDicomMimeType MitkCESTIOMimeTypes::CEST_DICOM_MIMETYPE() { return MitkCESTDicomMimeType(); } - // Names std::string MitkCESTIOMimeTypes::CEST_DICOM_MIMETYPE_NAME() { // create a unique and sensible name for this mime type static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".image.dicom.cest"; return name; } + + MitkCESTIOMimeTypes::MitkCESTDicomWithMetaFileMimeType::MitkCESTDicomWithMetaFileMimeType() : IOMimeTypes::BaseDicomMimeType(CEST_DICOM_WITH_META_FILE_NAME()) + { + this->SetCategory(IOMimeTypes::CATEGORY_IMAGES()); + this->SetComment("CEST DICOM"); + } + + bool MitkCESTIOMimeTypes::MitkCESTDicomWithMetaFileMimeType::AppliesTo(const std::string& path) const + { + bool canRead(IOMimeTypes::BaseDicomMimeType::AppliesTo(path)); + + // fix for bug 18572 + // Currently this function is called for writing as well as reading, in that case + // the image information can of course not be read + // This is a bug, this function should only be called for reading. + if (!itksys::SystemTools::FileExists(path.c_str())) + { + return canRead; + } + // end fix for bug 18572 + + if (!canRead) + { + return canRead; + } + + std::string dir = path; + if (!itksys::SystemTools::FileIsDirectory(path)) + { + dir = itksys::SystemTools::GetProgramPath(path); + } + + std::string metafilePath = dir +"/" + "CEST_META.json"; + + canRead = itksys::SystemTools::FileExists(metafilePath.c_str()); + + return canRead; + } + + MitkCESTIOMimeTypes::MitkCESTDicomWithMetaFileMimeType* MitkCESTIOMimeTypes::MitkCESTDicomWithMetaFileMimeType::Clone() const + { + return new MitkCESTDicomWithMetaFileMimeType(*this); + } + + MitkCESTIOMimeTypes::MitkCESTDicomWithMetaFileMimeType MitkCESTIOMimeTypes::CEST_DICOM_WITH_META_FILE_MIMETYPE() + { + return MitkCESTDicomWithMetaFileMimeType(); + } + + std::string MitkCESTIOMimeTypes::CEST_DICOM_WITH_META_FILE_NAME() + { + // create a unique and sensible name for this mime type + static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".image.dicom.cest.generic.meta"; + return name; + } + + MitkCESTIOMimeTypes::MitkCESTDicomWOMetaFileMimeType::MitkCESTDicomWOMetaFileMimeType() : IOMimeTypes::BaseDicomMimeType(CEST_DICOM_WITHOUT_META_FILE_NAME()) + { + this->SetCategory(IOMimeTypes::CATEGORY_IMAGES()); + this->SetComment("CEST DICOM"); + } + + bool MitkCESTIOMimeTypes::MitkCESTDicomWOMetaFileMimeType::AppliesTo(const std::string& path) const + { + bool canRead(IOMimeTypes::BaseDicomMimeType::AppliesTo(path)); + + // fix for bug 18572 + // Currently this function is called for writing as well as reading, in that case + // the image information can of course not be read + // This is a bug, this function should only be called for reading. + if (!itksys::SystemTools::FileExists(path.c_str())) + { + return canRead; + } + // end fix for bug 18572 + + if (!canRead) + { + return canRead; + } + + std::string dir = path; + if (!itksys::SystemTools::FileIsDirectory(path)) + { + dir = itksys::SystemTools::GetProgramPath(path); + } + + std::string metafilePath = dir + "/" + "CEST_META.json"; + + canRead = !itksys::SystemTools::FileExists(metafilePath.c_str()); + + return canRead; + } + + MitkCESTIOMimeTypes::MitkCESTDicomWOMetaFileMimeType* MitkCESTIOMimeTypes::MitkCESTDicomWOMetaFileMimeType::Clone() const + { + return new MitkCESTDicomWOMetaFileMimeType(*this); + } + + MitkCESTIOMimeTypes::MitkCESTDicomWOMetaFileMimeType MitkCESTIOMimeTypes::CEST_DICOM_WITHOUT_META_FILE_MIMETYPE() + { + return MitkCESTDicomWOMetaFileMimeType(); + } + + std::string MitkCESTIOMimeTypes::CEST_DICOM_WITHOUT_META_FILE_NAME() + { + // create a unique and sensible name for this mime type + static std::string name = IOMimeTypes::DEFAULT_BASE_NAME() + ".image.dicom.cest.generic.nometa"; + return name; + } + } diff --git a/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.h b/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.h index e94a72adf8..a35ba83274 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.h +++ b/Modules/CEST/autoload/IO/mitkCESTIOMimeTypes.h @@ -1,52 +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. ============================================================================*/ #ifndef MITKCESTIOMIMETYPES_H #define MITKCESTIOMIMETYPES_H -#include "mitkCustomMimeType.h" +#include "mitkIOMimeTypes.h" #include namespace mitk { /// Provides the custom mime types for MitkCEST class MitkCESTIOMimeTypes { public: /** Mime type that parses dicom files to determine whether they are CEST dicom files. * * Accepts dicom files that contain the substring "CEST_Rev" (via the mitk::CustomTagParser parsing) * in the tSequenceFileName parameter in dicom tag (0x0029, 0x1020) */ - class MitkCESTDicomMimeType : public CustomMimeType + class MitkCESTDicomMimeType : public IOMimeTypes::BaseDicomMimeType { public: MitkCESTDicomMimeType(); bool AppliesTo(const std::string &path) const override; MitkCESTDicomMimeType *Clone() const override; }; static MitkCESTDicomMimeType CEST_DICOM_MIMETYPE(); static std::string CEST_DICOM_MIMETYPE_NAME(); + /** Mime type that indicated generic CEST dicom files. + * + * The mime type assumes that dicom files that have a CEST_META.json file in the + * same directory are CEST DICOMs and relevant for this mime type. + */ + class MitkCESTDicomWithMetaFileMimeType : public IOMimeTypes::BaseDicomMimeType + { + public: + MitkCESTDicomWithMetaFileMimeType(); + bool AppliesTo(const std::string& path) const override; + MitkCESTDicomWithMetaFileMimeType* Clone() const override; + }; + + static MitkCESTDicomWithMetaFileMimeType CEST_DICOM_WITH_META_FILE_MIMETYPE(); + static std::string CEST_DICOM_WITH_META_FILE_NAME(); + + /** Mime type that indicated dicom files that can be potantially read as Generic + * CEST but have *NO* CEST meta information. + * + * The mime type is used to offer the manual CEST loading for all DICOM images with + * low priority if no CEST meta file is present. + */ + class MitkCESTDicomWOMetaFileMimeType : public IOMimeTypes::BaseDicomMimeType + { + public: + MitkCESTDicomWOMetaFileMimeType(); + bool AppliesTo(const std::string& path) const override; + MitkCESTDicomWOMetaFileMimeType* Clone() const override; + }; + + static MitkCESTDicomWOMetaFileMimeType CEST_DICOM_WITHOUT_META_FILE_MIMETYPE(); + static std::string CEST_DICOM_WITHOUT_META_FILE_NAME(); + // Get all Mime Types static std::vector Get(); private: // purposely not implemented MitkCESTIOMimeTypes(); MitkCESTIOMimeTypes(const MitkCESTIOMimeTypes &); }; } #endif // MITKCESTIOMIMETYPES_H diff --git a/Modules/CEST/files.cmake b/Modules/CEST/files.cmake index 34a71a20af..8d29b23be8 100644 --- a/Modules/CEST/files.cmake +++ b/Modules/CEST/files.cmake @@ -1,13 +1,15 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCESTImageNormalizationFilter.cpp mitkCustomTagParser.cpp mitkCESTImageDetectionHelper.cpp + mitkExtractCESTOffset.cpp + mitkCESTPropertyHelper.cpp ) set(RESOURCE_FILES 1416.json 1485.json 1494.json ) diff --git a/Modules/CEST/include/mitkCESTPropertyHelper.h b/Modules/CEST/include/mitkCESTPropertyHelper.h new file mode 100644 index 0000000000..f23f9140b1 --- /dev/null +++ b/Modules/CEST/include/mitkCESTPropertyHelper.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 __CEST_PROERTY_HELPER_H +#define __CEST_PROERTY_HELPER_H + +#include "mitkIPropertyProvider.h" +#include "mitkIPropertyOwner.h" + +#include "MitkCESTExports.h" + +namespace mitk +{ + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PREPERATIONTYPE(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_RECOVERYMODE(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_SPOILINGTYPE(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_OFFSETS(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TREC(); + + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_FREQ(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PULSEDURATION(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_B1Amplitude(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_DutyCycle(); + + /**Helper function that gets the CEST B1 amplitude property ("CEST.B1Amplitude") from the passed property provider. + If it is not possible to generate/get the value an mitk::Exception will be thrown.*/ + double MITKCEST_EXPORT GetCESTB1Amplitude(const IPropertyProvider* provider); + + /**Helper function that gets the CEST frequency property ("CEST.FREQ") from the input image. + If it is not possible to generate/get the value an mitk::Exception will be thrown. + The value is returned in [MHz]. Normally in the property it is stored in [Hz].*/ + double MITKCEST_EXPORT GetCESTFrequency(const IPropertyProvider* provider); + + /**Helper function that sets the CEST frequency property ("CEST.FREQ") in the passed owner. + If it owner is nullptr nothing will be done. + The value is passed in [MHz] and set in the property in [Hz].*/ + void MITKCEST_EXPORT SetCESTFrequencyMHz(IPropertyOwner* owner, double freqInMHz); + + /**Helper function that gets the CEST pulse duration property ("CEST.PulseDuration") from the input image. + If it is not possible to generate/get the value an mitk::Exception will be thrown. + The value is returned in [s]. Normally in the property it is stored in micro secs.*/ + double MITKCEST_EXPORT GetCESTPulseDuration(const IPropertyProvider* provider); + + /**Helper function that gets the CEST duty cycle property ("CEST.DutyCycle") from the input image. + If it is not possible to generate/get the value an mitk::Exception will be thrown. + The value is returned as scaling factor (1 == 100%), in contrast to the porperty where it is stored as + a percentage value (e.g. 56 %, so the function return will be 0.56).*/ + double MITKCEST_EXPORT GetCESTDutyCycle(const IPropertyProvider* provider); +} + +#endif // __CEST_PROERTY_HELPER_H diff --git a/Modules/CEST/include/mitkCustomTagParser.h b/Modules/CEST/include/mitkCustomTagParser.h index 9798b975d3..4718aab1c9 100644 --- a/Modules/CEST/include/mitkCustomTagParser.h +++ b/Modules/CEST/include/mitkCustomTagParser.h @@ -1,147 +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 MITKCUSTOMTAGPARSER_H #define MITKCUSTOMTAGPARSER_H #include #include #include namespace mitk { /** The custom tag parser can be used to parse the custom dicom tag of the siemens private tag (0x0029, 0x1020) to extract relevant CEST data. An initial parsing determines whether the provided string belongs to CEST data at all. To make the check and extract the revision number the following rules are aplied: \n
  1. Sequence name (tSequenceFileName) must either
    1. start with the substring "CEST" (case insensitiv), or
    2. contain the substring "_CEST" (case insensitiv).
  2. Sequence name (tSequenceFileName) must contain the substring "_Rev" (case insensitiv).
  3. All numbers after "_Rev" represent the revision number; until either
    1. the next _, or
    2. end of sequence name.
Which custom parameters to save and to which property name can be controlled by a json file. This file can be either provided as a resource for the MitkCEST module during compilation or placed next to the MitkCEST library in your binary folder. The expected format for the file "REVISIONNUMBER.json":
{
"REVISIONNUMBER" : "revision_json",
"sWiPMemBlock.alFree[1]" : "AdvancedMode",
"sWiPMemBlock.alFree[2]" : "RetreatMode"
}
where :
  • REVISIONNUMBER is the revision number of this json parameter mapping (files with non digit characters in their name will be ignored)
  • sWiPMemBlock.alFree[1] is the name of one parameter in the private dicom tag
  • AdvancedMode is the name of the property the content of sWiPMemBlock.alFree[1] should be saved to
\note It is assumed that the entire content of tag (0x0029, 0x1020) is provided and that it es hex encoded (12\23\04...). If the sampling type is list it will try to access LIST.txt at the location provided in the constructor to read the offsets. */ class MITKCEST_EXPORT CustomTagParser { public: /// the constructor expects a path to one of the files to be loaded or the directory of the dicom files CustomTagParser(std::string relevantFile); /// parse the provided dicom property and return a property list based on the closest revision parameter mapping mitk::PropertyList::Pointer ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty); /// parse the provided string and return a property list based on the closest revision parameter mapping mitk::PropertyList::Pointer ParseDicomPropertyString(std::string dicomPropertyString); + static std::string ReadListFromFile(const std::string& filePath); + /** Extract the revision out of the passed sequenceFileName. If the file name is not a valid CEST file name (see rules in the class documentation) exceptions will be thrown. If the file name is valid but contains no revision number an empty string will be returned. */ static std::string ExtractRevision(std::string sequenceFileName); void SetParseStrategy(std::string parseStrategy); void SetRevisionMappingStrategy(std::string revisionMappingStrategy); - /// name of the property for the offsets, including normalization offsets - static const std::string m_OffsetsPropertyName; - /// name of the property for the data acquisition revision static const std::string m_RevisionPropertyName; /// name of the property for the json parameter mapping revision static const std::string m_JSONRevisionPropertyName; /// prefix for all CEST related property names static const std::string m_CESTPropertyPrefix; protected: std::string GetRevisionAppropriateJSONString(std::string revisionString); void GetClosestLowerRevision(std::string revisionString); std::string GetClosestLowerRevision(std::string revisionString, std::vector availableRevisionsVector); /// Decides whether or not the image is likely to be a T1Map, if not it is assumed to be a CEST sequence bool IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString); /// Get a string filled with the properly formated offsets based on the sampling type and offset std::string GetOffsetString(std::string samplingType, std::string offset, std::string measurements); /// returns a vector revision numbers of all REVISIONNUMBER.json found beside the MitkCEST library std::vector GetExternalRevisions(); /// returns a vector revision numbers of all REVISIONNUMBER.json provided as resources during the compile std::vector GetInternalRevisions(); /// returns the path where external jsons are expected to be located std::string GetExternalJSONDirectory(); /// the closest lower revision provided as resource, empty if none found std::string m_ClosestInternalRevision; /// the closest lower revision provided as a json beside the library, empty if none found std::string m_ClosestExternalRevision; /// revision independent mapping to inject into the revision dependent json string static const std::string m_RevisionIndependentMapping; /// default revision dependent json string if none is found static const std::string m_DefaultJsonString; /// path to the dicom data std::string m_DicomDataPath; /// Should the kind of data be automatically determined or should it be parsed as a specific one std::string m_ParseStrategy; /// How to handle parameter mapping based on absent revision jsons std::string m_RevisionMappingStrategy; }; - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TOTALSCANTIME(); - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PREPERATIONTYPE(); - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_RECOVERYMODE(); - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_SPOILINGTYPE(); - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_OFFSETS(); - const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TREC(); - } #endif // MITKCUSTOMTAGPARSER_H diff --git a/Modules/CEST/include/mitkExtractCESTOffset.h b/Modules/CEST/include/mitkExtractCESTOffset.h new file mode 100644 index 0000000000..6434060a8b --- /dev/null +++ b/Modules/CEST/include/mitkExtractCESTOffset.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 __MITK_EXTRACT_CEST_OFFSET_H_ +#define __MITK_EXTRACT_CEST_OFFSET_H_ + +#include + +#include "MitkCESTExports.h" + +namespace mitk +{ + /**Helper function that gets the CEST offset property ("CEST.Offsets") from the input + image as vector of ScalarType. + If it is not possible to generate/get the offset an mitk::Excpetion will be thrown. + The values of the vector are in [ppm]. + @post Number of extracted offsets equal the number of timesteps of the image. + */ + MITKCEST_EXPORT std::vector ExtractCESTOffset(const BaseData* image); + + /**Helper function that gets the CEST offset property ("CEST.TREC") from the input image as vector of ScalarType. + If it is not possible to generate/get the T1 times an mitk::Excpetion will be thrown. + The values of the vector are in [sec]. In the property they are stored in [ms] and scaled appropriately + before returning. + @post Number of extracted T1 times equal the number of timesteps of the image. + */ + MITKCEST_EXPORT std::vector ExtractCESTT1Time(const BaseData* image); +} + +#endif diff --git a/Modules/CEST/src/mitkCESTImageDetectionHelper.cpp b/Modules/CEST/src/mitkCESTImageDetectionHelper.cpp index b653029097..f87333b2f8 100644 --- a/Modules/CEST/src/mitkCESTImageDetectionHelper.cpp +++ b/Modules/CEST/src/mitkCESTImageDetectionHelper.cpp @@ -1,85 +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. ============================================================================*/ #include "mitkCESTImageDetectionHelper.h" -#include "mitkCustomTagParser.h" +#include "mitkCESTPropertyHelper.h" #include "mitkImage.h" #include "mitkDataNode.h" #include "mitkNodePredicateFunction.h" bool mitk::IsAnyCESTImage(const Image* cestImage) { - if (!cestImage) return false; - - auto prop = cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_TOTALSCANTIME().c_str()); - - return prop.IsNotNull(); + return IsCESTorWasabiImage(cestImage) || IsCESTT1Image(cestImage); }; bool mitk::IsCESTorWasabiImage(const Image* cestImage) { if (!cestImage) return false; - return IsAnyCESTImage(cestImage) && !IsCESTT1Image(cestImage); + auto prop = cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str()); + + return prop.IsNotNull(); }; bool mitk::IsCESTT1Image(const Image* cestImage) { if (!cestImage) return false; auto prop = cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_TREC().c_str()); return prop.IsNotNull(); }; mitk::NodePredicateBase::Pointer mitk::CreateAnyCESTImageNodePredicate() { auto cestCheck = [](const mitk::DataNode * node) { if (node) { return mitk::IsAnyCESTImage(dynamic_cast(node->GetData())); } return false; }; return mitk::NodePredicateFunction::New(cestCheck).GetPointer(); }; mitk::NodePredicateBase::Pointer mitk::CreateCESTorWasabiImageNodePredicate() { auto cestCheck = [](const mitk::DataNode * node) { if (node) { return mitk::IsCESTorWasabiImage(dynamic_cast(node->GetData())); } return false; }; return mitk::NodePredicateFunction::New(cestCheck).GetPointer(); }; mitk::NodePredicateBase::Pointer mitk::CreateCESTT1ImageNodePredicate() { auto cestCheck = [](const mitk::DataNode * node) { if (node) { return mitk::IsCESTT1Image(dynamic_cast(node->GetData())); } return false; }; return mitk::NodePredicateFunction::New(cestCheck).GetPointer(); }; diff --git a/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp b/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp index ac4f25cb88..1edb27f3e3 100644 --- a/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp +++ b/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp @@ -1,229 +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. ============================================================================*/ #include "mitkCESTImageNormalizationFilter.h" -#include +#include +#include #include #include #include #include mitk::CESTImageNormalizationFilter::CESTImageNormalizationFilter() { } mitk::CESTImageNormalizationFilter::~CESTImageNormalizationFilter() { } void mitk::CESTImageNormalizationFilter::GenerateData() { mitk::Image::ConstPointer inputImage = this->GetInput(0); if ((inputImage->GetDimension() != 4)) { mitkThrow() << "mitk::CESTImageNormalizationFilter:GenerateData works only with 4D images, sorry."; return; } auto resultMitkImage = this->GetOutput(); AccessFixedDimensionByItk(inputImage, NormalizeTimeSteps, 4); auto originalTimeGeometry = this->GetInput()->GetTimeGeometry(); auto resultTimeGeometry = mitk::ProportionalTimeGeometry::New(); unsigned int numberOfNonM0s = m_NonM0Indices.size(); resultTimeGeometry->Expand(numberOfNonM0s); for (unsigned int index = 0; index < numberOfNonM0s; ++index) { resultTimeGeometry->SetTimeStepGeometry(originalTimeGeometry->GetGeometryCloneForTimeStep(m_NonM0Indices.at(index)), index); } resultMitkImage->SetTimeGeometry(resultTimeGeometry); resultMitkImage->SetPropertyList(this->GetInput()->GetPropertyList()->Clone()); - resultMitkImage->GetPropertyList()->SetStringProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str(), m_RealOffsets.c_str()); + resultMitkImage->GetPropertyList()->SetStringProperty(CEST_PROPERTY_NAME_OFFSETS().c_str(), m_RealOffsets.c_str()); // remove uids resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0008.0018"); resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0020.000D"); resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0020.000E"); } -std::vector ExtractOffsets(const mitk::Image* image) -{ - std::vector result; - - if (image) - { - std::string offsets = ""; - std::vector parts; - if (image->GetPropertyList()->GetStringProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str(), offsets) && !offsets.empty()) - { - boost::algorithm::trim(offsets); - boost::split(parts, offsets, boost::is_any_of(" ")); - - for (auto part : parts) - { - std::istringstream iss(part); - iss.imbue(std::locale("C")); - double d; - iss >> d; - result.push_back(d); - } - } - } - - return result; -} - - template void mitk::CESTImageNormalizationFilter::NormalizeTimeSteps(const itk::Image* image) { typedef itk::Image ImageType; typedef itk::Image OutputImageType; - auto offsets = ExtractOffsets(this->GetInput()); + auto offsets = ExtractCESTOffset(this->GetInput()); // determine normalization images std::vector mZeroIndices; std::stringstream offsetsWithoutM0; offsetsWithoutM0.imbue(std::locale("C")); m_NonM0Indices.clear(); for (unsigned int index = 0; index < offsets.size(); ++index) { if ((offsets.at(index) < -299) || (offsets.at(index) > 299)) { mZeroIndices.push_back(index); } else { offsetsWithoutM0 << offsets.at(index) << " "; m_NonM0Indices.push_back(index); } } auto resultImage = OutputImageType::New(); typename ImageType::RegionType targetEntireRegion = image->GetLargestPossibleRegion(); targetEntireRegion.SetSize(3, m_NonM0Indices.size()); resultImage->SetRegions(targetEntireRegion); resultImage->Allocate(); resultImage->FillBuffer(0); unsigned int numberOfTimesteps = image->GetLargestPossibleRegion().GetSize(3); typename ImageType::RegionType lowerMZeroRegion = image->GetLargestPossibleRegion(); lowerMZeroRegion.SetSize(3, 1); typename ImageType::RegionType upperMZeroRegion = image->GetLargestPossibleRegion(); upperMZeroRegion.SetSize(3, 1); typename ImageType::RegionType sourceRegion = image->GetLargestPossibleRegion(); sourceRegion.SetSize(3, 1); typename OutputImageType::RegionType targetRegion = resultImage->GetLargestPossibleRegion(); targetRegion.SetSize(3, 1); unsigned int targetTimestep = 0; for (unsigned int sourceTimestep = 0; sourceTimestep < numberOfTimesteps; ++sourceTimestep) { unsigned int lowerMZeroIndex = mZeroIndices[0]; unsigned int upperMZeroIndex = mZeroIndices[0]; for (unsigned int loop = 0; loop < mZeroIndices.size(); ++loop) { if (mZeroIndices[loop] <= sourceTimestep) { lowerMZeroIndex = mZeroIndices[loop]; } if (mZeroIndices[loop] > sourceTimestep) { upperMZeroIndex = mZeroIndices[loop]; break; } } bool isMZero = (lowerMZeroIndex == sourceTimestep); double weight = 0.0; if (lowerMZeroIndex == upperMZeroIndex) { weight = 1.0; } else { weight = 1.0 - double(sourceTimestep - lowerMZeroIndex) / double(upperMZeroIndex - lowerMZeroIndex); } if (isMZero) { //do nothing } else { lowerMZeroRegion.SetIndex(3, lowerMZeroIndex); upperMZeroRegion.SetIndex(3, upperMZeroIndex); sourceRegion.SetIndex(3, sourceTimestep); targetRegion.SetIndex(3, targetTimestep); itk::ImageRegionConstIterator lowerMZeroIterator(image, lowerMZeroRegion); itk::ImageRegionConstIterator upperMZeroIterator(image, upperMZeroRegion); itk::ImageRegionConstIterator sourceIterator(image, sourceRegion); itk::ImageRegionIterator targetIterator(resultImage.GetPointer(), targetRegion); while (!sourceIterator.IsAtEnd()) { double normalizationFactor = weight * lowerMZeroIterator.Get() + (1.0 - weight) * upperMZeroIterator.Get(); if (mitk::Equal(normalizationFactor, 0)) { targetIterator.Set(0); } else { targetIterator.Set(double(sourceIterator.Get()) / normalizationFactor); } ++lowerMZeroIterator; ++upperMZeroIterator; ++sourceIterator; ++targetIterator; } ++targetTimestep; } } // get Pointer to output image mitk::Image::Pointer resultMitkImage = this->GetOutput(); // write into output image mitk::CastToMitkImage(resultImage, resultMitkImage); m_RealOffsets = offsetsWithoutM0.str(); } void mitk::CESTImageNormalizationFilter::GenerateOutputInformation() { mitk::Image::ConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); } bool mitk::IsNotNormalizedCESTImage(const Image* cestImage) { - auto offsets = ExtractOffsets(cestImage); + auto offsets = ExtractCESTOffset(cestImage); - for (auto offset : offsets) + for (const auto& offset : offsets) { if (offset < -299 || offset > 299) { return true; } } return false; }; diff --git a/Modules/CEST/src/mitkCESTPropertyHelper.cpp b/Modules/CEST/src/mitkCESTPropertyHelper.cpp new file mode 100644 index 0000000000..5cf42bfe28 --- /dev/null +++ b/Modules/CEST/src/mitkCESTPropertyHelper.cpp @@ -0,0 +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 "mitkCESTPropertyHelper.h" + +#include "mitkDICOMProperty.h" +#include "mitkStringProperty.h" + +const std::string mitk::CEST_PROPERTY_NAME_PREPERATIONTYPE() +{ + return "CEST.PreparationType"; +} + +const std::string mitk::CEST_PROPERTY_NAME_RECOVERYMODE() +{ + return "CEST.RecoveryMode"; +} + +const std::string mitk::CEST_PROPERTY_NAME_SPOILINGTYPE() +{ + return "CEST.SpoilingType"; +} + +const std::string mitk::CEST_PROPERTY_NAME_OFFSETS() +{ + return "CEST.Offsets"; +} + +const std::string mitk::CEST_PROPERTY_NAME_TREC() +{ + return std::string("CEST.TREC"); +} + +const std::string mitk::CEST_PROPERTY_NAME_FREQ() +{ + return std::string("CEST.FREQ"); +} + +const std::string mitk::CEST_PROPERTY_NAME_PULSEDURATION() +{ + return std::string("CEST.PulseDuration"); +} + +const std::string mitk::CEST_PROPERTY_NAME_B1Amplitude() +{ + return std::string("CEST.B1Amplitude"); +} + +const std::string mitk::CEST_PROPERTY_NAME_DutyCycle() +{ + return std::string("CEST.DutyCycle"); +} + + +double mitk::GetCESTB1Amplitude(const IPropertyProvider* provider) +{ + if (!provider) + { + mitkThrow() << "Cannot determine B! amplitude. Passed property provider is invalid."; + } + + double result = 0.0; + + auto prop = provider->GetConstProperty(CEST_PROPERTY_NAME_B1Amplitude().c_str()); + if (prop.IsNotNull()) + { + result = mitk::ConvertDICOMStrToValue(prop->GetValueAsString()); + } + else mitkThrow() << "Cannot determine B! amplitude. Selected input has no property \"" << CEST_PROPERTY_NAME_B1Amplitude() << "\""; + + return result; +} + +double mitk::GetCESTFrequency(const IPropertyProvider* provider) +{ + if (!provider) + { + mitkThrow() << "Cannot determine frequency. Passed property provider is invalid."; + } + + double result = 0.0; + + auto prop = provider->GetConstProperty(CEST_PROPERTY_NAME_FREQ().c_str()); + if (prop.IsNotNull()) + { + result = mitk::ConvertDICOMStrToValue(prop->GetValueAsString()) * 0.000001; + } + else mitkThrow() << "Cannot determine frequency. Selected input has no property \"" << CEST_PROPERTY_NAME_FREQ() << "\""; + + return result; +} + +void mitk::SetCESTFrequencyMHz(IPropertyOwner* owner, double freqInMHz) +{ + if (nullptr != owner) + { + owner->SetProperty(CEST_PROPERTY_NAME_FREQ().c_str(), mitk::StringProperty::New(ConvertValueToDICOMStr(freqInMHz * 1000000))); + } +} + +double mitk::GetCESTPulseDuration(const IPropertyProvider* provider) +{ + if (!provider) + { + mitkThrow() << "Cannot determine pulse duration. Passed property provider is invalid."; + } + + double result = 0.0; + + auto prop = provider->GetConstProperty(CEST_PROPERTY_NAME_PULSEDURATION().c_str()); + if (prop.IsNotNull()) + { + result = mitk::ConvertDICOMStrToValue(prop->GetValueAsString()) * 0.000001; + } + else mitkThrow() << "Cannot determine pulse duration. Selected input has no property \"" << CEST_PROPERTY_NAME_PULSEDURATION() << "\""; + + return result; +} + +double mitk::GetCESTDutyCycle(const IPropertyProvider* provider) +{ + if (!provider) + { + mitkThrow() << "Cannot determine duty cycle. Passed property provider is invalid."; + } + + double result = 0.0; + + auto prop = provider->GetConstProperty(CEST_PROPERTY_NAME_DutyCycle().c_str()); + if (prop.IsNotNull()) + { + result = mitk::ConvertDICOMStrToValue(prop->GetValueAsString()) * 0.01; + } + else mitkThrow() << "Cannot determine duty cycle. Selected input has no property \"" << CEST_PROPERTY_NAME_DutyCycle() << "\""; + + return result; +} diff --git a/Modules/CEST/src/mitkCustomTagParser.cpp b/Modules/CEST/src/mitkCustomTagParser.cpp index 4f0f0f3d9d..304c76fc12 100644 --- a/Modules/CEST/src/mitkCustomTagParser.cpp +++ b/Modules/CEST/src/mitkCustomTagParser.cpp @@ -1,872 +1,846 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkCustomTagParser.h" #include #include -#include +#include "mitkCESTPropertyHelper.h" #include "mitkIPropertyPersistence.h" + #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" #include "usModuleResource.h" #include "usModuleResourceStream.h" #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IPropertyPersistence *GetPersistenceService() { mitk::IPropertyPersistence *result = nullptr; std::vector> persRegisters = us::GetModuleContext()->GetServiceReferences(); if (!persRegisters.empty()) { if (persRegisters.size() > 1) { MITK_WARN << "Multiple property description services found. Using just one."; } result = us::GetModuleContext()->GetService(persRegisters.front()); } return result; }; } const std::string mitk::CustomTagParser::m_CESTPropertyPrefix = "CEST."; -const std::string mitk::CustomTagParser::m_OffsetsPropertyName = m_CESTPropertyPrefix + "Offsets"; const std::string mitk::CustomTagParser::m_RevisionPropertyName = m_CESTPropertyPrefix + "Revision"; const std::string mitk::CustomTagParser::m_JSONRevisionPropertyName = m_CESTPropertyPrefix + "revision_json"; const std::string mitk::CustomTagParser::m_RevisionIndependentMapping = "\n" " \"sProtConsistencyInfo.tSystemType\" : \"SysType\",\n" " \"sProtConsistencyInfo.flNominalB0\" : \"NominalB0\",\n" " \"sTXSPEC.asNucleusInfo[0].lFrequency\" : \"FREQ\",\n" " \"sTXSPEC.asNucleusInfo[0].flReferenceAmplitude\" : \"RefAmp\",\n" " \"alTR[0]\" : \"TR\",\n" " \"alTE[0]\" : \"TE\",\n" " \"lAverages\" : \"averages\",\n" " \"lRepetitions\" : \"repetitions\",\n" " \"adFlipAngleDegree[0]\" : \"ImageFlipAngle\",\n" " \"lTotalScanTimeSec\" : \"TotalScanTime\","; const std::string mitk::CustomTagParser::m_DefaultJsonString = "{\n" " \"default mapping, corresponds to revision 1416\" : \"revision_json\",\n" " \"sWiPMemBlock.alFree[1]\" : \"AdvancedMode\",\n" " \"sWiPMemBlock.alFree[2]\" : \"RecoveryMode\",\n" " \"sWiPMemBlock.alFree[3]\" : \"DoubleIrrMode\",\n" " \"sWiPMemBlock.alFree[4]\" : \"BinomMode\",\n" " \"sWiPMemBlock.alFree[5]\" : \"MtMode\",\n" " \"sWiPMemBlock.alFree[6]\" : \"PreparationType\",\n" " \"sWiPMemBlock.alFree[7]\" : \"PulseType\",\n" " \"sWiPMemBlock.alFree[8]\" : \"SamplingType\",\n" " \"sWiPMemBlock.alFree[9]\" : \"SpoilingType\",\n" " \"sWiPMemBlock.alFree[10]\" : \"measurements\",\n" " \"sWiPMemBlock.alFree[11]\" : \"NumberOfPulses\",\n" " \"sWiPMemBlock.alFree[12]\" : \"NumberOfLockingPulses\",\n" " \"sWiPMemBlock.alFree[13]\" : \"PulseDuration\",\n" " \"sWiPMemBlock.alFree[14]\" : \"DutyCycle\",\n" " \"sWiPMemBlock.alFree[15]\" : \"RecoveryTime\",\n" " \"sWiPMemBlock.alFree[16]\" : \"RecoveryTimeM0\",\n" " \"sWiPMemBlock.alFree[17]\" : \"ReadoutDelay\",\n" " \"sWiPMemBlock.alFree[18]\" : \"BinomDuration\",\n" " \"sWiPMemBlock.alFree[19]\" : \"BinomDistance\",\n" " \"sWiPMemBlock.alFree[20]\" : \"BinomNumberofPulses\",\n" " \"sWiPMemBlock.alFree[21]\" : \"BinomPreRepetions\",\n" " \"sWiPMemBlock.alFree[22]\" : \"BinomType\",\n" " \"sWiPMemBlock.adFree[1]\" : \"Offset\",\n" " \"sWiPMemBlock.adFree[2]\" : \"B1Amplitude\",\n" " \"sWiPMemBlock.adFree[3]\" : \"AdiabaticPulseMu\",\n" " \"sWiPMemBlock.adFree[4]\" : \"AdiabaticPulseBW\",\n" " \"sWiPMemBlock.adFree[5]\" : \"AdiabaticPulseLength\",\n" " \"sWiPMemBlock.adFree[6]\" : \"AdiabaticPulseAmp\",\n" " \"sWiPMemBlock.adFree[7]\" : \"FermiSlope\",\n" " \"sWiPMemBlock.adFree[8]\" : \"FermiFWHM\",\n" " \"sWiPMemBlock.adFree[9]\" : \"DoubleIrrDuration\",\n" " \"sWiPMemBlock.adFree[10]\" : \"DoubleIrrAmplitude\",\n" " \"sWiPMemBlock.adFree[11]\" : \"DoubleIrrRepetitions\",\n" " \"sWiPMemBlock.adFree[12]\" : \"DoubleIrrPreRepetitions\"\n" "}"; mitk::CustomTagParser::CustomTagParser(std::string relevantFile) : m_ClosestInternalRevision(""), m_ClosestExternalRevision("") { std::string pathToDirectory; std::string fileName; itksys::SystemTools::SplitProgramPath(relevantFile, pathToDirectory, fileName); m_DicomDataPath = pathToDirectory; m_ParseStrategy = "Automatic"; m_RevisionMappingStrategy = "Fuzzy"; } std::string mitk::CustomTagParser::ExtractRevision(std::string sequenceFileName) { //all rules are case insesitive. Thus we convert everything to lower case //in order to check everything only once. std::string cestPrefix = "cest"; std::string cestPrefix2 = "_cest"; std::string cestPrefix3 = "\\cest"; //this version covers the fact that the strings extracted //from the SIEMENS tag has an additional prefix that is seperated by backslash. std::string revisionPrefix = "_rev"; std::transform(sequenceFileName.begin(), sequenceFileName.end(), sequenceFileName.begin(), ::tolower); bool isCEST = sequenceFileName.compare(0, cestPrefix.length(), cestPrefix) == 0; std::size_t foundPosition = 0; if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix2); isCEST = foundPosition != std::string::npos; } if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix3); isCEST = foundPosition != std::string::npos; } if (!isCEST) { mitkThrow() << "Invalid CEST sequence file name. No CEST prefix found. Could not extract revision."; } foundPosition = sequenceFileName.find(revisionPrefix, foundPosition); if (foundPosition == std::string::npos) { mitkThrow() << "Invalid CEST sequence file name. No revision prefix was found in CEST sequence file name. Could not extract revision."; } std::string revisionString = sequenceFileName.substr(foundPosition + revisionPrefix.length(), std::string::npos); std::size_t firstNoneNumber = revisionString.find_first_not_of("0123456789"); if (firstNoneNumber != std::string::npos) { revisionString.erase(firstNoneNumber, std::string::npos); } return revisionString; } bool mitk::CustomTagParser::IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString) { bool isT1 = false; // if a forced parse strategy is set, use that one if ("T1" == m_ParseStrategy) { return true; } if ("CEST/WASABI" == m_ParseStrategy) { return false; } if (("T1Recovery" == preparationType) || ("T1Inversion" == preparationType)) { isT1 = true; } // How to interpret the recoveryMode depends on the age of the sequence // older sequences use 0 = false and 1 = true, newer ones 1 = false and 2 = true. // A rough rule of thumb is to assume that if the SpoilingType is 0, then the first // convention is chosen, if it is 1, then the second applies. Otherwise // we assume revision 1485 and newer to follow the new convention. // This unfortunate heuristic is due to somewhat arbitrary CEST sequence implementations. if (!isT1) { std::string thisIsTrue = "1"; std::string thisIsFalse = "0"; if ("0" == spoilingType) { thisIsFalse = "0"; thisIsTrue = "1"; } else if ("1" == spoilingType) { thisIsFalse = "1"; thisIsTrue = "2"; } else { int revisionNrWeAssumeToBeDifferenciating = 1485; if (std::stoi(revisionString) - revisionNrWeAssumeToBeDifferenciating < 0) { thisIsFalse = "0"; thisIsTrue = "1"; } else { thisIsFalse = "1"; thisIsTrue = "2"; } } if (thisIsFalse == recoveryMode) { isT1 = false; } else if (thisIsTrue == recoveryMode) { isT1 = true; } } return isT1; } mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomPropertyString(std::string dicomPropertyString) { auto results = mitk::PropertyList::New(); if ("" == dicomPropertyString) { //MITK_ERROR << "Could not parse empty custom dicom string"; return results; } std::map privateParameters; // The Siemens private tag contains information like "43\52\23\34". // We jump over each "\" and convert the number; std::string bytes; { const std::size_t SUBSTR_LENGTH = 2; const std::size_t INPUT_LENGTH = dicomPropertyString.length(); if (INPUT_LENGTH < SUBSTR_LENGTH) return results; const std::size_t MAX_INPUT_OFFSET = INPUT_LENGTH - SUBSTR_LENGTH; bytes.reserve(INPUT_LENGTH / 3 + 1); try { for (std::size_t i = 0; i <= MAX_INPUT_OFFSET; i += 3) { std::string byte_string = dicomPropertyString.substr(i, SUBSTR_LENGTH); int byte = static_cast(std::stoi(byte_string.c_str(), nullptr, 16)); bytes.push_back(byte); } } catch (const std::invalid_argument&) // std::stoi() could not perform conversion { return results; } } // extract parameter list std::string parameterListString; { const std::string ASCCONV_BEGIN = "### ASCCONV BEGIN ###"; const std::string ASCCONV_END = "### ASCCONV END ###"; auto offset = bytes.find(ASCCONV_BEGIN); if (std::string::npos == offset) return results; offset += ASCCONV_BEGIN.length(); auto count = bytes.find(ASCCONV_END, offset); if (std::string::npos == count) return results; count -= offset; parameterListString = bytes.substr(offset, count); } boost::replace_all(parameterListString, "\r\n", "\n"); boost::char_separator newlineSeparator("\n"); boost::tokenizer> parameters(parameterListString, newlineSeparator); for (const auto ¶meter : parameters) { std::vector parts; boost::split(parts, parameter, boost::is_any_of("=")); if (parts.size() == 2) { parts[0].erase(std::remove(parts[0].begin(), parts[0].end(), ' '), parts[0].end()); parts[1].erase(parts[1].begin(), parts[1].begin() + 1); // first character is a space privateParameters[parts[0]] = parts[1]; } } std::string revisionString = ""; try { revisionString = ExtractRevision(privateParameters["tSequenceFileName"]); } catch (const std::exception &e) { MITK_ERROR << "Cannot deduce revision information. Reason: "<< e.what(); return results; } results->SetProperty(m_RevisionPropertyName, mitk::StringProperty::New(revisionString)); std::string jsonString = GetRevisionAppropriateJSONString(revisionString); boost::property_tree::ptree root; std::istringstream jsonStream(jsonString); try { boost::property_tree::read_json(jsonStream, root); } catch (const boost::property_tree::json_parser_error &e) { mitkThrow() << "Could not parse json file. Error was:\n" << e.what(); } for (auto it : root) { if (it.second.empty()) { std::string propertyName = m_CESTPropertyPrefix + it.second.data(); if (m_JSONRevisionPropertyName == propertyName) { results->SetProperty(propertyName, mitk::StringProperty::New(it.first)); } else { results->SetProperty(propertyName, mitk::StringProperty::New(privateParameters[it.first])); } } else { MITK_ERROR << "Currently no support for nested dicom tag descriptors in json file."; } } std::string offset = ""; std::string measurements = ""; results->GetStringProperty("CEST.Offset", offset); results->GetStringProperty("CEST.measurements", measurements); if (measurements.empty()) { std::string stringRepetitions = ""; results->GetStringProperty("CEST.repetitions", stringRepetitions); std::string stringAverages = ""; results->GetStringProperty("CEST.averages", stringAverages); const auto ERROR_STRING = "Could not find measurements, fallback assumption of repetitions + averages could not be determined either."; if (!stringRepetitions.empty() && !stringAverages.empty()) { std::stringstream measurementStream; try { measurementStream << std::stoi(stringRepetitions) + std::stoi(stringAverages); measurements = measurementStream.str(); MITK_INFO << "Could not find measurements, assuming repetitions + averages. That is: " << measurements; } catch (const std::invalid_argument&) { MITK_ERROR << ERROR_STRING; } } else { MITK_WARN << ERROR_STRING; } } std::string preparationType = ""; std::string recoveryMode = ""; std::string spoilingType = ""; results->GetStringProperty(CEST_PROPERTY_NAME_PREPERATIONTYPE().c_str(), preparationType); results->GetStringProperty(CEST_PROPERTY_NAME_RECOVERYMODE().c_str(), recoveryMode); results->GetStringProperty(CEST_PROPERTY_NAME_SPOILINGTYPE().c_str(), spoilingType); if (this->IsT1Sequence(preparationType, recoveryMode, spoilingType, revisionString)) { MITK_INFO << "Parsed as T1 image"; - mitk::LocaleSwitch localeSwitch("C"); - std::stringstream trecStream; std::string trecPath = m_DicomDataPath + "/TREC.txt"; - std::ifstream list(trecPath.c_str()); + auto trec = ReadListFromFile(trecPath); - if (list.good()) - { - std::string currentTime; - while (std::getline(list, currentTime)) - { - trecStream << currentTime << " "; - } - } - else + if(trec.empty()) { MITK_WARN << "Assumed T1, but could not load TREC at " << trecPath; } - results->SetStringProperty(CEST_PROPERTY_NAME_TREC().c_str(), trecStream.str().c_str()); + results->SetStringProperty(CEST_PROPERTY_NAME_TREC().c_str(), trec.c_str()); } else { MITK_INFO << "Parsed as CEST or WASABI image"; std::string sampling = ""; bool hasSamplingInformation = results->GetStringProperty("CEST.SamplingType", sampling); if (hasSamplingInformation) { std::string offsets = GetOffsetString(sampling, offset, measurements); - results->SetStringProperty(m_OffsetsPropertyName.c_str(), offsets.c_str()); + results->SetStringProperty(CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets.c_str()); } else { MITK_WARN << "Could not determine sampling type."; } } //persist all properties mitk::IPropertyPersistence *persSrv = GetPersistenceService(); if (persSrv) { auto propertyMap = results->GetMap(); for (auto const &prop : *propertyMap) { PropertyPersistenceInfo::Pointer info = PropertyPersistenceInfo::New(); std::string key = prop.first; std::replace(key.begin(), key.end(), '.', '_'); info->SetNameAndKey(prop.first, key); persSrv->AddInfo(info); } } return results; } +std::string mitk::CustomTagParser::ReadListFromFile(const std::string& filePath) +{ + std::stringstream listStream; + std::ifstream list(filePath.c_str()); + list.imbue(std::locale("C")); + + if (list.good()) + { + std::string currentValue; + while (std::getline(list, currentValue)) + { + listStream << currentValue << " "; + } + } + return listStream.str(); +} + mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty) { if (!dicomProperty) { MITK_ERROR << "DICOM property empty"; } auto results = mitk::PropertyList::New(); if (dicomProperty) { results = ParseDicomPropertyString(dicomProperty->GetValue()); } return results; } std::vector mitk::CustomTagParser::GetInternalRevisions() { const std::vector configs = us::GetModuleContext()->GetModule()->FindResources("/", "*.json", false); std::vector availableRevisionsVector; for (auto const resource : configs) { availableRevisionsVector.push_back(std::stoi(resource.GetBaseName())); } return availableRevisionsVector; } std::vector mitk::CustomTagParser::GetExternalRevisions() { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonsPath = stringToJSONDirectory + "/*.json"; std::set JsonFiles; Poco::Glob::glob(prospectiveJsonsPath, JsonFiles, Poco::Glob::GLOB_CASELESS); std::vector availableRevisionsVector; for (auto const jsonpath : JsonFiles) { std::string jsonDir; std::string jsonName; itksys::SystemTools::SplitProgramPath(jsonpath, jsonDir, jsonName); std::string revision = itksys::SystemTools::GetFilenameWithoutExtension(jsonName); // disregard jsons which contain letters in their name bool onlyNumbers = (revision.find_first_not_of("0123456789") == std::string::npos); if(onlyNumbers) { availableRevisionsVector.push_back(std::stoi(revision)); } } return availableRevisionsVector; } std::string mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString, std::vector availableRevisionsVector) { // descending order std::sort(availableRevisionsVector.begin(), availableRevisionsVector.end(), std::greater<>()); int revision = std::stoi(revisionString); int index = 0; int numberOfRevisions = availableRevisionsVector.size(); while (index < numberOfRevisions) { // current mapping still has a higher revision number if ((availableRevisionsVector[index] - revision) > 0) { ++index; } else { break; } } if (index < numberOfRevisions) { std::stringstream foundRevisionStream; foundRevisionStream << availableRevisionsVector[index]; return foundRevisionStream.str(); } return ""; } void mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString) { m_ClosestInternalRevision = GetClosestLowerRevision(revisionString, GetInternalRevisions()); m_ClosestExternalRevision = GetClosestLowerRevision(revisionString, GetExternalRevisions()); if ("Strict" == m_RevisionMappingStrategy && !((0 == m_ClosestInternalRevision.compare(revisionString)) || (0 == m_ClosestExternalRevision.compare(revisionString)))) { // strict revision mapping and neither revision does match the dicom meta data std::stringstream errorMessageStream; errorMessageStream << "\nCould not parse dicom data in strict mode, data revision " << revisionString << " has no known matching parameter mapping. To use the closest known older parameter mapping select the " << "\"Fuzzy\" revision mapping option when loading the data.\n" << "\nCurrently known revision mappings are:\n Precompiled:"; for (const auto revision : GetInternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n External:"; for (const auto revision : GetExternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n\nExternal revision mapping descriptions should be located at\n\n"; std::string stringToJSONDirectory = GetExternalJSONDirectory(); errorMessageStream << stringToJSONDirectory; errorMessageStream << "\n\nTo provide an external mapping for this revision create a " << revisionString << ".json there. You might need to create the directory first."; mitkThrow() << errorMessageStream.str(); } } std::string mitk::CustomTagParser::GetRevisionAppropriateJSONString(std::string revisionString) { std::string returnValue = ""; if ("" == revisionString) { MITK_WARN << "Could not extract revision"; } else { GetClosestLowerRevision(revisionString); bool useExternal = false; bool useInternal = false; if ("" != m_ClosestExternalRevision) { useExternal = true; } if ("" != m_ClosestInternalRevision) { useInternal = true; } if (useExternal && useInternal) { if (std::stoi(m_ClosestInternalRevision) > std::stoi(m_ClosestExternalRevision)) { useExternal = false; } } if (useExternal) { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonPath = stringToJSONDirectory + "/" + m_ClosestExternalRevision + ".json"; std::ifstream externalJSON(prospectiveJsonPath.c_str()); if (externalJSON.good()) { MITK_INFO << "Found external json for CEST parameters at " << prospectiveJsonPath; std::stringstream buffer; buffer << externalJSON.rdbuf(); returnValue = buffer.str(); useInternal = false; } } if (useInternal) { std::string filename = m_ClosestInternalRevision + ".json"; us::ModuleResource jsonResource = us::GetModuleContext()->GetModule()->GetResource(filename); if (jsonResource.IsValid() && jsonResource.IsFile()) { MITK_INFO << "Found no external json for CEST parameters. Closest internal mapping is for revision " << m_ClosestInternalRevision; us::ModuleResourceStream jsonStream(jsonResource); std::stringstream buffer; buffer << jsonStream.rdbuf(); returnValue = buffer.str(); } } } if ("" == returnValue) { MITK_WARN << "Could not identify parameter mapping for the given revision " << revisionString << ", using default mapping."; returnValue = m_DefaultJsonString; } // inject the revision independent mapping before the first newline { returnValue.insert(returnValue.find("\n"), m_RevisionIndependentMapping); } return returnValue; } std::string mitk::CustomTagParser::GetOffsetString(std::string samplingType, std::string offset, std::string measurements) { - mitk::LocaleSwitch localeSwitch("C"); std::stringstream results; + results.imbue(std::locale("C")); std::string normalizationIndicatingOffset = "-300"; double offsetDouble = 0.0; int measurementsInt = 0; bool validOffset = false; bool validMeasurements = false; if ("" != offset) { validOffset = true; offsetDouble = std::stod(offset); } if ("" != measurements) { validMeasurements = true; measurementsInt = std::stoi(measurements); } std::vector offsetVector; if (validOffset && validMeasurements) { for (int step = 0; step < measurementsInt -1; ++step) { double currentOffset = -offsetDouble + 2 * step * offsetDouble / (measurementsInt - 2.0); offsetVector.push_back(currentOffset); } } else { MITK_WARN << "Invalid offset or measurements, offset calculation will only work for list sampling type."; } if (samplingType == "1" || samplingType == "Regular") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (const auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "2" || samplingType == "Alternating") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (auto& entry : offsetVector) { entry = std::abs(entry); } std::sort(offsetVector.begin(), offsetVector.end(), std::greater<>()); for (unsigned int index = 0; index < offsetVector.size(); ++index) { offsetVector[index] = std::pow(-1, index) * offsetVector[index]; } for (auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "3" || samplingType == "List") { std::string listPath = m_DicomDataPath + "/LIST.txt"; - std::ifstream list(listPath.c_str()); - if (list.good()) + auto values = ReadListFromFile(listPath); + + if (!values.empty()) { - std::string currentOffset; - while (std::getline(list, currentOffset)) - { - results << currentOffset << " "; - } + results << values; } else { MITK_ERROR << "Could not load list at " << listPath; } } else if (samplingType == "4" || samplingType == "SingleOffset") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (int step = 0; step < measurementsInt - 1; ++step) { results << offsetDouble << " "; } } } else { MITK_WARN << "Encountered unknown sampling type."; } std::string resultString = results.str(); // replace multiple spaces by a single space std::string::iterator newEnditerator = std::unique(resultString.begin(), resultString.end(), [=](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); } ); resultString.erase(newEnditerator, resultString.end()); if ((resultString.length() > 0) && (resultString.at(resultString.length() - 1) == ' ')) { resultString.erase(resultString.end() - 1, resultString.end()); } if ((resultString.length() > 0) && (resultString.at(0) == ' ')) { resultString.erase(resultString.begin(), ++(resultString.begin())); } return resultString; } void mitk::CustomTagParser::SetParseStrategy(std::string parseStrategy) { m_ParseStrategy = parseStrategy; } void mitk::CustomTagParser::SetRevisionMappingStrategy(std::string revisionMappingStrategy) { m_RevisionMappingStrategy = revisionMappingStrategy; } std::string mitk::CustomTagParser::GetExternalJSONDirectory() { std::string moduleLocation = us::GetModuleContext()->GetModule()->GetLocation(); std::string stringToModule; std::string libraryName; itksys::SystemTools::SplitProgramPath(moduleLocation, stringToModule, libraryName); std::stringstream jsonDirectory; jsonDirectory << stringToModule << "/CESTRevisionMapping"; return jsonDirectory.str(); } - -const std::string mitk::CEST_PROPERTY_NAME_TOTALSCANTIME() -{ - return "CEST.TotalScanTime"; -}; - -const std::string mitk::CEST_PROPERTY_NAME_PREPERATIONTYPE() -{ - return "CEST.PreparationType"; -}; - -const std::string mitk::CEST_PROPERTY_NAME_RECOVERYMODE() -{ - return "CEST.RecoveryMode"; -}; - -const std::string mitk::CEST_PROPERTY_NAME_SPOILINGTYPE() -{ - return "CEST.SpoilingType"; -}; - -const std::string mitk::CEST_PROPERTY_NAME_OFFSETS() -{ - return "CEST.Offsets"; -}; - -const std::string mitk::CEST_PROPERTY_NAME_TREC() -{ - return std::string("CEST.TREC"); -} diff --git a/Modules/CEST/src/mitkExtractCESTOffset.cpp b/Modules/CEST/src/mitkExtractCESTOffset.cpp new file mode 100644 index 0000000000..4599ebc345 --- /dev/null +++ b/Modules/CEST/src/mitkExtractCESTOffset.cpp @@ -0,0 +1,82 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkExtractCESTOffset.h" +#include "mitkCESTPropertyHelper.h" + +#include +#include + + +std::vector mitk::ExtractCESTT1Time(const BaseData* image) +{ + std::vector result; + + auto prop = image->GetProperty(CEST_PROPERTY_NAME_TREC().c_str()); + if (prop.IsNotNull()) + { + auto valueStr = prop->GetValueAsString(); + + std::istringstream iss; + iss.imbue(std::locale("C")); + iss.str(valueStr); + double d; + + while (iss >> d) + { + if (!iss.fail()) + { + result.emplace_back(d); + } + } + + if (result.size() != image->GetTimeSteps()) mitkThrow() << "Cannot determine T1 times. Selected input has an property \"" << CEST_PROPERTY_NAME_TREC() << "\" that don't match the number of time steps of input."; + + for (auto& value : result) + { + value *= 0.001; + } + } + else mitkThrow() << "Cannot determine T1 time grid (TREC). Selected input has no property \"" << CEST_PROPERTY_NAME_TREC() << "\""; + + return result; +} + +std::vector mitk::ExtractCESTOffset(const BaseData* image) +{ + std::vector result; + + auto prop = image->GetProperty(CEST_PROPERTY_NAME_OFFSETS().c_str()); + if (prop.IsNotNull()) + { + auto valueStr = prop->GetValueAsString(); + + std::istringstream iss; + iss.imbue(std::locale("C")); + iss.str(valueStr); + double d; + + while (iss >> d) + { + if (!iss.fail()) + { + result.emplace_back(d); + } + } + + if (result.size() != image->GetTimeSteps()) mitkThrow() << "Cannot determine offset. Selected input has an property \"" << CEST_PROPERTY_NAME_OFFSETS() << "\" that don't match the number of time steps of input."; + + } + else mitkThrow() << "Cannot determine frequency. Selected input has no property \"" << CEST_PROPERTY_NAME_OFFSETS() << "\""; + + return result; +} diff --git a/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp b/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp index 9e3e9d1bde..4bdf8f651f 100644 --- a/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp +++ b/Modules/CEST/test/mitkCESTDICOMReaderServiceTest.cpp @@ -1,94 +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. ============================================================================*/ // Testing #include "mitkTestFixture.h" #include "mitkTestingMacros.h" // std includes #include // MITK includes #include #include #include #include "mitkCESTImageDetectionHelper.h" #include "mitkCustomTagParser.h" +#include "mitkCESTPropertyHelper.h" class mitkCESTDICOMReaderServiceTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkCESTDICOMReaderServiceTestSuite); // Test the dicom property parsing MITK_TEST(LoadCESTDICOMData_Success); MITK_TEST(LoadNormalizedCESTDICOMData_Success); MITK_TEST(LoadT1DICOMData_Success); CPPUNIT_TEST_SUITE_END(); private: public: void setUp() override { } void tearDown() override { } void LoadCESTDICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string( "Automatic" ); options["Revision mapping"] = std::string( "Strict" ); options["Normalize data"] = std::string("No"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/B1=0.6MUT/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0587.0001.2017.10.25.22.11.10.373351.41828677.IMA"), options); CPPUNIT_ASSERT_MESSAGE("Make certain offsets have been correctly loaded for CEST image." ,cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString() == "-300 2 -2 1.92982 -1.92982 1.85965 -1.85965 1.78947 -1.78947 1.7193 -1.7193 1.64912 -1.64912 1.57895 -1.57895 1.50877 -1.50877 1.4386 -1.4386 1.36842 -1.36842 1.29825 -1.29825 1.22807 -1.22807 1.15789 -1.15789 1.08772 -1.08772 1.01754 -1.01754 0.947368 -0.947368 0.877193 -0.877193 0.807018 -0.807018 0.736842 -0.736842 0.666667 -0.666667 0.596491 -0.596491 0.526316 -0.526316 0.45614 -0.45614 0.385965 -0.385965 0.315789 -0.315789 0.245614 -0.245614 0.175439 -0.175439 0.105263 -0.105263 0.0350877 -0.0350877"); CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 59); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is not loaded as T1.", !mitk::IsCESTT1Image(cestImage)); } void LoadNormalizedCESTDICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string("Automatic"); options["Revision mapping"] = std::string("Strict"); options["Normalize data"] = std::string("Automatic"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/B1=0.6MUT/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0587.0001.2017.10.25.22.11.10.373351.41828677.IMA"), options); auto tempREsult = cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString(); CPPUNIT_ASSERT_MESSAGE("Make certain offsets have been correctly loaded for CEST image.", cestImage->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str())->GetValueAsString() == "2 -2 1.92982 -1.92982 1.85965 -1.85965 1.78947 -1.78947 1.7193 -1.7193 1.64912 -1.64912 1.57895 -1.57895 1.50877 -1.50877 1.4386 -1.4386 1.36842 -1.36842 1.29825 -1.29825 1.22807 -1.22807 1.15789 -1.15789 1.08772 -1.08772 1.01754 -1.01754 0.947368 -0.947368 0.877193 -0.877193 0.807018 -0.807018 0.736842 -0.736842 0.666667 -0.666667 0.596491 -0.596491 0.526316 -0.526316 0.45614 -0.45614 0.385965 -0.385965 0.315789 -0.315789 0.245614 -0.245614 0.175439 -0.175439 0.105263 -0.105263 0.0350877 -0.0350877 "); CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 58); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is not loaded as T1.", !mitk::IsCESTT1Image(cestImage)); } void LoadT1DICOMData_Success() { mitk::IFileReader::Options options; options["Force type"] = std::string("Automatic"); options["Revision mapping"] = std::string("Strict"); mitk::Image::Pointer cestImage = mitk::IOUtil::Load(GetTestDataFilePath("CEST/T1MAP/MEI_NER_PHANTOM.MR.E0202_MEISSNER.0279.0001.2017.10.25.20.21.27.996834.41803047.IMA"), options); std::string temp; CPPUNIT_ASSERT_MESSAGE("Make certain image is loaded as T1.", mitk::IsCESTT1Image(cestImage)); CPPUNIT_ASSERT_MESSAGE("Wrong number of frames are reconstructed.", cestImage->GetTimeSteps() == 17); } }; MITK_TEST_SUITE_REGISTRATION(mitkCESTDICOMReaderService) diff --git a/Modules/CEST/test/mitkCustomTagParserTest.cpp b/Modules/CEST/test/mitkCustomTagParserTest.cpp index 9aa1a04dba..6b34a86053 100644 --- a/Modules/CEST/test/mitkCustomTagParserTest.cpp +++ b/Modules/CEST/test/mitkCustomTagParserTest.cpp @@ -1,374 +1,375 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Testing #include "mitkTestFixture.h" #include "mitkTestingMacros.h" // std includes #include // MITK includes #include "mitkCustomTagParser.h" +#include "mitkCESTPropertyHelper.h" #include //itksys #include #include // microservice includes #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" // VTK includes #include class mitkCustomTagParserTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkCustomTagParserTestSuite); // Test the dicom property parsing MITK_TEST(ValidPropertyParsedToPropertyList_Success); MITK_TEST(ValidPropertyMissingParametersParsedToEmptyPropertiesPropertyList_Success); MITK_TEST(ValidPropertyRevisionVeryLow_UseDefault_Success); MITK_TEST(ValidPropertyNoExactRevisionMatchUseInternal_Success); MITK_TEST(ValidPropertyNoExactRevisionMatchUseExternal_Success); MITK_TEST(ValidPropertyWindowsLineEndings_Success); MITK_TEST(InvalidPropertyInvalidRevision_Failure); MITK_TEST(InvalidPropertyNoCESTSequence_Failure); MITK_TEST(InvalidPropertyGarbageInDelimiters_Failure); MITK_TEST(ValidPropertyAlternatingOffset_Success); MITK_TEST(ValidPropertySimpleOffset_Success); MITK_TEST(ValidPropertyListOffset_Success); MITK_TEST(ExtractRevision); CPPUNIT_TEST_SUITE_END(); private: std::string m_PathToModule; std::string m_ValidCESTCustomTag; std::string m_ValidCESTCustomTagAllParametersMissing; std::string m_ValidCESTCustomTagUnsupportedRevisionTooLow; // rev 12 std::string m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseInternal; // rev 1417 std::string m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseExternal; // rev 120 std::string m_InvalidCESTCustomTagRevisionNoNumber; //rev abc std::string m_NonCESTCustomTag; // no CEST_Rev substring std::string m_GarbageWithinDelimiters; // ASCII part of custom tag is just garbage std::string m_ValidCESTCustomTagWindowsLineEndings; // have windows line endings encoded std::string m_ValidCESTCustomTagAlternatingOffset; std::string m_ValidCESTCustomTagSingleOffset; std::string m_ValidCESTCustomTagListOffset; public: void setUp() override { std::string moduleLocation = us::GetModuleContext()->GetModule()->GetLocation(); std::string libraryName; itksys::SystemTools::SplitProgramPath(moduleLocation, m_PathToModule, libraryName); m_ValidCESTCustomTag = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagAllParametersMissing = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagUnsupportedRevisionTooLow = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\32\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseInternal = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\37\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseExternal = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\32\\30\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_InvalidCESTCustomTagRevisionNoNumber = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\61\\62\\63\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_NonCESTCustomTag = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\59\\59\\59\\59\\5F\\5A\\5A\\5A\\5A\\5A\\5A\\5A\\22\\22\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_GarbageWithinDelimiters = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\32\\33\\39\\37\\34\\30\\32\\62\\76\\30\\77\\6E\\65\\72\\62\\66\\6B\\3C\\73\\79\\64\\68\\66\\6A\\6F\\5A\\49\\47\\28\\3D\\2F\\51\\22\\26\\54\\24\\29\\28\\2F\\26\\54\\C2\\A7\\51\\3D\\28\\25\\2F\\36\\7A\\C3\\9F\\61\\62\\72\\6E\\65\\62\\74\\6C\\61\\6B\\73\\64\\20\\76\\6E\\66\\6C\\61\\69\\72\\62\\74\\7A\\38\\33\\34\\35\\74\\31\\38\\33\\30\\37\\34\\7A\\62\\74\\28\\2F\\54\\26\\3D\\26\\28\\51\\50\\57\\C2\\A7\\45\\25\\3D\\51\\57\\C2\\A7\\62\\70\\39\\38\\65\\72\\74\\76\\68\\6E\\38\\39\\33\\34\\36\\7A\\31\\76\\C3\\9F\\30\\35\\39\\34\\75\\31\\6E\\30\\20\\74\\76\\6C\\61\\62\\72\\7A\\70\\39\\30\\65\\77\\35\\37\\32\\33\\38\\34\\36\\30\\39\\74\\7A\\C3\\9F\\39\\42\\20\\4E\\50\\29\\28\\20\\4E\\26\\2F\\3D\\2F\\24\\22\\26\\20\\50\\29\\4E\\72\\65\\74\\62\\68\\61\\70\\74\\6E\\76\\61\\70\\39\\72\\38\\74\\7A\\76\\42\\20\\26\\60\\3F\\29\\C2\\A7\\3D\\57\\26\\2F\\28\\29\\3F\\57\\26\\2F\\42\\20\\56\\24\\45\\57\\28\\26\\3F\\20\\4E\\62\\38\\34\\77\\7A\\61\\77\\C3\\9F\\20\\38\\34\\62\\6D\\C3\\9F\\61\\39\\65\\6D\\63\\36\\7A\\76\\77\\75\\69\\35\\34\\62\\36\\C3\\9F\\61\\38\\39\\6E\\36\\7A\\34\\35\\76\\6D\\38\\20\\6E\\75\\20\\20\\20\\20\\38\\33\\7A\\6E\\36\\76\\30\\33\\7A\\35\\36\\76\\C3\\9F\\5E\\5E\\39\\20\\5E\\62\\74\\7A\\C3\\9F\\34\\39\\38\\62\\7A\\6E\\20\\71\\38\\34\\65\\36\\7A\\76\\37\\38\\39\\C3\\9F\\C3\\9F\\20\\28\\2F\\20\\24\\3F\\57\\20\\26\\2F\\5A\\3F\\4E\\57\\24\\42\\20\\4D\\48\\4A\\55\\20\\28\\24\\2F\\20\\56\\4E\\5A\\3F\\57\\29\\28\\24\\29\\42\\26\\24\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0A\\20\\20\\20\\20\\7D\\0A\\20\\20\\7D\\0A\\7D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagWindowsLineEndings = "20\\20\\20\\20\\20\\20\\20\\20\\3C\\43\\6F\\6E\\6E\\65\\63\\74\\69\\6F\\6E\\2E\\22\\22\\63\\31\\22\\22\\3E\\20\\20\\7B\\20\\22\\22\\49\\6D\\61\\67\\65\\52\\65\\61\\64\\79\\22\\22\\20\\22\\22\\22\\22\\20\\22\\22\\43\\6F\\6D\\70\\75\\74\\65\\49\\6D\\61\\67\\65\\22\\22\\20\\20\\7D\\0D\\0A\\20\\20\\20\\20\\20\\20\\7D\\0D\\0A\\20\\20\\20\\20\\7D\\0D\\0A\\20\\20\\7D\\0D\\0A\\7D\\0D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0D\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0D\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0D\\0A\\74\\50\\72\\6F\\74\\6F\\63\\6F\\6C\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\57\\41\\53\\41\\42\\49\\2B\\41\\46\\38\\2D\\4D\\49\\54\\4B\\2D\\54\\45\\53\\54\\2B\\41\\46\\38\\2D\\37\\54\\2B\\41\\46\\38\\2D\\33\\73\\6C\\69\\63\\65\\73\\22\\22\\0D\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\74\\53\\79\\73\\74\\65\\6D\\54\\79\\70\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\30\\39\\35\\22\\22\\0D\\0A\\73\\50\\72\\6F\\74\\43\\6F\\6E\\73\\69\\73\\74\\65\\6E\\63\\79\\49\\6E\\66\\6F\\2E\\66\\6C\\4E\\6F\\6D\\69\\6E\\61\\6C\\42\\30\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\2E\\39\\38\\0D\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\6C\\46\\72\\65\\71\\75\\65\\6E\\63\\79\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\39\\37\\31\\35\\34\\37\\35\\36\\0D\\0A\\73\\54\\58\\53\\50\\45\\43\\2E\\61\\73\\4E\\75\\63\\6C\\65\\75\\73\\49\\6E\\66\\6F\\5B\\30\\5D\\2E\\66\\6C\\52\\65\\66\\65\\72\\65\\6E\\63\\65\\41\\6D\\70\\6C\\69\\74\\75\\64\\65\\20\\3D\\20\\31\\36\\31\\2E\\34\\33\\35\\0D\\0A\\61\\6C\\54\\52\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\33\\30\\30\\30\\0D\\0A\\61\\6C\\54\\45\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\36\\34\\30\\0D\\0A\\6C\\41\\76\\65\\72\\61\\67\\65\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\6C\\52\\65\\70\\65\\74\\69\\74\\69\\6F\\6E\\73\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\31\\0D\\0A\\61\\64\\46\\6C\\69\\70\\41\\6E\\67\\6C\\65\\44\\65\\67\\72\\65\\65\\5B\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\0D\\0A\\6C\\54\\6F\\74\\61\\6C\\53\\63\\61\\6E\\54\\69\\6D\\65\\53\\65\\63\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\39\\37\\0D\\0A\\73\\45\\46\\49\\53\\50\\45\\43\\2E\\62\\45\\46\\49\\44\\61\\74\\61\\56\\61\\6C\\69\\64\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\36\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\30\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\39\\30\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\32\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\32\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\33\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\36\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\34\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\32\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\35\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\38\\30\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\36\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\37\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\2E\\31\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\31\\32\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\39\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\35\\30\\30\\30\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\2E\\37\\0D\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\31\\0D\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22\\20\\0D\\0A\\20\\20\\20\\20\\7D\\0D\\0A\\20\\20\\7D\\0D\\0A\\7D\\0D\\0A\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\47\\72\\61\\64\\69\\65\\6E\\74\\4D\\6F\\64\\65\\20\\72\\6D\\61\\6E\\63\\65\\43\\61\\63\\68\\65\\2E\\69\\6E\\6C\\69\\6E\\65\\5F\\70\\6F\\73\\64\\69\\73\\70\\5F\\63\\61\\6E\\5F\\73\\65\\74\\22\\22\\20\\3C\\44\\6C\\6C\\3E\\20\\22\\22\\4D\\72\\4D\\75\\6C\\74\\01\\20\\20\\20\\53\\48\\20\\20\\16\\20\\20\\20\\06\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\05\\20\\20\\20\\4D\\20\\20\\20\\05\\20\\20\\20\\46\\61\\73\\74\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\CD\\A0\\20\\20\\20\\20\\20\\20\\46\\6C\\6F\\77\\43\\6F\\6D\\70\\65\\6E\\73\\61\\74\\69\\6F\\6E\\20\\72\\65\\53\\6F\\75\\6E\\64\\22\\22\\20\\22\\22\\50\\72\\6F\\70\\65\\72\\74\\69\\65\\73\\2E\\53\\6F\\75\\6E\\64\\2E\\50\\6F\\73\\74\\53\\6F\\75\\6E\\64\\22\\22"; m_ValidCESTCustomTagAlternatingOffset = "36\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22"; m_ValidCESTCustomTagSingleOffset = "36\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\34\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22"; m_ValidCESTCustomTagListOffset = "36\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\42\\45\\47\\49\\4E\\20\\23\\23\\23\\0A\\75\\6C\\56\\65\\72\\73\\69\\6F\\6E\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\30\\78\\31\\34\\62\\34\\34\\62\\36\\0A\\74\\53\\65\\71\\75\\65\\6E\\63\\65\\46\\69\\6C\\65\\4E\\61\\6D\\65\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\22\\22\\25\\43\\75\\73\\74\\6F\\6D\\65\\72\\53\\65\\71\\25\\5C\\58\\58\\58\\58\\58\\5F\\43\\45\\53\\54\\5F\\52\\65\\76\\31\\34\\31\\36\\22\\22\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\38\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\6C\\46\\72\\65\\65\\5B\\31\\30\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\33\\32\\0A\\73\\57\\69\\50\\4D\\65\\6D\\42\\6C\\6F\\63\\6B\\2E\\61\\64\\46\\72\\65\\65\\5B\\31\\5D\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\20\\3D\\20\\32\\0A\\23\\23\\23\\20\\41\\53\\43\\43\\4F\\4E\\56\\20\\45\\4E\\44\\20\\23\\23\\23\\22"; } void tearDown() override { } void ValidPropertyParsedToPropertyList_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto tsproperty = mitk::TemporoSpatialStringProperty::New(); tsproperty->SetValue(0, 0, m_ValidCESTCustomTag); auto parsedPropertyList = tagParser.ParseDicomProperty(tsproperty); std::string sampling = ""; std::string offset = ""; std::string offsets = ""; std::string measurements = ""; std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); parsedPropertyList->GetStringProperty("CEST.Offset", offset); parsedPropertyList->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets); parsedPropertyList->GetStringProperty("CEST.measurements", measurements); parsedPropertyList->GetStringProperty("CEST.SamplingType", sampling); parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool offsetsMatch =( offsets == "-300 -2 -1.86667 -1.73333 -1.6 -1.46667 -1.33333 -1.2 -1.06667 -0.933333 -0.8 -0.666667 -0.533333 -0.4 -0.266667 -0.133333 0 0.133333 0.266667 0.4 0.533333 0.666667 0.8 0.933333 1.06667 1.2 1.33333 1.46667 1.6 1.73333 1.86667 2"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify the revision is the one we expect.", revision == "1416"); CPPUNIT_ASSERT_MESSAGE("Verify the revision and the json revision match.", revision == jsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify a couple of resulting properties match our expectation.", offset == "2" && sampling == "1" && measurements == "32"); CPPUNIT_ASSERT_MESSAGE("Verify offsets are correctly parsed.", offsetsMatch); } void ValidPropertyMissingParametersParsedToEmptyPropertiesPropertyList_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagAllParametersMissing); std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); bool hasJsonRevision = parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); auto propertyMap = parsedPropertyList->GetMap(); bool propertiesEmpty = true; for (auto const &prop : *propertyMap) { std::string key = prop.first; if (key != "CEST.Revision" && key != "CEST.revision_json") { propertiesEmpty = propertiesEmpty && prop.second->GetValueAsString() == ""; } } CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify we found a json revision.", hasJsonRevision); CPPUNIT_ASSERT_MESSAGE("Property list properties are empty but for the revision information", propertiesEmpty); } void ValidPropertyRevisionVeryLow_UseDefault_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagUnsupportedRevisionTooLow); std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); bool hasJsonRevision = parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool usedDefault = (jsonRevision == "default mapping, corresponds to revision 1416"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify we found a json revision.", hasJsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify we used default mapping.", usedDefault); } void ValidPropertyNoExactRevisionMatchUseInternal_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseInternal); std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); bool hasJsonRevision = parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool usedInternal = (jsonRevision == "1416"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify we found a json revision.", hasJsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify we used internal mapping.", usedInternal); } void ValidPropertyNoExactRevisionMatchUseExternal_Success() { std::string externalMappingString = "{\n" " \"external mapping for test\" : \"revision_json\",\n" " \"sWiPMemBlock.alFree[1]\" : \"AdvancedMode\"\n" "}"; // we assume the test library will be in the same location as the MitkCEST library on windows // on linux the test driver should have a relative path of ../bin/ #ifdef _WIN32 std::string dirname = m_PathToModule + "/CESTRevisionMapping"; #else std::string dirname = m_PathToModule + "/../lib/CESTRevisionMapping"; #endif //bool dirWasThere = itksys::SystemTools::FileIsDirectory(dirname); std::string filename = dirname + "/118.json"; itk::FileTools::CreateDirectory(dirname); std::ofstream externalFile(filename.c_str()); if (externalFile.is_open()) { externalFile << externalMappingString; externalFile.close(); } mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagUnsupportedRevisionNoExactJSONUseExternal); std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); bool hasJsonRevision = parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool usedExternal = (jsonRevision == "external mapping for test"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify we found a json revision.", hasJsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify we used external mapping.", usedExternal); bool wasError = std::remove(filename.c_str()); if (wasError) { MITK_ERROR << "Could not delete test revision file"; } } void ValidPropertyWindowsLineEndings_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagWindowsLineEndings); std::string sampling = ""; std::string offset = ""; std::string offsets = ""; std::string measurements = ""; std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); parsedPropertyList->GetStringProperty("CEST.Offset", offset); parsedPropertyList->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets); parsedPropertyList->GetStringProperty("CEST.measurements", measurements); parsedPropertyList->GetStringProperty("CEST.SamplingType", sampling); parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool offsetsMatch = (offsets == "-300 -2 -1.86667 -1.73333 -1.6 -1.46667 -1.33333 -1.2 -1.06667 -0.933333 -0.8 -0.666667 -0.533333 -0.4 -0.266667 -0.133333 0 0.133333 0.266667 0.4 0.533333 0.666667 0.8 0.933333 1.06667 1.2 1.33333 1.46667 1.6 1.73333 1.86667 2"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify the revision is the one we expect.", revision == "1416"); CPPUNIT_ASSERT_MESSAGE("Verify the revision and the json revision match.", revision == jsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify a couple of resulting properties match our expectation.", offset == "2" && sampling == "1" && measurements == "32"); CPPUNIT_ASSERT_MESSAGE("Verify offsets are correctly parsed.", offsetsMatch); } void InvalidPropertyInvalidRevision_Failure() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_InvalidCESTCustomTagRevisionNoNumber); std::string revision = ""; std::string jsonRevision = ""; bool hasRevision = parsedPropertyList->GetStringProperty("CEST.Revision", revision); bool hasJsonRevision = parsedPropertyList->GetStringProperty("CEST.revision_json", jsonRevision); bool usedDefault = (jsonRevision == "default mapping, corresponds to revision 1416"); CPPUNIT_ASSERT_MESSAGE("Verify we found a revision.", hasRevision); CPPUNIT_ASSERT_MESSAGE("Verify we found a json revision.", hasJsonRevision); CPPUNIT_ASSERT_MESSAGE("Verify we used default mapping.", usedDefault); } void InvalidPropertyNoCESTSequence_Failure() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_NonCESTCustomTag); auto size = parsedPropertyList->GetMap()->size(); CPPUNIT_ASSERT_MESSAGE("Property list is empty", mitk::Equal(size, 0)); } void InvalidPropertyGarbageInDelimiters_Failure() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_GarbageWithinDelimiters); auto size = parsedPropertyList->GetMap()->size(); CPPUNIT_ASSERT_MESSAGE("Property list is empty", mitk::Equal(size, 0)); } void ValidPropertyAlternatingOffset_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagAlternatingOffset); std::string offsets = ""; parsedPropertyList->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets); bool offsetsMatch = (offsets == "-300 2 -2 1.86667 -1.86667 1.73333 -1.73333 1.6 -1.6 1.46667 -1.46667 1.33333 -1.33333 1.2 -1.2 1.06667 -1.06667 0.933333 -0.933333 0.8 -0.8 0.666667 -0.666667 0.533333 -0.533333 0.4 -0.4 0.266667 -0.266667 0.133333 -0.133333 0"); CPPUNIT_ASSERT_MESSAGE("Verify offsets are correctly parsed.", offsetsMatch); } void ValidPropertySimpleOffset_Success() { mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagSingleOffset); std::string offsets = ""; parsedPropertyList->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets); bool offsetsMatch = (offsets == "-300 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2"); CPPUNIT_ASSERT_MESSAGE("Verify offsets are correctly parsed.", offsetsMatch); } void ValidPropertyListOffset_Success() { std::string offsetList = "-300\n -100 \n -50 \n -35\n -25 \n -17\n -12\n -9.5 \n -8.25\n -7\n -6.1 \n -5.4 \n -4.7 \n -4\n -3.3\n -2.7\n -2\n -1.7\n -1.5 \n -1.1 \n -0.9\n -300\n -0.6 \n -0.4\n -0.2\n 0 \n 0.2\n 0.4\n 0.6\n 0.95 \n 1.1 \n 1.25 \n 1.4\n 1.55\n 1.7\n 1.85 \n 2 \n 2.15 \n 2.3\n 2.45 \n 2.6\n 2.75 \n 2.9 \n 3.05\n -300 \n 3.2\n 3.35 \n 3.5\n 3.65 \n 3.8 \n 3.95\n 4.1 \n 4.25\n 4.4 \n 4.7\n 5.2\n 6\n 7\n 9\n 12 \n 17\n 25\n 35\n 50 \n 100\n -300" ; std::string filename = m_PathToModule + "/" + "LIST.txt"; std::ofstream externalFile(filename.c_str()); if (externalFile.is_open()) { externalFile << offsetList; externalFile.close(); } mitk::CustomTagParser tagParser(m_PathToModule); auto parsedPropertyList = tagParser.ParseDicomPropertyString(m_ValidCESTCustomTagListOffset); std::string offsets = ""; parsedPropertyList->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets); std::string referenceString = "-300 -100 -50 -35 -25 -17 -12 -9.5 -8.25 -7 -6.1 -5.4 -4.7 -4 -3.3 -2.7 -2 -1.7 -1.5 -1.1 -0.9 -300 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.95 1.1 1.25 1.4 1.55 1.7 1.85 2 2.15 2.3 2.45 2.6 2.75 2.9 3.05 -300 3.2 3.35 3.5 3.65 3.8 3.95 4.1 4.25 4.4 4.7 5.2 6 7 9 12 17 25 35 50 100 -300"; bool offsetsMatch = (offsets == referenceString); CPPUNIT_ASSERT_MESSAGE("Verify offsets are correctly parsed.", offsetsMatch); bool wasError = std::remove(filename.c_str()); if (wasError) { MITK_ERROR << "Could not delete test offset list file"; } } void ExtractRevision() { std::string empty = ""; std::string invalidRule1a = "CESaaaaaaa"; std::string invalidRule1b = "aaaCESTaaa"; std::string invalidRule2a = "CESTaaaa"; std::string invalidRule2b = "aaa_CESTaaa"; std::string invalidRule3a = "CESTaaa_REVaaaa"; std::string valid1 = "CEST_REV12345"; std::string valid2 = "aaa_CESTaaaa_REV2"; std::string valid3 = "CESTaaaa_REV3_c"; std::string valid4 = "cest_rev4"; std::string valid5 = "aaa_cestaaaa_rev5"; std::string valid6 = "cestaaaa_rev6_c"; CPPUNIT_ASSERT_THROW_MESSAGE("Verify exception on empty", mitk::CustomTagParser::ExtractRevision(empty), mitk::Exception); CPPUNIT_ASSERT_THROW_MESSAGE("Verify exception on invalidRule1a", mitk::CustomTagParser::ExtractRevision(invalidRule1a), mitk::Exception); CPPUNIT_ASSERT_THROW_MESSAGE("Verify exception on invalidRule1b", mitk::CustomTagParser::ExtractRevision(invalidRule1b), mitk::Exception); CPPUNIT_ASSERT_THROW_MESSAGE("Verify exception on invalidRule2a", mitk::CustomTagParser::ExtractRevision(invalidRule2a), mitk::Exception); CPPUNIT_ASSERT_THROW_MESSAGE("Verify exception on invalidRule2b", mitk::CustomTagParser::ExtractRevision(invalidRule2b), mitk::Exception); CPPUNIT_ASSERT_MESSAGE("Verify empty revision on invalidRule3a", mitk::CustomTagParser::ExtractRevision(invalidRule3a) == ""); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid1.", mitk::CustomTagParser::ExtractRevision(valid1) == "12345"); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid2.", mitk::CustomTagParser::ExtractRevision(valid2) == "2"); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid3.", mitk::CustomTagParser::ExtractRevision(valid3) == "3"); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid4.", mitk::CustomTagParser::ExtractRevision(valid4) == "4"); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid5.", mitk::CustomTagParser::ExtractRevision(valid5) == "5"); CPPUNIT_ASSERT_MESSAGE("Extract revision from valid6.", mitk::CustomTagParser::ExtractRevision(valid6) == "6"); } }; MITK_TEST_SUITE_REGISTRATION(mitkCustomTagParser) diff --git a/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp b/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp index 402a93d674..84e7bacc3b 100644 --- a/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp +++ b/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp @@ -1,233 +1,233 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include mitk::ContourModelMapper3D::ContourModelMapper3D() { } mitk::ContourModelMapper3D::~ContourModelMapper3D() { } const mitk::ContourModel *mitk::ContourModelMapper3D::GetInput(void) { // convient way to get the data from the dataNode return static_cast(GetDataNode()->GetData()); } vtkProp *mitk::ContourModelMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actor; } void mitk::ContourModelMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { /* First convert the contourModel to vtkPolyData, then tube filter it and * set it input for our mapper */ LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *inputContour = static_cast(GetDataNode()->GetData()); localStorage->m_OutlinePolyData = this->CreateVtkPolyDataFromContour(inputContour); this->ApplyContourProperties(renderer); // tube filter the polyData localStorage->m_TubeFilter->SetInputData(localStorage->m_OutlinePolyData); float lineWidth(1.0); if (this->GetDataNode()->GetFloatProperty("contour.3D.width", lineWidth, renderer)) { localStorage->m_TubeFilter->SetRadius(lineWidth); } else { localStorage->m_TubeFilter->SetRadius(0.5); } localStorage->m_TubeFilter->CappingOn(); localStorage->m_TubeFilter->SetNumberOfSides(10); localStorage->m_TubeFilter->Update(); localStorage->m_Mapper->SetInputConnection(localStorage->m_TubeFilter->GetOutputPort()); } void mitk::ContourModelMapper3D::Update(mitk::BaseRenderer *renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); auto *data = static_cast(GetDataNode()->GetData()); if (data == nullptr) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep(renderer); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = data->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || - (!dataTimeGeometry->IsValidTimeStep(renderer->GetTimeStep())) || (this->GetTimestep() == -1)) + (!dataTimeGeometry->IsValidTimePoint(renderer->GetTime())) || (this->GetTimestep() == -1)) { // clear the rendered polydata localStorage->m_Mapper->SetInputData(vtkSmartPointer::New()); return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); // check if something important has changed and we need to rerender if ((localStorage->m_LastUpdateTime < node->GetMTime()) // was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) // Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) // was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) // was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer(renderer); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } vtkSmartPointer mitk::ContourModelMapper3D::CreateVtkPolyDataFromContour(mitk::ContourModel *inputContour) { unsigned int timestep = this->GetTimestep(); // the points to draw vtkSmartPointer points = vtkSmartPointer::New(); // the lines to connect the points vtkSmartPointer lines = vtkSmartPointer::New(); // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // iterate over the control points auto current = inputContour->IteratorBegin(timestep); auto next = inputContour->IteratorBegin(timestep); if (next != inputContour->IteratorEnd(timestep)) { next++; auto end = inputContour->IteratorEnd(timestep); while (next != end) { mitk::ContourModel::VertexType *currentControlPoint = *current; mitk::ContourModel::VertexType *nextControlPoint = *next; if (!(currentControlPoint->Coordinates[0] == nextControlPoint->Coordinates[0] && currentControlPoint->Coordinates[1] == nextControlPoint->Coordinates[1] && currentControlPoint->Coordinates[2] == nextControlPoint->Coordinates[2])) { vtkIdType p1 = points->InsertNextPoint(currentControlPoint->Coordinates[0], currentControlPoint->Coordinates[1], currentControlPoint->Coordinates[2]); vtkIdType p2 = points->InsertNextPoint( nextControlPoint->Coordinates[0], nextControlPoint->Coordinates[1], nextControlPoint->Coordinates[2]); // add the line between both contorlPoints lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } current++; next++; } if (inputContour->IsClosed(timestep)) { // If the contour is closed add a line from the last to the first control point mitk::ContourModel::VertexType *firstControlPoint = *(inputContour->IteratorBegin(timestep)); mitk::ContourModel::VertexType *lastControlPoint = *(--(inputContour->IteratorEnd(timestep))); if (lastControlPoint->Coordinates[0] != firstControlPoint->Coordinates[0] || lastControlPoint->Coordinates[1] != firstControlPoint->Coordinates[1] || lastControlPoint->Coordinates[2] != firstControlPoint->Coordinates[2]) { vtkIdType p2 = points->InsertNextPoint( lastControlPoint->Coordinates[0], lastControlPoint->Coordinates[1], lastControlPoint->Coordinates[2]); vtkIdType p1 = points->InsertNextPoint( firstControlPoint->Coordinates[0], firstControlPoint->Coordinates[1], firstControlPoint->Coordinates[2]); // add the line to the cellArray lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); } return polyData; } void mitk::ContourModelMapper3D::ApplyContourProperties(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty("contour.color", renderer)); if (colorprop) { // set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); localStorage->m_Actor->GetProperty()->SetColor(red, green, blue); } } /*+++++++++++++++++++ LocalStorage part +++++++++++++++++++++++++*/ mitk::ContourModelMapper3D::LocalStorage *mitk::ContourModelMapper3D::GetLocalStorage(mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } mitk::ContourModelMapper3D::LocalStorage::LocalStorage() { m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_TubeFilter = vtkSmartPointer::New(); // set the mapper for the actor m_Actor->SetMapper(m_Mapper); } void mitk::ContourModelMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("color", ColorProperty::New(1.0, 0.0, 0.0), renderer, overwrite); node->AddProperty("contour.3D.width", mitk::FloatProperty::New(0.5), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/Core/include/mitkBaseController.h b/Modules/Core/include/mitkBaseController.h index 5c823d4847..e3ba5e89c8 100644 --- a/Modules/Core/include/mitkBaseController.h +++ b/Modules/Core/include/mitkBaseController.h @@ -1,78 +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 BASECONTROLLER_H_HEADER_INCLUDED_C1E745A3 #define BASECONTROLLER_H_HEADER_INCLUDED_C1E745A3 #include "mitkEventStateMachine.h" #include "mitkOperationActor.h" #include "mitkStepper.h" #include #include namespace mitk { class BaseRenderer; //##Documentation //## @brief Baseclass for renderer slice-/camera-control //## //## Tells the render (subclass of BaseRenderer) which slice (subclass //## SliceNavigationController) or from which direction (subclass //## CameraController) it has to render. Contains two Stepper for stepping //## through the slices or through different camera views (e.g., for the //## creation of a movie around the data), respectively, and through time, if //## there is 3D+t data. //## @note not yet implemented //## @ingroup NavigationControl - class MITKCORE_EXPORT BaseController : public mitk::OperationActor, public itk::Object + class MITKCORE_EXPORT BaseController : public OperationActor, public itk::Object { public: /** Standard class typedefs. */ - mitkClassMacroItkParent(BaseController, mitk::OperationActor); + mitkClassMacroItkParent(BaseController, OperationActor); itkFactorylessNewMacro(Self); - /** Method for creation through ::New */ - // mitkNewMacro(Self); - - //##Documentation - //## @brief Get the Stepper through the slices - mitk::Stepper *GetSlice(); + //##Documentation + //## @brief Get the Stepper through the slices + Stepper *GetSlice(); + const Stepper* GetSlice() const; //##Documentation //## @brief Get the Stepper through the time - mitk::Stepper *GetTime(); + Stepper *GetTime(); + const Stepper* GetTime() const; protected: /** * @brief Default Constructor **/ BaseController(); /** * @brief Default Destructor **/ ~BaseController() override; void ExecuteOperation(Operation *) override; //## @brief Stepper through the time Stepper::Pointer m_Time; //## @brief Stepper through the slices Stepper::Pointer m_Slice; unsigned long m_LastUpdateTime; }; } // namespace mitk #endif /* BASECONTROLLER_H_HEADER_INCLUDED_C1E745A3 */ diff --git a/Modules/Core/include/mitkIOMimeTypes.h b/Modules/Core/include/mitkIOMimeTypes.h index 8df3c8f990..c51f1eb3d0 100644 --- a/Modules/Core/include/mitkIOMimeTypes.h +++ b/Modules/Core/include/mitkIOMimeTypes.h @@ -1,92 +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 MITKIOMIMETYPES_H #define MITKIOMIMETYPES_H #include "mitkCustomMimeType.h" #include namespace mitk { /** * @ingroup IO * @brief The IOMimeTypes class */ class MITKCORE_EXPORT IOMimeTypes { public: - class MITKCORE_EXPORT DicomMimeType : public CustomMimeType + + /** Base mime types for all kind of DICOM images, that can be reused + by more specific mime types based on DICOM images.*/ + class MITKCORE_EXPORT BaseDicomMimeType : public CustomMimeType + { + public: + BaseDicomMimeType(const std::string &name); + BaseDicomMimeType(const BaseDicomMimeType& other) = default; + bool AppliesTo(const std::string& path) const override; + BaseDicomMimeType* Clone() const override; + }; + + class MITKCORE_EXPORT DicomMimeType : public BaseDicomMimeType { public: DicomMimeType(); - bool AppliesTo(const std::string &path) const override; DicomMimeType *Clone() const override; }; static std::vector Get(); static std::string DEFAULT_BASE_NAME(); // application/vnd.mitk static std::string CATEGORY_IMAGES(); // Images static std::string CATEGORY_SURFACES(); // Surfaces // ------------------------------ VTK formats ---------------------------------- static CustomMimeType VTK_IMAGE_MIMETYPE(); // (mitk::Image) vti static CustomMimeType VTK_IMAGE_LEGACY_MIMETYPE(); // (mitk::Image) vtk static CustomMimeType VTK_PARALLEL_IMAGE_MIMETYPE(); // (mitk::Image) pvti static CustomMimeType VTK_POLYDATA_MIMETYPE(); // (mitk::Surface) vtp, vtk static CustomMimeType VTK_POLYDATA_LEGACY_MIMETYPE(); // (mitk::Surface) vtk static CustomMimeType VTK_PARALLEL_POLYDATA_MIMETYPE(); // (mitk::Surface) pvtp static CustomMimeType STEREOLITHOGRAPHY_MIMETYPE(); // (mitk::Surface) stl static CustomMimeType WAVEFRONT_OBJ_MIMETYPE(); // (mitk::Surface) obj static CustomMimeType STANFORD_PLY_MIMETYPE(); // (mitk::Surface) ply static std::string STEREOLITHOGRAPHY_NAME(); // DEFAULT_BASE_NAME.stl static std::string VTK_IMAGE_NAME(); // DEFAULT_BASE_NAME.vtk.image static std::string VTK_IMAGE_LEGACY_NAME(); // DEFAULT_BASE_NAME.vtk.image.legacy static std::string VTK_PARALLEL_IMAGE_NAME(); // DEFAULT_BASE_NAME.vtk.parallel.image static std::string VTK_POLYDATA_NAME(); // DEFAULT_BASE_NAME.vtk.polydata static std::string VTK_POLYDATA_LEGACY_NAME(); // DEFAULT_BASE_NAME.vtk.polydata.legacy static std::string VTK_PARALLEL_POLYDATA_NAME(); // DEFAULT_BASE_NAME.vtk.parallel.polydata static std::string WAVEFRONT_OBJ_NAME(); // DEFAULT_BASE_NAME.obj static std::string STANFORD_PLY_NAME(); // DEFAULT_BASE_NAME.ply // ------------------------- Image formats (ITK based) -------------------------- static CustomMimeType NRRD_MIMETYPE(); // nrrd, nhdr static CustomMimeType NIFTI_MIMETYPE(); static CustomMimeType RAW_MIMETYPE(); // raw static DicomMimeType DICOM_MIMETYPE(); static std::string NRRD_MIMETYPE_NAME(); // DEFAULT_BASE_NAME.nrrd static std::string NIFTI_MIMETYPE_NAME(); static std::string RAW_MIMETYPE_NAME(); // DEFAULT_BASE_NAME.raw static std::string DICOM_MIMETYPE_NAME(); // ------------------------------ MITK formats ---------------------------------- static CustomMimeType POINTSET_MIMETYPE(); // mps static CustomMimeType GEOMETRY_DATA_MIMETYPE(); // .mitkgeometry static std::string POINTSET_MIMETYPE_NAME(); // DEFAULT_BASE_NAME.pointset private: // purposely not implemented IOMimeTypes(); IOMimeTypes(const IOMimeTypes &); }; } #endif // MITKIOMIMETYPES_H diff --git a/Modules/Core/include/mitkSliceNavigationController.h b/Modules/Core/include/mitkSliceNavigationController.h index 093f43d205..a7169a6274 100644 --- a/Modules/Core/include/mitkSliceNavigationController.h +++ b/Modules/Core/include/mitkSliceNavigationController.h @@ -1,506 +1,477 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F #define SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F #include "mitkBaseController.h" #include "mitkMessage.h" #include "mitkRenderingManager.h" #include "mitkTimeGeometry.h" #include #pragma GCC visibility push(default) #include #pragma GCC visibility pop #include "mitkDataStorage.h" #include "mitkRestorePlanePositionOperation.h" #include #include -// DEPRECATED -#include namespace mitk { -#define mitkTimeSlicedGeometryEventMacro(classname, super) \ - class MITKCORE_EXPORT DEPRECATED(classname) : public super \ - { \ - public: \ - typedef classname Self; \ - typedef super Superclass; \ - classname(TimeGeometry *aTimeGeometry, unsigned int aPos) : Superclass(aTimeGeometry, aPos) {} \ - virtual ~classname() {} \ - virtual const char *GetEventName() const { return #classname; } \ - virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ - virtual ::itk::EventObject *MakeObject() const { return new Self(GetTimeGeometry(), GetPos()); } \ - private: \ - void operator=(const Self &); \ - } #define mitkTimeGeometryEventMacro(classname, super) \ class MITKCORE_EXPORT classname : public super \ { \ public: \ typedef classname Self; \ typedef super Superclass; \ classname(TimeGeometry *aTimeGeometry, unsigned int aPos) : Superclass(aTimeGeometry, aPos) {} \ virtual ~classname() {} \ virtual const char *GetEventName() const { return #classname; } \ virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ virtual ::itk::EventObject *MakeObject() const { return new Self(GetTimeGeometry(), GetPos()); } \ private: \ void operator=(const Self &); \ } class PlaneGeometry; class BaseGeometry; class BaseRenderer; /** * \brief Controls the selection of the slice the associated BaseRenderer * will display * * A SliceNavigationController takes a BaseGeometry or a TimeGeometry as input world geometry * (TODO what are the exact requirements?) and generates a TimeGeometry * as output. The TimeGeometry holds a number of SlicedGeometry3Ds and * these in turn hold a series of PlaneGeometries. One of these PlaneGeometries is * selected as world geometry for the BaseRenderers associated to 2D views. * * The SliceNavigationController holds has Steppers (one for the slice, a * second for the time step), which control the selection of a single * PlaneGeometry from the TimeGeometry. SliceNavigationController generates * ITK events to tell observers, like a BaseRenderer, when the selected slice * or timestep changes. * * Example: * \code * // Initialization * sliceCtrl = mitk::SliceNavigationController::New(); * * // Tell the navigator the geometry to be sliced (with geometry a * // BaseGeometry::ConstPointer) - * sliceCtrl->SetInputWorldGeometry(geometry.GetPointer()); + * sliceCtrl->SetInputWorldGeometry3D(geometry.GetPointer()); * * // Tell the navigator in which direction it shall slice the data * sliceCtrl->SetViewDirection(mitk::SliceNavigationController::Axial); * * // Connect one or more BaseRenderer to this navigator, i.e.: events sent * // by the navigator when stepping through the slices (e.g. by * // sliceCtrl->GetSlice()->Next()) will be received by the BaseRenderer * // (in this example only slice-changes, see also ConnectGeometryTimeEvent * // and ConnectGeometryEvents.) * sliceCtrl->ConnectGeometrySliceEvent(renderer.GetPointer()); * * //create a world geometry and send the information to the connected renderer(s) * sliceCtrl->Update(); * \endcode * * * You can connect visible navigators to a SliceNavigationController, e.g., a * QmitkSliderNavigator (for Qt): * * \code * // Create the visible navigator (a slider with a spin-box) * QmitkSliderNavigator* navigator = * new QmitkSliderNavigator(parent, "slidernavigator"); * * // Connect the navigator to the slice-stepper of the * // SliceNavigationController. For initialization (position, mininal and * // maximal values) the values of the SliceNavigationController are used. * // Thus, accessing methods of a navigator is normally not necessary, since * // everything can be set via the (Qt-independent) SliceNavigationController. * // The QmitkStepperAdapter converts the Qt-signals to Qt-independent * // itk-events. * new QmitkStepperAdapter(navigator, sliceCtrl->GetSlice(), "navigatoradaptor"); * \endcode * * If you do not want that all renderwindows are updated when a new slice is * selected, you can use a specific RenderingManager, which updates only those * renderwindows that should be updated. This is sometimes useful when a 3D view * does not need to be updated when the slices in some 2D views are changed. * QmitkSliderNavigator (for Qt): * * \code * // create a specific RenderingManager * mitk::RenderingManager::Pointer myManager = mitk::RenderingManager::New(); * * // tell the RenderingManager to update only renderwindow1 and renderwindow2 * myManager->AddRenderWindow(renderwindow1); * myManager->AddRenderWindow(renderwindow2); * * // tell the SliceNavigationController of renderwindow1 and renderwindow2 * // to use the specific RenderingManager instead of the global one * renderwindow1->GetSliceNavigationController()->SetRenderingManager(myManager); * renderwindow2->GetSliceNavigationController()->SetRenderingManager(myManager); * \endcode * * \todo implement for non-evenly-timed geometry! * \ingroup NavigationControl */ class MITKCORE_EXPORT SliceNavigationController : public BaseController { public: mitkClassMacro(SliceNavigationController, BaseController); // itkFactorylessNewMacro(Self) // mitkNewMacro1Param(Self, const char *); itkNewMacro(Self); // itkCloneMacro(Self) /** * \brief Possible view directions, \a Original will uses * the PlaneGeometry instances in a SlicedGeometry3D provided - * as input world geometry (by SetInputWorldGeometry). + * as input world geometry (by SetInputWorldGeometry3D). */ enum ViewDirection { Axial, Sagittal, Frontal, Original }; /** * \brief Set the input world geometry3D out of which the * geometries for slicing will be created. * * Any previous previous set input geometry (3D or Time) will * be ignored in future. */ void SetInputWorldGeometry3D(const mitk::BaseGeometry *geometry); itkGetConstObjectMacro(InputWorldGeometry3D, mitk::BaseGeometry); - /** - * \brief Set the input world geometry3D out of which the - * geometries for slicing will be created. - * - * Any previous previous set input geometry (3D or Time) will - * be ignored in future. - * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(void SetInputWorldGeometry(const mitk::TimeSlicedGeometry *geometry)); - /** - * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(TimeSlicedGeometry *GetInputWorldGeometry()); - void SetInputWorldTimeGeometry(const mitk::TimeGeometry *geometry); itkGetConstObjectMacro(InputWorldTimeGeometry, mitk::TimeGeometry); /** * \brief Access the created geometry */ itkGetConstObjectMacro(CreatedWorldGeometry, mitk::TimeGeometry); /** * \brief Set the desired view directions * * \sa ViewDirection * \sa Update(ViewDirection viewDirection, bool top = true, * bool frontside = true, bool rotated = false) */ itkSetEnumMacro(ViewDirection, ViewDirection); itkGetEnumMacro(ViewDirection, ViewDirection); /** * \brief Set the default view direction * * This is used to re-initialize the view direction of the SNC to the * default value with SetViewDirectionToDefault() * * \sa ViewDirection * \sa Update(ViewDirection viewDirection, bool top = true, * bool frontside = true, bool rotated = false) */ itkSetEnumMacro(DefaultViewDirection, ViewDirection); itkGetEnumMacro(DefaultViewDirection, ViewDirection); const char *GetViewDirectionAsString() const; virtual void SetViewDirectionToDefault(); /** * \brief Do the actual creation and send it to the connected * observers (renderers) * */ virtual void Update(); /** * \brief Extended version of Update, additionally allowing to * specify the direction/orientation of the created geometry. * */ virtual void Update(ViewDirection viewDirection, bool top = true, bool frontside = true, bool rotated = false); /** * \brief Send the created geometry to the connected * observers (renderers) * * Called by Update(). */ virtual void SendCreatedWorldGeometry(); /** * \brief Tell observers to re-read the currently selected 2D geometry * */ virtual void SendCreatedWorldGeometryUpdate(); /** * \brief Send the currently selected slice to the connected * observers (renderers) * * Called by Update(). */ virtual void SendSlice(); /** * \brief Send the currently selected time to the connected * observers (renderers) * * Called by Update(). */ virtual void SendTime(); #pragma GCC visibility push(default) itkEventMacro(UpdateEvent, itk::AnyEvent); #pragma GCC visibility pop class MITKCORE_EXPORT TimeGeometryEvent : public itk::AnyEvent { public: typedef TimeGeometryEvent Self; typedef itk::AnyEvent Superclass; TimeGeometryEvent(TimeGeometry *aTimeGeometry, unsigned int aPos) : m_TimeGeometry(aTimeGeometry), m_Pos(aPos) {} ~TimeGeometryEvent() override {} const char *GetEventName() const override { return "TimeGeometryEvent"; } bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } ::itk::EventObject *MakeObject() const override { return new Self(m_TimeGeometry, m_Pos); } TimeGeometry *GetTimeGeometry() const { return m_TimeGeometry; } unsigned int GetPos() const { return m_Pos; } private: TimeGeometry::Pointer m_TimeGeometry; unsigned int m_Pos; // TimeGeometryEvent(const Self&); void operator=(const Self &); // just hide }; - /** - * \deprecatedSince{2013_09} Please use TimeGeometryEvent instead: For additional information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(typedef TimeGeometryEvent TimeSlicedGeometryEvent); mitkTimeGeometryEventMacro(GeometrySendEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometryUpdateEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometryTimeEvent, TimeGeometryEvent); mitkTimeGeometryEventMacro(GeometrySliceEvent, TimeGeometryEvent); template void ConnectGeometrySendEvent(T *receiver) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometry); unsigned long tag = AddObserver(GeometrySendEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); } template void ConnectGeometryUpdateEvent(T *receiver) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::UpdateGeometry); unsigned long tag = AddObserver(GeometryUpdateEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); } template void ConnectGeometrySliceEvent(T *receiver, bool connectSendEvent = true) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometrySlice); unsigned long tag = AddObserver(GeometrySliceEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); if (connectSendEvent) ConnectGeometrySendEvent(receiver); } template void ConnectGeometryTimeEvent(T *receiver, bool connectSendEvent = true) { typedef typename itk::ReceptorMemberCommand::Pointer ReceptorMemberCommandPointer; ReceptorMemberCommandPointer eventReceptorCommand = itk::ReceptorMemberCommand::New(); eventReceptorCommand->SetCallbackFunction(receiver, &T::SetGeometryTime); unsigned long tag = AddObserver(GeometryTimeEvent(nullptr, 0), eventReceptorCommand); m_ReceiverToObserverTagsMap[static_cast(receiver)].push_back(tag); if (connectSendEvent) ConnectGeometrySendEvent(receiver); } template void ConnectGeometryEvents(T *receiver) { // connect sendEvent only once ConnectGeometrySliceEvent(receiver, false); ConnectGeometryTimeEvent(receiver); } // use a templated method to get the right offset when casting to void* template void Disconnect(T *receiver) { auto i = m_ReceiverToObserverTagsMap.find(static_cast(receiver)); if (i == m_ReceiverToObserverTagsMap.end()) return; const std::list &tags = i->second; for (auto tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { RemoveObserver(*tagIter); } m_ReceiverToObserverTagsMap.erase(i); } Message1 SetCrosshairEvent; /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface * \warning not implemented */ virtual void SetGeometry(const itk::EventObject &geometrySliceEvent); /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface */ virtual void SetGeometrySlice(const itk::EventObject &geometrySliceEvent); /** * \brief To connect multiple SliceNavigationController, we can * act as an observer ourselves: implemented interface */ virtual void SetGeometryTime(const itk::EventObject &geometryTimeEvent); /** \brief Positions the SNC according to the specified point */ void SelectSliceByPoint(const mitk::Point3D &point); /** \brief Returns the TimeGeometry created by the SNC. */ mitk::TimeGeometry *GetCreatedWorldGeometry(); /** \brief Returns the BaseGeometry of the currently selected time step. */ const mitk::BaseGeometry *GetCurrentGeometry3D(); /** \brief Returns the currently selected Plane in the current * BaseGeometry (if existent). */ const mitk::PlaneGeometry *GetCurrentPlaneGeometry(); /** \brief Sets the BaseRenderer associated with this SNC (if any). While * the BaseRenderer is not directly used by SNC, this is a convenience * method to enable BaseRenderer access via the SNC. */ void SetRenderer(BaseRenderer *renderer); /** \brief Gets the BaseRenderer associated with this SNC (if any). While * the BaseRenderer is not directly used by SNC, this is a convenience * method to enable BaseRenderer access via the SNC. Returns nullptr if no * BaseRenderer has been specified*/ BaseRenderer *GetRenderer() const; /** \brief Re-orients the slice stack. All slices will be oriented to the given normal vector. The given point (world coordinates) defines the selected slice. Careful: The resulting axis vectors are not clearly defined this way. If you want to define them clearly, use ReorientSlices (const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1). */ void ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &normal); /** \brief Re-orients the slice stack so that all planes are oriented according to the * given axis vectors. The given Point eventually defines selected slice. */ void ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1); void ExecuteOperation(Operation *operation) override; /** * \brief Feature option to lock planes during mouse interaction. * This option flag disables the mouse event which causes the center * cross to move near by. */ itkSetMacro(SliceLocked, bool); itkGetMacro(SliceLocked, bool); itkBooleanMacro(SliceLocked); /** * \brief Feature option to lock slice rotation. * * This option flag disables separately the rotation of a slice which is * implemented in mitkSliceRotator. */ itkSetMacro(SliceRotationLocked, bool); itkGetMacro(SliceRotationLocked, bool); itkBooleanMacro(SliceRotationLocked); /** * \brief Adjusts the numerical range of the slice stepper according to * the current geometry orientation of this SNC's SlicedGeometry. */ void AdjustSliceStepperRange(); + /** \brief Convenience method that returns the time step currently selected by the controller.*/ + TimeStepType GetSelectedTimeStep() const; + + /** \brief Convenience method that returns the time point that corresponds to the selected + * time step. The conversion is done using the time geometry of the SliceNavigationController. + * If the time geometry is not yet set, this function will always return 0.0.*/ + TimePointType GetSelectedTimePoint() const; + protected: SliceNavigationController(); ~SliceNavigationController() override; mitk::BaseGeometry::ConstPointer m_InputWorldGeometry3D; mitk::TimeGeometry::ConstPointer m_InputWorldTimeGeometry; mitk::TimeGeometry::Pointer m_CreatedWorldGeometry; ViewDirection m_ViewDirection; ViewDirection m_DefaultViewDirection; mitk::RenderingManager::Pointer m_RenderingManager; mitk::BaseRenderer *m_Renderer; itkSetMacro(Top, bool); itkGetMacro(Top, bool); itkBooleanMacro(Top); itkSetMacro(FrontSide, bool); itkGetMacro(FrontSide, bool); itkBooleanMacro(FrontSide); itkSetMacro(Rotated, bool); itkGetMacro(Rotated, bool); itkBooleanMacro(Rotated); bool m_Top; bool m_FrontSide; bool m_Rotated; bool m_BlockUpdate; bool m_SliceLocked; bool m_SliceRotationLocked; unsigned int m_OldPos; typedef std::map> ObserverTagsMapType; ObserverTagsMapType m_ReceiverToObserverTagsMap; }; } // namespace mitk #endif /* SLICENAVIGATIONCONTROLLER_H_HEADER_INCLUDED_C1C55A2F */ diff --git a/Modules/Core/src/Controllers/mitkBaseController.cpp b/Modules/Core/src/Controllers/mitkBaseController.cpp index 738ba09be8..135ac0763f 100644 --- a/Modules/Core/src/Controllers/mitkBaseController.cpp +++ b/Modules/Core/src/Controllers/mitkBaseController.cpp @@ -1,38 +1,48 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkBaseController.h" #include "mitkBaseRenderer.h" mitk::BaseController::BaseController() : m_LastUpdateTime(0) { m_Slice = Stepper::New(); m_Time = Stepper::New(); } mitk::BaseController::~BaseController() { } void mitk::BaseController::ExecuteOperation(mitk::Operation * /* *operation */) { } mitk::Stepper *mitk::BaseController::GetSlice() { return m_Slice.GetPointer(); } +const mitk::Stepper* mitk::BaseController::GetSlice() const +{ + return m_Slice.GetPointer(); +} + mitk::Stepper *mitk::BaseController::GetTime() { return m_Time.GetPointer(); } + +const mitk::Stepper* mitk::BaseController::GetTime() const +{ + return m_Time.GetPointer(); +} diff --git a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp index d5ca0d7991..b6db4bd18a 100644 --- a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp +++ b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp @@ -1,632 +1,656 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSliceNavigationController.h" #include "mitkAction.h" #include "mitkBaseRenderer.h" #include "mitkCrosshairPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkOperation.h" #include "mitkOperationActor.h" #include "mitkPlaneGeometry.h" #include "mitkProportionalTimeGeometry.h" #include "mitkArbitraryTimeGeometry.h" #include "mitkRenderingManager.h" #include "mitkSlicedGeometry3D.h" #include "mitkVtkPropRenderer.h" #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkInteractionConst.h" #include "mitkNodePredicateDataType.h" #include "mitkOperationEvent.h" #include "mitkPixelTypeMultiplex.h" #include "mitkPlaneOperation.h" #include "mitkPointOperation.h" #include "mitkStatusBar.h" #include "mitkUndoController.h" #include "mitkApplyTransformMatrixOperation.h" #include "mitkMemoryUtilities.h" #include namespace mitk { SliceNavigationController::SliceNavigationController() : BaseController(), m_InputWorldGeometry3D( mitk::BaseGeometry::ConstPointer() ), m_InputWorldTimeGeometry( mitk::TimeGeometry::ConstPointer() ), m_CreatedWorldGeometry( mitk::TimeGeometry::Pointer() ), m_ViewDirection(Axial), m_DefaultViewDirection(Axial), m_RenderingManager( mitk::RenderingManager::Pointer() ), m_Renderer( nullptr ), m_Top(false), m_FrontSide(false), m_Rotated(false), m_BlockUpdate(false), m_SliceLocked(false), m_SliceRotationLocked(false), m_OldPos(0) { typedef itk::SimpleMemberCommand SNCCommandType; SNCCommandType::Pointer sliceStepperChangedCommand, timeStepperChangedCommand; sliceStepperChangedCommand = SNCCommandType::New(); timeStepperChangedCommand = SNCCommandType::New(); sliceStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendSlice); timeStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendTime); m_Slice->AddObserver(itk::ModifiedEvent(), sliceStepperChangedCommand); m_Time->AddObserver(itk::ModifiedEvent(), timeStepperChangedCommand); m_Slice->SetUnitName("mm"); m_Time->SetUnitName("ms"); m_Top = false; m_FrontSide = false; m_Rotated = false; } SliceNavigationController::~SliceNavigationController() {} void SliceNavigationController::SetInputWorldGeometry3D(const BaseGeometry *geometry) { if ( geometry != nullptr ) { if (geometry->GetBoundingBox()->GetDiagonalLength2() < eps) { itkWarningMacro("setting an empty bounding-box"); geometry = nullptr; } } if (m_InputWorldGeometry3D != geometry) { m_InputWorldGeometry3D = geometry; m_InputWorldTimeGeometry = mitk::TimeGeometry::ConstPointer(); this->Modified(); } } void SliceNavigationController::SetInputWorldTimeGeometry(const TimeGeometry *geometry) { if ( geometry != nullptr ) { if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() < eps) { itkWarningMacro("setting an empty bounding-box"); geometry = nullptr; } } if (m_InputWorldTimeGeometry != geometry) { m_InputWorldTimeGeometry = geometry; m_InputWorldGeometry3D = mitk::BaseGeometry::ConstPointer(); this->Modified(); } } void SliceNavigationController::SetViewDirectionToDefault() { m_ViewDirection = m_DefaultViewDirection; } const char *SliceNavigationController::GetViewDirectionAsString() const { const char *viewDirectionString; switch (m_ViewDirection) { case SliceNavigationController::Axial: viewDirectionString = "Axial"; break; case SliceNavigationController::Sagittal: viewDirectionString = "Sagittal"; break; case SliceNavigationController::Frontal: viewDirectionString = "Coronal"; break; case SliceNavigationController::Original: viewDirectionString = "Original"; break; default: viewDirectionString = "No View Direction Available"; break; } return viewDirectionString; } void SliceNavigationController::Update() { if (!m_BlockUpdate) { if (m_ViewDirection == Sagittal) { this->Update(Sagittal, true, true, false); } else if (m_ViewDirection == Frontal) { this->Update(Frontal, false, true, false); } else if (m_ViewDirection == Axial) { this->Update(Axial, false, false, true); } else { this->Update(m_ViewDirection); } } } void SliceNavigationController::Update(SliceNavigationController::ViewDirection viewDirection, bool top, bool frontside, bool rotated) { TimeGeometry::ConstPointer worldTimeGeometry = m_InputWorldTimeGeometry; if (m_BlockUpdate || (m_InputWorldTimeGeometry.IsNull() && m_InputWorldGeometry3D.IsNull()) || ((worldTimeGeometry.IsNotNull()) && (worldTimeGeometry->CountTimeSteps() == 0))) { return; } m_BlockUpdate = true; if (m_InputWorldTimeGeometry.IsNotNull() && m_LastUpdateTime < m_InputWorldTimeGeometry->GetMTime()) { Modified(); } if (m_InputWorldGeometry3D.IsNotNull() && m_LastUpdateTime < m_InputWorldGeometry3D->GetMTime()) { Modified(); } this->SetViewDirection(viewDirection); this->SetTop(top); this->SetFrontSide(frontside); this->SetRotated(rotated); if (m_LastUpdateTime < GetMTime()) { m_LastUpdateTime = GetMTime(); // initialize the viewplane SlicedGeometry3D::Pointer slicedWorldGeometry = SlicedGeometry3D::Pointer(); BaseGeometry::ConstPointer currentGeometry = BaseGeometry::ConstPointer(); if (m_InputWorldTimeGeometry.IsNotNull()) if (m_InputWorldTimeGeometry->IsValidTimeStep(GetTime()->GetPos())) currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(GetTime()->GetPos()); else currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(0); else currentGeometry = m_InputWorldGeometry3D; m_CreatedWorldGeometry = mitk::TimeGeometry::Pointer(); switch (viewDirection) { case Original: if (worldTimeGeometry.IsNotNull()) { m_CreatedWorldGeometry = worldTimeGeometry->Clone(); worldTimeGeometry = m_CreatedWorldGeometry.GetPointer(); slicedWorldGeometry = dynamic_cast( m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()).GetPointer()); if (slicedWorldGeometry.IsNotNull()) { break; } } else { const auto *worldSlicedGeometry = dynamic_cast(currentGeometry.GetPointer()); if ( worldSlicedGeometry != nullptr ) { slicedWorldGeometry = static_cast(currentGeometry->Clone().GetPointer()); break; } } slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::None, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Axial: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Axial, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Frontal: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Frontal, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; case Sagittal: slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes(currentGeometry, PlaneGeometry::Sagittal, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); break; default: itkExceptionMacro("unknown ViewDirection"); } m_Slice->SetPos(0); m_Slice->SetSteps((int)slicedWorldGeometry->GetSlices()); if ( worldTimeGeometry.IsNull() ) { auto createdTimeGeometry = ProportionalTimeGeometry::New(); createdTimeGeometry->Initialize( slicedWorldGeometry, 1 ); m_CreatedWorldGeometry = createdTimeGeometry; m_Time->SetSteps(0); m_Time->SetPos(0); m_Time->InvalidateRange(); } else { m_BlockUpdate = true; m_Time->SetSteps(worldTimeGeometry->CountTimeSteps()); m_Time->SetPos(0); const TimeBounds &timeBounds = worldTimeGeometry->GetTimeBounds(); m_Time->SetRange(timeBounds[0], timeBounds[1]); m_BlockUpdate = false; const auto currentTemporalPosition = this->GetTime()->GetPos(); assert( worldTimeGeometry->GetGeometryForTimeStep( currentTemporalPosition ).IsNotNull() ); if ( dynamic_cast( worldTimeGeometry.GetPointer() ) != nullptr ) { const TimePointType minimumTimePoint = worldTimeGeometry->TimeStepToTimePoint( currentTemporalPosition ); const TimePointType stepDuration = worldTimeGeometry->TimeStepToTimePoint( currentTemporalPosition + 1 ) - minimumTimePoint; auto createdTimeGeometry = ProportionalTimeGeometry::New(); createdTimeGeometry->Initialize( slicedWorldGeometry, worldTimeGeometry->CountTimeSteps() ); createdTimeGeometry->SetFirstTimePoint( minimumTimePoint ); createdTimeGeometry->SetStepDuration( stepDuration ); m_CreatedWorldGeometry = createdTimeGeometry; } else { auto createdTimeGeometry = mitk::ArbitraryTimeGeometry::New(); const TimeStepType numberOfTimeSteps = worldTimeGeometry->CountTimeSteps(); createdTimeGeometry->ReserveSpaceForGeometries( numberOfTimeSteps ); for ( TimeStepType i = 0; i < numberOfTimeSteps; ++i ) { const BaseGeometry::Pointer clonedGeometry = slicedWorldGeometry->Clone().GetPointer(); const auto bounds = worldTimeGeometry->GetTimeBounds( i ); createdTimeGeometry->AppendNewTimeStep( clonedGeometry, bounds[0], bounds[1]); } createdTimeGeometry->Update(); m_CreatedWorldGeometry = createdTimeGeometry; } } } // unblock update; we may do this now, because if m_BlockUpdate was already // true before this method was entered, then we will never come here. m_BlockUpdate = false; // Send the geometry. Do this even if nothing was changed, because maybe // Update() was only called to re-send the old geometry and time/slice data. this->SendCreatedWorldGeometry(); this->SendSlice(); this->SendTime(); // Adjust the stepper range of slice stepper according to geometry this->AdjustSliceStepperRange(); } void SliceNavigationController::SendCreatedWorldGeometry() { // Send the geometry. Do this even if nothing was changed, because maybe // Update() was only called to re-send the old geometry. if (!m_BlockUpdate) { this->InvokeEvent(GeometrySendEvent(m_CreatedWorldGeometry, 0)); } } void SliceNavigationController::SendCreatedWorldGeometryUpdate() { if (!m_BlockUpdate) { this->InvokeEvent(GeometryUpdateEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); } } void SliceNavigationController::SendSlice() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometrySliceEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SendTime() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometryTimeEvent(m_CreatedWorldGeometry, m_Time->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SetGeometry(const itk::EventObject &) {} void SliceNavigationController::SetGeometryTime(const itk::EventObject &geometryTimeEvent) { if (m_CreatedWorldGeometry.IsNull()) { return; } const auto *timeEvent = dynamic_cast< const SliceNavigationController::GeometryTimeEvent * >(&geometryTimeEvent); assert( timeEvent != nullptr ); TimeGeometry *timeGeometry = timeEvent->GetTimeGeometry(); assert( timeGeometry != nullptr ); auto timeStep = (int)timeEvent->GetPos(); ScalarType timeInMS; timeInMS = timeGeometry->TimeStepToTimePoint(timeStep); timeStep = m_CreatedWorldGeometry->TimePointToTimeStep(timeInMS); this->GetTime()->SetPos(timeStep); } void SliceNavigationController::SetGeometrySlice(const itk::EventObject &geometrySliceEvent) { const auto *sliceEvent = dynamic_cast(&geometrySliceEvent); assert(sliceEvent!=nullptr); this->GetSlice()->SetPos(sliceEvent->GetPos()); } void SliceNavigationController::SelectSliceByPoint(const Point3D &point) { if (m_CreatedWorldGeometry.IsNull()) { return; } //@todo add time to PositionEvent and use here!! SlicedGeometry3D *slicedWorldGeometry = dynamic_cast( m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()).GetPointer()); if (slicedWorldGeometry) { int bestSlice = -1; double bestDistance = itk::NumericTraits::max(); int s, slices; slices = slicedWorldGeometry->GetSlices(); if (slicedWorldGeometry->GetEvenlySpaced()) { mitk::PlaneGeometry *plane = slicedWorldGeometry->GetPlaneGeometry(0); const Vector3D &direction = slicedWorldGeometry->GetDirectionVector(); Point3D projectedPoint; plane->Project(point, projectedPoint); // Check whether the point is somewhere within the slice stack volume; // otherwise, the default slice (0) will be selected if (direction[0] * (point[0] - projectedPoint[0]) + direction[1] * (point[1] - projectedPoint[1]) + direction[2] * (point[2] - projectedPoint[2]) >= 0) { bestSlice = (int)(plane->Distance(point) / slicedWorldGeometry->GetSpacing()[2] + 0.5); } } else { Point3D projectedPoint; for (s = 0; s < slices; ++s) { slicedWorldGeometry->GetPlaneGeometry(s)->Project(point, projectedPoint); const Vector3D distance = projectedPoint - point; ScalarType currentDistance = distance.GetSquaredNorm(); if (currentDistance < bestDistance) { bestDistance = currentDistance; bestSlice = s; } } } if (bestSlice >= 0) { this->GetSlice()->SetPos(bestSlice); } else { this->GetSlice()->SetPos(0); } this->SendCreatedWorldGeometryUpdate(); // send crosshair event SetCrosshairEvent.Send(point); } } void SliceNavigationController::ReorientSlices(const Point3D &point, const Vector3D &normal) { if (m_CreatedWorldGeometry.IsNull()) { return; } PlaneOperation op(OpORIENT, point, normal); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } void SliceNavigationController::ReorientSlices(const mitk::Point3D &point, const mitk::Vector3D &axisVec0, const mitk::Vector3D &axisVec1) { if (m_CreatedWorldGeometry) { PlaneOperation op(OpORIENT, point, axisVec0, axisVec1); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } } mitk::TimeGeometry *SliceNavigationController::GetCreatedWorldGeometry() { return m_CreatedWorldGeometry; } const mitk::BaseGeometry *SliceNavigationController::GetCurrentGeometry3D() { if (m_CreatedWorldGeometry.IsNotNull()) { return m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()); } else { return nullptr; } } const mitk::PlaneGeometry *SliceNavigationController::GetCurrentPlaneGeometry() { const auto *slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); if (slicedGeometry) { const mitk::PlaneGeometry *planeGeometry = (slicedGeometry->GetPlaneGeometry(this->GetSlice()->GetPos())); return planeGeometry; } else { return nullptr; } } void SliceNavigationController::SetRenderer(BaseRenderer *renderer) { m_Renderer = renderer; } BaseRenderer *SliceNavigationController::GetRenderer() const { return m_Renderer; } void SliceNavigationController::AdjustSliceStepperRange() { const auto *slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); const Vector3D &direction = slicedGeometry->GetDirectionVector(); int c = 0; int i, k = 0; for (i = 0; i < 3; ++i) { if (fabs(direction[i]) < 0.000000001) { ++c; } else { k = i; } } if (c == 2) { ScalarType min = slicedGeometry->GetOrigin()[k]; ScalarType max = min + slicedGeometry->GetExtentInMM(k); m_Slice->SetRange(min, max); } else { m_Slice->InvalidateRange(); } } void SliceNavigationController::ExecuteOperation(Operation *operation) { // switch on type // - select best slice for a given point // - rotate created world geometry according to Operation->SomeInfo() if (!operation || m_CreatedWorldGeometry.IsNull()) { return; } switch (operation->GetOperationType()) { case OpMOVE: // should be a point operation { if (!m_SliceLocked) // do not move the cross position { // select a slice auto *po = dynamic_cast(operation); if (po && po->GetIndex() == -1) { this->SelectSliceByPoint(po->GetPoint()); } else if (po && po->GetIndex() != -1) // undo case because index != -1, index holds the old position of this slice { this->GetSlice()->SetPos(po->GetIndex()); } } break; } case OpRESTOREPLANEPOSITION: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } case OpAPPLYTRANSFORMMATRIX: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } default: { // do nothing break; } } } + TimeStepType SliceNavigationController::GetSelectedTimeStep() const + { + return this->GetTime()->GetPos(); + } + + TimePointType SliceNavigationController::GetSelectedTimePoint() const + { + auto timeStep = this->GetSelectedTimeStep(); + + if (m_CreatedWorldGeometry.IsNull()) + { + return 0.0; + } + + if (!m_CreatedWorldGeometry->IsValidTimeStep(timeStep)) + { + mitkThrow() << "SliceNavigationController is in an invalid state. It has a time step" + << "selected that is not covered by its time geometry. Selected time step: " + << timeStep << "; TimeGeometry steps count: " << m_CreatedWorldGeometry->CountTimeSteps(); + } + + return m_CreatedWorldGeometry->TimeStepToTimePoint(timeStep); + } + } // namespace diff --git a/Modules/Core/src/IO/mitkIOMimeTypes.cpp b/Modules/Core/src/IO/mitkIOMimeTypes.cpp index 15983508f6..e9cf938b29 100644 --- a/Modules/Core/src/IO/mitkIOMimeTypes.cpp +++ b/Modules/Core/src/IO/mitkIOMimeTypes.cpp @@ -1,352 +1,359 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkIOMimeTypes.h" #include "mitkCustomMimeType.h" #include "mitkLogMacros.h" #include "itkGDCMImageIO.h" #include "itkMetaDataObject.h" #include #include namespace mitk { - IOMimeTypes::DicomMimeType::DicomMimeType() : CustomMimeType(DICOM_MIMETYPE_NAME()) + IOMimeTypes::BaseDicomMimeType::BaseDicomMimeType(const std::string& name) : CustomMimeType(name) { this->AddExtension("gdcm"); this->AddExtension("dcm"); this->AddExtension("DCM"); this->AddExtension("dc3"); this->AddExtension("DC3"); this->AddExtension("ima"); this->AddExtension("img"); this->SetCategory(CATEGORY_IMAGES()); this->SetComment("DICOM"); } - bool IOMimeTypes::DicomMimeType::AppliesTo(const std::string &path) const + bool IOMimeTypes::BaseDicomMimeType::AppliesTo(const std::string &path) const { // check whether directory or file // if directory try to find first file within it instead bool pathIsDirectory = itksys::SystemTools::FileIsDirectory(path); std::string filepath = path; if (pathIsDirectory) { itksys::Directory input; input.Load(path.c_str()); std::vector files; for (unsigned long idx = 0; idxSetFileName(filepath); try { gdcmIO->ReadImageInformation(); } catch (const itk::ExceptionObject & /*err*/) { return false; } //DICOMRT modalities have specific reader, don't read with normal DICOM readers std::string modality; itk::MetaDataDictionary& dict = gdcmIO->GetMetaDataDictionary(); itk::ExposeMetaData(dict, "0008|0060", modality); - MITK_INFO << "DICOM Modality is " << modality; + MITK_DEBUG << "DICOM Modality detected by MimeType "<< this->GetName() << " is " << modality; if (modality == "RTSTRUCT" || modality == "RTDOSE" || modality == "RTPLAN") { return false; } else { return gdcmIO->CanReadFile(filepath.c_str()); } } - IOMimeTypes::DicomMimeType *IOMimeTypes::DicomMimeType::Clone() const { return new DicomMimeType(*this); } + IOMimeTypes::BaseDicomMimeType*IOMimeTypes::BaseDicomMimeType::Clone() const { return new BaseDicomMimeType(*this); } + + IOMimeTypes::DicomMimeType::DicomMimeType() : BaseDicomMimeType(DICOM_MIMETYPE_NAME()) + { + } + + IOMimeTypes::DicomMimeType* IOMimeTypes::DicomMimeType::Clone() const { return new DicomMimeType(*this); } + std::vector IOMimeTypes::Get() { std::vector mimeTypes; // order matters here (descending rank for mime types) mimeTypes.push_back(NRRD_MIMETYPE().Clone()); mimeTypes.push_back(NIFTI_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(DICOM_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(STEREOLITHOGRAPHY_MIMETYPE().Clone()); mimeTypes.push_back(WAVEFRONT_OBJ_MIMETYPE().Clone()); mimeTypes.push_back(STANFORD_PLY_MIMETYPE().Clone()); mimeTypes.push_back(RAW_MIMETYPE().Clone()); mimeTypes.push_back(POINTSET_MIMETYPE().Clone()); return mimeTypes; } CustomMimeType IOMimeTypes::VTK_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_NAME()); mimeType.AddExtension("vti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_IMAGE_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Legacy Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_IMAGE_NAME()); mimeType.AddExtension("pvti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Parallel Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_NAME()); mimeType.AddExtension("vtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Legacy PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_POLYDATA_NAME()); mimeType.AddExtension("pvtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Parallel PolyData"); return mimeType; } CustomMimeType IOMimeTypes::STEREOLITHOGRAPHY_MIMETYPE() { CustomMimeType mimeType(STEREOLITHOGRAPHY_NAME()); mimeType.AddExtension("stl"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stereolithography"); return mimeType; } CustomMimeType IOMimeTypes::WAVEFRONT_OBJ_MIMETYPE() { CustomMimeType mimeType(WAVEFRONT_OBJ_NAME()); mimeType.AddExtension("obj"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Wavefront OBJ"); return mimeType; } CustomMimeType IOMimeTypes::STANFORD_PLY_MIMETYPE() { CustomMimeType mimeType(STANFORD_PLY_NAME()); mimeType.AddExtension("ply"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stanford PLY"); return mimeType; } std::string IOMimeTypes::STEREOLITHOGRAPHY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".stl"; return name; } std::string IOMimeTypes::WAVEFRONT_OBJ_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".obj"; return name; } std::string IOMimeTypes::STANFORD_PLY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".ply"; return name; } std::string IOMimeTypes::DEFAULT_BASE_NAME() { static std::string name = "application/vnd.mitk"; return name; } std::string IOMimeTypes::CATEGORY_IMAGES() { static std::string cat = "Images"; return cat; } std::string IOMimeTypes::CATEGORY_SURFACES() { static std::string cat = "Surfaces"; return cat; } std::string IOMimeTypes::VTK_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image"; return name; } std::string IOMimeTypes::VTK_IMAGE_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.image"; return name; } std::string IOMimeTypes::VTK_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata"; return name; } std::string IOMimeTypes::VTK_POLYDATA_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.polydata"; return name; } CustomMimeType IOMimeTypes::NRRD_MIMETYPE() { CustomMimeType mimeType(NRRD_MIMETYPE_NAME()); mimeType.AddExtension("nrrd"); mimeType.AddExtension("nhdr"); mimeType.SetCategory("Images"); mimeType.SetComment("NRRD"); return mimeType; } CustomMimeType IOMimeTypes::NIFTI_MIMETYPE() { CustomMimeType mimeType(NIFTI_MIMETYPE_NAME()); mimeType.AddExtension("nii"); mimeType.AddExtension("nii.gz"); mimeType.AddExtension("hdr"); mimeType.AddExtension("hdr.gz"); mimeType.AddExtension("img"); mimeType.AddExtension("img.gz"); mimeType.AddExtension("nia"); mimeType.SetCategory("Images"); mimeType.SetComment("Nifti"); return mimeType; } CustomMimeType IOMimeTypes::RAW_MIMETYPE() { CustomMimeType mimeType(RAW_MIMETYPE_NAME()); mimeType.AddExtension("raw"); mimeType.SetCategory("Images"); mimeType.SetComment("Raw data"); return mimeType; } IOMimeTypes::DicomMimeType IOMimeTypes::DICOM_MIMETYPE() { return DicomMimeType(); } std::string IOMimeTypes::NRRD_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nrrd"; return name; } std::string IOMimeTypes::NIFTI_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nifti"; return name; } std::string IOMimeTypes::RAW_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.raw"; return name; } std::string IOMimeTypes::DICOM_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.dicom"; return name; } CustomMimeType IOMimeTypes::POINTSET_MIMETYPE() { CustomMimeType mimeType(POINTSET_MIMETYPE_NAME()); mimeType.AddExtension("mps"); mimeType.SetCategory("Point Sets"); mimeType.SetComment("MITK Point Set"); return mimeType; } std::string IOMimeTypes::POINTSET_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".pointset"; return name; } CustomMimeType IOMimeTypes::GEOMETRY_DATA_MIMETYPE() { mitk::CustomMimeType mimeType(DEFAULT_BASE_NAME() + ".geometrydata"); mimeType.AddExtension("mitkgeometry"); mimeType.SetCategory("Geometries"); mimeType.SetComment("GeometryData object"); return mimeType; } } diff --git a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp index 3cbe865b7f..bb6a9ae2d8 100644 --- a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp @@ -1,947 +1,944 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkDisplayInteractor.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkInteractionPositionEvent.h" #include "mitkPropertyList.h" #include #include #include // level window #include "mitkLevelWindow.h" #include "mitkLevelWindowProperty.h" #include "mitkLine.h" #include "mitkNodePredicateDataType.h" #include "mitkStandaloneDataStorage.h" #include "vtkRenderWindowInteractor.h" // Rotation #include "mitkInteractionConst.h" #include "rotate_cursor.xpm" #include #include #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkStatusBar.h" #include void mitk::DisplayInteractor::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { HandleEvent(interactionEvent, nullptr); } } mitk::DisplayInteractor::DisplayInteractor() : m_IndexToSliceModifier(4) , m_AutoRepeat(false) , m_InvertScrollDirection(false) , m_InvertZoomDirection(false) , m_InvertMoveDirection(false) , m_InvertLevelWindowDirection(false) , m_AlwaysReact(false) , m_ZoomFactor(2) , m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); } mitk::DisplayInteractor::~DisplayInteractor() { // nothing here } void mitk::DisplayInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); CONNECT_FUNCTION("IncreaseTimeStep", IncreaseTimeStep); CONNECT_FUNCTION("DecreaseTimeStep", DecreaseTimeStep); } bool mitk::DisplayInteractor::CheckPositionEvent(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return false; } return true; } bool mitk::DisplayInteractor::CheckRotationPossible(const mitk::InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO interesection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const auto *posEvent = dynamic_cast(interactionEvent); if (posEvent == nullptr) return false; BaseRenderer *clickedRenderer = posEvent->GetSender(); const PlaneGeometry *ourViewportGeometry = (clickedRenderer->GetCurrentWorldPlaneGeometry()); if (!ourViewportGeometry) return false; Point3D cursorPosition = posEvent->GetPositionInWorld(); const auto spacing = ourViewportGeometry->GetSpacing(); const PlaneGeometry *geometryToBeRotated = nullptr; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = nullptr; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const double threshholdDistancePixels = 12.0; auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; const PlaneGeometry *otherRenderersRenderPlane = snc->GetCurrentPlaneGeometry(); if (otherRenderersRenderPlane == nullptr) continue; // ignore, we don't see a plane // check if there is an intersection Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed if (!ourViewportGeometry->IntersectionLine(otherRenderersRenderPlane, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line const double distanceFromIntersectionLine = intersectionLine.Distance(cursorPosition) / spacing[snc->GetDefaultViewDirection()]; // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just // need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used if (m_LinkPlanes) { m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (geometryToBeRotated == nullptr) // first one close to the cursor { geometryToBeRotated = otherRenderersRenderPlane; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < mitk::eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we DO have enough information for rotation m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project( cursorPosition); // remember where the last cursor position ON THE LINE has been observed if (anyOtherGeometry->IntersectionPoint( intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) // find center of rotation by intersection with any of the OTHER lines { return true; } else { return false; } } return false; } bool mitk::DisplayInteractor::CheckSwivelPossible(const mitk::InteractionEvent *interactionEvent) { const ScalarType ThresholdDistancePixels = 6.0; // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const auto *posEvent = dynamic_cast(interactionEvent); BaseRenderer *renderer = interactionEvent->GetSender(); if (!posEvent || !renderer) return false; const Point3D &cursor = posEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry *clickedGeometry(nullptr); const PlaneGeometry *otherGeometry1(nullptr); const PlaneGeometry *otherGeometry2(nullptr); auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; - // unsigned int slice = (*iter)->GetSlice()->GetPos(); - // unsigned int time = (*iter)->GetTime()->GetPos(); - const PlaneGeometry *planeGeometry = snc->GetCurrentPlaneGeometry(); if (!planeGeometry) continue; if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = planeGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (otherGeometry1 == nullptr) { otherGeometry1 = planeGeometry; } else { otherGeometry2 = planeGeometry; } if (m_LinkPlanes) { // If planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } mitk::Line3D line; mitk::Point3D point; if ((clickedGeometry != nullptr) && (otherGeometry1 != nullptr) && (otherGeometry2 != nullptr) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(cursor) < ThresholdDistancePixels) { return false; } else { m_ReferenceCursor = posEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = {1.0, 0.0, 0.0}; ScalarType yVector[] = {0.0, 1.0, 0.0}; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayInteractor::Init(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayInteractor::Move(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer *sender = interactionEvent->GetSender(); auto *positionEvent = static_cast(interactionEvent); float invertModifier = -1.0; if (m_InvertMoveDirection) { invertModifier = 1.0; } // perform translation Vector2D moveVector = (positionEvent->GetPointerPositionOnScreen() - m_LastDisplayCoordinate) * invertModifier; moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); sender->GetCameraController()->MoveBy(moveVector); RenderingManager::GetInstance()->RequestUpdate(sender->GetRenderWindow()); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::SetCrosshair(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { auto* positionEvent = static_cast(interactionEvent); Point3D pos = positionEvent->GetPositionInWorld(); const BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto renWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard2D && renWin != sender->GetRenderWindow()) { BaseRenderer::GetInstance(renWin)->GetSliceNavigationController()->SelectSliceByPoint(pos); } } } void mitk::DisplayInteractor::IncreaseTimeStep(StateMachineAction *, InteractionEvent *) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Next(); } void mitk::DisplayInteractor::DecreaseTimeStep(StateMachineAction *, InteractionEvent *) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Previous(); } void mitk::DisplayInteractor::Zoom(StateMachineAction *, InteractionEvent *interactionEvent) { float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } auto* positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); if (factor != 1.0) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); sender->GetCameraController()->Zoom(factor, m_StartCoordinateInMM); RenderingManager::GetInstance()->RequestUpdate(sender->GetRenderWindow()); } } void mitk::DisplayInteractor::Scroll(StateMachineAction *, InteractionEvent *interactionEvent) { auto* positionEvent = static_cast(interactionEvent); mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (sliceNaviController) { int delta = 0; // Scrolling direction if (m_ScrollDirection == "updown") { delta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); } else { delta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); } if (m_InvertScrollDirection) { delta *= -1; } // Set how many pixels the mouse has to be moved to scroll one slice // if we moved less than 'm_IndexToSliceModifier' pixels slice ONE slice only if (delta > 0 && delta < m_IndexToSliceModifier) { delta = m_IndexToSliceModifier; } else if (delta < 0 && delta > -m_IndexToSliceModifier) { delta = -m_IndexToSliceModifier; } delta /= m_IndexToSliceModifier; int newPos = sliceNaviController->GetSlice()->GetPos() + delta; // if auto repeat is on, start at first slice if you reach the last slice and vice versa int maxSlices = sliceNaviController->GetSlice()->GetSteps(); if (m_AutoRepeat) { while (newPos < 0) { newPos += maxSlices; } while (newPos >= maxSlices) { newPos -= maxSlices; } } else { // if the new slice is below 0 we still show slice 0 // due to the stepper using unsigned int we have to do this ourselves if (newPos < 1) { newPos = 0; } } m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // set the new position sliceNaviController->GetSlice()->SetPos(newPos); } } void mitk::DisplayInteractor::ScrollOneDown(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Next(); } } void mitk::DisplayInteractor::ScrollOneUp(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Previous(); } } void mitk::DisplayInteractor::AdjustLevelWindow(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // search for active image mitk::DataStorage::Pointer storage = sender->GetDataStorage(); mitk::DataNode::Pointer node = nullptr; mitk::DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(mitk::NodePredicateDataType::New("Image")); for (unsigned int i = 0; i < allImageNodes->size(); ++i) { bool isActiveImage = false; bool propFound = allImageNodes->at(i)->GetBoolProperty("imageForLevelWindow", isActiveImage); if (propFound && isActiveImage) { node = allImageNodes->at(i); continue; } } if (node.IsNull()) { node = storage->GetNode(mitk::NodePredicateDataType::New("Image")); } if (node.IsNull()) { return; } mitk::LevelWindow lv = mitk::LevelWindow(); node->GetLevelWindow(lv); ScalarType level = lv.GetLevel(); ScalarType window = lv.GetWindow(); int levelIndex = 0; int windowIndex = 1; if (m_LevelDirection != "leftright") { levelIndex = 1; windowIndex = 0; } int directionModifier = 1; if (m_InvertLevelWindowDirection) { directionModifier = -1; } // calculate adjustments from mouse movements level += (m_CurrentDisplayCoordinate[levelIndex] - m_LastDisplayCoordinate[levelIndex]) * static_cast(2) * directionModifier; window += (m_CurrentDisplayCoordinate[windowIndex] - m_LastDisplayCoordinate[windowIndex]) * static_cast(2) * directionModifier; lv.SetLevelWindow(level, window); dynamic_cast(node->GetProperty("levelwindow"))->SetLevelWindow(lv); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::StartRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->SetMouseCursor(rotate_cursor_xpm, 0, 0); } void mitk::DisplayInteractor::EndRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->ResetMouseCursor(); } void mitk::DisplayInteractor::Rotate(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto *posEvent = dynamic_cast(event); if (posEvent == nullptr) return; Point3D cursor = posEvent->GetPositionInWorld(); Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; Vector3D toCursor = cursor - m_CenterOfRotation; // cross product: | A x B | = |A| * |B| * sin(angle) Vector3D axisOfRotation; vnl_vector_fixed vnlDirection = vnl_cross_3d(toCursor.GetVnlVector(), toProjected.GetVnlVector()); axisOfRotation.SetVnlVector(vnlDirection); // scalar product: A * B = |A| * |B| * cos(angle) // tan = sin / cos ScalarType angle = -atan2((double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected)); angle *= 180.0 / vnl_math::pi; m_LastCursorPosition = cursor; // create RotationOperation and apply to all SNCs that should be rotated RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); // iterate the OTHER slice navigation controllers: these are filled in DoDecideBetweenRotationAndSliceSelection for (auto iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&rotationOperation); (*iter)->SendCreatedWorldGeometryUpdate(); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::Swivel(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto *posEvent = dynamic_cast(event); if (!posEvent) return; // Determine relative mouse movement projected onto world space Point2D cursor = posEvent->GetPointerPositionOnScreen(); Vector2D relativeCursor = cursor - m_ReferenceCursor; Vector3D relativeCursorAxis = m_RotationPlaneXVector * relativeCursor[0] + m_RotationPlaneYVector * relativeCursor[1]; // Determine rotation axis (perpendicular to rotation plane and cursor // movement) Vector3D rotationAxis = itk::CrossProduct(m_RotationPlaneNormal, relativeCursorAxis); ScalarType rotationAngle = relativeCursor.GetNorm() / 2.0; // Restore the initial plane pose by undoing the previous rotation // operation RotationOperation op(OpROTATE, m_CenterOfRotation, m_PreviousRotationAxis, -m_PreviousRotationAngle); SNCVector::iterator iter; for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&op); (*iter)->SendCreatedWorldGeometryUpdate(); } } // Apply new rotation operation to all relevant SNCs RotationOperation op2(OpROTATE, m_CenterOfRotation, rotationAxis, rotationAngle); for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { // Retrieve the TimeGeometry of this SliceNavigationController TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; // Execute the new rotation timeGeometry->ExecuteOperation(&op2); // Notify listeners (*iter)->SendCreatedWorldGeometryUpdate(); } } m_PreviousRotationAxis = rotationAxis; m_PreviousRotationAngle = rotationAngle; RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::DisplayInteractor::UpdateStatusbar(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const auto* posEvent = dynamic_cast(event); if (nullptr == posEvent) { return; } const mitk::BaseRenderer::Pointer baseRenderer = posEvent->GetSender(); TNodePredicateDataType::Pointer isImageData = TNodePredicateDataType::New(); auto globalCurrentTimePoint = baseRenderer->GetTime(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = baseRenderer->GetDataStorage()->GetSubset(isImageData).GetPointer(); if (nodes.IsNull()) { return; } // posEvent->GetPositionInWorld() would return the world position at the // time of initiating the interaction. However, we need to update the // status bar with the position after changing slice. Therefore, we // translate the same display position with the renderer again to // get the new world position. Point3D worldposition; baseRenderer->DisplayToWorld(posEvent->GetPointerPositionOnScreen(), worldposition); mitk::Image::Pointer image3D; mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; int component = 0; node = FindTopmostVisibleNode(nodes, worldposition, globalCurrentTimePoint, baseRenderer); if (node.IsNull()) { return; } bool isBinary(false); node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = baseRenderer->GetDataStorage()->GetSources(node, nullptr, true); if (!sourcenodes->empty()) { topSourceNode = mitk::FindTopmostVisibleNode(sourcenodes, worldposition, globalCurrentTimePoint, baseRenderer); } if (topSourceNode.IsNotNull()) { image3D = dynamic_cast(topSourceNode->GetData()); topSourceNode->GetIntProperty("Image.Displayed Component", component); } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } // get the position and gray value from the image and build up status bar text auto statusBar = StatusBar::GetInstance(); if (image3D.IsNotNull() && statusBar != nullptr) { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(worldposition, p); auto pixelType = image3D->GetChannelDescriptor().GetPixelType().GetPixelType(); if (pixelType == itk::ImageIOBase::RGB || pixelType == itk::ImageIOBase::RGBA) { std::string pixelValue = "Pixel RGB(A) value: "; pixelValue.append(ConvertCompositePixelValueToString(image3D, p)); statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue.c_str()); } else if (pixelType == itk::ImageIOBase::DIFFUSIONTENSOR3D || pixelType == itk::ImageIOBase::SYMMETRICSECONDRANKTENSOR) { std::string pixelValue = "See ODF Details view. "; statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue.c_str()); } else { mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, image3D->GetVolumeData(image3D->GetTimeGeometry()->TimePointToTimeStep(globalCurrentTimePoint)), p, pixelValue, component); statusBar->DisplayImageInfo(worldposition, p, globalCurrentTimePoint, pixelValue); } } else { statusBar->DisplayImageInfoInvalid(); } } void mitk::DisplayInteractor::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // auto repeat std::string strAutoRepeat = ""; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } else { m_AutoRepeat = false; } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } else { m_IndexToSliceModifier = 4; } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") m_LinkPlanes = true; else m_LinkPlanes = false; } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } // allwaysReact std::string strAlwaysReact = ""; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } else { m_AlwaysReact = false; } } else { m_AlwaysReact = false; } } bool mitk::DisplayInteractor::FilterEvents(InteractionEvent *interactionEvent, DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } bool mitk::DisplayInteractor::GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } } diff --git a/Modules/Core/test/mitkSliceNavigationControllerTest.cpp b/Modules/Core/test/mitkSliceNavigationControllerTest.cpp index df1861e2bb..59950d9697 100644 --- a/Modules/Core/test/mitkSliceNavigationControllerTest.cpp +++ b/Modules/Core/test/mitkSliceNavigationControllerTest.cpp @@ -1,167 +1,205 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 // T22254 class mitkSliceNavigationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSliceNavigationControllerTestSuite); CPPUNIT_TEST(validateAxialViewDirection); CPPUNIT_TEST(validateCoronalViewDirection); CPPUNIT_TEST(validateSagittalViewDirection); + CPPUNIT_TEST(GetSelectedTimePoint); CPPUNIT_TEST_SUITE_END(); + mitk::Geometry3D::Pointer m_Geometry3D; + mitk::ArbitraryTimeGeometry::Pointer m_TimeGeometry; + public: void setUp() override { mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 20.0, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 50.0, 0.0); mitk::Vector3D spacing; mitk::FillVector3D(spacing, 1.0, 1.0, 2.0); auto planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->InitializeStandardPlane(firstAxisVector, secondAxisVector, &spacing); planeGeometry->SetOrigin(origin); unsigned int numberOfSlices = 100U; auto slicedGeometry3D = mitk::SlicedGeometry3D::New(); slicedGeometry3D->InitializeEvenlySpaced(planeGeometry, numberOfSlices); m_Geometry3D = mitk::Geometry3D::New(); m_Geometry3D->SetBounds(slicedGeometry3D->GetBounds()); m_Geometry3D->SetIndexToWorldTransform(slicedGeometry3D->GetIndexToWorldTransform()); + + m_TimeGeometry = mitk::ArbitraryTimeGeometry::New(); + m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 0.5, 10.); + m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 10., 30.); + m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 30., 50.); + m_TimeGeometry->AppendNewTimeStepClone(m_Geometry3D, 50., 60.); + m_TimeGeometry->Update(); } void tearDown() override { } void validateAxialViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Axial); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 70.0, 229.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, -50.0, 0.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 0.0, 0.0, -200.0); std::cout << "Axial view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } void validateCoronalViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Frontal); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.0, 69.5, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 100.0, 0.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 0.0, 200.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 0.0, -50.0, 0.0); std::cout << "Coronal view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } void validateSagittalViewDirection() { auto sliceNavigationController = mitk::SliceNavigationController::New(); sliceNavigationController->SetInputWorldGeometry3D(m_Geometry3D); sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Sagittal); sliceNavigationController->Update(); mitk::Point3D origin; mitk::FillVector3D(origin, 10.5, 20.0, 30.0); mitk::Vector3D firstAxisVector; mitk::FillVector3D(firstAxisVector, 0.0, 50.0, 0.0); mitk::Vector3D secondAxisVector; mitk::FillVector3D(secondAxisVector, 0.0, 0.0, 200.0); mitk::Vector3D thirdAxisVector; mitk::FillVector3D(thirdAxisVector, 100.0, 0.0, 0.0); std::cout << "Sagittal view direction" << std::endl; CPPUNIT_ASSERT(this->validateGeometry(sliceNavigationController->GetCurrentGeometry3D(), origin, firstAxisVector, secondAxisVector, thirdAxisVector)); } + void GetSelectedTimePoint() + { + auto sliceNavigationController = mitk::SliceNavigationController::New(); + + CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 0.); + + sliceNavigationController->SetInputWorldTimeGeometry(m_TimeGeometry); + sliceNavigationController->SetViewDirection(mitk::SliceNavigationController::Sagittal); + sliceNavigationController->Update(); + + CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimeStep() == 0); + CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 0.5); + + sliceNavigationController->GetTime()->SetPos(2); + CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimeStep() == 2); + CPPUNIT_ASSERT(sliceNavigationController->GetSelectedTimePoint() == 30.0); + + auto sliceNavigationController2 = mitk::SliceNavigationController::New(); + + sliceNavigationController2->SetInputWorldGeometry3D(m_Geometry3D); + sliceNavigationController2->SetViewDirection(mitk::SliceNavigationController::Sagittal); + sliceNavigationController2->Update(); + + CPPUNIT_ASSERT(sliceNavigationController2->GetSelectedTimeStep() == 0); + CPPUNIT_ASSERT(sliceNavigationController2->GetSelectedTimePoint() == 0.0); + } + private: bool validateGeometry(mitk::BaseGeometry::ConstPointer geometry, const mitk::Point3D &origin, const mitk::Vector3D &firstAxisVector, const mitk::Vector3D &secondAxisVector, const mitk::Vector3D &thirdAxisVector) { bool result = true; std::cout << " Origin" << std::endl; if (!mitk::Equal(geometry->GetOrigin(), origin, mitk::eps, true)) result = false; std::cout << " First axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(0), firstAxisVector, mitk::eps, true)) result = false; std::cout << " Second axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(1), secondAxisVector, mitk::eps, true)) result = false; std::cout << " Third axis vector" << std::endl; if (!mitk::Equal(geometry->GetAxisVector(2), thirdAxisVector, mitk::eps, true)) result = false; return result; } - mitk::Geometry3D::Pointer m_Geometry3D; }; MITK_TEST_SUITE_REGISTRATION(mitkSliceNavigationController) diff --git a/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h b/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h index aa56718ad8..e8527a8a93 100644 --- a/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h +++ b/Modules/DICOMReader/include/mitkBaseDICOMReaderService.h @@ -1,70 +1,70 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKBASEDICOMREADERSERVICE_H #define MITKBASEDICOMREADERSERVICE_H #include #include #include "MitkDICOMReaderExports.h" namespace mitk { /** Base class for service wrappers that make DICOMFileReader from the DICOMReader module usable. */ class MITKDICOMREADER_EXPORT BaseDICOMReaderService : public AbstractFileReader { public: - BaseDICOMReaderService(const std::string& description); - BaseDICOMReaderService(const mitk::CustomMimeType& customType, const std::string& description); - using AbstractFileReader::Read; IFileReader::ConfidenceLevel GetConfidenceLevel() const override; protected: + BaseDICOMReaderService(const std::string& description); + BaseDICOMReaderService(const mitk::CustomMimeType& customType, const std::string& description); + /** Uses this->GetRelevantFile() and this->GetReader to load the image. * data and puts it into base data instances-*/ std::vector> DoRead() override; /** Returns the list of all DCM files that are in the same directory * like this->GetLocalFileName().*/ mitk::StringList GetDICOMFilesInSameDirectory() const; /** Returns the reader instance that should be used. The descission may be based * one the passed relevant file list.*/ virtual mitk::DICOMFileReader::Pointer GetReader(const mitk::StringList& relevantFiles) const = 0; void SetOnlyRegardOwnSeries(bool); bool GetOnlyRegardOwnSeries() const; private: /** Flags that constrols if the read() operation should only regard DICOM files of the same series if the specified GetLocalFileName() is a file. If it is a director, this flag has no impact (it is assumed false then). */ bool m_OnlyRegardOwnSeries = true; }; class IPropertyProvider; /** Helper function that generates a name string (e.g. for DataNode names) from the DICOM properties of the passed provider instance. If the instance is nullptr, or has no dicom properties DataNode::NO_NAME_VALUE() will be returned.*/ std::string MITKDICOMREADER_EXPORT GenerateNameFromDICOMProperties(const mitk::IPropertyProvider* provider); } #endif // MITKBASEDICOMREADERSERVICE_H diff --git a/Modules/DICOMReader/include/mitkDICOMProperty.h b/Modules/DICOMReader/include/mitkDICOMProperty.h index 895c9e8cda..4f24614af2 100644 --- a/Modules/DICOMReader/include/mitkDICOMProperty.h +++ b/Modules/DICOMReader/include/mitkDICOMProperty.h @@ -1,65 +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 mitkDICOMProperty_h #define mitkDICOMProperty_h #include "mitkDICOMImageBlockDescriptor.h" #include "mitkTemporoSpatialStringProperty.h" #include "mitkDICOMTagPath.h" #include "MitkDICOMReaderExports.h" namespace mitk { typedef TemporoSpatialStringProperty DICOMProperty; /** Generation functor for DICOMFileReader classes to convert the collected tag values into DICOMProperty instances. */ MITKDICOMREADER_EXPORT mitk::BaseProperty::Pointer GetDICOMPropertyForDICOMValuesFunctor(const DICOMCachedValueLookupTable& cacheLookupTable); class PropertyList; class BaseData; /** Helper function that searches for all properties in a given property list that matches the passed path. * The result will be the matching properties in a map*/ MITKDICOMREADER_EXPORT std::map< std::string, BaseProperty::Pointer> GetPropertyByDICOMTagPath(const PropertyList* list, const DICOMTagPath& path); /** Helper function that searches for all properties in a given base data that matches the passed path. * The result will be the matching properties in a map*/ MITKDICOMREADER_EXPORT std::map< std::string, BaseProperty::Pointer> GetPropertyByDICOMTagPath(const BaseData* data, const DICOMTagPath& path); /**Helper function that can be used to convert the content of a DICOM property into the given return type. The function makes the following assumptions: 1. dcmValueString does only encode one number. 2. The value is encoded compliant to locale "C". @pre dcmValueString must be convertibel into the return type. If this is not the case an exception will be thrown. */ template TNumericReturnType ConvertDICOMStrToValue(const std::string& dcmValueString) { std::istringstream iss(dcmValueString); iss.imbue(std::locale("C")); TNumericReturnType d; if (!(iss >> d) || !(iss.eof())) { mitkThrow() << "Cannot convert string to value type. Type: " << typeid(TNumericReturnType).name() << "; String: " << dcmValueString; } return d; }; + /**Helper function that can be used to convert a numeric value into content of a DICOM property. + @pre value must be convertibel into a string. + If this is not the case an exception will be thrown. + */ + template + std::string ConvertValueToDICOMStr(const TNumericType value) + { + std::ostringstream oss; + oss.imbue(std::locale("C")); + if (!(oss << value)) + { + mitkThrow() << "Cannot convert value type to dicom string. Type: " << typeid(TNumericType).name() << "; value: " << value; + } + + return oss.str(); + }; + } #endif diff --git a/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp b/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp index cf891996ff..96218f9203 100644 --- a/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp +++ b/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp @@ -1,159 +1,166 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkDICOMProperty.h" #include "mitkImage.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" class mitkDICOMPropertyTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkDICOMPropertyTestSuite); MITK_TEST(GetPropertyByDICOMTagPath); MITK_TEST(GetPropertyByDICOMTagPath_2); MITK_TEST(ConvertDICOMStrToValue); + MITK_TEST(ConvertValueToDICOMStr); CPPUNIT_TEST_SUITE_END(); private: mitk::DICOMTagPath simplePath; mitk::DICOMTagPath deepPath; mitk::DICOMTagPath deepPath2; mitk::DICOMTagPath deepPath_withAnyElement; mitk::DICOMTagPath deepPath_withAnySelection; mitk::DICOMTagPath deepPath_withSelection; mitk::DICOMTagPath deepPath_withSelection2; mitk::DICOMTagPath emptyPath; mitk::Image::Pointer data; std::string simplePathStr; std::string deepPathStr; std::string deepPath2Str; std::string deepPath_withSelectionStr; public: void setUp() override { simplePath.AddElement(0x0010, 0x0010); deepPath.AddElement(0x0010, 0x0011).AddElement(0x0020, 0x0022).AddElement(0x0030, 0x0033); deepPath2.AddElement(0x0010, 0x0011).AddElement(0x0020, 0x0023).AddElement(0x0030, 0x0033); deepPath_withAnyElement.AddElement(0x0010, 0x0011).AddAnyElement().AddElement(0x0030, 0x0033); deepPath_withAnySelection.AddElement(0x0010, 0x0011).AddAnySelection(0x0020, 0x0024).AddElement(0x0030, 0x0033); deepPath_withSelection.AddElement(0x0010, 0x0011).AddSelection(0x0020, 0x0024, 1).AddElement(0x0030, 0x0033); deepPath_withSelection2.AddElement(0x0010, 0x0011).AddSelection(0x0020, 0x0024, 2).AddElement(0x0030, 0x0033); simplePathStr = mitk::DICOMTagPathToPropertyName(simplePath); deepPathStr = mitk::DICOMTagPathToPropertyName(deepPath); deepPath2Str = mitk::DICOMTagPathToPropertyName(deepPath2); deepPath_withSelectionStr = mitk::DICOMTagPathToPropertyName(deepPath_withSelection); data = mitk::Image::New(); data->GetPropertyList()->SetStringProperty(simplePathStr.c_str(), "simplePath"); data->GetPropertyList()->SetStringProperty(deepPathStr.c_str(), "deepPath"); data->GetPropertyList()->SetStringProperty(deepPath2Str.c_str(), "deepPath2"); data->GetPropertyList()->SetStringProperty(deepPath_withSelectionStr.c_str(), "deepPath_withSelection"); data->GetPropertyList()->SetStringProperty("DICOM.0003.0003", "otherPath"); data->GetPropertyList()->SetStringProperty("not_a_dicom_prop", "not_a_dicom_prop"); } void tearDown() override { } void GetPropertyByDICOMTagPath() { std::map< std::string, mitk::BaseProperty::Pointer> result = mitk::GetPropertyByDICOMTagPath(data, simplePath); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("simplePath")); result = mitk::GetPropertyByDICOMTagPath(data, deepPath); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath")); result = mitk::GetPropertyByDICOMTagPath(data, deepPath2); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath2")); result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withAnyElement); CPPUNIT_ASSERT(result.size() == 3); CPPUNIT_ASSERT_EQUAL(result[deepPathStr]->GetValueAsString(), std::string("deepPath")); CPPUNIT_ASSERT_EQUAL(result[deepPath2Str]->GetValueAsString(), std::string("deepPath2")); CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withSelection); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withSelection2); CPPUNIT_ASSERT(result.size() == 0); result = mitk::GetPropertyByDICOMTagPath(data, emptyPath); CPPUNIT_ASSERT(result.size() == 0); } void GetPropertyByDICOMTagPath_2() { std::map< std::string, mitk::BaseProperty::Pointer> result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), simplePath); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("simplePath")); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath")); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath2); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath2")); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withAnyElement); CPPUNIT_ASSERT(result.size() == 3); CPPUNIT_ASSERT_EQUAL(result[deepPathStr]->GetValueAsString(), std::string("deepPath")); CPPUNIT_ASSERT_EQUAL(result[deepPath2Str]->GetValueAsString(), std::string("deepPath2")); CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withSelection); CPPUNIT_ASSERT(result.size() == 1); CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withSelection2); CPPUNIT_ASSERT(result.size() == 0); result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), emptyPath); CPPUNIT_ASSERT(result.size() == 0); } void ConvertDICOMStrToValue() { CPPUNIT_ASSERT_EQUAL(mitk::ConvertDICOMStrToValue("1.35"), 1.35); CPPUNIT_ASSERT_EQUAL(mitk::ConvertDICOMStrToValue("1"), 1.); CPPUNIT_ASSERT_THROW(mitk::ConvertDICOMStrToValue("1.35"), mitk::Exception); CPPUNIT_ASSERT_EQUAL(mitk::ConvertDICOMStrToValue("1"), 1); CPPUNIT_ASSERT_THROW(mitk::ConvertDICOMStrToValue("1,35"), mitk::Exception); CPPUNIT_ASSERT_THROW(mitk::ConvertDICOMStrToValue("nonumber"), mitk::Exception); } + void ConvertValueToDICOMStr() + { + CPPUNIT_ASSERT_EQUAL(mitk::ConvertValueToDICOMStr(1.35), std::string("1.35")); + CPPUNIT_ASSERT_EQUAL(mitk::ConvertValueToDICOMStr(1), std::string("1")); + } + }; MITK_TEST_SUITE_REGISTRATION(mitkDICOMProperty) diff --git a/Modules/IGTUI/Qmitk/QmitkIGTPlayerWidget.cpp b/Modules/IGTUI/Qmitk/QmitkIGTPlayerWidget.cpp index 0ac7dcf116..c5c4cca0be 100644 --- a/Modules/IGTUI/Qmitk/QmitkIGTPlayerWidget.cpp +++ b/Modules/IGTUI/Qmitk/QmitkIGTPlayerWidget.cpp @@ -1,564 +1,564 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkIGTPlayerWidget.h" //mitk headers #include "mitkTrackingTypes.h" #include #include #include #include #include #include #include #include #include //qt headers #include #include #include QmitkIGTPlayerWidget::QmitkIGTPlayerWidget(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f), m_RealTimePlayer(mitk::NavigationDataPlayer::New()), m_SequentialPlayer(mitk::NavigationDataSequentialPlayer::New()), m_StartTime(-1.0), m_CurrentSequentialPointNumber(0), m_Controls(new Ui::QmitkIGTPlayerWidgetControls) { m_Controls->setupUi(this); m_PlayingTimer = new QTimer(this); // initialize update timer CreateConnections(); m_Controls->samplePositionHorizontalSlider->setVisible(false); this->ResetLCDNumbers(); // reset lcd numbers at start } QmitkIGTPlayerWidget::~QmitkIGTPlayerWidget() { m_PlayingTimer->stop(); delete m_Controls; } void QmitkIGTPlayerWidget::CreateConnections() { connect( (QObject*)(m_Controls->playPushButton), SIGNAL(clicked(bool)), this, SLOT(OnPlayButtonClicked(bool)) ); // play button connect( (QObject*)(m_PlayingTimer), SIGNAL(timeout()), this, SLOT(OnPlaying()) ); // update timer connect( (QObject*) (m_Controls->beginPushButton), SIGNAL(clicked()), this, SLOT(OnGoToBegin()) ); // reset player and go to begin connect( (QObject*) (m_Controls->stopPushButton), SIGNAL(clicked()), this, SLOT(OnGoToEnd()) ); // reset player // pass this widgets protected combobox signal to public signal connect( (QObject*) (m_Controls->trajectorySelectComboBox), SIGNAL(currentIndexChanged(int)), this, SIGNAL(SignalCurrentTrajectoryChanged(int)) ); // pass this widgets protected checkbox signal to public signal connect( m_Controls->splineModeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(SignalSplineModeToggled(bool)) ); //connect( m_Controls->sequencialModeCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnSequencialModeToggled(bool)) ); connect( m_Controls->samplePositionHorizontalSlider, SIGNAL(sliderPressed()), this, SLOT(OnSliderPressed()) ); connect( m_Controls->samplePositionHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(OnSliderReleased()) ); connect( m_Controls->m_OpenFileButton, SIGNAL(clicked()), this, SLOT(OnOpenFileButtonPressed()) ); } bool QmitkIGTPlayerWidget::IsTrajectoryInSplineMode() { return m_Controls->splineModeCheckBox->isChecked(); } bool QmitkIGTPlayerWidget::CheckInputFileValid() { QFile file(m_CmpFilename); // check if file exists if(!file.exists()) { QMessageBox::warning(nullptr, "IGTPlayer: Error", "No valid input file was loaded. Please load input file first!"); return false; } return true; } unsigned int QmitkIGTPlayerWidget::GetNumberOfTools() { unsigned int result = 0; if(this->GetCurrentPlaybackMode() == RealTimeMode) { if(m_RealTimePlayer.IsNotNull()) result = m_RealTimePlayer->GetNumberOfOutputs(); } else if(this->GetCurrentPlaybackMode() == SequentialMode) { if(m_SequentialPlayer.IsNotNull()) result = m_SequentialPlayer->GetNumberOfOutputs(); } // at the moment this works only if player is initialized return result; } void QmitkIGTPlayerWidget::SetUpdateRate(unsigned int msecs) { m_PlayingTimer->setInterval((int) msecs); // set update timer update rate } void QmitkIGTPlayerWidget::OnPlayButtonClicked(bool checked) { if ( ! checked ) { if ( this->GetCurrentPlaybackMode() == RealTimeMode ) { m_RealTimePlayer->StopPlaying(); } else if ( this->GetCurrentPlaybackMode() == SequentialMode ) { // m_SequentialPlayer-> } } if(CheckInputFileValid()) // no playing possible without valid input file { switch ( this->GetCurrentPlaybackMode() ) { case RealTimeMode: { break; } case SequentialMode: { break; } } PlaybackMode currentMode = this->GetCurrentPlaybackMode(); bool isRealTimeMode = currentMode == RealTimeMode; bool isSequentialMode = currentMode == SequentialMode; if(checked) // play { if( (isRealTimeMode && m_RealTimePlayer.IsNull()) || (isSequentialMode && m_SequentialPlayer.IsNull())) // start play { mitk::NavigationDataSet::Pointer navigationDataSet; try { navigationDataSet = dynamic_cast (mitk::IOUtil::Load(m_CmpFilename.toStdString())[0].GetPointer()); } catch(const mitk::IGTException&) { std::string errormessage = "Error during start playing. Invalid or wrong file?"; QMessageBox::warning(nullptr, "IGTPlayer: Error", errormessage.c_str()); m_Controls->playPushButton->setChecked(false); m_RealTimePlayer = nullptr; return; } if(isRealTimeMode) { m_RealTimePlayer = mitk::NavigationDataPlayer::New(); m_RealTimePlayer->SetNavigationDataSet(navigationDataSet); try { m_RealTimePlayer->StartPlaying(); } catch(const mitk::IGTException&) { std::string errormessage = "Error during start playing. Invalid or wrong file?"; QMessageBox::warning(nullptr, "IGTPlayer: Error", errormessage.c_str()); m_Controls->playPushButton->setChecked(false); m_RealTimePlayer = nullptr; return; } } else if(isSequentialMode) { m_SequentialPlayer = mitk::NavigationDataSequentialPlayer::New(); try { m_SequentialPlayer->SetNavigationDataSet(navigationDataSet); } catch(const mitk::IGTException&) { std::string errormessage = "Error during start playing. Invalid or wrong file type?"; QMessageBox::warning(nullptr, "IGTPlayer: Error", errormessage.c_str()); m_Controls->playPushButton->setChecked(false); m_RealTimePlayer = nullptr; return; } m_Controls->samplePositionHorizontalSlider->setMinimum(0); m_Controls->samplePositionHorizontalSlider->setMaximum(m_SequentialPlayer->GetNumberOfSnapshots()); m_Controls->samplePositionHorizontalSlider->setEnabled(true); } m_PlayingTimer->start(100); emit SignalPlayingStarted(); } else // resume play { if(isRealTimeMode) m_RealTimePlayer->Resume(); m_PlayingTimer->start(100); emit SignalPlayingResumed(); } } else // pause { if(isRealTimeMode) m_RealTimePlayer->Pause(); m_PlayingTimer->stop(); emit SignalPlayingPaused(); } } else { m_Controls->playPushButton->setChecked(false); // uncheck play button if file unvalid } } QmitkIGTPlayerWidget::PlaybackMode QmitkIGTPlayerWidget::GetCurrentPlaybackMode() { /*if(m_Controls->sequencialModeCheckBox->isChecked()) return SequentialMode; else*/ return RealTimeMode; } QTimer* QmitkIGTPlayerWidget::GetPlayingTimer() { return m_PlayingTimer; } void QmitkIGTPlayerWidget::OnStopPlaying() { this->StopPlaying(); } void QmitkIGTPlayerWidget::StopPlaying() { m_PlayingTimer->stop(); emit SignalPlayingStopped(); if(m_RealTimePlayer.IsNotNull()) m_RealTimePlayer->StopPlaying(); m_StartTime = -1; // set starttime back m_CurrentSequentialPointNumber = 0; m_Controls->samplePositionHorizontalSlider->setSliderPosition(m_CurrentSequentialPointNumber); m_Controls->sampleLCDNumber->display(static_cast(m_CurrentSequentialPointNumber)); this->ResetLCDNumbers(); m_Controls->playPushButton->setChecked(false); // set play button unchecked } void QmitkIGTPlayerWidget::OnPlaying() { switch ( this->GetCurrentPlaybackMode() ) { case RealTimeMode: { if ( m_RealTimePlayer.IsNull() ) { return; } if ( m_StartTime < 0 ) { // get playback start time m_StartTime = m_RealTimePlayer->GetOutput()->GetTimeStamp(); } if( ! m_RealTimePlayer->IsAtEnd() ) { m_RealTimePlayer->Update(); // update player int msc = (int) (m_RealTimePlayer->GetOutput()->GetTimeStamp() - m_StartTime); // calculation for playing time display int ms = msc % 1000; msc = (msc - ms) / 1000; int s = msc % 60; int min = (msc-s) / 60; // set lcd numbers m_Controls->msecLCDNumber->display(ms); m_Controls->secLCDNumber->display(s); m_Controls->minLCDNumber->display(min); emit SignalPlayerUpdated(); // player successfully updated } else { this->StopPlaying(); // if player is at EOF } break; } case SequentialMode: { if ( m_SequentialPlayer.IsNull() ) { return; } if ( m_CurrentSequentialPointNumber < m_SequentialPlayer->GetNumberOfSnapshots() ) { m_SequentialPlayer->Update(); // update sequential player m_Controls->samplePositionHorizontalSlider->setSliderPosition(m_CurrentSequentialPointNumber++); // refresh slider position m_Controls->sampleLCDNumber->display(static_cast(m_CurrentSequentialPointNumber)); //for debugging purposes //std::cout << "Sample: " << m_CurrentSequentialPointNumber << " X: " << m_SequentialPlayer->GetOutput()->GetPosition()[0] << " Y: " << m_SequentialPlayer->GetOutput()->GetPosition()[1] << " Y: " << m_SequentialPlayer->GetOutput()->GetPosition()[2] << std::endl; emit SignalPlayerUpdated(); // player successfully updated } else { this->StopPlaying(); // if player is at EOF } break; } } } const std::vector QmitkIGTPlayerWidget::GetNavigationDatas() { std::vector navDatas; if(this->GetCurrentPlaybackMode() == RealTimeMode && m_RealTimePlayer.IsNotNull()) { for(unsigned int i=0; i < m_RealTimePlayer->GetNumberOfOutputs(); ++i) { navDatas.push_back(m_RealTimePlayer->GetOutput(i)); // push back current navigation data for each tool } } else if(this->GetCurrentPlaybackMode() == SequentialMode && m_SequentialPlayer.IsNotNull()) { for(unsigned int i=0; i < m_SequentialPlayer->GetNumberOfOutputs(); ++i) { navDatas.push_back(m_SequentialPlayer->GetOutput(i)); // push back current navigation data for each tool } } return navDatas; } const mitk::PointSet::Pointer QmitkIGTPlayerWidget::GetNavigationDatasPointSet() { mitk::PointSet::Pointer result = mitk::PointSet::New(); mitk::PointSet::PointType pointType; PlaybackMode currentMode = this->GetCurrentPlaybackMode(); bool isRealTimeMode = currentMode == RealTimeMode; bool isSequentialMode = currentMode == SequentialMode; if( (isRealTimeMode && m_RealTimePlayer.IsNotNull()) || (isSequentialMode && m_SequentialPlayer.IsNotNull())) { for(unsigned int i=0; i < m_RealTimePlayer->GetNumberOfOutputs(); ++i) { mitk::NavigationData::PositionType position; if(isRealTimeMode) position = m_RealTimePlayer->GetOutput(i)->GetPosition(); - else if(isSequentialMode) + else position = m_SequentialPlayer->GetOutput(i)->GetPosition(); pointType[0] = position[0]; pointType[1] = position[1]; pointType[2] = position[2]; result->InsertPoint(i,pointType); // insert current ND as Pointtype in PointSet for return } } return result; } const mitk::PointSet::PointType QmitkIGTPlayerWidget::GetNavigationDataPoint(unsigned int index) { if( index > this->GetNumberOfTools() ) throw std::out_of_range("Tool Index out of range!"); PlaybackMode currentMode = this->GetCurrentPlaybackMode(); bool isRealTimeMode = currentMode == RealTimeMode; bool isSequentialMode = currentMode == SequentialMode; // create return PointType from current ND for tool index mitk::PointSet::PointType result; if( (isRealTimeMode && m_RealTimePlayer.IsNotNull()) || (isSequentialMode && m_SequentialPlayer.IsNotNull())) { mitk::NavigationData::PositionType position; if(isRealTimeMode) position = m_RealTimePlayer->GetOutput(index)->GetPosition(); else if(isSequentialMode) position = m_SequentialPlayer->GetOutput(index)->GetPosition(); result[0] = position[0]; result[1] = position[1]; result[2] = position[2]; } return result; } /*void QmitkIGTPlayerWidget::SetRealTimePlayer( mitk::NavigationDataPlayer::Pointer player ) { if(player.IsNotNull()) m_RealTimePlayer = player; } void QmitkIGTPlayerWidget::SetSequentialPlayer( mitk::NavigationDataSequentialPlayer::Pointer player ) { if(player.IsNotNull()) m_SequentialPlayer = player; }*/ void QmitkIGTPlayerWidget::OnOpenFileButtonPressed() { QString filename = QFileDialog::getOpenFileName(this, "Load tracking data", QmitkIGTCommonHelper::GetLastFileLoadPath(),"XML files (*.xml)"); QFile file(filename); QmitkIGTCommonHelper::SetLastFileLoadPathByFileName(filename); // if something went wrong or user pressed cancel in the save dialog if ( filename.isEmpty() || ! file.exists() ) { QMessageBox::warning(nullptr, "Warning", QString("Please enter valid path. Using previous path again.")); return; } m_CmpFilename = filename; this->OnGoToEnd(); /// stops playing and resets lcd numbers m_Controls->m_ActiveFileLabel->setText(m_CmpFilename); emit SignalInputFileChanged(); mitk::NavigationDataSet::Pointer navigationDataSet = dynamic_cast (mitk::IOUtil::Load(m_CmpFilename.toStdString())[0].GetPointer()); m_RealTimePlayer->SetNavigationDataSet(navigationDataSet); m_SequentialPlayer->SetNavigationDataSet(navigationDataSet); m_Controls->m_PlayerControlsGroupBox->setEnabled(true); } void QmitkIGTPlayerWidget::OnGoToEnd() { this->StopPlaying(); // reset lcd numbers this->ResetLCDNumbers(); } void QmitkIGTPlayerWidget::OnGoToBegin() { // stop player manual so no PlayingStopped() m_PlayingTimer->stop(); if(this->GetCurrentPlaybackMode() == RealTimeMode && m_RealTimePlayer.IsNotNull()) { m_RealTimePlayer->StopPlaying(); m_RealTimePlayer = nullptr; // set player to nullptr so it can be initialized again if playback is called afterwards } m_StartTime = -1; // set starttime back //reset view elements m_Controls->playPushButton->setChecked(false); this->ResetLCDNumbers(); } void QmitkIGTPlayerWidget::ResetLCDNumbers() { m_Controls->minLCDNumber->display(QString("00")); m_Controls->secLCDNumber->display(QString("00")); m_Controls->msecLCDNumber->display(QString("000")); } void QmitkIGTPlayerWidget::SetTrajectoryNames(const QStringList toolNames) { QComboBox* cBox = m_Controls->trajectorySelectComboBox; if(cBox->count() > 0) this->ClearTrajectorySelectCombobox(); // before making changed to QComboBox it is recommended to disconnet it's SIGNALS and SLOTS disconnect( (QObject*) (m_Controls->trajectorySelectComboBox), SIGNAL(currentIndexChanged(int)), this, SIGNAL(SignalCurrentTrajectoryChanged(int)) ); if(!toolNames.isEmpty()) m_Controls->trajectorySelectComboBox->insertItems(0, toolNames); // adding current tool names to combobox // reconnect after performed changes connect( (QObject*) (m_Controls->trajectorySelectComboBox), SIGNAL(currentIndexChanged(int)), this, SIGNAL(SignalCurrentTrajectoryChanged(int)) ); } int QmitkIGTPlayerWidget::GetResolution() { return m_Controls->resolutionSpinBox->value(); // return currently selected trajectory resolution } void QmitkIGTPlayerWidget::ClearTrajectorySelectCombobox() { // before making changed to QComboBox it is recommended to disconnet it's SIGNALS and SLOTS disconnect( (QObject*) (m_Controls->trajectorySelectComboBox), SIGNAL(currentIndexChanged(int)), this, SIGNAL(SignalCurrentTrajectoryChanged(int)) ); m_Controls->trajectorySelectComboBox->clear(); // reconnect after performed changes connect( (QObject*) (m_Controls->trajectorySelectComboBox), SIGNAL(currentIndexChanged(int)), this, SIGNAL(SignalCurrentTrajectoryChanged(int)) ); } void QmitkIGTPlayerWidget::OnSequencialModeToggled(bool toggled) { this->StopPlaying(); // stop playing when mode is changed if(toggled) { m_Controls->samplePositionHorizontalSlider->setEnabled(true); // enable slider if sequential mode } else if(!toggled) { m_Controls->samplePositionHorizontalSlider->setSliderPosition(0); // set back and disable slider m_Controls->samplePositionHorizontalSlider->setDisabled(true); } } void QmitkIGTPlayerWidget::OnSliderReleased() { int currentSliderValue = m_Controls->samplePositionHorizontalSlider->value(); // current slider value selected through user movement if(currentSliderValue > static_cast(m_CurrentSequentialPointNumber)) // at the moment only forward scrolling is possible { auto snapshotNumber = static_cast(currentSliderValue); m_SequentialPlayer->GoToSnapshot(snapshotNumber); // move player to selected snapshot m_CurrentSequentialPointNumber = currentSliderValue; m_Controls->sampleLCDNumber->display(currentSliderValue); // update lcdnumber in widget } else m_Controls->samplePositionHorizontalSlider->setValue(m_CurrentSequentialPointNumber); } void QmitkIGTPlayerWidget::OnSliderPressed() { if(m_Controls->playPushButton->isChecked()) // check if widget is playing m_Controls->playPushButton->click(); // perform click to pause the play } diff --git a/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h index 041b7e91bd..b8c3af8dbc 100644 --- a/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkAbstractNodeSelectionWidget.h @@ -1,246 +1,259 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - #ifndef QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H #define QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H +#include + #include #include #include -#include - #include class QmitkAbstractDataStorageModel; class QAbstractItemVew; /** * \class QmitkAbstractNodeSelectionWidget * \brief Abstract base class for the selection of data from a data storage. */ class MITKQTWIDGETS_EXPORT QmitkAbstractNodeSelectionWidget : public QWidget { Q_OBJECT public: explicit QmitkAbstractNodeSelectionWidget(QWidget* parent = nullptr); virtual ~QmitkAbstractNodeSelectionWidget() override; /** - * @brief Sets the data storage that will be used /monitored by widget. + * @brief Sets the data storage that will be used / monitored by widget. * * @par dataStorage A pointer to the data storage to set. */ void SetDataStorage(mitk::DataStorage* dataStorage); /** * Sets the node predicate and updates the widget, according to the node predicate. * Implement OnNodePredicateChange() for custom actualization of a derived widget class. * * @par nodePredicate A pointer to node predicate. */ void SetNodePredicate(const mitk::NodePredicateBase* nodePredicate); const mitk::NodePredicateBase* GetNodePredicate() const; QString GetInvalidInfo() const; QString GetEmptyInfo() const; QString GetPopUpTitel() const; QString GetPopUpHint() const; bool GetSelectionIsOptional() const; bool GetSelectOnlyVisibleNodes() const; using NodeList = QList; /** Other node container type often used in the code base.*/ using ConstNodeStdVector = std::vector; /** Returns the selected nodes, as emitted with CurrentSelectionChanged*/ NodeList GetSelectedNodes() const; /** Convinience method that returns the selected nodes as ConstNodeStdVector. This is a type also often used in the mitk code base.*/ ConstNodeStdVector GetSelectedNodesStdVector() const; Q_SIGNALS: - /* + /** * @brief A signal that will be emitted if the selected node has changed. * * @par nodes A list of data nodes that are newly selected. */ void CurrentSelectionChanged(NodeList nodes); public Q_SLOTS: - /* + /** * @brief Change the selection modus of the item view's selection model. * * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. * An outgoing selection can then at most contain the filtered nodes. * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, * to include the original selection that could not be modified. * The part of the original selection, that is non-visible are the nodes, that do not fullfill the predicate. * * @par selectOnlyVisibleNodes The bool value to define the selection modus. */ void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); - /* - * @brief Transform a list of data nodes into a model selection and set this as a new selection of the + /** + * @brief Transform a list of data nodes (a selection) into a model selection and set this as a new selection of the * selection model of the private member item view. * * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). * * @par nodes A list of data nodes that should be newly selected. */ void SetCurrentSelection(NodeList selectedNodes); /** Set the info text that should be displayed if no (valid) node is selected, - * but a selection is mandatory. - * The string can contain HTML code. if wanted*/ + * but a selection is mandatory. + * The string can contain HTML code, if desired. + */ void SetInvalidInfo(QString info); /** Set the info text that should be displayed if no (valid) node is selected, - * but a selection is optional. - * The string can contain HTML code. if wanted*/ + * but a selection is optional. + * The string can contain HTML code, if desired. + */ void SetEmptyInfo(QString info); /** Set the caption of the popup that is displayed to alter the selection. - * The string can contain HTML code. if wanted*/ + * The string can contain HTML code, if desired. + */ void SetPopUpTitel(QString info); /** Set the hint text of the popup that is displayed to alter the selection. - * The string can contain HTML code. if wanted*/ + * The string can contain HTML code, if desired. + */ void SetPopUpHint(QString info); /** Set the widget into an optional mode. Optional means that the selection of no valid - node does not mean an invalid state. Thus no node is a valid "node" selection too.*/ + * node does not mean an invalid state. Thus no node is a valid "node" selection too. + */ void SetSelectionIsOptional(bool isOptional); protected Q_SLOTS: /** Call to remove a node from the current selection. If the node is part of the current selection, - this will trigger ReviseSelectionChanged(), AllowEmissionOfSelection() and if there is really a change, - will also emit CurrentSelectionChanged.*/ + * this will trigger ReviseSelectionChanged(), AllowEmissionOfSelection() and if there is really a change, + * will also emit CurrentSelectionChanged. + */ void RemoveNodeFromSelection(const mitk::DataNode* node); protected: - /**Method is called if the display of the selected nodes should be updated (e.g. because the selection changed)*/ + /** Method is called if the display of the selected nodes should be updated (e.g. because the selection changed). */ virtual void UpdateInfo() = 0; - /**Method is called if the predicate has changed, before the selection will be updated according to the new predicate. - The default implementation does nothing. - @remark If you are only interested to know when the selection has changed, overwrite OnInternalSelectionChange().*/ + /** Method is called if the predicate has changed, before the selection will be updated according to the new predicate. + * The default implementation does nothing. + * @remark If you are only interested to know when the selection has changed, overwrite OnInternalSelectionChange(). + */ virtual void OnNodePredicateChanged(); - /**Method is called if the data storage has changed. The selection will be automatically be reseted afterwards. - The default implementation does nothing.*/ + /** Method is called if the data storage has changed. The selection will be automatically be reseted afterwards. + * The default implementation does nothing. + */ virtual void OnDataStorageChanged(); /** This member function will called when ever a new internal selection has been determined. This can be - used to update the state of internal widgets. The default implementation does nothing.*/ + * used to update the state of internal widgets. The default implementation does nothing. + */ virtual void OnInternalSelectionChanged(); - /**Method is called when a node is added to the storage. Default implementation does nothing. - Derived widgets can override the method if they want to react on new nodes in the storage.*/ + /** Method is called when a node is added to the storage. Default implementation does nothing. + * Derived widgets can override the method if they want to react on new nodes in the storage. + */ virtual void OnNodeAddedToStorage(const mitk::DataNode* node); - /**Method is called when a node is removed from the storage. The removed node is passed as - variable. This member is called directly before the node will be removed from the current selection if - he was a part. Default implementation does nothing. */ + /** Method is called when a node is removed from the storage. The removed node is passed as + * variable. This member is called directly before the node will be removed from the current selection if + * he was a part. Default implementation does nothing. + */ virtual void OnNodeRemovedFromStorage(const mitk::DataNode* node); - /**Method is called if the internal selection has changed. It will call following methods, that can be overriden to change - behavior in derived classes: - - pre internal selection change: ReviseSelectionChanged() - - post internal selection change: OnInternalSelectionChanged(), UpdateInfo() and AllowEmissionOfSelection() (via EmitSelection()) - . - If the emission is needed and allowed it will also trigger the emission via EmitSelection().*/ + /** Method is called if the internal selection has changed. It will call following methods, that can be overriden to change + * behavior in derived classes: + * - pre internal selection change: ReviseSelectionChanged() + * - post internal selection change: OnInternalSelectionChanged(), UpdateInfo() and AllowEmissionOfSelection() (via EmitSelection()). + * If the emission is needed and allowed it will also trigger the emission via EmitSelection(). + */ void HandleChangeOfInternalSelection(NodeList newInternalSelection); - /**Compiles the list of node that would be emitted. It always contains the internal selection. - Depending on SelectOnlyVisibleNodes it also adds all external select nodes that weren't visible - (failed the predicate).*/ + /** Compiles the list of node that would be emitted. It always contains the internal selection. + * Depending on SelectOnlyVisibleNodes it also adds all external select nodes that weren't visible (failed the predicate). + */ NodeList CompileEmitSelection() const; - /** This member function is called if the internal selection is about to be changed by the - base implementation. - This is the slot where derived classes can revise and change the internal selection before widget updates, - signal emissions and other things are triggered. Default implementation does nothing, thus it keeps the - passed internal selection as compiled by the base implementation.*/ + /** This member function is called if the internal selection is about to be changed by the base implementation. + * This is the slot where derived classes can revise and change the internal selection before widget updates, + * signal emissions and other things are triggered. Default implementation does nothing, thus it keeps the + * passed internal selection as compiled by the base implementation. + */ virtual void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); /** This function will be called before the CurrentSelectionChanged signal is emitted. The return value indicates - if the signal should be emitted (true = emission; false = no emission). The default implementation always - returns true. - @param emissionCandidates The nodes that will be emitted if the function returns true.*/ + * if the signal should be emitted (true = emission; false = no emission). The default implementation always + * returns true. + * @param emissionCandidates The nodes that will be emitted if the function returns true. + */ virtual bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const; /** Checks if the new emission differs from the last emission. If this is the case and AllowEmissionOfSelection() - returns true the new selection will be emited. */ + * returns true the new selection will be emited. + */ void EmitSelection(const NodeList& emissionCandidates); void SetCurrentInternalSelection(NodeList selectedNodes); const NodeList& GetCurrentInternalSelection() const; const NodeList& GetCurrentExternalSelection() const; mitk::WeakPointer m_DataStorage; mitk::NodePredicateBase::ConstPointer m_NodePredicate; QString m_InvalidInfo; QString m_EmptyInfo; QString m_PopUpTitel; QString m_PopUpHint; /** See documentation of SetSelectOnlyVisibleNodes for details*/ bool m_IsOptional; /** See documentation of SetSelectionIsOptional for details*/ bool m_SelectOnlyVisibleNodes; private: /** Helper triggered on the storage delete event */ void SetDataStorageDeleted(); /**Member is called when a node is added to the storage. Derived widgets can override the method OnNodeAddedToStorage if they want to react on new nodes in the storage.*/ void NodeAddedToStorage(const mitk::DataNode* node); /**Member is called when a node is removed from the storage. It calls OnNodeRemovedFromStorage() and afterwards it removes the removed node form the selection (if it is part of the current selection). Derived classes can override OnNodeRemovedFromStorage() to react on the fact that a node might be removed and their selection might change, because the removed node is part of there selection.*/ void NodeRemovedFromStorage(const mitk::DataNode* node); void OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject &); void AddNodeObserver(mitk::DataNode* node); void RemoveNodeObserver(mitk::DataNode* node); unsigned long m_DataStorageDeletedTag; NodeList m_CurrentInternalSelection; NodeList m_CurrentExternalSelection; NodeList m_LastEmission; bool m_LastEmissionAllowance; using NodeObserverTagMapType = std::map; NodeObserverTagMapType m_NodeObserverTags; /** Help to prevent recursions due to signal loops when emitting selections.*/ bool m_RecursionGuard; }; -#endif // QmitkAbstractNodeSelectionWidget_H + +#endif // QMITK_ABSTRACT_NODE_SELECTION_WIDGET_H diff --git a/Modules/QtWidgets/include/QmitkDataStorageSimpleTreeModel.h b/Modules/QtWidgets/include/QmitkDataStorageSimpleTreeModel.h index d70ddec93b..f27390cb5a 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageSimpleTreeModel.h +++ b/Modules/QtWidgets/include/QmitkDataStorageSimpleTreeModel.h @@ -1,101 +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 QMITKDATASTORAGESIMPLETREEMODEL_H #define QMITKDATASTORAGESIMPLETREEMODEL_H #include // qt widgets module #include class QmitkDataStorageTreeModelInternalItem; /** * @brief The 'QmitkDataStorageSimpleTreeModel' is a basic tree model, derived from the 'QmitkAbstractDataStorageModel'. * It provides functions to accept a data storage and a node predicate in order to customize the model data nodes. * Furthermore it overrides the functions of 'QAbstractItemModel' to create a simple qt list model.* * This model can be used in conjunction with a 'QmitkDataStorageSelectionConnector'. * This model is a "light" version of the classic QmitkDataStorgageTreeModel. The differences between both are the following: * - This class currently does not support DragNDrop. -* - This class does not have the ability to change hirarchy or changes the layer property -*of nodes. This was skipped on purpose, because that is not the job of the storage model. -* - If a tree item A is removed this class does not attach children of A to the parent -*of A. Instead the complete tree representation is updated. This was changed on purpose -*because otherwise the internal representation of the model would not reflect the data storage graph anymore. +* - This class does not have the ability to change hierarchy or changes the layer property of nodes. +* This was skipped on purpose, because that is not the job of the storage model. +* - If a tree item A is removed this class does not attach children of A to the parent of A. +* Instead the complete tree representation is updated. This was changed on purpose because otherwise the internal +* representation of the model would not reflect the data storage graph anymore. */ class MITKQTWIDGETS_EXPORT QmitkDataStorageSimpleTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkDataStorageSimpleTreeModel(QObject *parent); ~QmitkDataStorageSimpleTreeModel() override; // override from 'QmitkAbstractDataStorageModel' /* * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode *node) override; // override pure virtual from 'QAbstractItemModel' QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; protected: using TreeItem = QmitkDataStorageTreeModelInternalItem; private: void UpdateModelData(); void AddNodeInternal(const mitk::DataNode *node); mitk::DataNode *GetParentNode(const mitk::DataNode *node) const; TreeItem *TreeItemFromIndex(const QModelIndex &index) const; QModelIndex IndexFromTreeItem(TreeItem *item) const; void ResetTree(); TreeItem *m_Root; - /**helper structure to check, if a tree item is realy part of the model. + /**helper structure to check, if a tree item is really part of the model. Prefered over iterating over the tree by hand because we can use std::find.*/ std::list m_TreeItems; }; -#endif // QmitkDataStorageSimpleTreeModel_H +#endif // QMITKDATASTORAGESIMPLETREEMODEL_H diff --git a/Modules/QtWidgets/include/QmitkFileReaderOptionsDialog.h b/Modules/QtWidgets/include/QmitkFileReaderOptionsDialog.h index fda8fc7f47..189648e7c0 100644 --- a/Modules/QtWidgets/include/QmitkFileReaderOptionsDialog.h +++ b/Modules/QtWidgets/include/QmitkFileReaderOptionsDialog.h @@ -1,45 +1,48 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QMITKFILEREADEROPTIONSDIALOG_H #define QMITKFILEREADEROPTIONSDIALOG_H #include "mitkIOUtil.h" #include namespace Ui { class QmitkFileReaderOptionsDialog; } class QmitkFileReaderWriterOptionsWidget; class QmitkFileReaderOptionsDialog : public QDialog { Q_OBJECT public: explicit QmitkFileReaderOptionsDialog(mitk::IOUtil::LoadInfo &loadInfo, QWidget *parent = nullptr); ~QmitkFileReaderOptionsDialog() override; bool ReuseOptions() const; void accept() override; +protected slots: + void SetCurrentReader(int index); + private: Ui::QmitkFileReaderOptionsDialog *ui; mitk::IOUtil::LoadInfo &m_LoadInfo; std::vector m_ReaderItems; }; #endif // QMITKFILEREADEROPTIONSDIALOG_H diff --git a/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp index ff1ca235c1..39acb451f4 100644 --- a/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkAbstractNodeSelectionWidget.cpp @@ -1,419 +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 "QmitkAbstractNodeSelectionWidget.h" #include "QmitkModelViewSelectionConnector.h" -QmitkAbstractNodeSelectionWidget::QmitkAbstractNodeSelectionWidget(QWidget* parent) : QWidget(parent), m_InvalidInfo("Error. Select data."), -m_EmptyInfo("Empty. Make a selection."), m_PopUpTitel("Select a data node"), m_PopUpHint(""), -m_IsOptional(false), m_SelectOnlyVisibleNodes(true), m_DataStorageDeletedTag(0), m_LastEmissionAllowance(true), m_RecursionGuard(false) +QmitkAbstractNodeSelectionWidget::QmitkAbstractNodeSelectionWidget(QWidget* parent) + : QWidget(parent) + , m_InvalidInfo("Error. Select data.") + , m_EmptyInfo("Empty. Make a selection.") + , m_PopUpTitel("Select a data node") + , m_PopUpHint("") + , m_IsOptional(false) + , m_SelectOnlyVisibleNodes(true) + , m_DataStorageDeletedTag(0) + , m_LastEmissionAllowance(true) + , m_RecursionGuard(false) { } QmitkAbstractNodeSelectionWidget::~QmitkAbstractNodeSelectionWidget() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNotNull()) { // remove Listener for the data storage itself dataStorage->RemoveObserver(m_DataStorageDeletedTag); // remove "add node listener" from data storage dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); // remove "remove node listener" from data storage dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); } for (auto& node : m_CurrentInternalSelection) { this->RemoveNodeObserver(node); } } QmitkAbstractNodeSelectionWidget::NodeList QmitkAbstractNodeSelectionWidget::GetSelectedNodes() const { return this->CompileEmitSelection(); } QmitkAbstractNodeSelectionWidget::ConstNodeStdVector QmitkAbstractNodeSelectionWidget::GetSelectedNodesStdVector() const { auto result = this->GetSelectedNodes(); return ConstNodeStdVector(result.begin(), result.end()); } void QmitkAbstractNodeSelectionWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (m_DataStorage == dataStorage) { return; } auto oldStorage = m_DataStorage.Lock(); if (oldStorage.IsNotNull()) { // remove Listener for the data storage itself oldStorage->RemoveObserver(m_DataStorageDeletedTag); // remove "add node listener" from old data storage oldStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); // remove "remove node listener" from old data storage oldStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); } m_DataStorage = dataStorage; auto newStorage = m_DataStorage.Lock(); if (newStorage.IsNotNull()) { // add Listener for the data storage itself auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted); m_DataStorageDeletedTag = newStorage->AddObserver(itk::DeleteEvent(), command); // add "add node listener" for new data storage newStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeAddedToStorage)); // add remove node listener for new data storage newStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage)); } this->OnDataStorageChanged(); this->HandleChangeOfInternalSelection({}); } void QmitkAbstractNodeSelectionWidget::SetNodePredicate(const mitk::NodePredicateBase* nodePredicate) { if (m_NodePredicate != nodePredicate) { m_NodePredicate = nodePredicate; this->OnNodePredicateChanged(); NodeList newInternalNodes; for (auto& node : m_CurrentInternalSelection) { if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { newInternalNodes.append(node); } } if (!m_SelectOnlyVisibleNodes) { for (auto& node : m_CurrentExternalSelection) { if (!newInternalNodes.contains(node) && (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node))) { newInternalNodes.append(node); } } } this->HandleChangeOfInternalSelection(newInternalNodes); } } void QmitkAbstractNodeSelectionWidget::HandleChangeOfInternalSelection(NodeList newInternalSelection) { if (!EqualNodeSelections(m_CurrentInternalSelection, newInternalSelection)) { this->ReviseSelectionChanged(m_CurrentInternalSelection, newInternalSelection); this->SetCurrentInternalSelection(newInternalSelection); this->OnInternalSelectionChanged(); auto newEmission = this->CompileEmitSelection(); this->EmitSelection(newEmission); this->UpdateInfo(); } } void QmitkAbstractNodeSelectionWidget::SetCurrentSelection(NodeList selectedNodes) { if (!m_RecursionGuard) { m_CurrentExternalSelection = selectedNodes; auto dataStorage = m_DataStorage.Lock(); NodeList newInternalSelection; for (auto node : selectedNodes) { if (dataStorage.IsNotNull() && dataStorage->Exists(node) && (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node))) { newInternalSelection.append(node); } } this->HandleChangeOfInternalSelection(newInternalSelection); } } const mitk::NodePredicateBase* QmitkAbstractNodeSelectionWidget::GetNodePredicate() const { return m_NodePredicate; } QString QmitkAbstractNodeSelectionWidget::GetInvalidInfo() const { return m_InvalidInfo; } QString QmitkAbstractNodeSelectionWidget::GetEmptyInfo() const { return m_EmptyInfo; } QString QmitkAbstractNodeSelectionWidget::GetPopUpTitel() const { return m_PopUpTitel; } QString QmitkAbstractNodeSelectionWidget::GetPopUpHint() const { return m_PopUpHint; } bool QmitkAbstractNodeSelectionWidget::GetSelectionIsOptional() const { return m_IsOptional; } bool QmitkAbstractNodeSelectionWidget::GetSelectOnlyVisibleNodes() const { return m_SelectOnlyVisibleNodes; } void QmitkAbstractNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) { if (m_SelectOnlyVisibleNodes != selectOnlyVisibleNodes) { m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; auto newEmission = this->CompileEmitSelection(); this->EmitSelection(newEmission); } } void QmitkAbstractNodeSelectionWidget::SetInvalidInfo(QString info) { m_InvalidInfo = info; this->UpdateInfo(); } void QmitkAbstractNodeSelectionWidget::SetEmptyInfo(QString info) { m_EmptyInfo = info; this->UpdateInfo(); } void QmitkAbstractNodeSelectionWidget::SetPopUpTitel(QString info) { m_PopUpTitel = info; } void QmitkAbstractNodeSelectionWidget::SetPopUpHint(QString info) { m_PopUpHint = info; } void QmitkAbstractNodeSelectionWidget::SetSelectionIsOptional(bool isOptional) { m_IsOptional = isOptional; this->UpdateInfo(); } void QmitkAbstractNodeSelectionWidget::SetDataStorageDeleted() { this->OnDataStorageChanged(); this->HandleChangeOfInternalSelection({}); } void QmitkAbstractNodeSelectionWidget::ReviseSelectionChanged(const NodeList& /*oldInternalSelection*/, NodeList& /*newInternalSelection*/) { } bool QmitkAbstractNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const { return true; } void QmitkAbstractNodeSelectionWidget::EmitSelection(const NodeList& emissionCandidates) { m_LastEmissionAllowance = this->AllowEmissionOfSelection(emissionCandidates); if (m_LastEmissionAllowance && !EqualNodeSelections(m_LastEmission, emissionCandidates)) { m_RecursionGuard = true; emit CurrentSelectionChanged(emissionCandidates); m_RecursionGuard = false; m_LastEmission = emissionCandidates; } } void QmitkAbstractNodeSelectionWidget::SetCurrentInternalSelection(NodeList selectedNodes) { for (auto& node : m_CurrentInternalSelection) { this->RemoveNodeObserver(node); } m_CurrentInternalSelection = selectedNodes; for (auto& node : m_CurrentInternalSelection) { this->AddNodeObserver(node); } } const QmitkAbstractNodeSelectionWidget::NodeList& QmitkAbstractNodeSelectionWidget::GetCurrentInternalSelection() const { return m_CurrentInternalSelection; } const QmitkAbstractNodeSelectionWidget::NodeList& QmitkAbstractNodeSelectionWidget::GetCurrentExternalSelection() const { return m_CurrentExternalSelection; } void QmitkAbstractNodeSelectionWidget::OnNodePredicateChanged() { } void QmitkAbstractNodeSelectionWidget::OnDataStorageChanged() { } void QmitkAbstractNodeSelectionWidget::OnInternalSelectionChanged() { } void QmitkAbstractNodeSelectionWidget::NodeAddedToStorage(const mitk::DataNode* node) { this->OnNodeAddedToStorage(node); } void QmitkAbstractNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) { } void QmitkAbstractNodeSelectionWidget::NodeRemovedFromStorage(const mitk::DataNode* node) { this->OnNodeRemovedFromStorage(node); this->RemoveNodeFromSelection(node); } void QmitkAbstractNodeSelectionWidget::OnNodeRemovedFromStorage(const mitk::DataNode* /*node*/) { } QmitkAbstractNodeSelectionWidget::NodeList QmitkAbstractNodeSelectionWidget::CompileEmitSelection() const { NodeList result = m_CurrentInternalSelection; if (!m_SelectOnlyVisibleNodes) { for (auto node : m_CurrentExternalSelection) { if (!result.contains(node) && m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { result.append(node); } } } return result; } void QmitkAbstractNodeSelectionWidget::RemoveNodeFromSelection(const mitk::DataNode* node) { auto newSelection = m_CurrentInternalSelection; auto finding = std::find(std::begin(newSelection), std::end(newSelection), node); if (finding != std::end(newSelection)) { newSelection.erase(finding); this->HandleChangeOfInternalSelection(newSelection); } } void QmitkAbstractNodeSelectionWidget::OnNodeModified(const itk::Object * caller, const itk::EventObject & event) { if (itk::ModifiedEvent().CheckEvent(&event)) { auto node = dynamic_cast(caller); if (node) { if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { this->RemoveNodeFromSelection(node); } else { auto oldAllowance = m_LastEmissionAllowance; auto newEmission = this->CompileEmitSelection(); auto nonConstNode = const_cast(node); if (newEmission.contains(nonConstNode) && (oldAllowance != this->AllowEmissionOfSelection(newEmission))) { this->EmitSelection(newEmission); this->UpdateInfo(); } } } } } void QmitkAbstractNodeSelectionWidget::AddNodeObserver(mitk::DataNode* node) { if (node) { auto modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkAbstractNodeSelectionWidget::OnNodeModified); auto nodeModifiedObserverTag = node->AddObserver(itk::ModifiedEvent(), modifiedCommand); m_NodeObserverTags.insert(std::make_pair(node, nodeModifiedObserverTag)); } } void QmitkAbstractNodeSelectionWidget::RemoveNodeObserver(mitk::DataNode* node) { if (node) { auto finding = m_NodeObserverTags.find(node); if (finding != std::end(m_NodeObserverTags)) { node->RemoveObserver(finding->second); } else { MITK_ERROR << "Selection widget is in a wrong state. A node should be removed from the internal selection but seems to have no observer. Node:" << node; } m_NodeObserverTags.erase(node); } } diff --git a/Modules/QtWidgets/src/QmitkDataStorageDefaultListModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageDefaultListModel.cpp index 34e8b97a14..dd134255e6 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageDefaultListModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageDefaultListModel.cpp @@ -1,178 +1,179 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // qt widgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" #include "QmitkNodeDescriptorManager.h" -QmitkDataStorageDefaultListModel::QmitkDataStorageDefaultListModel(QObject *parent) : QmitkAbstractDataStorageModel(parent) +QmitkDataStorageDefaultListModel::QmitkDataStorageDefaultListModel(QObject *parent) + : QmitkAbstractDataStorageModel(parent) { } void QmitkDataStorageDefaultListModel::DataStorageChanged() { UpdateModelData(); } void QmitkDataStorageDefaultListModel::NodePredicateChanged() { UpdateModelData(); } void QmitkDataStorageDefaultListModel::NodeAdded(const mitk::DataNode* /*node*/) { UpdateModelData(); } void QmitkDataStorageDefaultListModel::NodeChanged(const mitk::DataNode* node) { // since the "NodeChanged" event is sent quite often, we check here, if it is relevant for this model if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { UpdateModelData(); return; } // not relevant - need to check if we have to remove it if (std::find(m_DataNodes.begin(), m_DataNodes.end(), node) != m_DataNodes.end()) { UpdateModelData(); } } void QmitkDataStorageDefaultListModel::NodeRemoved(const mitk::DataNode* /*node*/) { UpdateModelData(); } QModelIndex QmitkDataStorageDefaultListModel::index(int row, int column, const QModelIndex &parent) const { bool hasIndex = this->hasIndex(row, column, parent); if (hasIndex) { return this->createIndex(row, column); } return QModelIndex(); } QModelIndex QmitkDataStorageDefaultListModel::parent(const QModelIndex &/*child*/) const { return QModelIndex(); } int QmitkDataStorageDefaultListModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_DataNodes.size(); } int QmitkDataStorageDefaultListModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return 1; } QVariant QmitkDataStorageDefaultListModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.model() != this) { return QVariant(); } if(index.row() < 0 || index.row() >= static_cast(m_DataNodes.size())) { return QVariant(); } mitk::DataNode::Pointer dataNode = m_DataNodes.at(index.row()); QString nodeName = QString::fromStdString(dataNode->GetName()); if (nodeName.isEmpty()) nodeName = "unnamed"; if (role == Qt::DisplayRole) return nodeName; else if (role == Qt::ToolTipRole) return nodeName; else if (role == Qt::DecorationRole) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } QVariant QmitkDataStorageDefaultListModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const { return QVariant(tr("Nodes")); } Qt::ItemFlags QmitkDataStorageDefaultListModel::flags(const QModelIndex &index) const { if (index.isValid() && index.model() == this) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } return Qt::NoItemFlags; } void QmitkDataStorageDefaultListModel::UpdateModelData() { mitk::DataStorage::SetOfObjects::ConstPointer dataNodes; if (!m_DataStorage.IsExpired()) { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNotNull() && m_NodePredicate.IsNotNull()) { dataNodes = dataStorage->GetSubset(m_NodePredicate); } else { dataNodes = dataStorage->GetAll(); } } // update the model, so that it will be filled with the nodes of the new data storage beginResetModel(); m_DataNodes.clear(); // add all (filtered) nodes to the vector of nodes if (dataNodes != nullptr) { for (auto& node : *dataNodes) { m_DataNodes.push_back(node); } } endResetModel(); } diff --git a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp index 90358e6033..e9a1c1314c 100755 --- a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp @@ -1,329 +1,332 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkDataStorageListModel.h" //# Own includes // mitk #include "mitkStringProperty.h" //# Toolkit includes // itk #include "itkCommand.h" QmitkDataStorageListModel::QmitkDataStorageListModel(mitk::DataStorage *dataStorage, mitk::NodePredicateBase::Pointer pred, QObject *parent) - : QAbstractListModel(parent), m_NodePredicate(nullptr), m_DataStorage(nullptr), m_BlockEvents(false) + : QAbstractListModel(parent) + , m_NodePredicate(nullptr) + , m_DataStorage(nullptr) + , m_BlockEvents(false) { this->SetPredicate(pred); this->SetDataStorage(dataStorage); } QmitkDataStorageListModel::~QmitkDataStorageListModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); } void QmitkDataStorageListModel::SetDataStorage(mitk::DataStorage::Pointer dataStorage) { if (m_DataStorage == dataStorage) { return; } // remove old listeners if (m_DataStorage != nullptr) { m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeAdded)); m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeRemoved)); m_DataStorage->RemoveObserver(m_DataStorageDeleteObserverTag); m_DataStorageDeleteObserverTag = 0; } m_DataStorage = dataStorage; if (m_DataStorage != nullptr) { // subscribe for node added/removed events m_DataStorage->AddNodeEvent.AddListener(mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeAdded)); m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeRemoved)); // add ITK delete listener on data storage itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDataStorageDeleted); m_DataStorageDeleteObserverTag = m_DataStorage->AddObserver(itk::DeleteEvent(), deleteCommand); } // reset/rebuild model reset(); } Qt::ItemFlags QmitkDataStorageListModel::flags(const QModelIndex &) const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QVariant QmitkDataStorageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole && index.isValid()) { const mitk::DataNode *node = std::get(m_NodesAndObserverTags.at(index.row())); return QVariant(QString::fromStdString(node->GetName())); } else { return QVariant(); } } QVariant QmitkDataStorageListModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const { return QVariant(tr("Nodes")); } int QmitkDataStorageListModel::rowCount(const QModelIndex & /*parent*/) const { return m_NodesAndObserverTags.size(); } std::vector QmitkDataStorageListModel::GetDataNodes() const { auto size = m_NodesAndObserverTags.size(); std::vector result(size); for (std::size_t i = 0; i < size; ++i) { result[i] = std::get(m_NodesAndObserverTags[i]); } return result; } mitk::DataStorage *QmitkDataStorageListModel::GetDataStorage() const { return m_DataStorage; } void QmitkDataStorageListModel::SetPredicate(mitk::NodePredicateBase *pred) { m_NodePredicate = pred; // in a prior implementation the call to beginResetModel() been after reset(). // Should this actually be the better order of calls, please document! QAbstractListModel::beginResetModel(); reset(); QAbstractListModel::endResetModel(); } mitk::NodePredicateBase *QmitkDataStorageListModel::GetPredicate() const { return m_NodePredicate; } void QmitkDataStorageListModel::reset() { mitk::DataStorage::SetOfObjects::ConstPointer modelNodes; if (m_DataStorage != nullptr) { if (m_NodePredicate != nullptr) { modelNodes = m_DataStorage->GetSubset(m_NodePredicate); } else { modelNodes = m_DataStorage->GetAll(); } } ClearInternalNodeList(); // add all filtered nodes to our list if (modelNodes != nullptr) { for (auto &node : *modelNodes) { AddNodeToInternalList(node); } } } void QmitkDataStorageListModel::AddNodeToInternalList(mitk::DataNode *node) { if (m_DataStorage != nullptr) { itk::MemberCommand::Pointer nodeModifiedCommand; // add modified observer nodeModifiedCommand = itk::MemberCommand::New(); nodeModifiedCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDataNodeModified); unsigned long nodeObserverTag = node->AddObserver(itk::ModifiedEvent(), nodeModifiedCommand); itk::MemberCommand::Pointer dataModifiedCommand; unsigned long dataObserverTag = 0; // add modified observer if (node->GetData() != nullptr) { dataModifiedCommand = itk::MemberCommand::New(); dataModifiedCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDataModified); dataObserverTag = node->GetData()->AddObserver(itk::ModifiedEvent(), dataModifiedCommand); } m_NodesAndObserverTags.push_back(std::make_tuple(node, nodeObserverTag, dataObserverTag)); } } void QmitkDataStorageListModel::ClearInternalNodeList() { for (auto &iter : m_NodesAndObserverTags) { auto node = std::get(iter); if (node != nullptr) { node->RemoveObserver(std::get(iter)); if (node->GetData() != nullptr) { node->GetData()->RemoveObserver(std::get(iter)); } } } m_NodesAndObserverTags.clear(); } void QmitkDataStorageListModel::RemoveNodeFromInternalList(mitk::DataNode *node) { for (auto iter = m_NodesAndObserverTags.begin(); iter != m_NodesAndObserverTags.end(); ++iter) { if (std::get(*iter) == node) { node->RemoveObserver(std::get(*iter)); if (node->GetData() != nullptr) { node->GetData()->RemoveObserver(std::get(*iter)); } m_NodesAndObserverTags.erase(iter); // invalidate iter break; } } } void QmitkDataStorageListModel::OnDataStorageNodeAdded(const mitk::DataNode *node) { // guarantee no recursions when a new node event is thrown if (!m_BlockEvents) { m_BlockEvents = true; // check if node should be added to the model bool addNode = true; if (m_NodePredicate && !m_NodePredicate->CheckNode(node)) addNode = false; if (addNode) { int newIndex = m_NodesAndObserverTags.size(); beginInsertRows(QModelIndex(), newIndex, newIndex); AddNodeToInternalList(const_cast(node)); endInsertRows(); } m_BlockEvents = false; } } void QmitkDataStorageListModel::OnDataStorageNodeRemoved(const mitk::DataNode *node) { // guarantee no recursions when a new node event is thrown if (!m_BlockEvents) { m_BlockEvents = true; int row = 0; for (auto iter = m_NodesAndObserverTags.begin(); iter != m_NodesAndObserverTags.end(); ++iter, ++row) { if (std::get(*iter) == node) { // node found, remove it beginRemoveRows(QModelIndex(), row, row); RemoveNodeFromInternalList(std::get(*iter)); endRemoveRows(); break; } } } m_BlockEvents = false; } void QmitkDataStorageListModel::OnDataNodeModified(const itk::Object *caller, const itk::EventObject & /*event*/) { if (m_BlockEvents) return; const mitk::DataNode *modifiedNode = dynamic_cast(caller); if (modifiedNode) { QModelIndex changedIndex = getIndex(modifiedNode); if (changedIndex.isValid()) { emit dataChanged(changedIndex, changedIndex); } } } void QmitkDataStorageListModel::OnDataModified(const itk::Object *caller, const itk::EventObject &event) { OnDataNodeModified(caller, event); // until different implementation } void QmitkDataStorageListModel::OnDataStorageDeleted(const itk::Object *, const itk::EventObject &) { if (m_BlockEvents) return; this->SetDataStorage(nullptr); } mitk::DataNode::Pointer QmitkDataStorageListModel::getNode(const QModelIndex &index) const { if (index.isValid()) { return std::get(m_NodesAndObserverTags.at(index.row())); } else { return nullptr; } } QModelIndex QmitkDataStorageListModel::getIndex(const mitk::DataNode *node) const { int row = 0; for (auto iter = m_NodesAndObserverTags.begin(); iter != m_NodesAndObserverTags.end(); ++iter, ++row) { if (std::get(*iter) == node) { return index(row); } } return QModelIndex(); } diff --git a/Modules/QtWidgets/src/QmitkDataStorageSimpleTreeModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageSimpleTreeModel.cpp index 5f7b269025..b85cb7e2bd 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageSimpleTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageSimpleTreeModel.cpp @@ -1,370 +1,371 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 // qt widgets module #include "QmitkCustomVariants.h" #include "QmitkEnums.h" QmitkDataStorageSimpleTreeModel::QmitkDataStorageSimpleTreeModel(QObject *parent) - : QmitkAbstractDataStorageModel(parent), m_Root(nullptr) + : QmitkAbstractDataStorageModel(parent) + , m_Root(nullptr) { ResetTree(); } QmitkDataStorageSimpleTreeModel::~QmitkDataStorageSimpleTreeModel() { m_Root->Delete(); m_Root = nullptr; -}; +} void QmitkDataStorageSimpleTreeModel::ResetTree() { mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Storage"); m_Root = new TreeItem(rootDataNode, nullptr); } void QmitkDataStorageSimpleTreeModel::DataStorageChanged() { if (m_Root) { m_Root->Delete(); } beginResetModel(); ResetTree(); UpdateModelData(); endResetModel(); } void QmitkDataStorageSimpleTreeModel::NodePredicateChanged() { beginResetModel(); ResetTree(); UpdateModelData(); endResetModel(); } void QmitkDataStorageSimpleTreeModel::NodeAdded(const mitk::DataNode *node) { if (node == nullptr || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; this->AddNodeInternal(node); } void QmitkDataStorageSimpleTreeModel::NodeChanged(const mitk::DataNode *node) { TreeItem *treeItem = m_Root->Find(node); if (treeItem) { TreeItem *parentTreeItem = treeItem->GetParent(); // as the root node should not be removed one should always have a parent item if (!parentTreeItem) return; QModelIndex index = this->createIndex(treeItem->GetIndex(), 0, treeItem); // now emit the dataChanged signal emit dataChanged(index, index); } } void QmitkDataStorageSimpleTreeModel::NodeRemoved(const mitk::DataNode *node) { if (node == nullptr || !m_Root) return; TreeItem *treeItem = m_Root->Find(node); if (!treeItem) return; // return because there is no treeitem containing this node TreeItem *parentTreeItem = treeItem->GetParent(); QModelIndex parentIndex = this->IndexFromTreeItem(parentTreeItem); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(parentIndex, treeItem->GetIndex(), treeItem->GetIndex()); // remove node std::vector children = treeItem->GetChildren(); m_TreeItems.remove(treeItem); delete treeItem; //delete in tree if (!children.empty()) { //if not empty we have to rebuild the whole representation, //because the children could be now top level, or at another //source/parent. this->UpdateModelData(); } } QModelIndex QmitkDataStorageSimpleTreeModel::index(int row, int column, const QModelIndex &parent) const { TreeItem *parentItem; if (!parent.isValid() || parent.model() != this) parentItem = m_Root; else parentItem = static_cast(parent.internalPointer()); if (parentItem) { TreeItem *childItem = parentItem->GetChild(row); if (childItem) return createIndex(row, column, childItem); } return QModelIndex(); } QModelIndex QmitkDataStorageSimpleTreeModel::parent(const QModelIndex &child) const { if (!child.isValid() || !m_Root || child.model() != this) return QModelIndex(); TreeItem *childItem = this->TreeItemFromIndex(child); if (!childItem) return QModelIndex(); TreeItem *parentItem = childItem->GetParent(); if (parentItem == m_Root) return QModelIndex(); return this->createIndex(parentItem->GetIndex(), 0, parentItem); } QmitkDataStorageSimpleTreeModel::TreeItem *QmitkDataStorageSimpleTreeModel::TreeItemFromIndex( const QModelIndex &index) const { if (index.isValid() && index.model() == this) { auto item = static_cast(index.internalPointer()); auto finding = std::find(std::begin(m_TreeItems), std::end(m_TreeItems), item); if (finding == std::end(m_TreeItems)) { return nullptr; } return item; } else return m_Root; } int QmitkDataStorageSimpleTreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentTreeItem = this->TreeItemFromIndex(parent); if (parentTreeItem) return parentTreeItem->GetChildCount(); else return 0; } int QmitkDataStorageSimpleTreeModel::columnCount(const QModelIndex &/*parent*/) const { return 1; } QVariant QmitkDataStorageSimpleTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.model() != this) { return QVariant(); } auto treeItem = this->TreeItemFromIndex(index); if (!treeItem) return QVariant(); mitk::DataNode *dataNode = treeItem->GetDataNode(); QString nodeName = QString::fromStdString(dataNode->GetName()); if (nodeName.isEmpty()) { nodeName = "unnamed"; } if (role == Qt::DisplayRole) return nodeName; else if (role == Qt::ToolTipRole) return nodeName; else if (role == Qt::DecorationRole) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageSimpleTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.model() != this) return false; auto treeItem = this->TreeItemFromIndex(index); if (!treeItem) return false; mitk::DataNode *dataNode = treeItem->GetDataNode(); if (!dataNode) return false; if (role == Qt::EditRole && !value.toString().isEmpty()) { dataNode->SetName(value.toString().toStdString().c_str()); } else if (role == Qt::CheckStateRole) { // Please note: value.toInt() returns 2, independentely from the actual checkstate of the index element. // Therefore the checkstate is being estimated again here. QVariant qcheckstate = index.data(Qt::CheckStateRole); int checkstate = qcheckstate.toInt(); bool isVisible = bool(checkstate); dataNode->SetVisibility(!isVisible); } // inform listeners about changes emit dataChanged(index, index); return true; } QVariant QmitkDataStorageSimpleTreeModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && m_Root) return QString::fromStdString(m_Root->GetDataNode()->GetName()); return QVariant(); } Qt::ItemFlags QmitkDataStorageSimpleTreeModel::flags(const QModelIndex &index) const { if (index.isValid() && index.model() == this) { auto treeItem = this->TreeItemFromIndex(index); if (!treeItem) return Qt::NoItemFlags; const auto dataNode = treeItem->GetDataNode(); if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } else { return Qt::NoItemFlags; } } return Qt::NoItemFlags; } mitk::DataNode *QmitkDataStorageSimpleTreeModel::GetParentNode(const mitk::DataNode *node) const { mitk::DataNode *dataNode = nullptr; mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage.Lock()->GetSources(node); if (_Sources->Size() > 0) dataNode = _Sources->front(); return dataNode; } void QmitkDataStorageSimpleTreeModel::AddNodeInternal(const mitk::DataNode *node) { if (node == nullptr || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; // find out if we have a root node TreeItem *parentTreeItem = m_Root; QModelIndex index; mitk::DataNode *parentDataNode = this->GetParentNode(node); if (parentDataNode) // no top level data node { parentTreeItem = m_Root->Find(parentDataNode); // find the corresponding tree item if (!parentTreeItem) { this->NodeAdded(parentDataNode); parentTreeItem = m_Root->Find(parentDataNode); if (!parentTreeItem) return; } // get the index of this parent with the help of the grand parent index = this->createIndex(parentTreeItem->GetIndex(), 0, parentTreeItem); } int firstRowWithASiblingBelow = 0; int nodeLayer = -1; node->GetIntProperty("layer", nodeLayer); for (TreeItem *siblingTreeItem : parentTreeItem->GetChildren()) { int siblingLayer = -1; if (mitk::DataNode *siblingNode = siblingTreeItem->GetDataNode()) { siblingNode->GetIntProperty("layer", siblingLayer); } if (nodeLayer > siblingLayer) { break; } ++firstRowWithASiblingBelow; } beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); auto newNode = new TreeItem(const_cast(node)); parentTreeItem->InsertChild(newNode, firstRowWithASiblingBelow); m_TreeItems.push_back(newNode); endInsertRows(); } QModelIndex QmitkDataStorageSimpleTreeModel::IndexFromTreeItem(TreeItem *item) const { if (item == m_Root) return QModelIndex(); else return this->createIndex(item->GetIndex(), 0, item); } void QmitkDataStorageSimpleTreeModel::UpdateModelData() { if (!m_DataStorage.IsExpired()) { auto nodeset = m_DataStorage.Lock()->GetAll(); if (m_NodePredicate != nullptr) { nodeset = m_DataStorage.Lock()->GetSubset(m_NodePredicate); } for (const auto& node : *nodeset) { this->AddNodeInternal(node); } } } diff --git a/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.cpp b/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.cpp index 0f1760a299..a8ef227f47 100644 --- a/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.cpp +++ b/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.cpp @@ -1,86 +1,89 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkFileReaderOptionsDialog.h" #include "ui_QmitkFileReaderOptionsDialog.h" #include "QmitkFileReaderWriterOptionsWidget.h" #include "mitkIFileReader.h" QmitkFileReaderOptionsDialog::QmitkFileReaderOptionsDialog(mitk::IOUtil::LoadInfo &loadInfo, QWidget *parent) : QDialog(parent, Qt::WindowStaysOnTopHint), ui(new Ui::QmitkFileReaderOptionsDialog), m_LoadInfo(loadInfo) { ui->setupUi(this); m_ReaderItems = loadInfo.m_ReaderSelector.Get(); - bool hasOptions = false; int selectedIndex = 0; long selectedReaderId = loadInfo.m_ReaderSelector.GetSelectedId(); int i = 0; for (std::vector::const_reverse_iterator iter = m_ReaderItems.rbegin(), iterEnd = m_ReaderItems.rend(); iter != iterEnd; ++iter) { ui->m_ReaderComboBox->addItem(QString::fromStdString(iter->GetDescription())); mitk::IFileReader::Options options = iter->GetReader()->GetOptions(); if (!options.empty()) { - hasOptions = true; } ui->m_StackedOptionsWidget->addWidget(new QmitkFileReaderWriterOptionsWidget(options)); if (iter->GetServiceId() == selectedReaderId) { selectedIndex = i; } } - ui->m_ReaderComboBox->setCurrentIndex(selectedIndex); - if (!hasOptions) - { - ui->m_OptionsBox->setVisible(false); - } + connect(ui->m_ReaderComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SetCurrentReader(int))); + ui->m_ReaderComboBox->setCurrentIndex(selectedIndex); if (m_ReaderItems.size() < 2) { ui->m_ReaderLabel->setVisible(false); ui->m_ReaderComboBox->setVisible(false); ui->m_FilePathLabel->setText(QString("File: %1").arg(QString::fromStdString(loadInfo.m_Path))); } else { ui->m_FilePathLabel->setText(QString("for %1").arg(QString::fromStdString(loadInfo.m_Path))); } + ui->m_OptionsBox->setVisible(!qobject_cast(ui->m_StackedOptionsWidget->currentWidget())->GetOptions().empty()); + this->setWindowTitle("File reading options"); } QmitkFileReaderOptionsDialog::~QmitkFileReaderOptionsDialog() { delete ui; } +void QmitkFileReaderOptionsDialog::SetCurrentReader(int index) +{ + ui->m_StackedOptionsWidget->setCurrentIndex(index); + ui->m_OptionsBox->setVisible(!qobject_cast(ui->m_StackedOptionsWidget->currentWidget())->GetOptions().empty()); +} + bool QmitkFileReaderOptionsDialog::ReuseOptions() const { return ui->m_ReuseOptionsCheckBox->isChecked(); } void QmitkFileReaderOptionsDialog::accept() { const int index = m_ReaderItems.size() - ui->m_ReaderComboBox->currentIndex() - 1; m_ReaderItems[index].GetReader()->SetOptions( qobject_cast(ui->m_StackedOptionsWidget->currentWidget())->GetOptions()); m_LoadInfo.m_ReaderSelector.Select(m_ReaderItems[index]); QDialog::accept(); } diff --git a/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.ui.autosave b/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.ui.autosave new file mode 100644 index 0000000000..ef10c99113 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkFileReaderOptionsDialog.ui.autosave @@ -0,0 +1,115 @@ + + + QmitkFileReaderOptionsDialog + + + + 0 + 0 + 272 + 186 + + + + Dialog + + + false + + + true + + + + QLayout::SetFixedSize + + + + + Choose file reader + + + + + + + + + + + + + true + + + + + + + Options + + + false + + + + + + + + + + + + Apply to the next files with same type + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QmitkFileReaderOptionsDialog + accept() + + + 254 + 179 + + + 157 + 274 + + + + + buttonBox + rejected() + QmitkFileReaderOptionsDialog + reject() + + + 265 + 179 + + + 286 + 274 + + + + + diff --git a/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp b/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp index 7c6a44a554..65cae86e50 100644 --- a/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp @@ -1,273 +1,271 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkContourModelSetToImageFilter.h" #include #include #include #include #include #include #include mitk::ContourModelSetToImageFilter::ContourModelSetToImageFilter() : m_MakeOutputBinary(true), m_TimeStep(0), m_ReferenceImage(nullptr) { // Create the output. itk::DataObject::Pointer output = this->MakeOutput(0); Superclass::SetNumberOfRequiredInputs(1); Superclass::SetNumberOfRequiredOutputs(1); Superclass::SetNthOutput(0, output); } mitk::ContourModelSetToImageFilter::~ContourModelSetToImageFilter() { } void mitk::ContourModelSetToImageFilter::GenerateInputRequestedRegion() { mitk::Image *output = this->GetOutput(); if ((output->IsInitialized() == false)) return; GenerateTimeInInputRegion(output, const_cast(m_ReferenceImage)); } void mitk::ContourModelSetToImageFilter::GenerateOutputInformation() { mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if ((m_ReferenceImage == nullptr) || (m_ReferenceImage->IsInitialized() == false) || (m_ReferenceImage->GetTimeGeometry() == nullptr)) return; if (m_MakeOutputBinary) { - output->Initialize(mitk::MakeScalarPixelType(), *m_ReferenceImage->GetTimeGeometry(), 1, 1); + output->Initialize(mitk::MakeScalarPixelType(), *m_ReferenceImage->GetTimeGeometry(), 1); } else { output->Initialize(m_ReferenceImage->GetPixelType(), *m_ReferenceImage->GetTimeGeometry()); } output->SetPropertyList(m_ReferenceImage->GetPropertyList()->Clone()); } itk::DataObject::Pointer mitk::ContourModelSetToImageFilter::MakeOutput(DataObjectPointerArraySizeType /*idx*/) { return OutputType::New().GetPointer(); } itk::DataObject::Pointer mitk::ContourModelSetToImageFilter::MakeOutput(const DataObjectIdentifierType &name) { itkDebugMacro("MakeOutput(" << name << ")"); if (this->IsIndexedOutputName(name)) { return this->MakeOutput(this->MakeIndexFromOutputName(name)); } return OutputType::New().GetPointer(); } const mitk::ContourModelSet *mitk::ContourModelSetToImageFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) { return nullptr; } return static_cast(this->ProcessObject::GetInput(0)); } void mitk::ContourModelSetToImageFilter::SetInput(const ContourModelSet *input) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(0, const_cast(input)); } void mitk::ContourModelSetToImageFilter::SetImage(const mitk::Image *refImage) { m_ReferenceImage = refImage; } const mitk::Image *mitk::ContourModelSetToImageFilter::GetImage(void) { return m_ReferenceImage; } void mitk::ContourModelSetToImageFilter::GenerateData() { auto *contourSet = const_cast(this->GetInput()); // Initializing progressbar unsigned int num_contours = contourSet->GetContourModelList()->size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(num_contours); // Assure that the volume data of the output is set (fill volume with zeros) this->InitializeOutputEmpty(); mitk::Image::Pointer outputImage = const_cast(this->GetOutput()); if (outputImage.IsNull() || outputImage->IsInitialized() == false || !outputImage->IsVolumeSet(m_TimeStep)) { - MITK_ERROR << "Error creating output for specified image!"; - return; + mitkThrow() << "Error creating output for specified image!"; } if (!contourSet || contourSet->GetContourModelList()->size() == 0) { - MITK_ERROR << "No contours specified!"; - return; + mitkThrow() << "No contours specified!"; } mitk::BaseGeometry *outputImageGeo = outputImage->GetGeometry(m_TimeStep); // Create mitkVtkImageOverwrite which is needed to write the slice back into the volume vtkSmartPointer reslice = vtkSmartPointer::New(); // Create ExtractSliceFilter for extracting the corresponding slices from the volume mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(outputImage); extractor->SetTimeStep(m_TimeStep); extractor->SetResliceTransformByGeometry(outputImageGeo); // Fill each contour of the contourmodelset into the image auto it = contourSet->Begin(); auto end = contourSet->End(); while (it != end) { mitk::ContourModel *contour = it->GetPointer(); // 1. Create slice geometry using the contour points mitk::PlaneGeometry::Pointer plane = mitk::PlaneGeometry::New(); mitk::Point3D point3D, tempPoint; mitk::Vector3D normal; mitk::Image::Pointer slice; int sliceIndex; bool isFrontside = true; bool isRotated = false; // Determine plane orientation point3D = contour->GetVertexAt(0)->Coordinates; tempPoint = contour->GetVertexAt(contour->GetNumberOfVertices() * 0.25)->Coordinates; mitk::Vector3D vec = point3D - tempPoint; vec.Normalize(); outputImageGeo->WorldToIndex(point3D, point3D); mitk::PlaneGeometry::PlaneOrientation orientation; if (mitk::Equal(vec[0], 0)) { orientation = mitk::PlaneGeometry::Sagittal; sliceIndex = point3D[0]; } else if (mitk::Equal(vec[1], 0)) { orientation = mitk::PlaneGeometry::Frontal; sliceIndex = point3D[1]; } else if (mitk::Equal(vec[2], 0)) { orientation = mitk::PlaneGeometry::Axial; sliceIndex = point3D[2]; } else { // TODO Maybe rotate geometry to extract slice? MITK_ERROR << "Cannot detect correct slice number! Only axial, sagittal and frontal oriented contours are supported!"; return; } // Initialize plane using the detected orientation plane->InitializeStandardPlane(outputImageGeo, orientation, sliceIndex, isFrontside, isRotated); point3D = plane->GetOrigin(); normal = plane->GetNormal(); normal.Normalize(); point3D += normal * 0.5; // pixelspacing is 1, so half the spacing is 0.5 plane->SetOrigin(point3D); // 2. Extract slice at the given position extractor->SetWorldGeometry(plane); extractor->SetVtkOutputRequest(false); reslice->SetOverwriteMode(false); extractor->Modified(); extractor->Update(); slice = extractor->GetOutput(); slice->DisconnectPipeline(); // 3. Fill contour into slice mitk::ContourModel::Pointer projectedContour = mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contour, true, false); mitk::ContourModelUtils::FillContourInSlice(projectedContour, slice, outputImage); // 4. Write slice back into image volume reslice->SetInputSlice(slice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); extractor->Modified(); extractor->Update(); reslice->SetInputSlice(nullptr); // Progress mitk::ProgressBar::GetInstance()->Progress(); ++it; } outputImage->Modified(); outputImage->GetVtkImageData()->Modified(); } void mitk::ContourModelSetToImageFilter::InitializeOutputEmpty() { // Initialize the output's volume with zeros mitk::Image *output = this->GetOutput(); unsigned int byteSize = output->GetPixelType().GetSize(); if (output->GetDimension() < 4) { for (unsigned int dim = 0; dim < output->GetDimension(); ++dim) { byteSize *= output->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(output, output->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= output->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < output->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(output, output->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index fd762377a1..c360e1db47 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,84 +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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } -mitk::Image::Pointer mitk::AutoSegmentationTool::Get3DImage(mitk::Image::Pointer image, unsigned int timestep) +mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImage(const mitk::Image* image, unsigned int timestep) const { + if (nullptr == image) + return image; + if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } +mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImageByTimePoint(const mitk::Image* image, TimePointType timePoint) const +{ + if (nullptr == image) + return image; + + if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) + return nullptr; + + return this->Get3DImage(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); +} + void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { m_OverwriteExistingSegmentation = overwrite; } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() { mitk::DataNode::Pointer emptySegmentation; if (m_OverwriteExistingSegmentation) { emptySegmentation = m_ToolManager->GetWorkingData(0); } else { mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); if (refNode.IsNull()) { // TODO create and use segmentation exceptions instead!! MITK_ERROR << "No valid reference data!"; return nullptr; } std::string nodename = m_ToolManager->GetReferenceData(0)->GetName() + "_" + this->GetName(); mitk::Color color; color.SetRed(1); color.SetBlue(0); color.SetGreen(0); emptySegmentation = CreateEmptySegmentationNode(dynamic_cast(refNode->GetData()), nodename, color); m_ToolManager->GetDataStorage()->Add(emptySegmentation, refNode); } return emptySegmentation; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index 334a684818..1a2d458950 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,64 +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 mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); void SetOverwriteExistingSegmentation(bool overwrite); /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Depending on the selected mode either returns the currently selected segmentation * or creates a new one from the selected reference data and adds the new segmentation * to the datastorage * @return a mitk::DataNode which contains a segmentation image */ virtual mitk::DataNode *GetTargetSegmentationNode(); protected: AutoSegmentationTool(); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; - virtual itk::SmartPointer Get3DImage(itk::SmartPointer image, unsigned int timestep); + virtual Image::ConstPointer Get3DImage(const Image* image, unsigned int timestep) const; + virtual Image::ConstPointer Get3DImageByTimePoint(const Image* image, TimePointType timePoint) const; bool m_OverwriteExistingSegmentation; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index b38f9af9a0..917451a48c 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,307 +1,277 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkImageAccessByItk.h" #include "mitkLabelSetImage.h" #include "mitkOtsuSegmentationFilter.h" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include #include #include #include #include #include // ITK #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } mitk::OtsuTool3D::OtsuTool3D() { } mitk::OtsuTool3D::~OtsuTool3D() { } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); if (m_ToolManager) { m_OriginalImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); m_BinaryPreviewNode = mitk::DataNode::New(); m_BinaryPreviewNode->SetName("Binary_Preview"); m_BinaryPreviewNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_BinaryPreviewNode->SetProperty("opacity", FloatProperty::New(0.3)); // m_BinaryPreviewNode->SetBoolProperty("helper object", true); // m_BinaryPreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_ToolManager->GetDataStorage()->Add(this->m_BinaryPreviewNode); m_MultiLabelResultNode = mitk::DataNode::New(); m_MultiLabelResultNode->SetName("Otsu_Preview"); // m_MultiLabelResultNode->SetBoolProperty("helper object", true); m_MultiLabelResultNode->SetVisibility(true); m_MaskedImagePreviewNode = mitk::DataNode::New(); m_MaskedImagePreviewNode->SetName("Volume_Preview"); // m_MultiLabelResultNode->SetBoolProperty("helper object", true); m_MaskedImagePreviewNode->SetVisibility(false); m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); } } void mitk::OtsuTool3D::Deactivated() { m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); m_MultiLabelResultNode = nullptr; m_ToolManager->GetDataStorage()->Remove(this->m_BinaryPreviewNode); m_BinaryPreviewNode = nullptr; m_ToolManager->GetDataStorage()->Remove(this->m_MaskedImagePreviewNode); m_MaskedImagePreviewNode = nullptr; Superclass::Deactivated(); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } void mitk::OtsuTool3D::RunSegmentation(int regions, bool useValley, int numberOfBins) { - // this->m_OtsuSegmentationDialog->setCursor(Qt::WaitCursor); - int numberOfThresholds = regions - 1; - unsigned int timestep = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->GetPos(); - mitk::Image::Pointer image3D = Get3DImage(m_OriginalImage, timestep); + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto image3D = Get3DImageByTimePoint(m_OriginalImage, timePoint); + + if (nullptr == image3D) + { + MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; + return; + } mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(useValley); otsuFilter->SetNumberOfBins(numberOfBins); otsuFilter->SetInput(image3D); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); m_MultiLabelResultNode = nullptr; m_MultiLabelResultNode = mitk::DataNode::New(); m_MultiLabelResultNode->SetName("Otsu_Preview"); m_MultiLabelResultNode->SetVisibility(true); m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); m_MultiLabelResultNode->SetOpacity(1.0); mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); resultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); this->m_MultiLabelResultNode->SetData(resultImage); m_MultiLabelResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); m_MultiLabelResultNode->SetProperty("Image Rendering.Mode", renderingMode); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetHueRange(1.0, 0.0); lookupTable->SetSaturationRange(1.0, 1.0); lookupTable->SetValueRange(1.0, 1.0); lookupTable->SetTableRange(-1.0, 1.0); lookupTable->Build(); lut->SetVtkLookupTable(lookupTable); prop->SetLookupTable(lut); m_MultiLabelResultNode->SetProperty("LookupTable", prop); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); mitk::LevelWindow levelwindow; levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); levWinProp->SetLevelWindow(levelwindow); m_MultiLabelResultNode->SetProperty("levelwindow", levWinProp); // m_BinaryPreviewNode->SetVisibility(false); // m_MultiLabelResultNode->SetVisibility(true); // this->m_OtsuSegmentationDialog->setCursor(Qt::ArrowCursor); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::OtsuTool3D::ConfirmSegmentation() { mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); resultImage->InitializeByLabeledImage(dynamic_cast(m_BinaryPreviewNode->GetData())); GetTargetSegmentationNode()->SetData(resultImage); m_ToolManager->ActivateTool(-1); } void mitk::OtsuTool3D::UpdateBinaryPreview(std::vector regionIDs) { m_MultiLabelResultNode->SetVisibility(false); mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); AccessByItk_1(multiLabelSegmentation, CalculatePreview, regionIDs); } template void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, std::vector regionIDs) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; typename OutputImageType::Pointer itkBinaryTempImage1; typename OutputImageType::Pointer itkBinaryTempImage2; typename OutputImageType::Pointer itkBinaryResultImage; // mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); // mitk::CastToItkImage(multiLabelSegmentation, itkImage); filter->SetInput(itkImage); filter->SetLowerThreshold(regionIDs[0]); filter->SetUpperThreshold(regionIDs[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); itkBinaryTempImage2 = filter->GetOutput(); typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); // if more than one region id is used compute the union of all given binary regions for (auto it = regionIDs.begin(); it != regionIDs.end(); ++it) { filter->SetLowerThreshold(*it); filter->SetUpperThreshold(*it); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); itkBinaryTempImage1 = filter->GetOutput(); orFilter->SetInput1(itkBinaryTempImage1); orFilter->SetInput2(itkBinaryTempImage2); orFilter->UpdateLargestPossibleRegion(); itkBinaryResultImage = orFilter->GetOutput(); itkBinaryTempImage2 = itkBinaryResultImage; } //---------------------------------------------------------------------------------------------------- mitk::Image::Pointer binarySegmentation; mitk::CastToMitkImage(itkBinaryResultImage, binarySegmentation); m_BinaryPreviewNode->SetData(binarySegmentation); m_BinaryPreviewNode->SetVisibility(true); m_BinaryPreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(false)); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } -// void mitk::OtsuTool3D::UpdateBinaryPreview(int regionID) -//{ -// m_MultiLabelResultNode->SetVisibility(false); -// //pixel with regionID -> binary image -// const unsigned short dim = 3; -// typedef unsigned char PixelType; -// -// typedef itk::Image< PixelType, dim > InputImageType; -// typedef itk::Image< PixelType, dim > OutputImageType; -// -// typedef itk::BinaryThresholdImageFilter< InputImageType, OutputImageType > FilterType; -// -// FilterType::Pointer filter = FilterType::New(); -// -// InputImageType::Pointer itkImage; -// -// mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); -// mitk::CastToItkImage(multiLabelSegmentation, itkImage); -// -// filter->SetInput(itkImage); -// filter->SetLowerThreshold(regionID); -// filter->SetUpperThreshold(regionID); -// filter->SetInsideValue(1); -// filter->SetOutsideValue(0); -// filter->Update(); -// mitk::Image::Pointer binarySegmentation; -// mitk::CastToMitkImage( filter->GetOutput(), binarySegmentation); -// m_BinaryPreviewNode->SetData(binarySegmentation); -// m_BinaryPreviewNode->SetVisibility(true); -// m_BinaryPreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(false)); -// -// mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -//} - const char *mitk::OtsuTool3D::GetName() const { return "Otsu"; } void mitk::OtsuTool3D::UpdateVolumePreview(bool volumeRendering) { if (volumeRendering) { m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", true); m_MaskedImagePreviewNode->SetBoolProperty("volumerendering.uselod", true); } else { m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", false); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::OtsuTool3D::ShowMultiLabelResultNode(bool show) { m_MultiLabelResultNode->SetVisibility(show); m_BinaryPreviewNode->SetVisibility(!show); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } int mitk::OtsuTool3D::GetNumberOfBins() { ScalarType min = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMin(); ScalarType max = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp index db48041dee..15fd3ff488 100644 --- a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp +++ b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp @@ -1,182 +1,188 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkWatershedTool.h" #include "mitkIOUtil.h" #include "mitkITKImageImport.h" #include "mitkImage.h" #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLevelWindowManager.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkRenderingModeProperty.h" #include "mitkToolCommand.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, WatershedTool, "Watershed tool"); } mitk::WatershedTool::WatershedTool() : m_Threshold(0.0), m_Level(0.0) { } mitk::WatershedTool::~WatershedTool() { } void mitk::WatershedTool::Activated() { Superclass::Activated(); } void mitk::WatershedTool::Deactivated() { Superclass::Deactivated(); } us::ModuleResource mitk::WatershedTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); return resource; } const char **mitk::WatershedTool::GetXPM() const { return nullptr; } const char *mitk::WatershedTool::GetName() const { return "Watershed"; } void mitk::WatershedTool::DoIt() { // get image from tool manager mitk::DataNode::Pointer referenceData = m_ToolManager->GetReferenceData(0); - mitk::Image::Pointer input = dynamic_cast(referenceData->GetData()); + mitk::Image::ConstPointer input = dynamic_cast(referenceData->GetData()); if (input.IsNull()) return; - unsigned int timestep = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->GetPos(); - input = Get3DImage(input, timestep); + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + input = Get3DImageByTimePoint(input, timePoint); + + if (nullptr == input) + { + MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; + return; + } mitk::Image::Pointer output; try { // create and run itk filter pipeline AccessByItk_1(input.GetPointer(), ITKWatershed, output); mitk::LabelSetImage::Pointer labelSetOutput = mitk::LabelSetImage::New(); labelSetOutput->InitializeByLabeledImage(output); // create a new datanode for output mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(labelSetOutput); // set name of data node std::string name = referenceData->GetName() + "_Watershed"; dataNode->SetName(name); // look, if there is already a node with this name mitk::DataStorage::SetOfObjects::ConstPointer children = m_ToolManager->GetDataStorage()->GetDerivations(referenceData); mitk::DataStorage::SetOfObjects::ConstIterator currentNode = children->Begin(); mitk::DataNode::Pointer removeNode; while (currentNode != children->End()) { if (dataNode->GetName().compare(currentNode->Value()->GetName()) == 0) { removeNode = currentNode->Value(); } currentNode++; } // remove node with same name if (removeNode.IsNotNull()) m_ToolManager->GetDataStorage()->Remove(removeNode); // add output to the data storage m_ToolManager->GetDataStorage()->Add(dataNode, referenceData); } catch (itk::ExceptionObject &e) { MITK_ERROR << "Watershed Filter Error: " << e.GetDescription(); } RenderingManager::GetInstance()->RequestUpdateAll(); } template -void mitk::WatershedTool::ITKWatershed(itk::Image *originalImage, +void mitk::WatershedTool::ITKWatershed(const itk::Image *originalImage, mitk::Image::Pointer &segmentation) { typedef itk::WatershedImageFilter> WatershedFilter; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter, itk::Image> MagnitudeFilter; // at first add a gradient magnitude filter typename MagnitudeFilter::Pointer magnitude = MagnitudeFilter::New(); magnitude->SetInput(originalImage); magnitude->SetSigma(1.0); // use the progress bar mitk::ToolCommand::Pointer command = mitk::ToolCommand::New(); command->AddStepsToDo(60); // then add the watershed filter to the pipeline typename WatershedFilter::Pointer watershed = WatershedFilter::New(); watershed->SetInput(magnitude->GetOutput()); watershed->SetThreshold(m_Threshold); watershed->SetLevel(m_Level); watershed->AddObserver(itk::ProgressEvent(), command); watershed->Update(); // then make sure, that the output has the desired pixel type typedef itk::CastImageFilter> CastFilter; typename CastFilter::Pointer cast = CastFilter::New(); cast->SetInput(watershed->GetOutput()); // start the whole pipeline cast->Update(); // reset the progress bar by setting progress command->SetProgress(10); // since we obtain a new image from our pipeline, we have to make sure, that our mitk::Image::Pointer // is responsible for the memory management of the output image segmentation = mitk::GrabItkImageMemory(cast->GetOutput()); } diff --git a/Modules/Segmentation/Interactions/mitkWatershedTool.h b/Modules/Segmentation/Interactions/mitkWatershedTool.h index 21e8a3e905..f50734141b 100644 --- a/Modules/Segmentation/Interactions/mitkWatershedTool.h +++ b/Modules/Segmentation/Interactions/mitkWatershedTool.h @@ -1,88 +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 mitkWatershedTool_h_Included #define mitkWatershedTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include #include namespace us { class ModuleResource; } namespace mitk { class Image; /** \brief Simple watershed segmentation tool. \ingroup Interaction \ingroup ToolManagerEtAl Wraps ITK Watershed Filter into tool concept of MITK. For more information look into ITK documentation. \warning Only to be instantiated by mitk::ToolManager. $Darth Vader$ */ class MITKSEGMENTATION_EXPORT WatershedTool : public AutoSegmentationTool { public: mitkClassMacro(WatershedTool, AutoSegmentationTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void SetThreshold(double t) { m_Threshold = t; } void SetLevel(double l) { m_Level = l; } /** \brief Grabs the tool reference data and creates an ITK pipeline consisting of a GradientMagnitude * image filter followed by a Watershed image filter. The output of the filter pipeline is then added * to the data storage. */ void DoIt(); /** \brief Creates and runs an ITK filter pipeline consisting of the filters: GradientMagnitude-, Watershed- and * CastImageFilter. * * \param originalImage The input image, which is delivered by the AccessByItk macro. * \param segmentation A pointer to the output image, which will point to the pipeline output after execution. */ template - void ITKWatershed(itk::Image *originalImage, itk::SmartPointer &segmentation); + void ITKWatershed(const itk::Image *originalImage, itk::SmartPointer &segmentation); const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; protected: WatershedTool(); // purposely hidden ~WatershedTool() override; void Activated() override; void Deactivated() override; /** \brief Threshold parameter of the ITK Watershed Image Filter. See ITK Documentation for more information. */ double m_Threshold; /** \brief Threshold parameter of the ITK Watershed Image Filter. See ITK Documentation for more information. */ double m_Level; }; } // namespace #endif diff --git a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp index 880d00ac5e..6bf2ba52ed 100644 --- a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp +++ b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp @@ -1,185 +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 "mitkBooleanOperation.h" #include #include #include #include #include #include typedef itk::Image ImageType; -static mitk::Image::Pointer Get3DSegmentation(mitk::Image::Pointer segmentation, unsigned int time) +static mitk::Image::Pointer Get3DSegmentation(mitk::Image::Pointer segmentation, mitk::TimePointType time) { if (segmentation->GetDimension() != 4) return segmentation; auto imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(segmentation); - imageTimeSelector->SetTimeNr(static_cast(time)); + imageTimeSelector->SetTimeNr(segmentation->GetTimeGeometry()->TimePointToTimeStep(time)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } -static ImageType::Pointer CastTo3DItkImage(mitk::Image::Pointer segmentation, unsigned int time) +static ImageType::Pointer CastTo3DItkImage(mitk::Image::Pointer segmentation, mitk::TimePointType time) { ImageType::Pointer result; mitk::CastToItkImage(Get3DSegmentation(segmentation, time), result); return result; } mitk::BooleanOperation::BooleanOperation(Type type, mitk::Image::Pointer segmentationA, mitk::Image::Pointer segmentationB, - unsigned int time) - : m_Type(type), m_SegmentationA(segmentationA), m_SegmentationB(segmentationB), m_Time(time) + TimePointType time) + : m_Type(type), m_SegmentationA(segmentationA), m_SegmentationB(segmentationB), m_TimePoint(time) { this->ValidateSegmentations(); } mitk::BooleanOperation::~BooleanOperation() { } mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetResult() const { switch (m_Type) { case Difference: return this->GetDifference(); case Intersection: return this->GetIntersection(); case Union: return this->GetUnion(); default: mitkThrow() << "Unknown boolean operation type '" << m_Type << "'!"; } } mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetDifference() const { - auto input1 = CastTo3DItkImage(m_SegmentationA, m_Time); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_Time); + auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); + auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); auto notFilter = itk::NotImageFilter::New(); notFilter->SetInput(input2); auto andFilter = itk::AndImageFilter::New(); andFilter->SetInput1(input1); andFilter->SetInput2(notFilter->GetOutput()); andFilter->UpdateLargestPossibleRegion(); auto tempResult = Image::New(); CastToMitkImage(andFilter->GetOutput(), tempResult); tempResult->DisconnectPipeline(); auto result = mitk::LabelSetImage::New(); result->InitializeByLabeledImage(tempResult); return result; } mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetIntersection() const { - auto input1 = CastTo3DItkImage(m_SegmentationA, m_Time); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_Time); + auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); + auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); auto andFilter = itk::AndImageFilter::New(); andFilter->SetInput1(input1); andFilter->SetInput2(input2); andFilter->UpdateLargestPossibleRegion(); auto tempResult = Image::New(); CastToMitkImage(andFilter->GetOutput(), tempResult); tempResult->DisconnectPipeline(); auto result = mitk::LabelSetImage::New(); result->InitializeByLabeledImage(tempResult); return result; } mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetUnion() const { - auto input1 = CastTo3DItkImage(m_SegmentationA, m_Time); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_Time); + auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); + auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); auto orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(input1); orFilter->SetInput2(input2); orFilter->UpdateLargestPossibleRegion(); auto tempResult = Image::New(); CastToMitkImage(orFilter->GetOutput(), tempResult); tempResult->DisconnectPipeline(); auto result = mitk::LabelSetImage::New(); result->InitializeByLabeledImage(tempResult); return result; } void mitk::BooleanOperation::ValidateSegmentation(mitk::Image::Pointer segmentation) const { if (segmentation.IsNull()) mitkThrow() << "Segmentation is nullptr!"; if (segmentation->GetImageDescriptor()->GetNumberOfChannels() != 1) mitkThrow() << "Segmentation has more than one channel!"; auto pixelType = segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType(); if (pixelType.GetPixelType() != itk::ImageIOBase::SCALAR || (pixelType.GetComponentType() != itk::ImageIOBase::UCHAR && pixelType.GetComponentType() != itk::ImageIOBase::USHORT)) mitkThrow() << "Segmentation is neither of type 'unsigned char' nor type 'unsigned short'!"; auto dimension = segmentation->GetDimension(); if (dimension > 4) mitkThrow() << "Segmentation has more than four dimensions!"; - if (m_Time != 0) - { - if (dimension < 4) - mitkThrow() << "Expected four-dimensional segmentation!"; - - if (segmentation->GetDimension(3) < m_Time) - mitkThrow() << "Extent of fourth dimension of segmentation is less than specified time!"; - } - else if (dimension < 3) - { + if (dimension < 3) mitkThrow() << "Segmentation has less than three dimensions!"; - } + + if (!segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) + mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << m_TimePoint; } void mitk::BooleanOperation::ValidateSegmentations() const { this->ValidateSegmentation(m_SegmentationA); this->ValidateSegmentation(m_SegmentationB); if (m_SegmentationA->GetDimension() != m_SegmentationB->GetDimension()) mitkThrow() << "Segmentations have different dimensions!"; + + const auto geometryA = m_SegmentationA->GetTimeGeometry()->GetGeometryForTimePoint(m_TimePoint); + const auto geometryB = m_SegmentationB->GetTimeGeometry()->GetGeometryForTimePoint(m_TimePoint); + if (!mitk::Equal(*(geometryA.GetPointer()), *(geometryB.GetPointer()),eps,false)) + { + mitkThrow() << "Segmentations have different geometries and cannot be used for boolean operations!"; + } } diff --git a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h index 5fc6858c52..8822957cd2 100644 --- a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h +++ b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h @@ -1,73 +1,73 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 mitkBooleanOperation_h #define mitkBooleanOperation_h #include #include namespace mitk { /** \brief Executes a boolean operation on two different segmentations. * * All parameters of the boolean operations must be specified during construction. * The actual operation is executed when calling GetResult(). */ class MITKSEGMENTATION_EXPORT BooleanOperation { public: enum Type { None, Difference, Intersection, Union }; /* \brief Construct a boolean operation. * * Throws an mitk::Exception when segmentations are somehow invalid. * * \param[in] type The type of the boolean operation. * \param[in] segmentationA The first operand of the boolean operation. * \param[in] segmentationB The second operand of the boolean operation. - * \param[in] The time step at which the operation will be executed. + * \param[in] The time point at which the operation will be executed. */ - BooleanOperation(Type type, Image::Pointer segmentationA, Image::Pointer segmentationB, unsigned int time = 0); + BooleanOperation(Type type, Image::Pointer segmentationA, Image::Pointer segmentationB, TimePointType time = 0.); ~BooleanOperation(); /* \brief Execute boolean operation and return resulting segmentation. * * \return The resulting segmentation. */ LabelSetImage::Pointer GetResult() const; private: BooleanOperation(const BooleanOperation &); BooleanOperation &operator=(const BooleanOperation &); LabelSetImage::Pointer GetDifference() const; LabelSetImage::Pointer GetIntersection() const; LabelSetImage::Pointer GetUnion() const; void ValidateSegmentation(Image::Pointer segmentation) const; void ValidateSegmentations() const; Type m_Type; Image::Pointer m_SegmentationA; Image::Pointer m_SegmentationB; - unsigned int m_Time; + TimePointType m_TimePoint; }; } #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp index bcb95a6598..81080382e7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp @@ -1,683 +1,710 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSliceBasedInterpolatorWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QmitkStdMultiWidget.h" #include #include #include #include #include QmitkSliceBasedInterpolatorWidget::QmitkSliceBasedInterpolatorWidget(QWidget *parent, const char * /*name*/) : QWidget(parent), m_SliceInterpolatorController(mitk::SliceBasedInterpolationController::New()), m_ToolManager(nullptr), m_Activated(false), m_DataStorage(nullptr), m_LastSNC(nullptr), m_LastSliceIndex(0) { m_Controls.setupUi(this); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); connect(m_Controls.m_btStart, SIGNAL(toggled(bool)), this, SLOT(OnToggleWidgetActivation(bool))); connect(m_Controls.m_btApplyForCurrentSlice, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_Controls.m_btApplyForAllSlices, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged); m_InterpolationInfoChangedObserverTag = m_SliceInterpolatorController->AddObserver(itk::ModifiedEvent(), command); // feedback node and its visualization properties m_PreviewNode = mitk::DataNode::New(); m_PreviewNode->SetName("3D tool preview"); m_PreviewNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_PreviewNode->SetProperty("layer", mitk::IntProperty::New(100)); m_PreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary shadow", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PreviewNode->SetOpacity(1.0); m_PreviewNode->SetColor(0.0, 1.0, 0.0); m_Controls.m_btApplyForCurrentSlice->setEnabled(false); m_Controls.m_btApplyForAllSlices->setEnabled(false); this->setEnabled(false); } QmitkSliceBasedInterpolatorWidget::~QmitkSliceBasedInterpolatorWidget() { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSliceDimensionMap.clear(); // remove observer m_SliceInterpolatorController->RemoveObserver(m_InterpolationInfoChangedObserverTag); } const QmitkSliceBasedInterpolatorWidget::ActionToSliceDimensionMapType QmitkSliceBasedInterpolatorWidget::CreateActionToSliceDimension() { ActionToSliceDimensionMapType actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { std::string name = slicer->GetRenderer()->GetName(); if (name == "stdmulti.widget0") name = "Axial (red window)"; else if (name == "stdmulti.widget1") name = "Sagittal (green window)"; else if (name == "stdmulti.widget2") name = "Coronal (blue window)"; actionToSliceDimension[new QAction(QString::fromStdString(name), nullptr)] = slicer; } return actionToSliceDimension; } void QmitkSliceBasedInterpolatorWidget::SetDataStorage(mitk::DataStorage &storage) { m_DataStorage = &storage; } void QmitkSliceBasedInterpolatorWidget::SetSliceNavigationControllers( const QList &controllers) { Q_ASSERT(!controllers.empty()); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; - m_TimeStep.insert(slicer, slicer->GetTime()->GetPos()); + m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } m_ActionToSliceDimensionMap = this->CreateActionToSliceDimension(); } void QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified() { mitk::DataNode *workingNode = this->m_ToolManager->GetWorkingData(0); if (!workingNode) { this->setEnabled(false); return; } mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); // TODO adapt tool manager so that this check is done there, e.g. convenience function // Q_ASSERT(workingImage); if (!workingImage) { this->setEnabled(false); return; } if (workingImage->GetDimension() > 4 || workingImage->GetDimension() < 3) { this->setEnabled(false); return; } m_WorkingImage = workingImage; this->setEnabled(true); } void QmitkSliceBasedInterpolatorWidget::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); - m_TimeStep[slicer] /* = event.GetPos()*/; + m_TimePoints[slicer] = slicer->GetSelectedTimePoint(); // TODO Macht das hier wirklich Sinn???? if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSliceBasedInterpolatorWidget::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (m_Activated && m_WorkingImage.IsNotNull()) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (slicer) { this->TranslateAndInterpolateChangedSlice(e, slicer); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // slicer->GetRenderer()->RequestUpdate(); } } } void QmitkSliceBasedInterpolatorWidget::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (m_Activated && m_WorkingImage.IsNotNull()) { const mitk::SliceNavigationController::GeometrySliceEvent &geometrySliceEvent = dynamic_cast(e); mitk::TimeGeometry *timeGeometry = geometrySliceEvent.GetTimeGeometry(); - if (timeGeometry && m_TimeStep.contains(slicer)) + if (timeGeometry && m_TimePoints.contains(slicer) && timeGeometry->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = - dynamic_cast(timeGeometry->GetGeometryForTimeStep(m_TimeStep[slicer]).GetPointer()); + dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { mitk::PlaneGeometry *plane = slicedGeometry->GetPlaneGeometry(geometrySliceEvent.GetPos()); if (plane) { m_LastSNC = slicer; - this->Interpolate(plane, m_TimeStep[slicer], slicer); + this->Interpolate(plane, m_TimePoints[slicer], slicer); } } } } } void QmitkSliceBasedInterpolatorWidget::Interpolate(mitk::PlaneGeometry *plane, - unsigned int timeStep, + mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { int clickedSliceDimension(-1); int clickedSliceIndex(-1); + if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; + return; + } + const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); + // calculate real slice position, i.e. slice of the image and not slice of the TimeSlicedGeometry // see if timestep is needed here mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_PreviewNode->SetData(interpolation); const mitk::Color &color = m_WorkingImage->GetActiveLabel()->GetColor(); m_PreviewNode->SetColor(color); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } mitk::Image::Pointer QmitkSliceBasedInterpolatorWidget::GetWorkingSlice(const mitk::PlaneGeometry *planeGeometry) { - unsigned int timeStep = m_LastSNC->GetTime()->GetPos(); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + + if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot get slice of WorkingImage. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; + return nullptr; + } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_WorkingImage); + const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return nullptr; } mitk::Image::Pointer slice = extractor->GetOutput(); // specify the undo operation with the non edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(slice->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_undoOperation = new mitk::DiffSliceOperation( m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); slice->DisconnectPipeline(); return slice; } void QmitkSliceBasedInterpolatorWidget::OnToggleWidgetActivation(bool enabled) { Q_ASSERT(m_ToolManager); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) return; m_Controls.m_btApplyForCurrentSlice->setEnabled(enabled); m_Controls.m_btApplyForAllSlices->setEnabled(enabled); if (enabled) m_Controls.m_btStart->setText("Stop"); else m_Controls.m_btStart->setText("Start"); unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { // mitk::SegTool2D* tool = dynamic_cast(m_ToolManager->GetToolById(i)); // MLI TODO // if (tool) tool->SetEnable2DInterpolation( enabled ); } if (enabled) { if (!m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Add(m_PreviewNode); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); this->UpdateVisibleSuggestion(); } else { if (m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Remove(m_PreviewNode); } mitk::UndoController::GetCurrentUndoModel()->Clear(); } m_Activated = enabled; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } template void QmitkSliceBasedInterpolatorWidget::WritePreviewOnWorkingImage(itk::Image *targetSlice, const mitk::Image *sourceSlice, int overwritevalue) { typedef itk::Image ImageType; typename ImageType::Pointer sourceSliceITK; mitk::CastToItkImage(sourceSlice, sourceSliceITK); // now the original slice and the ipSegmentation-painted slice are in the same format, and we can just copy all pixels // that are non-zero typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator InputIteratorType; InputIteratorType inputIterator(sourceSliceITK, sourceSliceITK->GetLargestPossibleRegion()); OutputIteratorType outputIterator(targetSlice, targetSlice->GetLargestPossibleRegion()); outputIterator.GoToBegin(); inputIterator.GoToBegin(); int activePixelValue = m_WorkingImage->GetActiveLabel()->GetValue(); if (activePixelValue == 0) // if exterior is the active label { while (!outputIterator.IsAtEnd()) { if (inputIterator.Get() != 0) { outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else if (overwritevalue != 0) // if we are not erasing { while (!outputIterator.IsAtEnd()) { int targetValue = static_cast(outputIterator.Get()); if (inputIterator.Get() != 0) { if (!m_WorkingImage->GetLabel(targetValue)->GetLocked()) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else // if we are erasing { while (!outputIterator.IsAtEnd()) { const int targetValue = outputIterator.Get(); if (inputIterator.Get() != 0) { if (targetValue == activePixelValue) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } } void QmitkSliceBasedInterpolatorWidget::OnAcceptInterpolationClicked() { if (m_WorkingImage.IsNotNull() && m_PreviewNode->GetData()) { const mitk::PlaneGeometry *planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); if (!planeGeometry) return; mitk::Image::Pointer sliceImage = this->GetWorkingSlice(planeGeometry); if (sliceImage.IsNull()) return; mitk::Image::Pointer previewSlice = dynamic_cast(m_PreviewNode->GetData()); AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, previewSlice, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); - unsigned int timeStep = m_LastSNC->GetTime()->GetPos(); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; + return; + } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); + const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } // the image was modified within the pipeline, but not marked so m_WorkingImage->Modified(); int clickedSliceDimension(-1); int clickedSliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice( m_WorkingImage, planeGeometry, clickedSliceDimension, clickedSliceIndex); m_SliceInterpolatorController->SetChangedSlice(sliceImage, clickedSliceDimension, clickedSliceIndex, timeStep); // specify the undo operation with the edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(sliceImage->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_doOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); // create an operation event for the undo stack mitk::OperationEvent *undoStackItem = new mitk::OperationEvent( mitk::DiffSliceOperationApplier::GetInstance(), m_doOperation, m_undoOperation, "Slice Interpolation"); // add it to the undo controller mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); // clear the pointers as the operation are stored in the undo controller and also deleted from there m_undoOperation = nullptr; m_doOperation = nullptr; m_PreviewNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSliceBasedInterpolatorWidget::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { // Since we need to shift the plane it must be clone so that the original plane isn't altered mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); - unsigned int timeStep = slicer->GetTime()->GetPos(); + const auto timePoint = slicer->GetSelectedTimePoint(); + if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept all interpolations. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; + + return; + } + const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); int sliceDimension(-1); int sliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, reslicePlane, sliceDimension, sliceIndex); unsigned int zslices = m_WorkingImage->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); mitk::Point3D origin = reslicePlane->GetOrigin(); for (unsigned int idx = 0; idx < zslices; ++idx) { // Transforming the current origin of the reslice plane // so that it matches the one of the next slice m_WorkingImage->GetSlicedGeometry()->WorldToIndex(origin, origin); origin[sliceDimension] = idx; m_WorkingImage->GetSlicedGeometry()->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(sliceDimension, idx, reslicePlane, timeStep); if (interpolation.IsNotNull()) { m_PreviewNode->SetData(interpolation); mitk::Image::Pointer sliceImage = this->GetWorkingSlice(reslicePlane); if (sliceImage.IsNull()) return; AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, interpolation, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(reslicePlane); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } m_WorkingImage->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_2DWINDOWS); } mitk::ProgressBar::GetInstance()->Progress(); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = m_ActionToSliceDimensionMap.begin(); it != m_ActionToSliceDimensionMap.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllPopupActivated(QAction *action) { ActionToSliceDimensionMapType::const_iterator iter = m_ActionToSliceDimensionMap.find(action); if (iter != m_ActionToSliceDimensionMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } void QmitkSliceBasedInterpolatorWidget::UpdateVisibleSuggestion() { if (m_Activated && m_LastSNC) { // determine which one is the current view, try to do an initial interpolation mitk::BaseRenderer *renderer = m_LastSNC->GetRenderer(); if (renderer && renderer->GetMapperID() == mitk::BaseRenderer::Standard2D) { const mitk::TimeGeometry *timeGeometry = dynamic_cast(renderer->GetWorldTimeGeometry()); if (timeGeometry) { mitk::SliceNavigationController::GeometrySliceEvent event(const_cast(timeGeometry), renderer->GetSlice()); this->TranslateAndInterpolateChangedSlice(event, m_LastSNC); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } } void QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSliceBasedInterpolatorWidget::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkSliceBasedInterpolatorWidget::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkSliceBasedInterpolatorWidget::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.h b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.h index af03e3829e..c23db6bcf3 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.h @@ -1,195 +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 QmitkSliceBasedInterpolatorWidget_h_Included #define QmitkSliceBasedInterpolatorWidget_h_Included #include "MitkSegmentationUIExports.h" #include "mitkDataStorage.h" #include "mitkSliceBasedInterpolationController.h" #include #include #include "ui_QmitkSliceBasedInterpolatorWidgetGUIControls.h" namespace mitk { class PlaneGeometry; class SliceNavigationController; class LabelSetImage; class ToolManager; class DiffSliceOperation; } /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSliceBasedInterpolatorWidget is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ class MITKSEGMENTATIONUI_EXPORT QmitkSliceBasedInterpolatorWidget : public QWidget { Q_OBJECT public: QmitkSliceBasedInterpolatorWidget(QWidget *parent = nullptr, const char *name = nullptr); ~QmitkSliceBasedInterpolatorWidget() override; void SetDataStorage(mitk::DataStorage &storage); /** Sets the slice navigation controllers for getting slice changed events from the views. */ void SetSliceNavigationControllers(const QList &controllers); void OnToolManagerWorkingDataModified(); void OnTimeChanged(itk::Object *sender, const itk::EventObject &); void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSliceInterpolationInfoChanged(const itk::EventObject &); Ui::QmitkSliceBasedInterpolatorWidgetGUIControls m_Controls; signals: void signalSliceBasedInterpolationEnabled(bool); public slots: /** \brief Reaction to "Start/Stop" button click */ void OnToggleWidgetActivation(bool); protected slots: /** \brief Reaction to "Accept Current Slice" button click. */ void OnAcceptInterpolationClicked(); /* \brief Reaction to "Accept All Slices" button click. Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* \brief Called from popup menu of OnAcceptAllInterpolationsClicked() Will trigger interpolation for all slices in given orientation */ void OnAcceptAllPopupActivated(QAction *action); protected: typedef std::map ActionToSliceDimensionMapType; const ActionToSliceDimensionMapType CreateActionToSliceDimension(); ActionToSliceDimensionMapType m_ActionToSliceDimensionMap; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); /** Gets the working slice based on the given plane geometry and last saved interaction \param planeGeometry a plane geometry */ mitk::Image::Pointer GetWorkingSlice(const mitk::PlaneGeometry *planeGeometry); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param slice the SliceNavigationController */ void TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ - void Interpolate(mitk::PlaneGeometry *plane, unsigned int timeStep, mitk::SliceNavigationController *slicer); + void Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); private: mitk::SliceBasedInterpolationController::Pointer m_SliceInterpolatorController; mitk::ToolManager *m_ToolManager; bool m_Activated; template void WritePreviewOnWorkingImage(itk::Image *target, const mitk::Image *source, int overwritevalue); QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; unsigned int m_InterpolationInfoChangedObserverTag; mitk::DiffSliceOperation *m_doOperation; mitk::DiffSliceOperation *m_undoOperation; mitk::DataNode::Pointer m_PreviewNode; mitk::Image::Pointer m_PreviewImage; mitk::LabelSetImage::Pointer m_WorkingImage; - QHash m_TimeStep; + QHash m_TimePoints; mitk::DataStorage::Pointer m_DataStorage; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index ab7aed39e0..22865bf8b4 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1352 +1,1385 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSlicesInterpolator.h" #include "QmitkSelectableGLWidget.h" #include "QmitkStdMultiWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map QmitkSlicesInterpolator::createActionToSliceDimension() { std::map actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; } return actionToSliceDimension; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), // ACTION_TO_SLICEDIMENSION( createActionToSliceDimension() ), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnSuggestPlane); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnSuggestPlane, SIGNAL(clicked()), this, SLOT(OnSuggestPlaneClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList &controllers) { Q_ASSERT(!controllers.empty()); if (m_Initialized) { // remove old observers Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; - m_TimeStep.insert(slicer, slicer->GetTime()->GetPos()); + m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } ACTION_TO_SLICEDIMENSION = createActionToSliceDimension(); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } ACTION_TO_SLICEDIMENSION.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); m_BtnSuggestPlane->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); return; } // Updating the current selected segmentation for the 3D interpolation SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } this->CheckSupportedImageDimension(); } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); - m_TimeStep[slicer] = slicer->GetTime()->GetPos(); + const auto timePoint = slicer->GetSelectedTimePoint(); + m_TimePoints[slicer] = timePoint; - m_SurfaceInterpolator->SetCurrentTimeStep(slicer->GetTime()->GetPos()); + m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); - if (tsg && m_TimeStep.contains(slicer)) + if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = - dynamic_cast(tsg->GetGeometryForTimeStep(m_TimeStep[slicer]).GetPointer()); + dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) - Interpolate(plane, m_TimeStep[slicer], slicer); + Interpolate(plane, m_TimePoints[slicer], slicer); return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane, - unsigned int timeStep, + mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); if (m_Segmentation) { + if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; + return; + } + const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + int clickedSliceDimension(-1); int clickedSliceIndex(-1); // calculate real slice position, i.e. slice of the image and not slice of the TimeSlicedGeometry mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (interpolatedSurface.IsNotNull() && workingNode && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) { m_BtnApply3D->setEnabled(true); m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } if (!m_DataStorage->Exists(m_3DContourNode)) { m_DataStorage->Add(m_3DContourNode, workingNode); } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); foreach (mitk::SliceNavigationController *slicer, m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { if (m_Segmentation && m_FeedbackNode->GetData()) { // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast(m_FeedbackNode->GetData()); reslice->SetInputSlice(slice->GetSliceData()->GetVtkImageAccessor(slice)->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; + return; + } + mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_Segmentation); - unsigned int timestep = m_LastSNC->GetTime()->GetPos(); - extractor->SetTimeStep(timestep); + const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(m_LastSNC->GetCurrentPlaneGeometry()); extractor->SetVtkOutputRequest(true); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timestep)); + extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so m_Segmentation->Modified(); m_Segmentation->GetVtkImageData()->Modified(); m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer image3D = m_Segmentation; - unsigned int timeStep(slicer->GetTime()->GetPos()); + unsigned int timeStep(0); + const auto timePoint = slicer->GetSelectedTimePoint(); if (m_Segmentation->GetDimension() == 4) { + if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; + return; + } + + timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image3D = timeSelector->GetOutput(); } // create a empty diff image for the undo operation mitk::Image::Pointer diffImage = mitk::Image::New(); diffImage->Initialize(image3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed // after the image is initialized. Otherwise later image access will lead to an error { mitk::ImageWriteAccessor imAccess(diffImage); // Set all pixels to zero mitk::PixelType pixelType(mitk::MakeScalarPixelType()); // For legacy purpose support former pixel type of segmentations (before multilabel) if (m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR) { pixelType = mitk::MakeScalarPixelType(); } memset(imAccess.GetData(), 0, (pixelType.GetBpe() >> 3) * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension(-1); int sliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, reslicePlane, sliceDimension, sliceIndex); unsigned int zslices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); mitk::Point3D origin = reslicePlane->GetOrigin(); unsigned int totalChangedSlices(0); for (unsigned int sliceIndex = 0; sliceIndex < zslices; ++sliceIndex) { // Transforming the current origin of the reslice plane // so that it matches the one of the next slice m_Segmentation->GetSlicedGeometry()->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; m_Segmentation->GetSlicedGeometry()->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); // Set the slice as 'input' mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(sliceDimension, sliceIndex, reslicePlane, timeStep); if (interpolation.IsNotNull()) // we don't check if interpolation is necessary/sensible - but m_Interpolator does { // Setting up the reslicing pipeline which allows us to write the interpolation results back into // the image volume vtkSmartPointer reslice = vtkSmartPointer::New(); // set overwrite mode to true to write back to the image volume reslice->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer diffslicewriter = mitk::ExtractSliceFilter::New(reslice); diffslicewriter->SetInput(diffImage); diffslicewriter->SetTimeStep(0); diffslicewriter->SetWorldGeometry(reslicePlane); diffslicewriter->SetVtkOutputRequest(true); diffslicewriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffslicewriter->Modified(); diffslicewriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); if (totalChangedSlices > 0) { // store undo stack items if (true) { // create do/undo operations mitk::ApplyDiffImageOperation *doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); mitk::ApplyDiffImageOperation *undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); std::stringstream comment; comment << "Confirm all interpolations (" << totalChangedSlices << ")"; mitk::OperationEvent *undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment.str()); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); // acutally apply the changes here to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } } m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = ACTION_TO_SLICEDIMENSION.begin(); it != ACTION_TO_SLICEDIMENSION.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { if (m_InterpolatedSurfaceNode.IsNotNull() && m_InterpolatedSurfaceNode->GetData()) { mitk::DataNode *segmentationNode = m_ToolManager->GetWorkingData(0); mitk::Image *currSeg = dynamic_cast(segmentationNode->GetData()); mitk::SurfaceToImageFilter::Pointer s2iFilter = mitk::SurfaceToImageFilter::New(); s2iFilter->MakeOutputBinaryOn(); if (currSeg->GetPixelType().GetComponentType() == itk::ImageIOBase::USHORT) s2iFilter->SetUShortBinaryPixelType(true); s2iFilter->SetInput(dynamic_cast(m_InterpolatedSurfaceNode->GetData())); // check if ToolManager holds valid ReferenceData if (m_ToolManager->GetReferenceData(0) == nullptr || m_ToolManager->GetWorkingData(0) == nullptr) { return; } s2iFilter->SetImage(dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData())); s2iFilter->Update(); mitk::Image::Pointer newSeg = s2iFilter->GetOutput(); - unsigned int timestep = m_LastSNC->GetTime()->GetPos(); - mitk::ImageReadAccessor readAccess(newSeg, newSeg->GetVolumeData(timestep)); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + if (!m_ToolManager->GetReferenceData(0)->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint) || + !m_ToolManager->GetWorkingData(0)->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of reference or working image. Time point: " << timePoint; + return; + } + + const auto newTimeStep = newSeg->GetTimeGeometry()->TimePointToTimeStep(timePoint); + mitk::ImageReadAccessor readAccess(newSeg, newSeg->GetVolumeData(newTimeStep)); const void *cPointer = readAccess.GetData(); if (currSeg && cPointer) { - currSeg->SetVolume(cPointer, timestep, 0); + const auto curTimeStep = currSeg->GetTimeGeometry()->TimePointToTimeStep(timePoint); + currSeg->SetVolume(cPointer, curTimeStep, 0); } else { return; } m_CmbInterpolation->setCurrentIndex(0); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); mitk::DataNode::Pointer segSurface = mitk::DataNode::New(); float rgb[3]; segmentationNode->GetColor(rgb); segSurface->SetColor(rgb); segSurface->SetData(m_InterpolatedSurfaceNode->GetData()); std::stringstream stream; stream << segmentationNode->GetName(); stream << "_"; stream << "3D-interpolation"; segSurface->SetName(stream.str()); segSurface->SetProperty("opacity", mitk::FloatProperty::New(0.7)); segSurface->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(true)); segSurface->SetProperty("3DInterpolationResult", mitk::BoolProperty::New(true)); segSurface->SetVisibility(false); m_DataStorage->Add(segSurface, segmentationNode); this->Show3DInterpolationResult(false); } } void ::QmitkSlicesInterpolator::OnSuggestPlaneClicked() { if (m_PlaneWatcher.isRunning()) m_PlaneWatcher.waitForFinished(); m_PlaneFuture = QtConcurrent::run(this, &QmitkSlicesInterpolator::RunPlaneSuggestion); m_PlaneWatcher.setFuture(m_PlaneFuture); } void ::QmitkSlicesInterpolator::RunPlaneSuggestion() { if (m_FirstRun) mitk::ProgressBar::GetInstance()->AddStepsToDo(7); else mitk::ProgressBar::GetInstance()->AddStepsToDo(3); m_EdgeDetector->SetSegmentationMask(m_Segmentation); m_EdgeDetector->SetInput(dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData())); m_EdgeDetector->Update(); mitk::UnstructuredGrid::Pointer uGrid = mitk::UnstructuredGrid::New(); uGrid->SetVtkUnstructuredGrid(m_EdgeDetector->GetOutput()->GetVtkUnstructuredGrid()); mitk::ProgressBar::GetInstance()->Progress(); mitk::Surface::Pointer surface = dynamic_cast(m_InterpolatedSurfaceNode->GetData()); vtkSmartPointer vtkpoly = surface->GetVtkPolyData(); vtkSmartPointer vtkpoints = vtkpoly->GetPoints(); vtkSmartPointer vGrid = vtkSmartPointer::New(); vtkSmartPointer verts = vtkSmartPointer::New(); verts->GetPointIds()->SetNumberOfIds(vtkpoints->GetNumberOfPoints()); for (int i = 0; i < vtkpoints->GetNumberOfPoints(); i++) { verts->GetPointIds()->SetId(i, i); } vGrid->Allocate(1); vGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); vGrid->SetPoints(vtkpoints); mitk::UnstructuredGrid::Pointer interpolationGrid = mitk::UnstructuredGrid::New(); interpolationGrid->SetVtkUnstructuredGrid(vGrid); m_PointScorer->SetInput(0, uGrid); m_PointScorer->SetInput(1, interpolationGrid); m_PointScorer->Update(); mitk::UnstructuredGrid::Pointer scoredGrid = mitk::UnstructuredGrid::New(); scoredGrid = m_PointScorer->GetOutput(); mitk::ProgressBar::GetInstance()->Progress(); double spacing = mitk::SurfaceInterpolationController::GetInstance()->GetDistanceImageSpacing(); mitk::UnstructuredGridClusteringFilter::Pointer clusterFilter = mitk::UnstructuredGridClusteringFilter::New(); clusterFilter->SetInput(scoredGrid); clusterFilter->SetMeshing(false); clusterFilter->SetMinPts(4); clusterFilter->Seteps(spacing); clusterFilter->Update(); mitk::ProgressBar::GetInstance()->Progress(); // Create plane suggestion mitk::BaseRenderer::Pointer br = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); mitk::PlaneProposer planeProposer; std::vector grids = clusterFilter->GetAllClusters(); planeProposer.SetUnstructuredGrids(grids); mitk::SliceNavigationController::Pointer snc = br->GetSliceNavigationController(); planeProposer.SetSliceNavigationController(snc); planeProposer.SetUseDistances(true); try { planeProposer.CreatePlaneInfo(); } catch (const mitk::Exception &e) { MITK_ERROR << e.what(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_FirstRun = false; } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("3DContourContainer", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { m_BtnApply3D->setEnabled(true); m_3DContourNode = contourNodes->at(0); mitk::Surface::Pointer contours = dynamic_cast(m_3DContourNode->GetData()); if (contours) mitk::SurfaceInterpolationController::GetInstance()->ReinitializeInterpolation(contours); m_BtnReinit3DInterpolation->setEnabled(false); } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { std::map::const_iterator iter = ACTION_TO_SLICEDIMENSION.find(action); if (iter != ACTION_TO_SLICEDIMENSION.end()) { mitk::SliceNavigationController *slicer = iter->second; AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); // additional error message on std::cerr std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { mitk::Image *segmentation = dynamic_cast(workingNode->GetData()); if (segmentation) { m_Interpolator->SetSegmentationVolume(segmentation); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { m_Timer->stop(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); if (currentColor[2] == SURFACE_COLOR_RGB[2]) { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 1.0f)); } else { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); } m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; this->CheckSupportedImageDimension(); try { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { bool isInterpolationResult(false); workingNode->GetBoolProperty("3DInterpolationResult", isInterpolationResult); mitk::NodePredicateAnd::Pointer pred = mitk::NodePredicateAnd::New( mitk::NodePredicateProperty::New("3DInterpolationResult", mitk::BoolProperty::New(true)), mitk::NodePredicateDataType::New("Surface")); mitk::DataStorage::SetOfObjects::ConstPointer interpolationResults = m_DataStorage->GetDerivations(workingNode, pred); for (unsigned int i = 0; i < interpolationResults->Size(); ++i) { mitk::DataNode::Pointer currNode = interpolationResults->at(i); if (currNode.IsNotNull()) m_DataStorage->Remove(currNode); } if ((workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) && !isInterpolationResult && m_3DInterpolationEnabled) { int ret = QMessageBox::Yes; if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) { QMessageBox msgBox; msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); ret = msgBox.exec(); } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } else { m_CmbInterpolation->setCurrentIndex(0); } } else if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } } else { QWidget::setEnabled(false); m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { bool isInterpolationResult(false); workingNode->GetBoolProperty("3DInterpolationResult", isInterpolationResult); if (!isInterpolationResult) { QWidget::setEnabled(true); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; - if (m_LastSNC->GetTime() != nullptr) - time_position = m_LastSNC->GetTime()->GetPos(); + if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; + return; + } + time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); double minSpacing(100); double maxSpacing(0); for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image *segmentationImage = dynamic_cast(workingNode->GetData()); - /*if (segmentationImage->GetDimension() == 3) - {*/ + m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); - m_SurfaceInterpolator->SetCurrentTimeStep(time_position); - //} - /*else - MITK_INFO<<"3D Interpolation is only supported for 3D images at the moment!";*/ + m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) m_3DContourNode->SetVisibility( status, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); /*if (m_3DInterpolationEnabled && m_Segmentation && m_Segmentation->GetDimension() != 3) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); }*/ } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index ce6c55bae2..19d55cea0f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,291 +1,291 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkSlicesInterpolator_h_Included #define QmitkSlicesInterpolator_h_Included #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; } class QPushButton; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = nullptr, const char *name = nullptr); /** To be called once before real use. */ void Initialize(mitk::ToolManager *toolManager, const QList &controllers); void Uninitialize(); ~QmitkSlicesInterpolator() override; void SetDataStorage(mitk::DataStorage::Pointer storage); mitk::DataStorage *GetDataStorage(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); void OnTimeChanged(itk::Object *sender, const itk::EventObject &); void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); void OnReinit3DInterpolation(); void OnSuggestPlaneClicked(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); void RunPlaneSuggestion(); void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); protected: const std::map createActionToSliceDimension(); std::map ACTION_TO_SLICEDIMENSION; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param slice the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ - void Interpolate(mitk::PlaneGeometry *plane, unsigned int timeStep, mitk::SliceNavigationController *slicer); + void Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer); // void InterpolateSurface(); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; mitk::PointCloudScoringFilter::Pointer m_PointScorer; mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; QPushButton *m_BtnSuggestPlane; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; mitk::DataNode::Pointer m_3DContourNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; - QHash m_TimeStep; + QHash m_TimePoints; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; // unsigned int m_CurrentListID; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; bool m_FirstRun; }; #endif diff --git a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp index 882985542c..f270c48b3d 100644 --- a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp +++ b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp @@ -1,824 +1,824 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkImagePixelWriteAccessor.h" #include "mitkImageTimeSelector.h" class mitkSurfaceInterpolationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceInterpolationControllerTestSuite); MITK_TEST(TestSingleton); MITK_TEST(TestSetCurrentInterpolationSession); MITK_TEST(TestReplaceInterpolationSession); MITK_TEST(TestRemoveAllInterpolationSessions); MITK_TEST(TestRemoveInterpolationSession); MITK_TEST(TestOnSegmentationDeleted); MITK_TEST(TestSetCurrentInterpolationSession4D); MITK_TEST(TestReplaceInterpolationSession4D); MITK_TEST(TestRemoveAllInterpolationSessions4D); MITK_TEST(TestRemoveInterpolationSession4D); MITK_TEST(TestOnSegmentationDeleted4D); /// \todo Workaround for memory leak in TestAddNewContour. Bug 18096. vtkDebugLeaks::SetExitError(0); MITK_TEST(TestAddNewContour); MITK_TEST(TestRemoveContour); CPPUNIT_TEST_SUITE_END(); private: mitk::SurfaceInterpolationController::Pointer m_Controller; public: mitk::Image::Pointer createImage(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType(); newImage->Initialize(p_type, 3, dimensions); return newImage; } mitk::Image::Pointer createImage4D(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType(); newImage->Initialize(p_type, 4, dimensions); return newImage; } void setUp() override { m_Controller = mitk::SurfaceInterpolationController::GetInstance(); - m_Controller->SetCurrentTimeStep(0); + m_Controller->SetCurrentTimePoint(0.); vtkSmartPointer polygonSource = vtkSmartPointer::New(); polygonSource->SetRadius(100); polygonSource->SetNumberOfSides(7); polygonSource->Update(); mitk::Surface::Pointer surface = mitk::Surface::New(); surface->SetVtkPolyData(polygonSource->GetOutput()); } void TestSingleton() { mitk::SurfaceInterpolationController::Pointer controller2 = mitk::SurfaceInterpolationController::GetInstance(); CPPUNIT_ASSERT_MESSAGE("SurfaceInterpolationController pointers are not equal!", m_Controller.GetPointer() == controller2.GetPointer()); } void TestSetCurrentInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::Image::Pointer segmentation_2 = createImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); MITK_ASSERT_EQUAL( m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); MITK_ASSERT_EQUAL( m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); MITK_ASSERT_EQUAL( m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); MITK_ASSERT_EQUAL( m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().IsNull()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } void TestReplaceInterpolationSession() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); vtkPolyData *poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); // Add contours m_Controller->AddNewContour(surf_1); m_Controller->AddNewContour(surf_2); // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; contourInfo1.contourNormal = normal_1; contourInfo1.contourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; contourInfo2.contourNormal = normal_2; contourInfo2.contourPoint = center_2; mitk::Image::Pointer segmentation_2 = createImage(dimensions1); bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); unsigned int dimensions2[] = {10, 20, 10}; mitk::Image::Pointer segmentation_3 = createImage(dimensions2); success = m_Controller->ReplaceInterpolationSession(segmentation_2, segmentation_3); CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == false); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); } void TestRemoveAllInterpolationSessions() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::Image::Pointer segmentation_2 = createImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::Image::Pointer segmentation_2 = createImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation().IsNotNull()); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation().IsNull()); } void TestOnSegmentationDeleted() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestAddNewContour() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); vtkPolyData *poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); double center_3[3] = {4.0f, 4.0f, 3.0f}; double normal_3[3] = {0.0f, 0.0f, 1.0f}; vtkSmartPointer p_source_3 = vtkSmartPointer::New(); p_source_3->SetNumberOfSides(10); p_source_3->SetCenter(center_3); p_source_3->SetRadius(4); p_source_3->SetNormal(normal_3); p_source_3->Update(); vtkPolyData *poly_3 = p_source_3->GetOutput(); mitk::Surface::Pointer surf_3 = mitk::Surface::New(); surf_3->SetVtkPolyData(poly_3); // Add contours m_Controller->AddNewContour(surf_1); m_Controller->AddNewContour(surf_2); m_Controller->AddNewContour(surf_3); // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; contourInfo1.contourNormal = normal_1; contourInfo1.contourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; contourInfo2.contourNormal = normal_2; contourInfo2.contourPoint = center_2; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; contourInfo3.contourNormal = normal_3; contourInfo3.contourPoint = center_3; const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); // Create another segmentation image unsigned int dimensions2[] = {20, 20, 20}; mitk::Image::Pointer segmentation_2 = createImage(dimensions2); m_Controller->SetCurrentInterpolationSession(segmentation_2); // Create some contours double center_4[3] = {10.0f, 10.0f, 10.0f}; double normal_4[3] = {0.0f, 1.0f, 0.0f}; vtkSmartPointer p_source_4 = vtkSmartPointer::New(); p_source_4->SetNumberOfSides(8); p_source_4->SetCenter(center_4); p_source_4->SetRadius(5); p_source_4->SetNormal(normal_4); p_source_4->Update(); vtkPolyData *poly_4 = p_source_4->GetOutput(); mitk::Surface::Pointer surf_4 = mitk::Surface::New(); surf_4->SetVtkPolyData(poly_4); double center_5[3] = {3.0f, 10.0f, 10.0f}; double normal_5[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_5 = vtkSmartPointer::New(); p_source_5->SetNumberOfSides(16); p_source_5->SetCenter(center_5); p_source_5->SetRadius(8); p_source_5->SetNormal(normal_5); p_source_5->Update(); vtkPolyData *poly_5 = p_source_5->GetOutput(); mitk::Surface::Pointer surf_5 = mitk::Surface::New(); surf_5->SetVtkPolyData(poly_5); double center_6[3] = {10.0f, 10.0f, 3.0f}; double normal_6[3] = {0.0f, 0.0f, 1.0f}; vtkSmartPointer p_source_6 = vtkSmartPointer::New(); p_source_6->SetNumberOfSides(100); p_source_6->SetCenter(center_6); p_source_6->SetRadius(5); p_source_6->SetNormal(normal_6); p_source_6->Update(); vtkPolyData *poly_6 = p_source_6->GetOutput(); mitk::Surface::Pointer surf_6 = mitk::Surface::New(); surf_6->SetVtkPolyData(poly_6); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; contourInfo4.contourNormal = normal_4; contourInfo4.contourPoint = center_4; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo5; contourInfo5.contourNormal = normal_5; contourInfo5.contourPoint = center_5; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo6; contourInfo6.contourNormal = normal_6; contourInfo6.contourPoint = center_6; // Add contours m_Controller->AddNewContour(surf_4); m_Controller->AddNewContour(surf_5); m_Controller->AddNewContour(surf_6); // Check if all contours are there auto contour_4 = m_Controller->GetContour(contourInfo4); auto contour_5 = m_Controller->GetContour(contourInfo5); auto contour_6 = m_Controller->GetContour(contourInfo6); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_5->GetVtkPolyData()), *(contour_5->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_6->GetVtkPolyData()), *(contour_6->GetVtkPolyData()), 0.000001, true)); // Modify some contours vtkSmartPointer p_source_7 = vtkSmartPointer::New(); p_source_7->SetNumberOfSides(200); p_source_7->SetCenter(3.0, 10.0, 10.0); p_source_7->SetRadius(5); p_source_7->SetNormal(1, 0, 0); p_source_7->Update(); vtkPolyData *poly_7 = p_source_7->GetOutput(); mitk::Surface::Pointer surf_7 = mitk::Surface::New(); surf_7->SetVtkPolyData(poly_7); m_Controller->AddNewContour(surf_7); auto contour_7 = m_Controller->GetContour(contourInfo5); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_7->GetVtkPolyData()), *(contour_7->GetVtkPolyData()), 0.000001, true)); // Change session and test if all contours are available m_Controller->SetCurrentInterpolationSession(segmentation_1); auto contour_8 = m_Controller->GetContour(contourInfo1); auto contour_9 = m_Controller->GetContour(contourInfo2); auto contour_10 = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_8->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_9->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_10->GetVtkPolyData()), 0.000001, true)); } void TestRemoveContour() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; mitk::Image::Pointer segmentation_1 = createImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {4.0f, 4.0f, 4.0f}; double normal_1[3] = {0.0f, 1.0f, 0.0f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); vtkPolyData *poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); // Add contours m_Controller->AddNewContour(surf_1); m_Controller->AddNewContour(surf_2); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; contourInfo3.contour = surf_1->Clone(); contourInfo3.contourNormal = normal_1; contourInfo3.contourPoint = center_1; // Shift the new contour so that it is different contourInfo3.contourPoint += 0.5; bool success = m_Controller->RemoveContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was unintentionally removed!", (m_Controller->GetNumberOfContours() == 2) && !success); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; contourInfo2.contourNormal = normal_2; contourInfo2.contourPoint = center_2; contourInfo2.contour = surf_2; success = m_Controller->RemoveContour(contourInfo2); CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was not removed!", (m_Controller->GetNumberOfContours() == 1) && success); // Let's see if the other contour No. 1 is still there contourInfo3.contourPoint -= 0.5; const mitk::Surface *remainingContour = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE( "Remove failed - contour was accidentally removed!", (m_Controller->GetNumberOfContours() == 1) && mitk::Equal(*(surf_1->GetVtkPolyData()), *(remainingContour->GetVtkPolyData()), 0.000001, true) && success); } bool AssertImagesEqual4D(mitk::Image *img1, mitk::Image *img2) { mitk::ImageTimeSelector::Pointer selector1 = mitk::ImageTimeSelector::New(); selector1->SetInput(img1); selector1->SetChannelNr(0); mitk::ImageTimeSelector::Pointer selector2 = mitk::ImageTimeSelector::New(); selector2->SetInput(img2); selector2->SetChannelNr(0); int numTs1 = img1->GetTimeSteps(); int numTs2 = img2->GetTimeSteps(); if (numTs1 != numTs2) { return false; } /*mitk::ImagePixelWriteAccessor accessor( img1 ); itk::Index<4> ind; ind[0] = 5; ind[1] = 5; ind[2] = 5; ind[3] = 2; accessor.SetPixelByIndex( ind, 7 );*/ for (int ts = 0; ts < numTs1; ++ts) { selector1->SetTimeNr(ts); selector2->SetTimeNr(ts); selector1->Update(); selector2->Update(); mitk::Image::Pointer imgSel1 = selector1->GetOutput(); mitk::Image::Pointer imgSel2 = selector2->GetOutput(); MITK_ASSERT_EQUAL(imgSel1, imgSel2, "Segmentation images are not equal"); } return true; } void TestSetCurrentInterpolationSession4D() { /*unsigned int testDimensions[] = {10, 10, 10, 5}; mitk::Image::Pointer testSeg = createImage4D(testDimensions); mitk::Image::Pointer testSegClone = testSeg->Clone(); int testTs = testSeg->GetTimeSteps(); MITK_ASSERT_EQUAL(testSeg, testSegClone, "Segmentation images are not equal");*/ // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 5}; mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 4}; mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not // equal"); AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().IsNull()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } void TestReplaceInterpolationSession4D() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10, 5}; mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); vtkPolyData *poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); // Add contours m_Controller->AddNewContour(surf_1); m_Controller->AddNewContour(surf_2); // Add contours for another timestep - m_Controller->SetCurrentTimeStep(2); + m_Controller->SetCurrentTimePoint(2); double center_3[3] = {1.3f, 3.5f, 4.6f}; double normal_3[3] = {0.20f, 1.6f, 0.8f}; vtkSmartPointer p_source_3 = vtkSmartPointer::New(); p_source_3->SetNumberOfSides(20); p_source_3->SetCenter(center_3); p_source_3->SetRadius(4); p_source_3->SetNormal(normal_3); p_source_3->Update(); vtkPolyData *poly_3 = p_source_3->GetOutput(); mitk::Surface::Pointer surf_3 = mitk::Surface::New(); surf_3->SetVtkPolyData(poly_3); double center_4[3] = {1.32f, 3.53f, 4.8f}; double normal_4[3] = {0.22f, 1.5f, 0.85f}; vtkSmartPointer p_source_4 = vtkSmartPointer::New(); p_source_4->SetNumberOfSides(20); p_source_4->SetCenter(center_4); p_source_4->SetRadius(4); p_source_4->SetNormal(normal_4); p_source_4->Update(); vtkPolyData *poly_4 = p_source_4->GetOutput(); mitk::Surface::Pointer surf_4 = mitk::Surface::New(); surf_4->SetVtkPolyData(poly_4); m_Controller->AddNewContour(surf_3); m_Controller->AddNewContour(surf_4); - m_Controller->SetCurrentTimeStep(0); + m_Controller->SetCurrentTimePoint(0); // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; contourInfo1.contourNormal = normal_1; contourInfo1.contourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; contourInfo2.contourNormal = normal_2; contourInfo2.contourPoint = center_2; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; contourInfo3.contourNormal = normal_3; contourInfo3.contourPoint = center_3; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; contourInfo4.contourNormal = normal_4; contourInfo4.contourPoint = center_4; mitk::Image::Pointer segmentation_2 = createImage4D(dimensions1); bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); - m_Controller->SetCurrentTimeStep(2); + m_Controller->SetCurrentTimePoint(2); // CPPUNIT_ASSERT_MESSAGE("Contour accessed from outside of timestep!", m_Controller->GetNumberOfContours() == 0); contour_1 = m_Controller->GetContour(contourInfo1); contour_2 = m_Controller->GetContour(contourInfo2); CPPUNIT_ASSERT_MESSAGE("Contour accessed from outside of timestep!", contour_1 == nullptr && contour_2 == nullptr); const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); const mitk::Surface *contour_4 = m_Controller->GetContour(contourInfo4); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); unsigned int dimensions2[] = {10, 20, 10, 4}; mitk::Image::Pointer segmentation_3 = createImage4D(dimensions2); success = m_Controller->ReplaceInterpolationSession(segmentation_2, segmentation_3); CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == false); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); - m_Controller->SetCurrentTimeStep(1); + m_Controller->SetCurrentTimePoint(1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 0); - m_Controller->SetCurrentTimeStep(0); + m_Controller->SetCurrentTimePoint(0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); - m_Controller->SetCurrentTimeStep(4); + m_Controller->SetCurrentTimePoint(4); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 0); - m_Controller->SetCurrentTimeStep(2); + m_Controller->SetCurrentTimePoint(2); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); } void TestRemoveAllInterpolationSessions4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 4}; mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 5}; mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 3}; mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 6}; mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation().IsNotNull()); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation().IsNull()); } void TestOnSegmentationDeleted4D() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 7}; mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - m_Controller->SetCurrentTimeStep(3); + m_Controller->SetCurrentTimePoint(3); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkSurfaceInterpolationController) diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index ced9997dbf..c5de38bbee 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,695 +1,719 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSurfaceInterpolationController.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkMemoryUtilities.h" #include "mitkImageToSurfaceFilter.h" //#include "vtkXMLPolyDataWriter.h" #include "vtkPolyDataWriter.h" // Check whether the given contours are coplanar bool ContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) { // Here we check two things: // 1. Whether the normals of both contours are at least parallel // 2. Whether both contours lie in the same plane // Check for coplanarity: // a. Span a vector between two points one from each contour // b. Calculate dot product for the vector and one of the normals // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar double vec[3]; vec[0] = leftHandSide.contourPoint[0] - rightHandSide.contourPoint[0]; vec[1] = leftHandSide.contourPoint[1] - rightHandSide.contourPoint[1]; vec[2] = leftHandSide.contourPoint[2] - rightHandSide.contourPoint[2]; double n[3]; n[0] = rightHandSide.contourNormal[0]; n[1] = rightHandSide.contourNormal[1]; n[2] = rightHandSide.contourNormal[2]; double dot = vtkMath::Dot(n, vec); double n2[3]; n2[0] = leftHandSide.contourNormal[0]; n2[1] = leftHandSide.contourNormal[1]; n2[2] = leftHandSide.contourNormal[2]; // The normals of both contours have to be parallel but not of the same orientation double lengthLHS = leftHandSide.contourNormal.GetNorm(); double lengthRHS = rightHandSide.contourNormal.GetNorm(); double dot2 = vtkMath::Dot(n, n2); bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) return true; else return false; } mitk::SurfaceInterpolationController::ContourPositionInformation CreateContourPositionInformation( mitk::Surface::Pointer contour) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contour = contour; double n[3]; double p[3]; contour->GetVtkPolyData()->GetPoints()->GetPoint(0, p); vtkPolygon::ComputeNormal(contour->GetVtkPolyData()->GetPoints(), n); contourInfo.contourNormal = n; contourInfo.contourPoint = p; return contourInfo; } mitk::SurfaceInterpolationController::SurfaceInterpolationController() - : m_SelectedSegmentation(nullptr), m_CurrentTimeStep(0) + : m_SelectedSegmentation(nullptr), m_CurrentTimePoint(0.) { m_DistanceImageSpacing = 0.0; m_ReduceFilter = ReduceContourSetFilter::New(); m_NormalsFilter = ComputeContourSetNormalsFilter::New(); m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); // m_TimeSelector = ImageTimeSelector::New(); m_ReduceFilter->SetUseProgressBar(false); // m_ReduceFilter->SetProgressStepSize(1); m_NormalsFilter->SetUseProgressBar(true); m_NormalsFilter->SetProgressStepSize(1); m_InterpolateSurfaceFilter->SetUseProgressBar(true); m_InterpolateSurfaceFilter->SetProgressStepSize(7); m_Contours = Surface::New(); m_PolyData = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); m_PolyData->SetPoints(points); m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); for (; dataIter != m_SegmentationObserverTags.end(); ++dataIter) { (*dataIter).first->RemoveObserver((*dataIter).second); } m_SegmentationObserverTags.clear(); } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } void mitk::SurfaceInterpolationController::AddNewContour(mitk::Surface::Pointer newContour) { if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { ContourPositionInformation contourInfo = CreateContourPositionInformation(newContour); this->AddToInterpolationPipeline(contourInfo); this->Modified(); } } void mitk::SurfaceInterpolationController::AddNewContours(std::vector newContours) { for (unsigned int i = 0; i < newContours.size(); ++i) { if (newContours.at(i)->GetVtkPolyData()->GetNumberOfPoints() > 0) { ContourPositionInformation contourInfo = CreateContourPositionInformation(newContours.at(i)); this->AddToInterpolationPipeline(contourInfo); } } this->Modified(); } void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation contourInfo) { if (!m_SelectedSegmentation) { return; } int pos(-1); - unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); - if (m_CurrentTimeStep >= numTimeSteps) + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { - MITK_ERROR << "Invalid time step requested for interpolation pipeline."; + MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); ContourPositionInformationVec2D currentContours = m_ListOfInterpolationSessions[m_SelectedSegmentation]; - ContourPositionInformationList currentContourList = currentContours[m_CurrentTimeStep]; + ContourPositionInformationList currentContourList = currentContours[currentTimeStep]; mitk::Surface *newContour = contourInfo.contour; for (unsigned int i = 0; i < currentContourList.size(); i++) { ContourPositionInformation contourFromList = currentContourList.at(i); if (ContoursCoplanar(contourInfo, contourFromList)) { pos = i; break; } } // Don't save a new empty contour if (pos == -1 && newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - m_ReduceFilter->SetInput(m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].size(), + m_ReduceFilter->SetInput(m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(), newContour); - m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].push_back(contourInfo); + m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].push_back(contourInfo); } else if (pos != -1 && newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].at(pos) = contourInfo; + m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].at(pos) = contourInfo; m_ReduceFilter->SetInput(pos, newContour); } else if (newContour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) { if (!m_SelectedSegmentation) { return false; } - unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); - if (m_CurrentTimeStep >= numTimeSteps) + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return false; } - auto it = m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].begin(); - while (it != m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].end()) + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + + auto it = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].begin(); + while (it != m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].end()) { ContourPositionInformation currentContour = (*it); if (ContoursCoplanar(currentContour, contourInfo)) { - m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].erase(it); + m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].erase(it); this->ReinitializeInterpolation(); return true; } ++it; } return false; } const mitk::Surface *mitk::SurfaceInterpolationController::GetContour(ContourPositionInformation contourInfo) { if (!m_SelectedSegmentation) { return nullptr; } - unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); - if (m_CurrentTimeStep >= numTimeSteps) + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return nullptr; } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - ContourPositionInformationList contourList = m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep]; + ContourPositionInformationList contourList = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep]; for (unsigned int i = 0; i < contourList.size(); ++i) { ContourPositionInformation currentContour = contourList.at(i); if (ContoursCoplanar(contourInfo, currentContour)) return currentContour.contour; } return nullptr; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfContours() { if (!m_SelectedSegmentation) { return -1; } - unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); - if (m_CurrentTimeStep >= numTimeSteps) + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return -1; } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - return m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].size(); + return m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); } void mitk::SurfaceInterpolationController::Interpolate() { + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + { + MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; + m_InterpolationResult = nullptr; + return; + } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + m_ReduceFilter->Update(); m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(m_CurrentTimeStep); + timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); for (unsigned int i = 0; i < m_CurrentNumberOfReducedContours; i++) { mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); m_NormalsFilter->SetInput(i, reducedContour); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } if (m_CurrentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result m_InterpolationResult = nullptr; return; } // Setting up progress bar mitk::ProgressBar::GetInstance()->AddStepsToDo(10); // create a surface from the distance-image mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(20); imageToSurfaceFilter->Update(); mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); - interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), m_CurrentTimeStep); + interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); m_InterpolationResult = interpolationResult; m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); vtkSmartPointer polyDataAppender = vtkSmartPointer::New(); - for (unsigned int i = 0; i < m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].size(); i++) + for (unsigned int i = 0; i < m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); i++) { polyDataAppender->AddInputData( - m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].at(i).contour->GetVtkPolyData()); + m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].at(i).contour->GetVtkPolyData()); } polyDataAppender->Update(); m_Contours->SetVtkPolyData(polyDataAppender->GetOutput()); // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } mitk::Surface *mitk::SurfaceInterpolationController::GetContoursAsSurface() { return m_Contours; } void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) { m_ReduceFilter->SetMinSpacing(minSpacing); } void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) { m_ReduceFilter->SetMaxSpacing(maxSpacing); m_NormalsFilter->SetMaxSpacing(maxSpacing); } void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); } mitk::Image::Pointer mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation; } mitk::Image *mitk::SurfaceInterpolationController::GetImage() { return m_InterpolateSurfaceFilter->GetOutput(); } double mitk::SurfaceInterpolationController::EstimatePortionOfNeededMemory() { double numberOfPointsAfterReduction = m_ReduceFilter->GetNumberOfPointsAfterReduction() * 3; double sizeOfPoints = pow(numberOfPointsAfterReduction, 2) * sizeof(double); double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); double percentage = sizeOfPoints / totalMem; return percentage; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return m_ListOfInterpolationSessions.size(); } template void mitk::SurfaceInterpolationController::GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation) { this->SetCurrentInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage) { if (currentSegmentationImage.GetPointer() == m_SelectedSegmentation) return; if (currentSegmentationImage.IsNull()) { m_SelectedSegmentation = nullptr; return; } m_SelectedSegmentation = currentSegmentationImage.GetPointer(); auto it = m_ListOfInterpolationSessions.find(currentSegmentationImage.GetPointer()); // If the session does not exist yet create a new ContourPositionPairList otherwise reinitialize the interpolation // pipeline if (it == m_ListOfInterpolationSessions.end()) { ContourPositionInformationVec2D newList; m_ListOfInterpolationSessions.insert( std::pair(m_SelectedSegmentation, newList)); m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); m_SegmentationObserverTags.insert(std::pair( m_SelectedSegmentation, m_SelectedSegmentation->AddObserver(itk::DeleteEvent(), command))); } this->ReinitializeInterpolation(); } bool mitk::SurfaceInterpolationController::ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession) { if (oldSession.IsNull() || newSession.IsNull()) return false; if (oldSession.GetPointer() == newSession.GetPointer()) return false; if (!mitk::Equal(*(oldSession->GetGeometry()), *(newSession->GetGeometry()), mitk::eps, false)) return false; auto it = m_ListOfInterpolationSessions.find(oldSession.GetPointer()); if (it == m_ListOfInterpolationSessions.end()) return false; + if (!newSession->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + { + MITK_WARN << "Interpolation session cannot be replaced. Currently selected timepoint is not in the time bounds of the new session. Time point: " << m_CurrentTimePoint; + return false; + } + ContourPositionInformationVec2D oldList = (*it).second; m_ListOfInterpolationSessions.insert( std::pair(newSession.GetPointer(), oldList)); itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); m_SegmentationObserverTags.insert( std::pair(newSession, newSession->AddObserver(itk::DeleteEvent(), command))); if (m_SelectedSegmentation == oldSession) m_SelectedSegmentation = newSession; + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(m_CurrentTimeStep); + timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); this->RemoveInterpolationSession(oldSession); return true; } void mitk::SurfaceInterpolationController::RemoveSegmentationFromContourList(mitk::Image *segmentation) { this->RemoveInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::Image::Pointer segmentationImage) { if (segmentationImage) { if (m_SelectedSegmentation == segmentationImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_ListOfInterpolationSessions.erase(segmentationImage); // Remove observer auto pos = m_SegmentationObserverTags.find(segmentationImage); if (pos != m_SegmentationObserverTags.end()) { segmentationImage->RemoveObserver((*pos).second); m_SegmentationObserverTags.erase(pos); } } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); while (dataIter != m_SegmentationObserverTags.end()) { mitk::Image *image = (*dataIter).first; image->RemoveObserver((*dataIter).second); ++dataIter; } m_SegmentationObserverTags.clear(); m_SelectedSegmentation = nullptr; m_ListOfInterpolationSessions.clear(); } void mitk::SurfaceInterpolationController::ReinitializeInterpolation(mitk::Surface::Pointer contours) { // 1. detect coplanar contours // 2. merge coplanar contours into a single surface // 4. add contour to pipeline // Split the surface into separate polygons vtkSmartPointer existingPolys; vtkSmartPointer existingPoints; existingPolys = contours->GetVtkPolyData()->GetPolys(); existingPoints = contours->GetVtkPolyData()->GetPoints(); existingPolys->InitTraversal(); vtkSmartPointer ids = vtkSmartPointer::New(); typedef std::pair PointNormalPair; std::vector list; std::vector> pointsList; int count(0); for (existingPolys->InitTraversal(); existingPolys->GetNextCell(ids);) { // Get the points vtkSmartPointer points = vtkSmartPointer::New(); existingPoints->GetPoints(ids, points); ++count; pointsList.push_back(points); PointNormalPair p_n; double n[3]; vtkPolygon::ComputeNormal(points, n); p_n.first = n; double p[3]; existingPoints->GetPoint(ids->GetId(0), p); p_n.second = p; ContourPositionInformation p_info; p_info.contourNormal = n; p_info.contourPoint = p; list.push_back(p_info); continue; } // Detect and sort coplanar polygons auto outer = list.begin(); std::vector>> relatedPoints; while (outer != list.end()) { auto inner = outer; ++inner; std::vector> rel; auto pointsIter = pointsList.begin(); rel.push_back((*pointsIter)); pointsIter = pointsList.erase(pointsIter); while (inner != list.end()) { if (ContoursCoplanar((*outer), (*inner))) { inner = list.erase(inner); rel.push_back((*pointsIter)); pointsIter = pointsList.erase(pointsIter); } else { ++inner; ++pointsIter; } } relatedPoints.push_back(rel); ++outer; } // Build the separate surfaces again std::vector finalSurfaces; for (unsigned int i = 0; i < relatedPoints.size(); ++i) { vtkSmartPointer contourSurface = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer polygons = vtkSmartPointer::New(); unsigned int pointId(0); for (unsigned int j = 0; j < relatedPoints.at(i).size(); ++j) { unsigned int numPoints = relatedPoints.at(i).at(j)->GetNumberOfPoints(); vtkSmartPointer polygon = vtkSmartPointer::New(); polygon->GetPointIds()->SetNumberOfIds(numPoints); polygon->GetPoints()->SetNumberOfPoints(numPoints); vtkSmartPointer currentPoints = relatedPoints.at(i).at(j); for (unsigned k = 0; k < numPoints; ++k) { points->InsertPoint(pointId, currentPoints->GetPoint(k)); polygon->GetPointIds()->SetId(k, pointId); ++pointId; } polygons->InsertNextCell(polygon); } contourSurface->SetPoints(points); contourSurface->SetPolys(polygons); contourSurface->BuildLinks(); mitk::Surface::Pointer surface = mitk::Surface::New(); surface->SetVtkPolyData(contourSurface); finalSurfaces.push_back(surface); } // Add detected contours to interpolation pipeline this->AddNewContours(finalSurfaces); } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto *tempImage = dynamic_cast(const_cast(caller)); if (tempImage) { if (m_SelectedSegmentation == tempImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_SegmentationObserverTags.erase(tempImage); m_ListOfInterpolationSessions.erase(tempImage); } } void mitk::SurfaceInterpolationController::ReinitializeInterpolation() { // If session has changed reset the pipeline m_ReduceFilter->Reset(); m_NormalsFilter->Reset(); m_InterpolateSurfaceFilter->Reset(); itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); if (m_SelectedSegmentation) { + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + { + MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; + return; + } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(m_CurrentTimeStep); + timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); unsigned int size = m_ListOfInterpolationSessions[m_SelectedSegmentation].size(); if (size != numTimeSteps) { m_ListOfInterpolationSessions[m_SelectedSegmentation].resize(numTimeSteps); } - if (m_CurrentTimeStep < numTimeSteps) + if (currentTimeStep < numTimeSteps) { - unsigned int numContours = m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep].size(); + unsigned int numContours = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); for (unsigned int c = 0; c < numContours; ++c) { m_ReduceFilter->SetInput(c, - m_ListOfInterpolationSessions[m_SelectedSegmentation][m_CurrentTimeStep][c].contour); + m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep][c].contour); } m_ReduceFilter->Update(); m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } for (unsigned int i = 0; i < m_CurrentNumberOfReducedContours; i++) { m_NormalsFilter->SetInput(i, m_ReduceFilter->GetOutput(i)); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } } Modified(); } } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 69ed695ea5..55d0bce56e 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,250 +1,250 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 mitkSurfaceInterpolationController_h_Included #define mitkSurfaceInterpolationController_h_Included #include "mitkColorProperty.h" #include "mitkCommon.h" #include "mitkInteractionConst.h" #include "mitkProperties.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkSurface.h" #include #include "mitkComputeContourSetNormalsFilter.h" #include "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkReduceContourSetFilter.h" #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "vtkAppendPolyData.h" #include "vtkCellArray.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkPolygon.h" #include "vtkSmartPointer.h" #include "mitkImageTimeSelector.h" #include "mitkVtkRepresentationProperty.h" #include "vtkImageData.h" #include "vtkMarchingCubes.h" #include "vtkProperty.h" #include "mitkProgressBar.h" namespace mitk { class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); itkGetMacro(DistanceImageSpacing, double); struct ContourPositionInformation { Surface::Pointer contour; Vector3D contourNormal; Point3D contourPoint; }; typedef std::vector ContourPositionInformationList; typedef std::vector ContourPositionInformationVec2D; - // typedef std::map ContourListMap; typedef std::map ContourListMap; static SurfaceInterpolationController *GetInstance(); - void SetCurrentTimeStep(unsigned int ts) + void SetCurrentTimePoint(TimePointType tp) { - if (m_CurrentTimeStep != ts) + if (m_CurrentTimePoint != tp) { - m_CurrentTimeStep = ts; + m_CurrentTimePoint = tp; if (m_SelectedSegmentation) { this->ReinitializeInterpolation(); } } }; - unsigned int GetCurrentTimeStep() { return m_CurrentTimeStep; }; + TimePointType GetCurrentTimePoint() const { return m_CurrentTimePoint; }; + /** * @brief Adds a new extracted contour to the list * @param newContour the contour to be added. If a contour at that position * already exists the related contour will be updated */ void AddNewContour(Surface::Pointer newContour); /** * @brief Removes the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be removed * @return true if a contour was found and removed, false if no contour was found */ bool RemoveContour(ContourPositionInformation contourInfo); /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively * @param newContours the list of the contours */ void AddNewContours(std::vector newContours); /** * @brief Returns the contour for a given plane for the current selected segmenation * @param ontourInfo the contour which should be returned * @return the contour as an mitk::Surface. If no contour is available at the give position nullptr is returned */ const mitk::Surface *GetContour(ContourPositionInformation contourInfo); /** * @brief Returns the number of available contours for the current selected segmentation * @return the number of contours */ unsigned int GetNumberOfContours(); /** * Interpolates the 3D surface from the given extracted contours */ void Interpolate(); mitk::Surface::Pointer GetInterpolationResult(); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface */ void SetMinSpacing(double minSpacing); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::Image::Pointer GetCurrentSegmentation(); Surface *GetContoursAsSurface(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param segmentation The current selected segmentation * \deprecatedSince{2014_03} */ DEPRECATED(void SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation)); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param segmentation The current selected segmentation */ void SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage); /** * Removes the segmentation and all its contours from the list * @param segmentation The segmentation to be removed * \deprecatedSince{2014_03} */ DEPRECATED(void RemoveSegmentationFromContourList(mitk::Image *segmentation)); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(mitk::Image::Pointer segmentationImage); /** * Replaces the current interpolation session with a new one. All contours form the old * session will be applied to the new session. This only works if the two images have the * geometry * @param oldSession the session which should be replaced * @param newSession the new session which replaces the old one * @return true it the the replacement was successful, false if not (e.g. the image's geometry differs) */ bool ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); /** * @brief Reinitializes the interpolation using the provided contour data * @param contours a mitk::Surface which contains the contours as polys in the vtkPolyData */ void ReinitializeInterpolation(mitk::Surface::Pointer contours); mitk::Image *GetImage(); /** * Estimates the memory which is needed to build up the equationsystem for the interpolation. * \returns The percentage of the real memory which will be used by the interpolation */ double EstimatePortionOfNeededMemory(); unsigned int GetNumberOfInterpolationSessions(); protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template void GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result); private: void ReinitializeInterpolation(); void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); void AddToInterpolationPipeline(ContourPositionInformation contourInfo); ReduceContourSetFilter::Pointer m_ReduceFilter; ComputeContourSetNormalsFilter::Pointer m_NormalsFilter; CreateDistanceImageFromSurfaceFilter::Pointer m_InterpolateSurfaceFilter; Surface::Pointer m_Contours; double m_DistanceImageSpacing; vtkSmartPointer m_PolyData; mitk::DataStorage::Pointer m_DataStorage; ContourListMap m_ListOfInterpolationSessions; mitk::Surface::Pointer m_InterpolationResult; unsigned int m_CurrentNumberOfReducedContours; mitk::Image *m_SelectedSegmentation; std::map m_SegmentationObserverTags; - unsigned int m_CurrentTimeStep; + mitk::TimePointType m_CurrentTimePoint; }; } #endif diff --git a/Plugins/org.mitk.gui.common/src/mitkIRenderWindowPart.h b/Plugins/org.mitk.gui.common/src/mitkIRenderWindowPart.h index 93efdf749a..eee6396cf4 100644 --- a/Plugins/org.mitk.gui.common/src/mitkIRenderWindowPart.h +++ b/Plugins/org.mitk.gui.common/src/mitkIRenderWindowPart.h @@ -1,190 +1,198 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKIRENDERWINDOWPART_H #define MITKIRENDERWINDOWPART_H #include #include #include #include #include #include #include #include class QmitkRenderWindow; namespace mitk { struct IRenderingManager; class SliceNavigationController; /** * \ingroup org_mitk_gui_common * * \brief Interface for a MITK Workbench Part providing a render window. * * This interface allows generic access to Workbench parts which provide some * kind of render window. The interface is intended to be implemented by * subclasses of berry::IWorkbenchPart. Usually, the interface is implemented * by a Workbench editor. * * A IRenderWindowPart provides zero or more QmitkRenderWindow instances which can * be controlled via this interface. QmitkRenderWindow instances have an associated * \e id, which is implementation specific. * Additionally the defined values AXIAL, SAGITTAL, CORONAL, THREE_D from mitk::BaseRenderer * can be used to retrieve a specific QmitkRenderWindow. * * \see ILinkedRenderWindowPart * \see IRenderWindowPartListener * \see QmitkAbstractRenderEditor */ struct MITK_GUI_COMMON_PLUGIN IRenderWindowPart { static const QString DECORATION_BORDER; // = "border" static const QString DECORATION_LOGO; // = "logo" static const QString DECORATION_MENU; // = "menu" static const QString DECORATION_BACKGROUND; // = "background" static const QString DECORATION_CORNER_ANNOTATION; // = "corner annotation" virtual ~IRenderWindowPart(); /** * Get the currently active (focused) render window. * Focus handling is implementation specific. * * \return The active QmitkRenderWindow instance; nullptr * if no render window is active. */ virtual QmitkRenderWindow* GetActiveQmitkRenderWindow() const = 0; /** * Get all render windows with their ids. * * \return A hash map mapping the render window id to the QmitkRenderWindow instance. */ virtual QHash GetQmitkRenderWindows() const = 0; /** * Get a render window with a specific id. * * \param id The render window id. * \return The QmitkRenderWindow instance for id */ virtual QmitkRenderWindow* GetQmitkRenderWindow(const QString& id) const = 0; /** * Get a render window with a specific view direction. * * \param viewDirection The render window view direction. * \return The QmitkRenderWindow instance for viewDirection */ virtual QmitkRenderWindow* GetQmitkRenderWindow(const mitk::BaseRenderer::ViewDirection& viewDirection) const = 0; /** * Get the rendering manager used by this render window part. * * \return The current IRenderingManager instance or nullptr * if no rendering manager is used. */ virtual mitk::IRenderingManager* GetRenderingManager() const = 0; /** * Request an update of all render windows. * * \param requestType Specifies the type of render windows for which an update * will be requested. */ virtual void RequestUpdate(mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) = 0; /** * Force an immediate update of all render windows. * * \param requestType Specifies the type of render windows for which an immediate update * will be requested. */ virtual void ForceImmediateUpdate(mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) = 0; /** * Get the SliceNavigationController for controlling time positions. * * \return A SliceNavigationController if the render window supports this * operation; otherwise returns nullptr. */ virtual mitk::SliceNavigationController* GetTimeNavigationController() const = 0; /** * Get the selected position in the render window with id id - * or in the active render window if id is nullptr. + * or in the active render window if id is an empty string. * * \param id The render window id. * \return The currently selected position in world coordinates. */ virtual mitk::Point3D GetSelectedPosition(const QString& id = QString()) const = 0; /** * Set the selected position in the render window with id id * or in the active render window if id is nullptr. * * \param pos The position in world coordinates which should be selected. * \param id The render window id in which the selection should take place. */ virtual void SetSelectedPosition(const mitk::Point3D& pos, const QString& id = QString()) = 0; + /** + * Get the time point selected in the render window with id id + * or in the active render window if id is an empty string. + * + * \param id The render window id. + * \return The currently selected position in world coordinates.*/ + virtual TimePointType GetSelectedTimePoint(const QString& id = QString()) const = 0; + /** * Enable \e decorations like colored borders, menu widgets, logos, text annotations, etc. * * Decorations are implementation specific. A set of standardized decoration names is listed * in GetDecorations(). * * \param enable If true enable the decorations specified in decorations, * otherwise disable them. * \param decorations A list of decoration names. If empty, all supported decorations are affected. * * \see GetDecorations() */ virtual void EnableDecorations(bool enable, const QStringList& decorations = QStringList()) = 0; /** * Return if a specific decoration is enabled. * * \return true if the decoration is enabled, false if it is disabled * or unknown. * * \see GetDecorations() */ virtual bool IsDecorationEnabled(const QString& decoration) const = 0; /** * Get a list of supported decorations. * * The following decoration names are standardized and should not be used for other decoration types: *
    *
  • \e DECORATION_BORDER Any border decorations like colored rectangles, etc. *
  • \e DECORATION_MENU Menus associated with render windows *
  • \e DECORATION_BACKGROUND All kinds of backgrounds (patterns, gradients, etc.) except for solid colored backgrounds *
  • \e DECORATION_LOGO Any kind of logo overlayed on the rendered scene *
* * \return A list of supported decoration names. */ virtual QStringList GetDecorations() const = 0; }; } Q_DECLARE_INTERFACE(mitk::IRenderWindowPart, "org.mitk.ui.IRenderWindowPart") #endif // MITKIRENDERWINDOWPART_H diff --git a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTNormalizeView.cpp b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTNormalizeView.cpp index 05c8fb560a..6ff6b43f96 100644 --- a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTNormalizeView.cpp +++ b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTNormalizeView.cpp @@ -1,110 +1,110 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkCESTNormalizeView.h" #include #include "mitkWorkbenchUtil.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataProperty.h" #include "mitkNodePredicateDataType.h" #include "QmitkDataStorageComboBoxWithSelectNone.h" #include #include "mitkCESTImageNormalizationFilter.h" -#include "mitkCustomTagParser.h" +#include "mitkCESTPropertyHelper.h" #include "mitkCESTImageDetectionHelper.h" const std::string QmitkCESTNormalizeView::VIEW_ID = "org.mitk.gui.qt.cest.normalize"; void QmitkCESTNormalizeView::SetFocus() { m_Controls.btnNormalize->setFocus(); } void QmitkCESTNormalizeView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnNormalize->setEnabled(false); m_Controls.comboCESTImage->SetPredicate(this->m_IsCESTImagePredicate); m_Controls.comboCESTImage->SetDataStorage(this->GetDataStorage()); connect(m_Controls.btnNormalize, SIGNAL(clicked()), this, SLOT(OnNormalizeButtonClicked())); connect(m_Controls.comboCESTImage, SIGNAL(OnSelectionChanged(const mitk::DataNode *)), this, SLOT(UpdateGUIControls())); UpdateGUIControls(); } void QmitkCESTNormalizeView::UpdateGUIControls() { m_Controls.btnNormalize->setEnabled(m_Controls.comboCESTImage->GetSelectedNode().IsNotNull()); } void QmitkCESTNormalizeView::OnNormalizeButtonClicked() { auto selectedImageNode = m_Controls.comboCESTImage->GetSelectedNode(); if (!selectedImageNode) { MITK_ERROR << "Invalid system state. CEST selection is invalid. Selected node is null_ptr."; return; } auto selectedImage = dynamic_cast(selectedImageNode->GetData()); if (!selectedImageNode) { MITK_ERROR << "Invalid system state. CEST selection is invalid. Selected node is not an image."; return; } std::string offsetsStr = ""; - bool hasOffsets = selectedImage->GetPropertyList()->GetStringProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str(), offsetsStr); + bool hasOffsets = selectedImage->GetPropertyList()->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsetsStr); if (!hasOffsets) { QMessageBox::information(nullptr, "CEST normalization", "Selected image was missing CEST offset information."); return; } if (!mitk::IsNotNormalizedCESTImage(selectedImage)) { QMessageBox::information(nullptr, "CEST normalization", "Selected image already seems to be normalized."); return; } if (selectedImage->GetDimension() == 4) { auto normalizationFilter = mitk::CESTImageNormalizationFilter::New(); normalizationFilter->SetInput(selectedImage); normalizationFilter->Update(); auto resultImage = normalizationFilter->GetOutput(); mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(resultImage); std::string normalizedName = selectedImageNode->GetName() + "_normalized"; dataNode->SetName(normalizedName); this->GetDataStorage()->Add(dataNode); } } QmitkCESTNormalizeView::QmitkCESTNormalizeView() { auto isImage = mitk::NodePredicateDataType::New("Image"); this->m_IsCESTImagePredicate = mitk::NodePredicateAnd::New(isImage, mitk::CreateAnyCESTImageNodePredicate()).GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp index 8c7f134f54..2b8c2c96bf 100644 --- a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.cpp @@ -1,816 +1,815 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // itk #include "itksys/SystemTools.hxx" #include #include // Blueberry #include #include // Qmitk #include "QmitkCESTStatisticsView.h" // Qt #include #include // qwt #include // mitk #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include // boost #include #include // stl #include #include #include #include #include #include namespace { template void GetSortPermutation(std::vector &out, const std::vector &determiningVector, Compare compare = std::less()) { out.resize(determiningVector.size()); std::iota(out.begin(), out.end(), 0); std::sort(out.begin(), out.end(), [&](unsigned i, unsigned j) { return compare(determiningVector[i], determiningVector[j]); }); } template void ApplyPermutation(const std::vector &order, std::vector &vectorToSort) { assert(order.size() == vectorToSort.size()); std::vector tempVector(vectorToSort.size()); for (unsigned i = 0; i < vectorToSort.size(); i++) { tempVector[i] = vectorToSort[order[i]]; } vectorToSort = tempVector; } template void ApplyPermutation(const std::vector &order, std::vector ¤tVector, std::vector &... remainingVectors) { ApplyPermutation(order, currentVector); ApplyPermutation(order, remainingVectors...); } template void SortVectors(const std::vector &orderDeterminingVector, Compare comparison, std::vector &... vectorsToBeSorted) { std::vector order; GetSortPermutation(order, orderDeterminingVector, comparison); ApplyPermutation(order, vectorsToBeSorted...); } } // namespace const std::string QmitkCESTStatisticsView::VIEW_ID = "org.mitk.views.ceststatistics"; QmitkCESTStatisticsView::QmitkCESTStatisticsView(QObject * /*parent*/, const char * /*name*/) { this->m_CalculatorJob = new QmitkImageStatisticsCalculationJob(); m_currentSelectedPosition.Fill(0.0); - m_currentSelectedTimeStep = 0; + m_currentSelectedTimePoint = 0.; m_CrosshairPointSet = mitk::PointSet::New(); } QmitkCESTStatisticsView::~QmitkCESTStatisticsView() { while (this->m_CalculatorJob->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculatorJob; } void QmitkCESTStatisticsView::SetFocus() { m_Controls.threeDimToFourDimPushButton->setFocus(); } void QmitkCESTStatisticsView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect( m_Controls.threeDimToFourDimPushButton, SIGNAL(clicked()), this, SLOT(OnThreeDimToFourDimPushButtonClicked())); connect((QObject *)this->m_CalculatorJob, SIGNAL(finished()), this, SLOT(OnThreadedStatisticsCalculationEnds()), Qt::QueuedConnection); connect((QObject *)(this->m_Controls.fixedRangeCheckBox), SIGNAL(toggled(bool)), (QObject *)this, SLOT(OnFixedRangeCheckBoxToggled(bool))); connect((QObject *)(this->m_Controls.fixedRangeLowerDoubleSpinBox), SIGNAL(editingFinished()), (QObject *)this, SLOT(OnFixedRangeDoubleSpinBoxChanged())); connect((QObject *)(this->m_Controls.fixedRangeUpperDoubleSpinBox), SIGNAL(editingFinished()), (QObject *)this, SLOT(OnFixedRangeDoubleSpinBoxChanged())); m_Controls.threeDimToFourDimPushButton->setEnabled(false); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); } void QmitkCESTStatisticsView::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkCESTStatisticsView::RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkCESTStatisticsView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList &nodes) { if (nodes.empty()) { std::stringstream message; message << "Please select an image."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } // iterate all selected objects bool atLeastOneWasCESTImage = false; foreach (mitk::DataNode::Pointer node, nodes) { if (node.IsNull()) { continue; } if (dynamic_cast(node->GetData()) != nullptr) { m_Controls.labelWarning->setVisible(false); bool zSpectrumSet = SetZSpectrum(dynamic_cast( - node->GetData()->GetProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str()).GetPointer())); + node->GetData()->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str()).GetPointer())); atLeastOneWasCESTImage = atLeastOneWasCESTImage || zSpectrumSet; if (zSpectrumSet) { m_ZImage = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetImageNodes({node.GetPointer()}); } else { m_MaskImage = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetMaskNodes({node.GetPointer()}); } } if (dynamic_cast(node->GetData()) != nullptr) { m_MaskPlanarFigure = dynamic_cast(node->GetData()); m_Controls.widget_statistics->SetMaskNodes({node.GetPointer()}); } if (dynamic_cast(node->GetData()) != nullptr) { m_PointSet = dynamic_cast(node->GetData()); } } // We only want to offer normalization or timestep copying if one object is selected if (nodes.size() == 1) { if (dynamic_cast(nodes.front()->GetData())) { m_Controls.threeDimToFourDimPushButton->setDisabled(atLeastOneWasCESTImage); } else { m_Controls.threeDimToFourDimPushButton->setEnabled(false); std::stringstream message; message << "The selected node is not an image."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } this->Clear(); return; } // we always need a mask, either image or planar figure as well as an image for further processing if (nodes.size() != 2) { this->Clear(); return; } m_Controls.threeDimToFourDimPushButton->setEnabled(false); if (!atLeastOneWasCESTImage) { std::stringstream message; message << "None of the selected data nodes contains required CEST meta information"; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } bool bothAreImages = (m_ZImage.GetPointer() != nullptr) && (m_MaskImage.GetPointer() != nullptr); if (bothAreImages) { bool geometriesMatch = mitk::Equal(*(m_ZImage->GetTimeGeometry()), *(m_MaskImage->GetTimeGeometry()), mitk::eps, false); if (!geometriesMatch) { std::stringstream message; message << "The selected images have different geometries."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); this->Clear(); return; } } if (!this->DataSanityCheck()) { this->Clear(); return; } if (m_PointSet.IsNull()) { // initialize thread and trigger it this->m_CalculatorJob->SetIgnoreZeroValueVoxel(false); this->m_CalculatorJob->Initialize(m_ZImage.GetPointer(), m_MaskImage.GetPointer(), m_MaskPlanarFigure.GetPointer()); std::stringstream message; message << "Calculating statistics..."; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); try { // Compute statistics this->m_CalculatorJob->start(); } catch (const mitk::Exception &e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } catch (const std::runtime_error &e) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } catch (const std::exception &e) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls.labelWarning->setText(message.str().c_str()); m_Controls.labelWarning->show(); } while (this->m_CalculatorJob->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } } if (m_PointSet.IsNotNull()) { if (m_ZImage->GetDimension() == 4) { AccessFixedDimensionByItk(m_ZImage, PlotPointSet, 4); } else { MITK_WARN << "Expecting a 4D image."; } } } void QmitkCESTStatisticsView::OnThreadedStatisticsCalculationEnds() { this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::xBottom, "delta w"); this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::yLeft, "z"); if (this->m_CalculatorJob->GetStatisticsUpdateSuccessFlag()) { auto statistics = this->m_CalculatorJob->GetStatisticsData(); std::string statisticsNodeName = "CEST_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statistics, statisticsNodeName); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statistics, m_CalculatorJob->GetStatisticsImage()); if (m_CalculatorJob->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(statistics, m_CalculatorJob->GetMaskImage()); } else if (m_CalculatorJob->GetPlanarFigure()) { auto planarFigureRule = mitk::StatisticsToMaskRelationRule::New(); planarFigureRule->Connect(statistics, m_CalculatorJob->GetPlanarFigure()); } this->GetDataStorage()->Add(statisticsNode); QmitkPlotWidget::DataVector::size_type numberOfSpectra = this->m_zSpectrum.size(); QmitkPlotWidget::DataVector means(numberOfSpectra); QmitkPlotWidget::DataVector stdevs(numberOfSpectra); for (unsigned int index = 0; index < numberOfSpectra; ++index) { means[index] = statistics->GetStatisticsForTimeStep(index).GetValueConverted( mitk::ImageStatisticsConstants::MEAN()); stdevs[index] = statistics->GetStatisticsForTimeStep(index).GetValueConverted( mitk::ImageStatisticsConstants::STANDARDDEVIATION()); } QmitkPlotWidget::DataVector xValues = this->m_zSpectrum; RemoveMZeros(xValues, means, stdevs); ::SortVectors(xValues, std::less(), xValues, means, stdevs); unsigned int curveId = this->m_Controls.m_DataViewWidget->InsertCurve("Spectrum"); this->m_Controls.m_DataViewWidget->SetCurveData(curveId, xValues, means, stdevs, stdevs); this->m_Controls.m_DataViewWidget->SetErrorPen(curveId, QPen(Qt::blue)); QwtSymbol *blueSymbol = new QwtSymbol(QwtSymbol::Rect, QColor(Qt::blue), QColor(Qt::blue), QSize(8, 8)); this->m_Controls.m_DataViewWidget->SetCurveSymbol(curveId, blueSymbol); this->m_Controls.m_DataViewWidget->SetLegendAttribute(curveId, QwtPlotCurve::LegendShowSymbol); QwtLegend *legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); this->m_Controls.m_DataViewWidget->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.m_DataViewWidget->GetPlot() ->axisScaleEngine(QwtPlot::Axis::xBottom) ->setAttributes(QwtScaleEngine::Inverted); this->m_Controls.m_DataViewWidget->Replot(); m_Controls.labelWarning->setVisible(false); m_Controls.m_StatisticsGroupBox->setEnabled(true); m_Controls.m_StatisticsGroupBox->setEnabled(true); if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale( 2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } else { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, true); } } else { m_Controls.labelWarning->setText(m_CalculatorJob->GetLastErrorMessage().c_str()); m_Controls.labelWarning->setVisible(true); this->Clear(); } } void QmitkCESTStatisticsView::OnFixedRangeDoubleSpinBoxChanged() { if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale(2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } this->m_Controls.m_DataViewWidget->Replot(); } template void QmitkCESTStatisticsView::PlotPointSet(itk::Image *image) { this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::xBottom, "delta w"); this->m_Controls.m_DataViewWidget->SetAxisTitle(QwtPlot::Axis::yLeft, "z"); QmitkPlotWidget::DataVector::size_type numberOfSpectra = this->m_zSpectrum.size(); mitk::PointSet::Pointer internalPointset; if (m_PointSet.IsNotNull()) { internalPointset = m_PointSet; } else { internalPointset = m_CrosshairPointSet; } if (internalPointset.IsNull()) { return; } if (!this->DataSanityCheck()) { m_Controls.labelWarning->setText("Data can not be plotted, internally inconsistent."); m_Controls.labelWarning->show(); return; } auto maxIndex = internalPointset->GetMaxId().Index(); for (std::size_t number = 0; number < maxIndex + 1; ++number) { mitk::PointSet::PointType point; if (!internalPointset->GetPointIfExists(number, &point)) { continue; } if (!this->m_ZImage->GetGeometry()->IsInside(point)) { continue; } itk::Index<3> itkIndex; this->m_ZImage->GetGeometry()->WorldToIndex(point, itkIndex); itk::Index itkIndexTime; itkIndexTime[0] = itkIndex[0]; itkIndexTime[1] = itkIndex[1]; itkIndexTime[2] = itkIndex[2]; QmitkPlotWidget::DataVector values(numberOfSpectra); for (std::size_t step = 0; step < numberOfSpectra; ++step) { if (VImageDimension == 4) { itkIndexTime[3] = step; } values[step] = image->GetPixel(itkIndexTime); } std::stringstream name; name << "Point " << number; // Qcolor enums go from 0 to 19, but 19 is transparent and 0,1 are for bitmaps // 3 is white and thus not visible QColor color(static_cast(number % 17 + 4)); QmitkPlotWidget::DataVector xValues = this->m_zSpectrum; RemoveMZeros(xValues, values); ::SortVectors(xValues, std::less(), xValues, values); unsigned int curveId = this->m_Controls.m_DataViewWidget->InsertCurve(name.str().c_str()); this->m_Controls.m_DataViewWidget->SetCurveData(curveId, xValues, values); this->m_Controls.m_DataViewWidget->SetCurvePen(curveId, QPen(color)); QwtSymbol *symbol = new QwtSymbol(QwtSymbol::Rect, color, color, QSize(8, 8)); this->m_Controls.m_DataViewWidget->SetCurveSymbol(curveId, symbol); this->m_Controls.m_DataViewWidget->SetLegendAttribute(curveId, QwtPlotCurve::LegendShowSymbol); } if (this->m_Controls.fixedRangeCheckBox->isChecked()) { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, false); this->m_Controls.m_DataViewWidget->GetPlot()->setAxisScale(2, this->m_Controls.fixedRangeLowerDoubleSpinBox->value(), this->m_Controls.fixedRangeUpperDoubleSpinBox->value()); } else { this->m_Controls.m_DataViewWidget->GetPlot()->setAxisAutoScale(2, true); } QwtLegend *legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); this->m_Controls.m_DataViewWidget->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.m_DataViewWidget->GetPlot() ->axisScaleEngine(QwtPlot::Axis::xBottom) ->setAttributes(QwtScaleEngine::Inverted); this->m_Controls.m_DataViewWidget->Replot(); m_Controls.labelWarning->setVisible(false); } void QmitkCESTStatisticsView::OnFixedRangeCheckBoxToggled(bool state) { this->m_Controls.fixedRangeLowerDoubleSpinBox->setEnabled(state); this->m_Controls.fixedRangeUpperDoubleSpinBox->setEnabled(state); } void QmitkCESTStatisticsView::RemoveMZeros(QmitkPlotWidget::DataVector &xValues, QmitkPlotWidget::DataVector &yValues) { QmitkPlotWidget::DataVector tempX; QmitkPlotWidget::DataVector tempY; for (std::size_t index = 0; index < xValues.size(); ++index) { if ((xValues.at(index) < -299) || (xValues.at(index)) > 299) { // do not include } else { tempX.push_back(xValues.at(index)); tempY.push_back(yValues.at(index)); } } xValues = tempX; yValues = tempY; } void QmitkCESTStatisticsView::RemoveMZeros(QmitkPlotWidget::DataVector &xValues, QmitkPlotWidget::DataVector &yValues, QmitkPlotWidget::DataVector &stdDevs) { QmitkPlotWidget::DataVector tempX; QmitkPlotWidget::DataVector tempY; QmitkPlotWidget::DataVector tempDevs; for (std::size_t index = 0; index < xValues.size(); ++index) { if ((xValues.at(index) < -299) || (xValues.at(index)) > 299) { // do not include } else { tempX.push_back(xValues.at(index)); tempY.push_back(yValues.at(index)); tempDevs.push_back(stdDevs.at(index)); } } xValues = tempX; yValues = tempY; stdDevs = tempDevs; } void QmitkCESTStatisticsView::OnThreeDimToFourDimPushButtonClicked() { QList nodes = this->GetDataManagerSelection(); if (nodes.empty()) return; mitk::DataNode *node = nodes.front(); if (!node) { // Nothing selected. Inform the user and return QMessageBox::information(nullptr, "CEST View", "Please load and select an image before starting image processing."); return; } // here we have a valid mitk::DataNode // a node itself is not very useful, we need its data item (the image) mitk::BaseData *data = node->GetData(); if (data) { // test if this data item is an image or not (could also be a surface or something totally different) mitk::Image *image = dynamic_cast(data); if (image) { if (image->GetDimension() == 4) { AccessFixedDimensionByItk(image, CopyTimesteps, 4); } this->Clear(); } } } template void QmitkCESTStatisticsView::CopyTimesteps(itk::Image *image) { typedef itk::Image ImageType; // typedef itk::PasteImageFilter PasteImageFilterType; unsigned int numberOfTimesteps = image->GetLargestPossibleRegion().GetSize(3); typename ImageType::RegionType sourceRegion = image->GetLargestPossibleRegion(); sourceRegion.SetSize(3, 1); typename ImageType::RegionType targetRegion = image->GetLargestPossibleRegion(); targetRegion.SetSize(3, 1); for (unsigned int timestep = 1; timestep < numberOfTimesteps; ++timestep) { targetRegion.SetIndex(3, timestep); itk::ImageRegionConstIterator sourceIterator(image, sourceRegion); itk::ImageRegionIterator targetIterator(image, targetRegion); while (!sourceIterator.IsAtEnd()) { targetIterator.Set(sourceIterator.Get()); ++sourceIterator; ++targetIterator; } } } bool QmitkCESTStatisticsView::SetZSpectrum(mitk::StringProperty *zSpectrumProperty) { if (zSpectrumProperty == nullptr) { return false; } mitk::LocaleSwitch localeSwitch("C"); std::string zSpectrumString = zSpectrumProperty->GetValueAsString(); std::istringstream iss(zSpectrumString); std::vector zSpectra; std::copy( std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(zSpectra)); m_zSpectrum.clear(); m_zSpectrum.resize(0); for (auto const &spectrumString : zSpectra) { m_zSpectrum.push_back(std::stod(spectrumString)); } return (m_zSpectrum.size() > 0); } bool QmitkCESTStatisticsView::DataSanityCheck() { QmitkPlotWidget::DataVector::size_type numberOfSpectra = m_zSpectrum.size(); // if we do not have a spectrum, the data can not be processed if (numberOfSpectra == 0) { return false; } // if we do not have CEST image data, the data can not be processed if (m_ZImage.IsNull()) { return false; } // if the CEST image data and the meta information do not match, the data can not be processed if (numberOfSpectra != m_ZImage->GetTimeSteps()) { MITK_INFO << "CEST meta information and number of volumes does not match."; return false; } // if we have neither a mask image, a point set nor a mask planar figure, we can not do statistics // statistics on the whole image would not make sense if (m_MaskImage.IsNull() && m_MaskPlanarFigure.IsNull() && m_PointSet.IsNull() && m_CrosshairPointSet->IsEmpty()) { return false; } // if we have a mask image and a mask planar figure, we can not do statistics // we do not know which one to use if (m_MaskImage.IsNotNull() && m_MaskPlanarFigure.IsNotNull()) { return false; } return true; } void QmitkCESTStatisticsView::Clear() { this->m_zSpectrum.clear(); this->m_zSpectrum.resize(0); this->m_ZImage = nullptr; this->m_MaskImage = nullptr; this->m_MaskPlanarFigure = nullptr; this->m_PointSet = nullptr; this->m_Controls.m_DataViewWidget->Clear(); this->m_Controls.m_StatisticsGroupBox->setEnabled(false); this->m_Controls.widget_statistics->SetImageNodes({}); this->m_Controls.widget_statistics->SetMaskNodes({}); } void QmitkCESTStatisticsView::OnSliceChanged() { mitk::Point3D currentSelectedPosition = this->GetRenderWindowPart()->GetSelectedPosition(nullptr); - unsigned int currentSelectedTimeStep = - this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); + mitk::TimePointType currentSelectedTimePoint = this->GetRenderWindowPart()->GetSelectedTimePoint(); - if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimeStep != currentSelectedTimeStep) + if (m_currentSelectedPosition != currentSelectedPosition || currentSelectedTimePoint != currentSelectedTimePoint) //|| m_selectedNodeTime > m_currentPositionTime) { // the current position has been changed or the selected node has been changed since the last position validation -> // check position m_currentSelectedPosition = currentSelectedPosition; - m_currentSelectedTimeStep = currentSelectedTimeStep; + m_currentSelectedTimePoint = currentSelectedTimePoint; m_currentPositionTime.Modified(); m_CrosshairPointSet->Clear(); m_CrosshairPointSet->SetPoint(0, m_currentSelectedPosition); QList nodes = this->GetDataManagerSelection(); if (nodes.empty() || nodes.size() > 1) return; mitk::DataNode *node = nodes.front(); if (!node) { return; } if (dynamic_cast(node->GetData()) != nullptr) { m_Controls.labelWarning->setVisible(false); bool zSpectrumSet = SetZSpectrum(dynamic_cast( - node->GetData()->GetProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str()).GetPointer())); + node->GetData()->GetProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str()).GetPointer())); if (zSpectrumSet) { m_ZImage = dynamic_cast(node->GetData()); } else { return; } } else { return; } this->m_Controls.m_DataViewWidget->Clear(); AccessFixedDimensionByItk(m_ZImage, PlotPointSet, 4); } } diff --git a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h index aacc637cec..9954b55c84 100644 --- a/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.cest/src/internal/QmitkCESTStatisticsView.h @@ -1,135 +1,133 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkCESTStatisticsView_h #define QmitkCESTStatisticsView_h #include #include #include #include "ui_QmitkCESTStatisticsViewControls.h" #include #include #include /** \brief QmitkCESTStatisticsView \warning Basic statistics view for CEST data. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ class QmitkCESTStatisticsView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default constructor */ QmitkCESTStatisticsView(QObject *parent = nullptr, const char *name = nullptr); /*! \brief default destructor */ ~QmitkCESTStatisticsView() override; protected slots: /// \brief Called when the user clicks the GUI button void OnThreeDimToFourDimPushButtonClicked(); /// \brief takes care of processing the computed data void OnThreadedStatisticsCalculationEnds(); /// \brief Toggle whether or not the plot uses a fixed x range void OnFixedRangeCheckBoxToggled(bool state); /// \brief Adapt axis scale when manual ranges are set void OnFixedRangeDoubleSpinBoxChanged(); /// \brief What to do if the crosshair moves void OnSliceChanged(); protected: void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; /// \brief called by QmitkFunctionality when DataManager's selection has changed void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes ) override; /// parse string and set data vector returns true if succesfull bool SetZSpectrum(mitk::StringProperty* zSpectrumProperty); /** Checks whether the currently set data appears reasonable */ bool DataSanityCheck(); /** Fills the plot based on a point set * * This will only use the first timestep */ template void PlotPointSet(itk::Image* image); /** Deletes all data */ void Clear(); /** Remove MZeros * * Will remove the data for the M0 images from the given input */ void RemoveMZeros(QmitkPlotWidget::DataVector& xValues, QmitkPlotWidget::DataVector& yValues); void RemoveMZeros(QmitkPlotWidget::DataVector& xValues, QmitkPlotWidget::DataVector& yValues, QmitkPlotWidget::DataVector& stdDevs); /** Copies the first timestep of a segmentation to all others */ template void CopyTimesteps(itk::Image* image); Ui::QmitkCESTStatisticsViewControls m_Controls; QmitkImageStatisticsCalculationJob* m_CalculatorJob; QmitkPlotWidget::DataVector m_zSpectrum; mitk::Image::Pointer m_ZImage; mitk::Image::Pointer m_MaskImage; mitk::PlanarFigure::Pointer m_MaskPlanarFigure; mitk::PointSet::Pointer m_PointSet; mitk::PointSet::Pointer m_CrosshairPointSet; QmitkSliceNavigationListener m_SliceChangeListener; itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentPositionTime; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; - /** @brief indicates if the currently selected position is valid for the currently selected fit. - * This it is within the input image */ - unsigned int m_currentSelectedTimeStep; + mitk::TimePointType m_currentSelectedTimePoint; }; #endif // QmitkCESTStatisticsView_h diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.cpp index ab915d6eb4..3c76c856b0 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.cpp @@ -1,142 +1,152 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkAbstractRenderEditor.h" #include "internal/QmitkCommonActivator.h" #include #include #include #include #include class QmitkAbstractRenderEditorPrivate { public: QmitkAbstractRenderEditorPrivate() : m_RenderingManagerInterface(mitk::MakeRenderingManagerInterface(mitk::RenderingManager::GetInstance())) , m_PrefServiceTracker(QmitkCommonActivator::GetContext()) { m_PrefServiceTracker.open(); } ~QmitkAbstractRenderEditorPrivate() { delete m_RenderingManagerInterface; } mitk::IRenderingManager* m_RenderingManagerInterface; ctkServiceTracker m_PrefServiceTracker; berry::IBerryPreferences::Pointer m_Prefs; }; QmitkAbstractRenderEditor::QmitkAbstractRenderEditor() : d(new QmitkAbstractRenderEditorPrivate) { } QmitkAbstractRenderEditor::~QmitkAbstractRenderEditor() { if (d->m_Prefs.IsNotNull()) { d->m_Prefs->OnChanged.RemoveListener(berry::MessageDelegate1 (this, &QmitkAbstractRenderEditor::OnPreferencesChanged ) ); } } void QmitkAbstractRenderEditor::Init(berry::IEditorSite::Pointer site, berry::IEditorInput::Pointer input) { if (input.Cast().IsNull()) throw berry::PartInitException("Invalid Input: Must be mitk::DataStorageEditorInput"); this->SetSite(site); this->SetInput(input); d->m_Prefs = this->GetPreferences().Cast(); if (d->m_Prefs.IsNotNull()) { d->m_Prefs->OnChanged.AddListener(berry::MessageDelegate1 (this, &QmitkAbstractRenderEditor::OnPreferencesChanged ) ); } } mitk::IDataStorageReference::Pointer QmitkAbstractRenderEditor::GetDataStorageReference() const { mitk::DataStorageEditorInput::Pointer input = this->GetEditorInput().Cast(); if (input.IsNotNull()) { return input->GetDataStorageReference(); } return mitk::IDataStorageReference::Pointer(nullptr); } mitk::DataStorage::Pointer QmitkAbstractRenderEditor::GetDataStorage() const { mitk::IDataStorageReference::Pointer ref = this->GetDataStorageReference(); if (ref.IsNotNull()) return ref->GetDataStorage(); return mitk::DataStorage::Pointer(nullptr); } berry::IPreferences::Pointer QmitkAbstractRenderEditor::GetPreferences() const { berry::IPreferencesService* prefService = d->m_PrefServiceTracker.getService(); if (prefService != nullptr) { return prefService->GetSystemPreferences()->Node(this->GetSite()->GetId()); } return berry::IPreferences::Pointer(nullptr); } mitk::IRenderingManager* QmitkAbstractRenderEditor::GetRenderingManager() const { // we use the global rendering manager here. This should maybe replaced // by a local one, managing only the render windows specific for the editor return d->m_RenderingManagerInterface; } void QmitkAbstractRenderEditor::RequestUpdate(mitk::RenderingManager::RequestType requestType) { if (GetRenderingManager()) GetRenderingManager()->RequestUpdateAll(requestType); } void QmitkAbstractRenderEditor::ForceImmediateUpdate(mitk::RenderingManager::RequestType requestType) { if (GetRenderingManager()) GetRenderingManager()->ForceImmediateUpdateAll(requestType); } mitk::SliceNavigationController* QmitkAbstractRenderEditor::GetTimeNavigationController() const { if (GetRenderingManager()) return GetRenderingManager()->GetTimeNavigationController(); return nullptr; } void QmitkAbstractRenderEditor::OnPreferencesChanged(const berry::IBerryPreferences *) {} void QmitkAbstractRenderEditor::DoSave() {} void QmitkAbstractRenderEditor::DoSaveAs() {} bool QmitkAbstractRenderEditor::IsDirty() const { return false; } bool QmitkAbstractRenderEditor::IsSaveAsAllowed() const { return false; } + +mitk::TimePointType QmitkAbstractRenderEditor::GetSelectedTimePoint(const QString& /*id*/) const +{ + auto timeNavigator = this->GetTimeNavigationController(); + if (nullptr != timeNavigator) + { + return timeNavigator->GetSelectedTimePoint(); + } + return 0; +} diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.h b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.h index 104e7b1b60..5ce38bc3ad 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractRenderEditor.h @@ -1,155 +1,162 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKABSTRACTRENDEREDITOR_H #define QMITKABSTRACTRENDEREDITOR_H #include #include #include "mitkIRenderWindowPart.h" #include #include #include class QmitkAbstractRenderEditorPrivate; /** * \ingroup org_mitk_gui_qt_common * * \brief A convenient base class for MITK render window BlueBerry Editors. * * QmitkAbstractRenderEditor provides several convenience methods that ease the introduction of * a new editor for rendering a MITK DataStorage: * *
    *
  1. Access to the DataStorage (~ the shared data repository) *
  2. Access to and update notification for the editor's preferences *
  3. Default implementation of some mitk::IRenderWindowPart methods *
* * When inheriting from QmitkAbstractRenderEditor, you must implement the following methods: *
    *
  • void CreateQtPartControl(QWidget* parent) *
  • void SetFocus() *
* * You may reimplement the following private virtual methods to be notified about certain changes: *
    *
  • void OnPreferencesChanged(const berry::IBerryPreferences*) *
* * \see IRenderWindowPart * \see ILinkedRenderWindowPart */ class MITK_QT_COMMON QmitkAbstractRenderEditor : public berry::QtEditorPart, public virtual mitk::IRenderWindowPart { Q_OBJECT Q_INTERFACES(mitk::IRenderWindowPart) public: berryObjectMacro(QmitkAbstractRenderEditor, QtEditorPart, mitk::IRenderWindowPart); + /** + * \see mitk::IRenderWindowPart::GetSelectedTimePoint() + This default implementation assumes that all renderer use the same TimeNavigationControl + provided by this class (GetTimeNavigationControl()). + */ + mitk::TimePointType GetSelectedTimePoint(const QString& id = QString()) const override; + QmitkAbstractRenderEditor(); ~QmitkAbstractRenderEditor() override; protected: /** * Initializes this editor by checking for a valid mitk::DataStorageEditorInput as input. * * \see berry::IEditorPart::Init */ void Init(berry::IEditorSite::Pointer site, berry::IEditorInput::Pointer input) override; /** * Get a reference to the DataStorage set by the editor input. */ virtual mitk::IDataStorageReference::Pointer GetDataStorageReference() const; /** * Get the DataStorage set by the editor input. */ virtual mitk::DataStorage::Pointer GetDataStorage() const; /** * Get the preferences for this editor. */ virtual berry::IPreferences::Pointer GetPreferences() const; /** * Get the RenderingManager used by this editor. This default implementation uses the * global MITK RenderingManager provided by mitk::RenderingManager::GetInstance(). * * \see mitk::IRenderWindowPart::GetRenderingManager */ mitk::IRenderingManager* GetRenderingManager() const override; /** * Request an update of this editor's render windows. * This implementation calls mitk::IRenderingManager::RequestUpdate on the rendering * manager interface returned by GetRenderingManager(); * * \param requestType The type of render windows for which an update is requested. * * \see mitk::IRenderWindowPart::RequestUpdate */ void RequestUpdate(mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) override; /** * Force an immediate update of this editor's render windows. * This implementation calls mitk::IRenderingManager::ForceImmediateUpdate() on the rendering * manager interface returned by GetRenderingManager(); * * \param requestType The type of render windows for which an immedate update is forced. * * \see mitk::IRenderWindowPart::ForceImmediateUpdate */ void ForceImmediateUpdate(mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL) override; /** * Get the time navigation controller for this editor. * This implementation returns the SliceNavigationController returned by the mitk::IRenderingManager::GetTimeNavigationController() * method of the interface implementation returned by GetRenderingManager(). * * \see mitk::IRenderingManager::GetTimeNavigationController */ mitk::SliceNavigationController* GetTimeNavigationController() const override; /** \see berry::IEditorPart::DoSave */ void DoSave() override; /** \see berry::IEditorPart::DoSaveAs */ void DoSaveAs() override; /** \see berry::IEditorPart::IsDirty */ bool IsDirty() const override; /** \see berry::IEditorPart::IsSaveAsAllowed */ bool IsSaveAsAllowed() const override; private: virtual void OnPreferencesChanged(const berry::IBerryPreferences*); private: QScopedPointer d; }; #endif // QMITKABSTRACTRENDEREDITOR_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp index dfca873947..ecae1b05e3 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.cpp @@ -1,159 +1,159 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkMultiNodeSelectionWidget.h" #include #include "QmitkNodeSelectionDialog.h" #include "QmitkCustomVariants.h" #include "internal/QmitkNodeSelectionListItemWidget.h" -QmitkMultiNodeSelectionWidget::QmitkMultiNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) +QmitkMultiNodeSelectionWidget::QmitkMultiNodeSelectionWidget(QWidget* parent) + : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_Overlay = new QmitkSimpleTextOverlayWidget(m_Controls.list); m_Overlay->setVisible(false); m_CheckFunction = [](const NodeList &) { return ""; }; this->OnInternalSelectionChanged(); this->UpdateInfo(); connect(m_Controls.btnChange, SIGNAL(clicked(bool)), this, SLOT(OnEditSelection())); } void QmitkMultiNodeSelectionWidget::SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction) { m_CheckFunction = checkFunction; auto newEmission = this->CompileEmitSelection(); auto newCheckResponse = m_CheckFunction(newEmission); if (newCheckResponse.empty() && !m_CheckResponse.empty()) { this->EmitSelection(newEmission); } m_CheckResponse = newCheckResponse; this->UpdateInfo(); } void QmitkMultiNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->CompileEmitSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetSelectionCheckFunction(m_CheckFunction); m_Controls.btnChange->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnChange->setChecked(false); delete dialog; } void QmitkMultiNodeSelectionWidget::UpdateInfo() { if (!m_Controls.list->count()) { if (m_IsOptional) { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } } else { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } } } else { if (!m_CheckResponse.empty()) { m_Overlay->SetOverlayText(QString::fromStdString(m_CheckResponse)); } } m_Overlay->setVisible(m_Controls.list->count() == 0 || !m_CheckResponse.empty()); for (auto i = 0; i < m_Controls.list->count(); ++i) { auto item = m_Controls.list->item(i); auto widget = qobject_cast(m_Controls.list->itemWidget(item)); widget->SetClearAllowed(m_IsOptional || m_Controls.list->count() > 1); } } void QmitkMultiNodeSelectionWidget::OnInternalSelectionChanged() { m_Controls.list->clear(); auto currentSelection = this->GetCurrentInternalSelection(); for (auto& node : currentSelection) { if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { QListWidgetItem *newItem = new QListWidgetItem; newItem->setSizeHint(QSize(0, 40)); QmitkNodeSelectionListItemWidget* widget = new QmitkNodeSelectionListItemWidget; widget->SetSelectedNode(node); widget->SetClearAllowed(m_IsOptional || currentSelection.size() > 1); connect(widget, &QmitkNodeSelectionListItemWidget::ClearSelection, this, &QmitkMultiNodeSelectionWidget::OnClearSelection); newItem->setData(Qt::UserRole, QVariant::fromValue(node)); m_Controls.list->addItem(newItem); m_Controls.list->setItemWidget(newItem, widget); } } } void QmitkMultiNodeSelectionWidget::OnClearSelection(const mitk::DataNode* node) { this->RemoveNodeFromSelection(node); } void QmitkMultiNodeSelectionWidget::changeEvent(QEvent *event) { if (event->type() == QEvent::EnabledChange) { this->UpdateInfo(); } QmitkAbstractNodeSelectionWidget::changeEvent(event); } bool QmitkMultiNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& emissionCandidates) const { m_CheckResponse = m_CheckFunction(emissionCandidates); return m_CheckResponse.empty(); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h index 747c9c1951..493db43ec0 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkMultiNodeSelectionWidget.h @@ -1,77 +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 QMITK_MULTI_NODE_SELECTION_WIDGET_H #define QMITK_MULTI_NODE_SELECTION_WIDGET_H #include #include #include #include "QmitkSimpleTextOverlayWidget.h" #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkMultiNodeSelectionWidget.h" #include class QmitkAbstractDataStorageModel; class QAbstractItemVew; /** -* \class QmitkMultiNodeSelectionWidget -* \brief Widget that allows to perform and represents a multiple node selection. +* @class QmitkMultiNodeSelectionWidget +* @brief Widget that allows to perform and represents a multiple node selection. */ class MITK_QT_COMMON QmitkMultiNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkMultiNodeSelectionWidget(QWidget* parent = nullptr); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; - /**Helper function that is used to check the given selection for consistency. - Returning an empty string assumes that everything is alright and the selection - is valid. If the string is not empty, the content of the string will be used - as error message in the overlay to indicate the problem.*/ + /** + * @brief Helper function that is used to check the given selection for consistency. + * Returning an empty string assumes that everything is alright and the selection + * is valid. If the string is not empty, the content of the string will be used + * as error message in the overlay to indicate the problem. + */ using SelectionCheckFunctionType = std::function; - /**A selection check function can be set. If set the widget uses this function to - check the made/set selection. If the selection is valid, everything is fine. - If selection is indicated as invalid, it will not be communicated by the widget - (no signal emission.*/ + /** + * @brief A selection check function can be set. If set the widget uses this function to + * check the made/set selection. If the selection is valid, everything is fine. + * If selection is indicated as invalid, it will not be communicated by the widget + * (no signal emission). + */ void SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction); public Q_SLOTS: void OnEditSelection(); protected Q_SLOTS: void OnClearSelection(const mitk::DataNode* node); protected: void changeEvent(QEvent *event) override; void UpdateInfo() override; void OnInternalSelectionChanged() override; bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; QmitkSimpleTextOverlayWidget* m_Overlay; SelectionCheckFunctionType m_CheckFunction; mutable std::string m_CheckResponse; Ui_QmitkMultiNodeSelectionWidget m_Controls; }; -#endif // QmitkMultiNodeSelectionWidget_H + +#endif // QMITK_MULTI_NODE_SELECTION_WIDGET_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp index 68adfbd9a2..072b5cd082 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.cpp @@ -1,278 +1,281 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - #include "QmitkNodeSelectionButton.h" -// berry includes -#include -#include - -#include "QPainter" -#include "QTextDocument" -#include "QEvent" - -#include -#include - // mitk core #include -#include #include #include #include #include -// vtk +// mitk qt widgets module +#include + +// berry includes +#include +#include + #include +#include +#include +#include + QPixmap GetPixmapFromImageNode(const mitk::DataNode* dataNode, int height) { if (nullptr == dataNode) { return QPixmap(); } const mitk::Image* image = dynamic_cast(dataNode->GetData()); if ((nullptr == image || !image->IsInitialized()) || // -> must be an image (image->GetPixelType().GetNumberOfComponents() != 1)) // -> for now only single component are allowed { auto descManager = QmitkNodeDescriptorManager::GetInstance(); auto desc = descManager->GetDescriptor(dataNode); auto icon = desc->GetIcon(dataNode); auto fallBackMap = icon.pixmap(height, height); return fallBackMap; } mitk::PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); int sliceNumber = image->GetDimension(2) / 2; planeGeometry->InitializeStandardPlane(image->GetGeometry(), mitk::PlaneGeometry::Axial, sliceNumber); mitk::ExtractSliceFilter::Pointer extractSliceFilter = mitk::ExtractSliceFilter::New(); extractSliceFilter->SetInput(image); extractSliceFilter->SetInterpolationMode(mitk::ExtractSliceFilter::RESLICE_CUBIC); extractSliceFilter->SetResliceTransformByGeometry(image->GetGeometry()); extractSliceFilter->SetWorldGeometry(planeGeometry); extractSliceFilter->SetOutputDimensionality(2); extractSliceFilter->SetVtkOutputRequest(true); extractSliceFilter->Update(); vtkImageData* imageData = extractSliceFilter->GetVtkOutput(); mitk::LevelWindow levelWindow; dataNode->GetLevelWindow(levelWindow); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); lookupTable->SetSaturationRange(0.0, 0.0); lookupTable->SetValueRange(0.0, 1.0); lookupTable->SetHueRange(0.0, 0.0); lookupTable->SetRampToLinear(); vtkSmartPointer levelWindowFilter = vtkSmartPointer::New(); levelWindowFilter->SetLookupTable(lookupTable); levelWindowFilter->SetInputData(imageData); levelWindowFilter->SetMinOpacity(0.0); levelWindowFilter->SetMaxOpacity(1.0); int dims[3]; imageData->GetDimensions(dims); double clippingBounds[] = { 0.0, static_cast(dims[0]), 0.0, static_cast(dims[1]) }; levelWindowFilter->SetClippingBounds(clippingBounds); levelWindowFilter->Update(); imageData = levelWindowFilter->GetOutput(); QImage thumbnailImage(reinterpret_cast(imageData->GetScalarPointer()), dims[0], dims[1], QImage::Format_ARGB32); if (dims[0] > dims[1]) { thumbnailImage = thumbnailImage.scaledToWidth(height, Qt::SmoothTransformation).rgbSwapped(); } else { thumbnailImage = thumbnailImage.scaledToHeight(height, Qt::SmoothTransformation).rgbSwapped(); } return QPixmap::fromImage(thumbnailImage); } QmitkNodeSelectionButton::QmitkNodeSelectionButton(QWidget *parent) - : QPushButton(parent), m_OutDatedThumbNail(true), m_DataMTime(0), m_IsOptional(true), m_NodeModifiedObserverTag(0), m_NodeObserved(false) -{ } + : QPushButton(parent) + , m_OutDatedThumbNail(true) + , m_DataMTime(0) + , m_IsOptional(true) + , m_NodeModifiedObserverTag(0) + , m_NodeObserved(false) +{ +} QmitkNodeSelectionButton::~QmitkNodeSelectionButton() { this->RemoveNodeObserver(); this->m_SelectedNode = nullptr; } void QmitkNodeSelectionButton::AddNodeObserver() { if (this->m_SelectedNode.IsNotNull()) { if (m_NodeObserved) { MITK_DEBUG << "Invalid observer state in QmitkNodeSelectionButton. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } auto modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkNodeSelectionButton::OnNodeModified); // const cast because we need non const nodes and it seems to be the lesser of two evil. // the changes to the node are only on the observer level. The other option would be to // make the public interface require non const nodes, this we don't want to introduce. auto nonconst_node = const_cast(this->m_SelectedNode.GetPointer()); m_NodeModifiedObserverTag = nonconst_node->AddObserver(itk::ModifiedEvent(), modifiedCommand); m_NodeObserved = true; } } void QmitkNodeSelectionButton::RemoveNodeObserver() { if (this->m_SelectedNode.IsNotNull()) { // const cast because we need non const nodes and it seems to be the lesser of two evil. // the changes to the node are only on the observer level. The other option would be to // make the public interface require non const nodes, this we don't want to introduce. auto nonconst_node = const_cast(this->m_SelectedNode.GetPointer()); nonconst_node->RemoveObserver(m_NodeModifiedObserverTag); } m_NodeObserved = false; } void QmitkNodeSelectionButton::OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject & event) { if (itk::ModifiedEvent().CheckEvent(&event)) { this->update(); } } const mitk::DataNode* QmitkNodeSelectionButton::GetSelectedNode() const { return m_SelectedNode; } void QmitkNodeSelectionButton::SetSelectedNode(const mitk::DataNode* node) { if (m_SelectedNode != node) { this->RemoveNodeObserver(); this->m_SelectedNode = node; this->m_OutDatedThumbNail = true; this->AddNodeObserver(); } this->update(); } void QmitkNodeSelectionButton::SetNodeInfo(QString info) { this->m_Info = info; this->update(); } void QmitkNodeSelectionButton::paintEvent(QPaintEvent *p) { QString stylesheet; ctkPluginContext* context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); stylesheet = styleManager->GetStylesheet(); } QPushButton::paintEvent(p); QPainter painter(this); QTextDocument td(this); td.setDefaultStyleSheet(stylesheet); auto widgetSize = this->size(); QPoint origin = QPoint(5, 5); if (this->m_SelectedNode) { auto iconLength = widgetSize.height() - 10; auto node = this->m_SelectedNode; itk::ModifiedTimeType dataMTime = 0; if (m_SelectedNode->GetData()) { dataMTime = m_SelectedNode->GetData()->GetMTime(); } if (dataMTime>m_DataMTime || this->m_OutDatedThumbNail) { this->m_ThumbNail = GetPixmapFromImageNode(node, iconLength); this->m_OutDatedThumbNail = false; m_DataMTime = dataMTime; } auto thumbNailOrigin = origin; thumbNailOrigin.setY(thumbNailOrigin.y() + ((iconLength - m_ThumbNail.height()) / 2)); painter.drawPixmap(thumbNailOrigin, m_ThumbNail); origin.setX(origin.x() + iconLength + 5); if (this->isEnabled()) { td.setHtml(QString::fromStdString("" + node->GetName() + "")); } else { td.setHtml(QString::fromStdString("" + node->GetName() + "")); } } else { if (this->isEnabled()) { if (this->m_IsOptional) { td.setHtml(QString("") + m_Info + QString("")); } else { td.setHtml(QString("") + m_Info + QString("")); } } else { td.setHtml(QString("") + m_Info + QString("")); } } auto textSize = td.size(); origin.setY( (widgetSize.height() - textSize.height()) / 2.); painter.translate(origin); td.drawContents(&painter); } void QmitkNodeSelectionButton::changeEvent(QEvent *event) { if (event->type() == QEvent::EnabledChange) { this->update(); } } bool QmitkNodeSelectionButton::GetSelectionIsOptional() const { return m_IsOptional; } void QmitkNodeSelectionButton::SetSelectionIsOptional(bool isOptional) { m_IsOptional = isOptional; this->update(); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h index ebd5980339..21d328da1c 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionButton.h @@ -1,73 +1,71 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - #ifndef QMITK_NODE_SELECTION_BUTTON_H #define QMITK_NODE_SELECTION_BUTTON_H -#include -#include - #include "org_mitk_gui_qt_common_Export.h" -#include "QPushButton" -#include "QPixmap" +#include +#include +#include -/** Button class that can be used to display informations about a passed node. - * If the passed node is a null ptr the node info text will be shown. - * In difference to the normal push button text property. The node info can - * be formated text (e.g. HTML code; like the tooltip text).*/ +/** +* @class QmitkNodeSelectionButton +* @brief Button class that can be used to display information about a given node. +* If the given node is a nullptr the node info text will be shown. +* The node info can be formated text (e.g. HTML code; like the tooltip text). +*/ class MITK_QT_COMMON QmitkNodeSelectionButton : public QPushButton { Q_OBJECT public: explicit QmitkNodeSelectionButton(QWidget *parent = nullptr); ~QmitkNodeSelectionButton() override; const mitk::DataNode* GetSelectedNode() const; bool GetSelectionIsOptional() const; -public Q_SLOTS : +public Q_SLOTS: virtual void SetSelectedNode(const mitk::DataNode* node); virtual void SetNodeInfo(QString info); /** Set the widget into an optional mode. Optional means that the selection of no valid node does not mean an invalid state. Thus no node is a valid "node" selection too. The state influences if the info text is handled as an information (optional) or a warning (optiona==false).*/ void SetSelectionIsOptional(bool isOptional); protected: void paintEvent(QPaintEvent *p) override; void changeEvent(QEvent *event) override; void AddNodeObserver(); void RemoveNodeObserver(); void OnNodeModified(const itk::Object * /*caller*/, const itk::EventObject &); mitk::DataNode::ConstPointer m_SelectedNode; QString m_Info; bool m_OutDatedThumbNail; QPixmap m_ThumbNail; itk::ModifiedTimeType m_DataMTime; itk::ModifiedTimeType m_SelectionPropMTime; bool m_IsOptional; unsigned long m_NodeModifiedObserverTag; bool m_NodeObserved; }; - -#endif // QmitkSingleNodeSelectionWidget_H +#endif // QMITK_NODE_SELECTION_BUTTON_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp index 56e426b8bf..979715cd2d 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp @@ -1,287 +1,291 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkNodeSelectionDialog.h" #include #include #include #include #include -QmitkNodeSelectionDialog::QmitkNodeSelectionDialog(QWidget* parent, QString title, QString hint) : QDialog(parent), - m_NodePredicate(nullptr), m_SelectOnlyVisibleNodes(false), m_SelectedNodes(NodeList()), m_SelectionMode(QAbstractItemView::SingleSelection) +QmitkNodeSelectionDialog::QmitkNodeSelectionDialog(QWidget* parent, QString title, QString hint) + : QDialog(parent) + , m_NodePredicate(nullptr) + , m_SelectOnlyVisibleNodes(false) + , m_SelectedNodes(NodeList()) + , m_SelectionMode(QAbstractItemView::SingleSelection) { m_Controls.setupUi(this); m_CheckFunction = [](const NodeList &) { return ""; }; auto providers = mitk::DataStorageInspectorGenerator::GetProviders(); auto visibleProviders = mitk::GetVisibleDataStorageInspectors(); auto preferredID = mitk::GetPreferredDataStorageInspector(); if (visibleProviders.empty()) { MITK_DEBUG << "No presets for visible node selection inspectors available. Use fallback (show all available inspectors)"; unsigned int order = 0; for (auto proIter : providers) { visibleProviders.insert(std::make_pair(order, proIter.first)); ++order; } } int preferredIndex = 0; bool preferredFound = false; for (auto proIter : visibleProviders) { auto finding = providers.find(proIter.second); if (finding != providers.end()) { if (finding->second->GetInspectorID() != QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID() && finding->second->GetInspectorID() != QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()) { auto provider = finding->second; this->AddPanel(provider, preferredID, preferredFound, preferredIndex); } } else { MITK_DEBUG << "No provider registered for inspector that is defined as visible in the preferences. Illegal inspector ID: " << proIter.second; } } if (mitk::GetShowFavoritesInspector()) { auto favoritesPorvider = mitk::DataStorageInspectorGenerator::GetProvider(QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID()); if (favoritesPorvider != nullptr) { this->AddPanel(favoritesPorvider, preferredID, preferredFound, preferredIndex); } } if (mitk::GetShowHistoryInspector()) { auto historyPorvider = mitk::DataStorageInspectorGenerator::GetProvider(QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()); if (historyPorvider != nullptr) { this->AddPanel(historyPorvider, preferredID, preferredFound, preferredIndex); } } m_Controls.tabWidget->setCurrentIndex(preferredIndex); this->setWindowTitle(title); this->setToolTip(hint); m_Controls.hint->setText(hint); m_Controls.hint->setVisible(!hint.isEmpty()); if(hint.isEmpty()) { m_Controls.layoutHint->setContentsMargins(0, 0, 0, 0); } else { m_Controls.layoutHint->setContentsMargins(6, 6, 6, 6); } this->SetErrorText(""); m_Controls.btnAddToFav->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/favorite_add.svg"))); connect(m_Controls.btnAddToFav, &QPushButton::clicked, this, &QmitkNodeSelectionDialog::OnFavoriteNodesButtonClicked); connect(m_Controls.buttonBox, &QDialogButtonBox::accepted, this, &QmitkNodeSelectionDialog::OnOK); connect(m_Controls.buttonBox, &QDialogButtonBox::rejected, this, &QmitkNodeSelectionDialog::OnCancel); } void QmitkNodeSelectionDialog::SetDataStorage(mitk::DataStorage* dataStorage) { if (m_DataStorage != dataStorage) { m_DataStorage = dataStorage; if (!m_DataStorage.IsExpired()) { for (auto panel : m_Panels) { panel->SetDataStorage(dataStorage); } } } } void QmitkNodeSelectionDialog::SetNodePredicate(const mitk::NodePredicateBase* nodePredicate) { if (m_NodePredicate != nodePredicate) { m_NodePredicate = nodePredicate; for (auto panel : m_Panels) { panel->SetNodePredicate(m_NodePredicate); } } } const mitk::NodePredicateBase* QmitkNodeSelectionDialog::GetNodePredicate() const { return m_NodePredicate; } QmitkNodeSelectionDialog::NodeList QmitkNodeSelectionDialog::GetSelectedNodes() const { return m_SelectedNodes; } void QmitkNodeSelectionDialog::SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction) { m_CheckFunction = checkFunction; auto checkResponse = m_CheckFunction(m_SelectedNodes); SetErrorText(checkResponse); m_Controls.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(checkResponse.empty()); } void QmitkNodeSelectionDialog::SetErrorText(const std::string& checkResponse) { m_Controls.error->setText(QString::fromStdString(checkResponse)); m_Controls.error->setVisible(!checkResponse.empty()); if (checkResponse.empty()) { m_Controls.layoutError->setContentsMargins(0, 0, 0, 0); } else { m_Controls.layoutError->setContentsMargins(6, 6, 6, 6); } } bool QmitkNodeSelectionDialog::GetSelectOnlyVisibleNodes() const { return m_SelectOnlyVisibleNodes; } void QmitkNodeSelectionDialog::SetSelectionMode(SelectionMode mode) { m_SelectionMode = mode; for (auto panel : m_Panels) { panel->SetSelectionMode(mode); } } QmitkNodeSelectionDialog::SelectionMode QmitkNodeSelectionDialog::GetSelectionMode() const { return m_SelectionMode; } void QmitkNodeSelectionDialog::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) { if (m_SelectOnlyVisibleNodes != selectOnlyVisibleNodes) { m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; for (auto panel : m_Panels) { panel->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); } } } void QmitkNodeSelectionDialog::SetCurrentSelection(NodeList selectedNodes) { m_SelectedNodes = selectedNodes; auto checkResponse = m_CheckFunction(m_SelectedNodes); SetErrorText(checkResponse); m_Controls.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(checkResponse.empty()); for (auto panel : m_Panels) { panel->SetCurrentSelection(selectedNodes); } } void QmitkNodeSelectionDialog::OnSelectionChanged(NodeList selectedNodes) { SetCurrentSelection(selectedNodes); emit CurrentSelectionChanged(selectedNodes); } void QmitkNodeSelectionDialog::OnFavoriteNodesButtonClicked() { for (auto node : m_SelectedNodes) { node->SetBoolProperty("org.mitk.selection.favorite", true); } } void QmitkNodeSelectionDialog::OnOK() { for (auto node : m_SelectedNodes) { QmitkDataStorageSelectionHistoryInspector::AddNodeToHistory(node); } this->accept(); } void QmitkNodeSelectionDialog::OnCancel() { this->reject(); } void QmitkNodeSelectionDialog::AddPanel(const mitk::IDataStorageInspectorProvider * provider, const mitk::IDataStorageInspectorProvider::InspectorIDType& preferredID, bool &preferredFound, int &preferredIndex) { auto inspector = provider->CreateInspector(); QString name = QString::fromStdString(provider->GetInspectorDisplayName()); QString desc = QString::fromStdString(provider->GetInspectorDescription()); inspector->setParent(this); inspector->SetSelectionMode(m_SelectionMode); auto tabPanel = new QWidget(); tabPanel->setObjectName(QString("tab_") + name); tabPanel->setToolTip(desc); auto verticalLayout = new QVBoxLayout(tabPanel); verticalLayout->setSpacing(0); verticalLayout->setContentsMargins(0, 0, 0, 0); verticalLayout->addWidget(inspector); auto panelPos = m_Controls.tabWidget->insertTab(m_Controls.tabWidget->count(), tabPanel, name); auto icon = provider->GetInspectorIcon(); if (!icon.isNull()) { m_Controls.tabWidget->setTabIcon(panelPos, icon); } m_Panels.push_back(inspector); connect(inspector, &QmitkAbstractDataStorageInspector::CurrentSelectionChanged, this, &QmitkNodeSelectionDialog::OnSelectionChanged); connect(inspector->GetView(), &QAbstractItemView::doubleClicked, this, &QmitkNodeSelectionDialog::OnDoubleClicked); preferredFound = preferredFound || provider->GetInspectorID() == preferredID; if (!preferredFound) { ++preferredIndex; } } void QmitkNodeSelectionDialog::OnDoubleClicked(const QModelIndex& /*index*/) { if (!m_SelectedNodes.empty()) { this->OnOK(); } } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h index 25ccda870d..c40dde3199 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h @@ -1,143 +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. ============================================================================*/ #ifndef QMITK_NODE_SELECTION_DIALOG_H #define QMITK_NODE_SELECTION_DIALOG_H +#include "org_mitk_gui_qt_common_Export.h" + +#include "ui_QmitkNodeSelectionDialog.h" + #include #include #include #include "mitkIDataStorageInspectorProvider.h" #include -#include "org_mitk_gui_qt_common_Export.h" - -#include "ui_QmitkNodeSelectionDialog.h" - #include #include /** -* \class QmitkNodeSelectionDialog -* \brief Widget that allows to show and edit the content of an mitk::IsoDoseLevel instance. +* @class QmitkNodeSelectionDialog +* @brief A customized QDialog that displays different data storage inspectors and allows to +* set and get a current selection by selecting data nodes in the data storage inspectors. */ class MITK_QT_COMMON QmitkNodeSelectionDialog : public QDialog { Q_OBJECT public: explicit QmitkNodeSelectionDialog(QWidget* parent = nullptr, QString caption = "", QString hint = ""); /** - * @brief Sets the data storage that will be used /monitored by widget. + * @brief Set the data storage that will be used. + * The function iterates over the dialog's panels and sets the data storage of each panel accordingly. + * Each panel is a specific data storage inspector. * * @param dataStorage A pointer to the data storage to set. */ void SetDataStorage(mitk::DataStorage* dataStorage); /** - * @brief Sets the node predicate and updates the widget, according to the node predicate. + * @brief Set the node predicate that will be used. + * The function iterates over the dialog's panels and sets the node predicate of each panel accordingly. + * Each panel is a specific data storage inspector. * * @param nodePredicate A pointer to node predicate. */ virtual void SetNodePredicate(const mitk::NodePredicateBase* nodePredicate); const mitk::NodePredicateBase* GetNodePredicate() const; using NodeList = QList; NodeList GetSelectedNodes() const; /** * @brief Helper function that is used to check the given selection for consistency. * Returning an empty string assumes that everything is alright and the selection is valid. * If the string is not empty, the content of the string will be used as error message. */ using SelectionCheckFunctionType = std::function; /** * @brief A selection check function can be set. If set the dialog uses this function to check the made/set selection. * If the selection is valid, everything is fine. * If the selection is indicated as invalid, the dialog will display the selection check function error message. */ void SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction); bool GetSelectOnlyVisibleNodes() const; using SelectionMode = QAbstractItemView::SelectionMode; + /** + * @brief Set the Qt selection mode (e.g. Single selection, multi selection). + * The function iterates over the dialog's panels and sets the Qt selection mode of each panel accordingly. + * Each panel is a concrete data storage inspector. + * + * @param mode The QAbstractItemView::SelectionMode to define the selection mode. + */ void SetSelectionMode(SelectionMode mode); SelectionMode GetSelectionMode() const; Q_SIGNALS: - /* + /** * @brief A signal that will be emitted if the selected node has changed. * * @param nodes A list of data nodes that are newly selected. */ void CurrentSelectionChanged(NodeList nodes); public Q_SLOTS: - /* - * @brief Change the selection modus of the item view's selection model. - * - * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. - * An outgoing selection can then at most contain the filtered nodes. - * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, - * to include the original selection that could not be modified. - * The part of the original selection, that is non-visible are the nodes that are not + /** + * @brief Set the selection modus to (not) include invisible nodes in the selection. + * The function iterates over the dialog's panels and sets the selection modus of each panel accordingly. + * Each panel is a concrete data storage inspector. * * @param selectOnlyVisibleNodes The bool value to define the selection modus. */ void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); - - /* - * @brief Transform a list of data nodes into a model selection and set this as a new selection of the - * selection model of the private member item view. - * - * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If - * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case - * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable - * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection - * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). + /** + * @brief Set the currently selected nodes given a list of data nodes. + * The function iterates over the dialog's panels and sets the current selection of each panel accordingly. + * Each panel is a concrete data storage inspector. * * @param nodes A list of data nodes that should be newly selected. */ void SetCurrentSelection(NodeList selectedNodes); protected Q_SLOTS: void OnSelectionChanged(NodeList selectedNodes); void OnFavoriteNodesButtonClicked(); void OnOK(); void OnCancel(); void OnDoubleClicked(const QModelIndex& index); protected: void SetErrorText(const std::string& checkResponse); void AddPanel(const mitk::IDataStorageInspectorProvider* provider, const mitk::IDataStorageInspectorProvider::InspectorIDType &preferredID, bool &preferredFound, int &preferredIndex); mitk::WeakPointer m_DataStorage; mitk::NodePredicateBase::ConstPointer m_NodePredicate; bool m_SelectOnlyVisibleNodes; NodeList m_SelectedNodes; SelectionCheckFunctionType m_CheckFunction; SelectionMode m_SelectionMode; using PanelVectorType = std::vector; PanelVectorType m_Panels; QPushButton* m_FavoriteNodesButton; Ui_QmitkNodeSelectionDialog m_Controls; }; + #endif // QMITK_NODE_SELECTION_DIALOG_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionPreferenceHelper.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionPreferenceHelper.cpp index 24c157539b..f80d85e67f 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionPreferenceHelper.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionPreferenceHelper.cpp @@ -1,161 +1,191 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkNodeSelectionPreferenceHelper.h" #include #include #include #include #include +#include +#include #include "mitkExceptionMacro.h" +#include "mitkDataStorageInspectorGenerator.h" void mitk::PutVisibleDataStorageInspectors(const VisibleDataStorageInspectorMapType &inspectors) { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); berry::IPreferences::Pointer visNode = prefNode->Node(mitk::NodeSelectionConstants::VISIBLE_INSPECTORS_NODE_ID.c_str()); visNode->RemoveNode(); prefNode->Flush(); // new empty preset node visNode = prefNode->Node(mitk::NodeSelectionConstants::VISIBLE_INSPECTORS_NODE_ID.c_str()); // store map in new node for (const auto &inspector : inspectors) { std::ostringstream sstr; sstr << inspector.first; berry::IPreferences::Pointer aNode = visNode->Node(QString::fromStdString(sstr.str())); aNode->Put(mitk::NodeSelectionConstants::VISIBLE_INSPECTOR_ID.c_str(), inspector.second.c_str()); aNode->Flush(); } visNode->Flush(); } mitk::VisibleDataStorageInspectorMapType mitk::GetVisibleDataStorageInspectors() { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); berry::IPreferences::Pointer visNode = prefNode->Node(mitk::NodeSelectionConstants::VISIBLE_INSPECTORS_NODE_ID.c_str()); typedef QStringList NamesType; NamesType names = visNode->ChildrenNames(); VisibleDataStorageInspectorMapType visMap; if (!names.empty()) { for (NamesType::const_iterator pos = names.begin(); pos != names.end(); ++pos) { berry::IPreferences::Pointer aNode = visNode->Node(*pos); if (aNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find preset node under given name. Name: " << (*pos).toStdString(); } std::istringstream isstr(pos->toStdString()); unsigned int order = 0; isstr >> order; auto id = aNode->Get(mitk::NodeSelectionConstants::VISIBLE_INSPECTOR_ID.c_str(), ""); if (id.isEmpty()) { mitkThrow() << "Error in preference interface. ID of visible inspector is not set. Inspector position: " << order; } visMap.insert(std::make_pair(order, id.toStdString())); } } + + if (visMap.empty()) + { //no visibility preferences set. Generate default + auto allProviders = mitk::DataStorageInspectorGenerator::GetProviders(); + + //fill inspector list + unsigned int pos = 0; + for (const auto& iter : allProviders) + { + if (iter.first != QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID() && iter.first != QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()) + { + visMap.insert(std::make_pair(pos, iter.first)); + ++pos; + } + } + } + return visMap; } mitk::DataStorageInspectorIDType mitk::GetPreferredDataStorageInspector() { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); auto id = prefNode->Get(mitk::NodeSelectionConstants::PREFERRED_INSPECTOR_ID.c_str(), ""); mitk::DataStorageInspectorIDType result = id.toStdString(); + + if (result.empty()) + { //nothing set, deduce default preferred inspector + auto visibleInspectors = GetVisibleDataStorageInspectors(); + if (!visibleInspectors.empty()) + { + result = visibleInspectors.begin()->second; + } + } + return result; } void mitk::PutPreferredDataStorageInspector(const DataStorageInspectorIDType &id) { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); prefNode->Put(mitk::NodeSelectionConstants::PREFERRED_INSPECTOR_ID.c_str(), id.c_str()); prefNode->Flush(); } void mitk::PutShowFavoritesInspector(bool show) { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); prefNode->PutBool(mitk::NodeSelectionConstants::SHOW_FAVORITE_INSPECTOR.c_str(), show); prefNode->Flush(); } bool mitk::GetShowFavoritesInspector() { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); return prefNode->GetBool(mitk::NodeSelectionConstants::SHOW_FAVORITE_INSPECTOR.c_str(), true); } void mitk::PutShowHistoryInspector(bool show) { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); prefNode->PutBool(mitk::NodeSelectionConstants::SHOW_HISTORY_INSPECTOR.c_str(), show); prefNode->Flush(); } bool mitk::GetShowHistoryInspector() { berry::IPreferencesService *prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::NodeSelectionConstants::ROOT_PREFERENCE_NODE_ID.c_str()); return prefNode->GetBool(mitk::NodeSelectionConstants::SHOW_HISTORY_INSPECTOR.c_str(), true); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSelectionServiceConnector.h b/Plugins/org.mitk.gui.qt.common/src/QmitkSelectionServiceConnector.h index f79dfa3420..b0c8f34647 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSelectionServiceConnector.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSelectionServiceConnector.h @@ -1,136 +1,133 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QMITKSELECTIONSERVICECONNECTOR_H #define QMITKSELECTIONSERVICECONNECTOR_H #include -// qt widgets module -#include - // mitk gui qt common plugin #include "QmitkDataNodeSelectionProvider.h" #include "internal/QmitkDataNodeItemModel.h" // blueberry ui qt plugin #include /* * @brief The 'QmitkSelectionServiceConnector' is used to handle the selections of the global selection bus (selection service). * * The selection service connector can listen to a selection service. This should be done by using 'AddPostSelectionListener' * with the existing selection service of the surrounding 'QmitkAbstractView'. * The selection service connector can provide selections. This should be done by using 'SetAsSelectionProvider' * with the existing selection provider of the surrounding 'QmitkAbstractView'. * * The 'QmitkSelectionServiceConnector' offers a public slot and signal that can be used to propagate the selected * nodes from or to the global selection bus: * The 'ChangeServiceSelection'-slot transforms the given list of selected nodes into a QItemSelection of a temporary * data node selection model. This data node selection model is set as the item selection model of the member selection provider. * So by temporary adding a new data node selection model and changing its selection the selection provider sends a new selection * that can be received at any place in the workbench. * * The 'ServiceSelectionChanged'-signal sends a list of selected nodes to it's local environment (e.g. containing widget). * The 'ServiceSelectionChanged'-signal is emitted by the 'ServiceSelectionChanged'-function, which transforms the * berry selection of the selection into a data node list. The 'ServiceSelectionChanged'-function is called whenever * the selection service sends a selection changed event. * * In order to connect the 'QmitkSelectionServiceConnector' with a model-view pair, a 'QmitkModelViewSelectionConnector' needs to be used: * The 'QmitkModelViewSelectionConnector' offers a 'SetCurrentSelection'-slot that can be connected with the * 'ServiceSelectionChanged'-signal of this class. * The 'QmitkModelViewSelectionConnector' offers a 'CurrentSelectionChanged'-signal that can be connected with the * 'ChangeServiceSelection'-slot of this class. */ class MITK_QT_COMMON QmitkSelectionServiceConnector : public QObject { Q_OBJECT public: QmitkSelectionServiceConnector(); ~QmitkSelectionServiceConnector() override; /* * @brief Create a selection listener and add it to the list of selection listener of the given selection service. * * The selection listener is connected to the 'ServiceSelectionChanged' member function, which is * called if a berry selection is changed in the workbench. */ void AddPostSelectionListener(berry::ISelectionService* selectionService); /* * @brief Remove a selection listener from the list of selection listener of the selection service member. */ void RemovePostSelectionListener(); /* * @brief Store the given selection provider as a private member. * In order to use the public slot 'ChangeServiceSelection'-function, the selection provider member had to be * previously set. */ void SetAsSelectionProvider(QmitkDataNodeSelectionProvider* selectionProvider); /* * @brief Set the selection provider member to a nullptr. This will prevent the public slot * 'ChangeServiceSelection'-function from working. */ void RemoveAsSelectionProvider(); Q_SIGNALS: /* * @brief A signal that will be emitted by the private 'ServiceSelectionChanged'-function. This happens if a selection is changed * via the selection service. * * @par nodes A list of data nodes that are newly selected. */ void ServiceSelectionChanged(QList nodes); /* * @brief A signal that will be emitted by the private 'ServiceSelectionChanged'-function. If sourcePart has send an invalid selection * (selection pointer was Null). * @par sourcePart Part that sent the null selection. */ void ServiceNullSelection(const berry::IWorkbenchPart::Pointer& sourcePart); public Q_SLOTS: /* * @brief Send new selections to the selection service via the private selection provider member. * * This slot-function is called whenever a local selection is changed in the surrounding widget and a selection provider was set. * The newly selected data nodes are added temporary to a 'QmitkDataNodeItemModel', which is then used to define * the indices to select. * The 'QItemSelectionModel' is set as the item selection model of the selection provider member and its items are * selected by the indices previously defined by the 'QmitkDataNodeItemModel'. */ void ChangeServiceSelection(QList nodes); private: std::unique_ptr m_BerrySelectionListener; berry::ISelectionService* m_SelectionService; QmitkDataNodeSelectionProvider* m_SelectionProvider; std::shared_ptr m_DataNodeItemModel; std::shared_ptr m_DataNodeSelectionModel; /* * @brief Handle a selection received from the selection service. * * This function is called whenever a berry selection of the selection service is changed in the workbench. * The new selection is transformed into a data node selection and the contained data nodes are propagated * as the new current selection of the item view member. * * @par sourcePart The workbench part containing the selection. * @par selection The current selection. */ void OnServiceSelectionChanged(const berry::IWorkbenchPart::Pointer& sourcePart, const berry::ISelection::ConstPointer& selection); }; #endif // QMITKSELECTIONSERVICECONNECTOR_H diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp index 87bb9cd71a..51b28638a5 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp @@ -1,230 +1,231 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSingleNodeSelectionWidget.h" #include #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include #include "QmitkNodeSelectionDialog.h" #include "QmitkNodeDetailsDialog.h" -QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent), m_AutoSelectNewNodes(false) +QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) + : QmitkAbstractNodeSelectionWidget(parent) + , m_AutoSelectNewNodes(false) { m_Controls.setupUi(this); m_Controls.btnSelect->installEventFilter(this); m_Controls.btnSelect->setVisible(true); m_Controls.btnClear->setVisible(false); m_Controls.btnClear->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.common/times.svg"))); this->UpdateInfo(); connect(m_Controls.btnClear, SIGNAL(clicked(bool)), this, SLOT(OnClearSelection())); } void QmitkSingleNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { if (newInternalSelection.empty()) { if (m_AutoSelectNewNodes) { auto autoSelectedNode = this->DetermineAutoSelectNode(oldInternalSelection); if (autoSelectedNode.IsNotNull()) { newInternalSelection.append(autoSelectedNode); } } } else if (newInternalSelection.size()>1) { //this widget only allows one internal selected node. newInternalSelection = { newInternalSelection.front() }; } } void QmitkSingleNodeSelectionWidget::OnClearSelection() { if (m_IsOptional) { this->SetCurrentSelection({}); } this->UpdateInfo(); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::GetSelectedNode() const { mitk::DataNode::Pointer result; auto selection = GetCurrentInternalSelection(); if (!selection.empty()) { result = selection.front(); } return result; } bool QmitkSingleNodeSelectionWidget::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_Controls.btnSelect) { if (ev->type() == QEvent::MouseButtonRelease) { auto mouseEv = dynamic_cast(ev); if (!mouseEv) { return false; } if (mouseEv->button() == Qt::LeftButton) { if (this->isEnabled()) { this->EditSelection(); return true; } } else { auto selection = this->CompileEmitSelection(); if (!selection.empty()) { QmitkNodeDetailsDialog infoDialog(selection, this); infoDialog.exec(); return true; } } } } return false; } void QmitkSingleNodeSelectionWidget::EditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->GetCurrentInternalSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); m_Controls.btnSelect->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnSelect->setChecked(false); delete dialog; } void QmitkSingleNodeSelectionWidget::UpdateInfo() { if (this->GetSelectedNode().IsNull()) { if (m_IsOptional) { m_Controls.btnSelect->SetNodeInfo(m_EmptyInfo); } else { m_Controls.btnSelect->SetNodeInfo(m_InvalidInfo); } m_Controls.btnSelect->SetSelectionIsOptional(m_IsOptional); m_Controls.btnClear->setVisible(false); } else { m_Controls.btnClear->setVisible(m_IsOptional); } m_Controls.btnSelect->SetSelectedNode(this->GetSelectedNode()); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::DetermineAutoSelectNode(const NodeList& ignoreNodes) { mitk::DataNode::Pointer result; auto storage = m_DataStorage.Lock(); if (storage.IsNotNull()) { auto ignoreCheck = [ignoreNodes](const mitk::DataNode * node) { bool result = true; for (const auto& ignoreNode : ignoreNodes) { if (node == ignoreNode) { result = false; break; } } return result; }; mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); if (m_NodePredicate.IsNotNull()) { predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); } result = storage->GetNode(predicate); } return result; } void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) { NodeList selection; if (selectedNode) { selection.append(selectedNode); } this->SetCurrentSelection(selection); -}; +} void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) { if (this->GetSelectedNode().IsNull() && m_AutoSelectNewNodes) { auto autoNode = this->DetermineAutoSelectNode(); if (autoNode.IsNotNull()) { this->HandleChangeOfInternalSelection({ autoNode }); } } } bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const { return m_AutoSelectNewNodes; } void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) { m_AutoSelectNewNodes = autoSelect; this->OnNodeAddedToStorage(nullptr); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h index 27635231ca..3c4571eb93 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h @@ -1,80 +1,79 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - #ifndef QMITK_SINGLE_NODE_SELECTION_WIDGET_H #define QMITK_SINGLE_NODE_SELECTION_WIDGET_H #include #include #include #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkSingleNodeSelectionWidget.h" #include #include class QmitkAbstractDataStorageModel; /** -* \class QmitkSingleNodeSelectionWidget -* \brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it -* allows to change the selection. +* @class QmitkSingleNodeSelectionWidget +* @brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it +* allows to change the selection. */ class MITK_QT_COMMON QmitkSingleNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkSingleNodeSelectionWidget(QWidget* parent = nullptr); mitk::DataNode::Pointer GetSelectedNode() const; bool GetAutoSelectNewNodes() const; using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; public Q_SLOTS: void SetCurrentSelectedNode(mitk::DataNode* selectedNode); /** Sets the auto selection mode (Default is false). If auto select is true and the following conditions are fullfilled, the widget will select a node automatically from the data storage: - a data storage is set - data storage contains at least one node that matches the given predicate - no selection is set.*/ void SetAutoSelectNewNodes(bool autoSelect); protected Q_SLOTS: virtual void OnClearSelection(); protected: void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; bool eventFilter(QObject *obj, QEvent *ev) override; void EditSelection(); void UpdateInfo() override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; /** Helper function that gets a suitable auto selected node from the datastorage that fits to the predicate settings. @param ignoreNodes You may pass a list of nodes that must not be choosen as auto selected node. */ mitk::DataNode::Pointer DetermineAutoSelectNode(const NodeList& ignoreNodes = {}); /** See documentation of SetAutoSelectNewNodes for details*/ bool m_AutoSelectNewNodes; Ui_QmitkSingleNodeSelectionWidget m_Controls; }; -#endif // QmitkSingleNodeSelectionWidget_H +#endif // QMITK_SINGLE_NODE_SELECTION_WIDGET_H diff --git a/Plugins/org.mitk.gui.qt.common/src/internal/QmitkNodeSelectionPreferencePage.cpp b/Plugins/org.mitk.gui.qt.common/src/internal/QmitkNodeSelectionPreferencePage.cpp index be25ed2846..62e9815bbd 100644 --- a/Plugins/org.mitk.gui.qt.common/src/internal/QmitkNodeSelectionPreferencePage.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/internal/QmitkNodeSelectionPreferencePage.cpp @@ -1,221 +1,215 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkNodeSelectionPreferencePage.h" #include "QmitkNodeSelectionPreferenceHelper.h" #include #include //----------------------------------------------------------------------------- QmitkNodeSelectionPreferencePage::QmitkNodeSelectionPreferencePage() : m_MainControl(nullptr), m_Controls(nullptr) { } //----------------------------------------------------------------------------- QmitkNodeSelectionPreferencePage::~QmitkNodeSelectionPreferencePage() { delete m_Controls; } //----------------------------------------------------------------------------- void QmitkNodeSelectionPreferencePage::Init(berry::IWorkbench::Pointer ) { } //----------------------------------------------------------------------------- void QmitkNodeSelectionPreferencePage::CreateQtControl(QWidget* parent) { m_MainControl = new QWidget(parent); m_Controls = new Ui::QmitkNodeSelectionPreferencePage; m_Controls->setupUi( m_MainControl ); connect(m_Controls->comboPreferred, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateWidgets())); connect(m_Controls->btnUp, SIGNAL(clicked(bool)), this, SLOT(MoveUp())); connect(m_Controls->btnDown, SIGNAL(clicked(bool)), this, SLOT(MoveDown())); connect(m_Controls->listInspectors, &QListWidget::itemSelectionChanged, this, &QmitkNodeSelectionPreferencePage::UpdateWidgets); this->Update(); } //----------------------------------------------------------------------------- QWidget* QmitkNodeSelectionPreferencePage::GetQtControl() const { return m_MainControl; } //----------------------------------------------------------------------------- bool QmitkNodeSelectionPreferencePage::PerformOk() { - //store favorite + //store preferred auto id = m_Controls->comboPreferred->currentData().toString(); mitk::PutPreferredDataStorageInspector(id.toStdString()); //store visible mitk::VisibleDataStorageInspectorMapType visibles; unsigned int visiblePos = 0; for (int i = 0; i < m_Controls->listInspectors->count(); ++i) { auto item = m_Controls->listInspectors->item(i); if (item->checkState() == Qt::Checked) { visibles.insert(std::make_pair(visiblePos++, item->data(Qt::UserRole).toString().toStdString())); } } mitk::PutVisibleDataStorageInspectors(visibles); mitk::PutShowFavoritesInspector(m_Controls->checkShowFav->isChecked()); mitk::PutShowHistoryInspector(m_Controls->checkShowHistory->isChecked()); return true; } //----------------------------------------------------------------------------- void QmitkNodeSelectionPreferencePage::PerformCancel() { } //----------------------------------------------------------------------------- void QmitkNodeSelectionPreferencePage::Update() { m_Providers = mitk::DataStorageInspectorGenerator::GetProviders(); auto visibleProviders = mitk::GetVisibleDataStorageInspectors(); auto allProviders = mitk::DataStorageInspectorGenerator::GetProviders(); - auto favorite = mitk::GetPreferredDataStorageInspector(); + auto preferredInspectorID = mitk::GetPreferredDataStorageInspector(); - auto finding = m_Providers.find(favorite); - if (finding == m_Providers.cend()) - { - favorite = m_Providers.begin()->first; - } - - //fill favorite combo + //fill preferred combo int index = 0; int currentIndex = 0; m_Controls->comboPreferred->clear(); for (auto iter : m_Providers) { - m_Controls->comboPreferred->addItem(QString::fromStdString(iter.second->GetInspectorDisplayName()),QVariant::fromValue(QString::fromStdString(iter.first))); - if (iter.first == favorite) + m_Controls->comboPreferred->addItem(QString::fromStdString(iter.second->GetInspectorDisplayName()), QVariant::fromValue(QString::fromStdString(iter.first))); + if (iter.first == preferredInspectorID) { currentIndex = index; }; ++index; } m_Controls->comboPreferred->setCurrentIndex(currentIndex); //fill inspector list m_Controls->listInspectors->clear(); for (const auto iter : allProviders) { if (iter.first != QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID() && iter.first != QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID()) { auto currentID = iter.first; QListWidgetItem* item = new QListWidgetItem; item->setText(QString::fromStdString(iter.second->GetInspectorDisplayName())); item->setData(Qt::UserRole, QVariant::fromValue(QString::fromStdString(currentID))); item->setToolTip(QString::fromStdString(iter.second->GetInspectorDescription())); auto finding = std::find_if(visibleProviders.cbegin(), visibleProviders.cend(), [¤tID](auto v) {return v.second == currentID; }); if (finding == visibleProviders.cend()) { item->setCheckState(Qt::Unchecked); m_Controls->listInspectors->addItem(item); } else { item->setCheckState(Qt::Checked); m_Controls->listInspectors->insertItem(finding->first, item); } } } m_Controls->checkShowFav->setChecked(mitk::GetShowFavoritesInspector()); m_Controls->checkShowHistory->setChecked(mitk::GetShowHistoryInspector()); this->UpdateWidgets(); } void QmitkNodeSelectionPreferencePage::UpdateWidgets() { int currentIndex = m_Controls->listInspectors->currentRow(); m_Controls->btnUp->setEnabled(!m_Controls->listInspectors->selectedItems().empty() && currentIndex > 0); m_Controls->btnDown->setEnabled(!m_Controls->listInspectors->selectedItems().empty() && currentIndex + 1 < m_Controls->listInspectors->count()); for (int i = 0; i < m_Controls->listInspectors->count(); ++i) { auto item = m_Controls->listInspectors->item(i); if (item->data(Qt::UserRole).toString() == m_Controls->comboPreferred->currentData().toString()) { //preferred inspector is always visible. item->setCheckState(Qt::Checked); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); } else { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable); } } auto isFavSelected = QmitkDataStorageFavoriteNodesInspector::INSPECTOR_ID() == m_Controls->comboPreferred->currentData().toString(); if (isFavSelected) { m_Controls->checkShowFav->setChecked(true); } m_Controls->checkShowFav->setEnabled(!isFavSelected); auto isHistorySelected = QmitkDataStorageSelectionHistoryInspector::INSPECTOR_ID() == m_Controls->comboPreferred->currentData().toString(); if (isHistorySelected) { m_Controls->checkShowHistory->setChecked(true); } m_Controls->checkShowHistory->setEnabled(!isHistorySelected); } void QmitkNodeSelectionPreferencePage::MoveDown() { int currentIndex = m_Controls->listInspectors->currentRow(); if (currentIndex+1 < m_Controls->listInspectors->count()) { QListWidgetItem *currentItem = m_Controls->listInspectors->takeItem(currentIndex); m_Controls->listInspectors->insertItem(currentIndex + 1, currentItem); m_Controls->listInspectors->setCurrentRow(currentIndex + 1); } this->UpdateWidgets(); } void QmitkNodeSelectionPreferencePage::MoveUp() { int currentIndex = m_Controls->listInspectors->currentRow(); if (currentIndex > 0) { QListWidgetItem *currentItem = m_Controls->listInspectors->takeItem(currentIndex); m_Controls->listInspectors->insertItem(currentIndex - 1, currentItem); m_Controls->listInspectors->setCurrentRow(currentIndex - 1); } this->UpdateWidgets(); } diff --git a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestControls.ui b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestControls.ui index 52adcb5c86..52a3edab6c 100644 --- a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestControls.ui +++ b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestControls.ui @@ -1,200 +1,210 @@ QmitkDataStorageViewerTestControls 0 0 945 456 0 0 Data storage viewer test + + + + + + - + Set as selection provider - - + + + + Set as selection provider + + - - + + - Only valid nodes + Set as selection listener Set as selection listener - - - - Only valid nodes + + + + + 0 + 0 + + + + + 0 + 40 + - - + + + + + - Allow only images + Set as selection provider - - + + - Is Optional + Set as selection provider - - + + - Set as selection provider + Set as selection listner - - + + - Is Enabled - - - true + Set as selection listner - - + + - Set as selection provider + Only valid nodes - - + + - Set as selection listener + Only valid nodes - - + + - Allow only uneven selection count -(using check funcktion feature) + Allow only images - - + + - Set as selection listner + Allow only images (using NodePredicate feature and general invalid message) - - + + - Is Optional + Allow only uneven selection count (using check funcktion feature) - - - - - + + - Set as selection listner + Is Optional - - - - - 0 - 0 - - - - - 0 - 40 - + + + + Is Optional - - + + Is Enabled true - - + + - Allow only images -(using NodePredicate feature and general invalid message) + Is Enabled - - - - - - - - - Set as selection provider + + true Auto select node + + QmitkDataStorageListInspector + QWidget +
QmitkDataStorageListInspector.h
+ 1 +
+ + QmitkDataStorageTreeInspector + QWidget +
QmitkDataStorageTreeInspector.h
+ 1 +
QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
QmitkMultiNodeSelectionWidget QWidget
QmitkMultiNodeSelectionWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp index 1ce16e51e0..a3708f6407 100644 --- a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp +++ b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.cpp @@ -1,277 +1,296 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // data storage viewer test plugin #include "QmitkDataStorageViewerTestView.h" #include "mitkNodePredicateDataType.h" // berry #include -// qt -#include - const std::string QmitkDataStorageViewerTestView::VIEW_ID = "org.mitk.views.datastorageviewertest"; void QmitkDataStorageViewerTestView::SetFocus() { // nothing here } void QmitkDataStorageViewerTestView::CreateQtPartControl(QWidget* parent) { // create GUI widgets m_Controls.setupUi(parent); - m_DataStorageDefaultListModel = new QmitkDataStorageDefaultListModel(this); - m_DataStorageDefaultListModel->SetDataStorage(GetDataStorage()); - m_Controls.selectionListView->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_Controls.selectionListView->setSelectionBehavior(QAbstractItemView::SelectRows); - m_Controls.selectionListView->setAlternatingRowColors(true); - m_Controls.selectionListView->setModel(m_DataStorageDefaultListModel); - - m_DataStorageDefaultListModel2 = new QmitkDataStorageDefaultListModel(this); - m_DataStorageDefaultListModel2->SetDataStorage(GetDataStorage()); - m_Controls.selectionListView2->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_Controls.selectionListView2->setSelectionBehavior(QAbstractItemView::SelectRows); - m_Controls.selectionListView2->setAlternatingRowColors(true); - m_Controls.selectionListView2->setModel(m_DataStorageDefaultListModel2); + m_Controls.listInspector->SetDataStorage(GetDataStorage()); + m_Controls.treeInspector->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetEmptyInfo(QStringLiteral("EmptyInfo: Set this to display info in empty state")); m_Controls.singleSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); m_Controls.singleSlot->SetPopUpTitel(QStringLiteral("This is the definable caption. Choose your data now!")); m_Controls.singleSlot->SetPopUpHint(QStringLiteral("I am an optional hint, that can be set by the developer

If not set the widget is invisible.")); m_Controls.multiSlot->SetDataStorage(GetDataStorage()); m_Controls.multiSlot->SetEmptyInfo(QStringLiteral("EmptyInfo: Set this to display info in empty state")); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); m_Controls.multiSlot->SetPopUpTitel(QStringLiteral("This is the definable caption. Choose your data now!")); m_Controls.multiSlot->SetPopUpHint(QStringLiteral("I am an optional hint, that can be set by the developer

If not set the widget is invisible.")); - m_ModelViewSelectionConnector = std::make_unique(); + m_ModelViewSelectionConnector1 = std::make_unique(); try { - m_ModelViewSelectionConnector->SetView(m_Controls.selectionListView); + m_ModelViewSelectionConnector1->SetView(m_Controls.listInspector->GetView()); } catch (mitk::Exception& e) { mitkReThrow(e) << "Cannot connect the model-view pair signals and slots."; } - m_SelectionServiceConnector = std::make_unique(); + m_SelectionServiceConnector1 = std::make_unique(); m_ModelViewSelectionConnector2 = std::make_unique(); try { - m_ModelViewSelectionConnector2->SetView(m_Controls.selectionListView2); + m_ModelViewSelectionConnector2->SetView(m_Controls.treeInspector->GetView()); } catch (mitk::Exception& e) { mitkReThrow(e) << "Cannot connect the model-view pair signals and slots."; } m_SelectionServiceConnector2 = std::make_unique(); m_SelectionServiceConnector3 = std::make_unique(); m_SelectionServiceConnector4 = std::make_unique(); - connect(m_Controls.selectionProviderCheckBox, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider1(bool))); - connect(m_Controls.selectionProviderCheckBox2, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider2(bool))); - - connect(m_Controls.selectionListenerCheckBox, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener1(bool))); - connect(m_Controls.selectionListenerCheckBox2, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener2(bool))); - - connect(m_Controls.selectionProviderCheckBox3, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider3(bool))); - connect(m_Controls.selectionListenerCheckBox3, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener3(bool))); - - connect(m_Controls.checkOnlyVisible, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetSelectOnlyVisibleNodes(bool))); - connect(m_Controls.checkOptional, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetSelectionIsOptional(bool))); - connect(m_Controls.checkAuto, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(SetAutoSelectNewNodes(bool))); - connect(m_Controls.checkOnlyImages, SIGNAL(toggled(bool)), this, SLOT(OnOnlyImages(bool))); - connect(m_Controls.checkEnabled, SIGNAL(toggled(bool)), m_Controls.singleSlot, SLOT(setEnabled(bool))); - - connect(m_Controls.selectionProviderCheckBox4, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionProvider4(bool))); - connect(m_Controls.selectionListenerCheckBox4, SIGNAL(toggled(bool)), this, SLOT(SetAsSelectionListener4(bool))); - - connect(m_Controls.checkOnlyVisible_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(SetSelectOnlyVisibleNodes(bool))); - connect(m_Controls.checkOptional_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(SetSelectionIsOptional(bool))); - connect(m_Controls.checkOnlyImages_2, SIGNAL(toggled(bool)), this, SLOT(OnOnlyImages2(bool))); - connect(m_Controls.checkOnlyUneven, SIGNAL(toggled(bool)), this, SLOT(OnOnlyUneven(bool))); - connect(m_Controls.checkEnabled_2, SIGNAL(toggled(bool)), m_Controls.multiSlot, SLOT(setEnabled(bool))); + connect(m_Controls.selectionProviderCheckBox1, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionProvider1); + connect(m_Controls.selectionProviderCheckBox2, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionProvider2); + + connect(m_Controls.selectionListenerCheckBox1, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionListener1); + connect(m_Controls.selectionListenerCheckBox2, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionListener2); + + connect(m_Controls.selectionProviderCheckBox3, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionProvider3); + connect(m_Controls.selectionListenerCheckBox3, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionListener3); + + connect(m_Controls.checkOnlyVisible1, &QCheckBox::toggled, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetSelectOnlyVisibleNodes); + connect(m_Controls.checkOnlyImages1, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::OnOnlyImages1); + connect(m_Controls.checkOptional1, &QCheckBox::toggled, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetSelectionIsOptional); + connect(m_Controls.checkEnabled1, &QCheckBox::toggled, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::setEnabled); + connect(m_Controls.checkAuto, &QCheckBox::toggled, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes); + + connect(m_Controls.selectionProviderCheckBox4, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionProvider4); + connect(m_Controls.selectionListenerCheckBox4, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::SetAsSelectionListener4); + + connect(m_Controls.checkOnlyVisible2, &QCheckBox::toggled, + m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetSelectOnlyVisibleNodes); + connect(m_Controls.checkOnlyImages2, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::OnOnlyImages2); + connect(m_Controls.checkOnlyUneven, &QCheckBox::toggled, + this, &QmitkDataStorageViewerTestView::OnOnlyUneven); + connect(m_Controls.checkOptional2, &QCheckBox::toggled, + m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetSelectionIsOptional); + connect(m_Controls.checkEnabled2, &QCheckBox::toggled, + m_Controls.multiSlot, &QmitkSingleNodeSelectionWidget::setEnabled); } void QmitkDataStorageViewerTestView::SetAsSelectionProvider1(bool checked) { if (checked) { - m_SelectionServiceConnector->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); + m_SelectionServiceConnector1->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); + connect(m_ModelViewSelectionConnector1.get(), &QmitkModelViewSelectionConnector::CurrentSelectionChanged, + m_SelectionServiceConnector1.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { - m_SelectionServiceConnector->RemoveAsSelectionProvider(); - disconnect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); + m_SelectionServiceConnector1->RemoveAsSelectionProvider(); + disconnect(m_ModelViewSelectionConnector1.get(), &QmitkModelViewSelectionConnector::CurrentSelectionChanged, + m_SelectionServiceConnector1.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener1(bool checked) { if (checked) { - m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); - connect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); + m_SelectionServiceConnector1->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); + connect(m_SelectionServiceConnector1.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_ModelViewSelectionConnector1.get(), &QmitkModelViewSelectionConnector::SetCurrentSelection); } else { - m_SelectionServiceConnector->RemovePostSelectionListener(); - disconnect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); + m_SelectionServiceConnector1->RemovePostSelectionListener(); + disconnect(m_SelectionServiceConnector1.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_ModelViewSelectionConnector1.get(), &QmitkModelViewSelectionConnector::SetCurrentSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider2(bool checked) { if (checked) { m_SelectionServiceConnector2->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_ModelViewSelectionConnector2.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector2.get(), SLOT(ChangeServiceSelection(QList))); + connect(m_ModelViewSelectionConnector2.get(), &QmitkModelViewSelectionConnector::CurrentSelectionChanged, + m_SelectionServiceConnector2.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { m_SelectionServiceConnector2->RemoveAsSelectionProvider(); - disconnect(m_ModelViewSelectionConnector2.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector2.get(), SLOT(ChangeServiceSelection(QList))); + disconnect(m_ModelViewSelectionConnector2.get(), &QmitkModelViewSelectionConnector::CurrentSelectionChanged, + m_SelectionServiceConnector2.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener2(bool checked) { if (checked) { m_SelectionServiceConnector2->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); - connect(m_SelectionServiceConnector2.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector2.get(), SLOT(SetCurrentSelection(QList))); + connect(m_SelectionServiceConnector2.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_ModelViewSelectionConnector2.get(), &QmitkModelViewSelectionConnector::SetCurrentSelection); } else { m_SelectionServiceConnector2->RemovePostSelectionListener(); - disconnect(m_SelectionServiceConnector2.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector2.get(), SLOT(SetCurrentSelection(QList))); + disconnect(m_SelectionServiceConnector2.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_ModelViewSelectionConnector2.get(), &QmitkModelViewSelectionConnector::SetCurrentSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider3(bool checked) { if (checked) { - m_SelectionServiceConnector3->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); + connect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { m_SelectionServiceConnector3->RemoveAsSelectionProvider(); - disconnect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); + disconnect(m_Controls.singleSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener3(bool checked) { if (checked) { m_SelectionServiceConnector3->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); - connect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); + connect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector3->RemovePostSelectionListener(); - disconnect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); + disconnect(m_SelectionServiceConnector3.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionProvider4(bool checked) { if (checked) { m_SelectionServiceConnector4->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); - connect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); + connect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } else { m_SelectionServiceConnector4->RemoveAsSelectionProvider(); - disconnect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); + disconnect(m_Controls.multiSlot, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ChangeServiceSelection); } } void QmitkDataStorageViewerTestView::SetAsSelectionListener4(bool checked) { if (checked) { m_SelectionServiceConnector4->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); - connect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); + connect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector4->RemovePostSelectionListener(); - disconnect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); + disconnect(m_SelectionServiceConnector4.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, + m_Controls.multiSlot, &QmitkMultiNodeSelectionWidget::SetCurrentSelection); } } -void QmitkDataStorageViewerTestView::OnOnlyImages(bool checked) +void QmitkDataStorageViewerTestView::OnOnlyImages1(bool checked) { if (checked) { m_Controls.singleSlot->SetNodePredicate(mitk::NodePredicateDataType::New("Image")); } else { m_Controls.singleSlot->SetNodePredicate(nullptr); } -}; +} void QmitkDataStorageViewerTestView::OnOnlyImages2(bool checked) { if (checked) { m_Controls.multiSlot->SetNodePredicate(mitk::NodePredicateDataType::New("Image")); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states. Only images allowed!")); } else { m_Controls.multiSlot->SetNodePredicate(nullptr); m_Controls.multiSlot->SetInvalidInfo(QStringLiteral("InvalidInfo: is displayed for invalid states")); } -}; +} void QmitkDataStorageViewerTestView::OnOnlyUneven(bool checked) { if (checked) { auto checkFunction = [](const QmitkMultiNodeSelectionWidget::NodeList & nodes) { if (!(nodes.size() % 2)) { std::stringstream ss; ss << "

Invalid selection.

The number of selected nodes must be uneven! the current number is " << nodes.size() << ".

"; return ss.str(); } return std::string(); }; m_Controls.multiSlot->SetSelectionCheckFunction(checkFunction); } else { auto checkFunction = [](const QmitkMultiNodeSelectionWidget::NodeList & /*nodes*/) { return std::string(); }; m_Controls.multiSlot->SetSelectionCheckFunction(checkFunction); } -}; +} diff --git a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.h b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.h index ff4465af2e..7bdd9390de 100644 --- a/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.h +++ b/Plugins/org.mitk.gui.qt.datastorageviewertest/src/internal/QmitkDataStorageViewerTestView.h @@ -1,74 +1,69 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKDATASTORAGEVIEWERTESTVIEW_H #define QMITKDATASTORAGEVIEWERTESTVIEW_H +// data storage viewer test plugin +#include "ui_QmitkDataStorageViewerTestControls.h" + // mitk gui qt common plugin #include #include "QmitkModelViewSelectionConnector.h" #include "QmitkSelectionServiceConnector.h" -// data storage viewer test plugin -#include "ui_QmitkDataStorageViewerTestControls.h" - -// qt widgets module -#include "QmitkDataStorageDefaultListModel.h" - /** * @brief DataStorageViewerTestView */ class QmitkDataStorageViewerTestView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; protected: void SetFocus() override; void CreateQtPartControl(QWidget* parent) override; private Q_SLOTS: void SetAsSelectionProvider1(bool checked); void SetAsSelectionProvider2(bool checked); void SetAsSelectionProvider3(bool checked); void SetAsSelectionProvider4(bool checked); void SetAsSelectionListener1(bool checked); void SetAsSelectionListener2(bool checked); void SetAsSelectionListener3(bool checked); void SetAsSelectionListener4(bool checked); - void OnOnlyImages(bool checked); + void OnOnlyImages1(bool checked); void OnOnlyImages2(bool checked); void OnOnlyUneven(bool checked); private: Ui::QmitkDataStorageViewerTestControls m_Controls; - QmitkDataStorageDefaultListModel* m_DataStorageDefaultListModel; - QmitkDataStorageDefaultListModel* m_DataStorageDefaultListModel2; - std::unique_ptr m_ModelViewSelectionConnector; - std::unique_ptr m_SelectionServiceConnector; + std::unique_ptr m_ModelViewSelectionConnector1; + std::unique_ptr m_SelectionServiceConnector1; std::unique_ptr m_ModelViewSelectionConnector2; std::unique_ptr m_SelectionServiceConnector2; std::unique_ptr m_SelectionServiceConnector3; std::unique_ptr m_SelectionServiceConnector4; }; #endif // QMITKDATASTORAGEVIEWERTESTVIEW_H diff --git a/Plugins/org.mitk.gui.qt.dicom/files.cmake b/Plugins/org.mitk.gui.qt.dicom/files.cmake index fd1508a301..d66ab515da 100644 --- a/Plugins/org.mitk.gui.qt.dicom/files.cmake +++ b/Plugins/org.mitk.gui.qt.dicom/files.cmake @@ -1,55 +1,54 @@ set(SRC_CPP_FILES ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkDicomBrowser.cpp QmitkDicomDirectoryListener.cpp QmitkStoreSCPLauncher.cpp QmitkStoreSCPLauncherBuilder.cpp QmitkDicomDataEventPublisher.cpp DicomEventHandler.cpp QmitkDicomPreferencePage.cpp ) set(UI_FILES src/internal/QmitkDicomBrowserControls.ui - src/internal/QmitkDicomPreferencePage.ui ) set(MOC_H_FILES src/internal/mitkPluginActivator.h src/internal/QmitkDicomBrowser.h src/internal/QmitkDicomDirectoryListener.h src/internal/QmitkStoreSCPLauncher.h src/internal/QmitkStoreSCPLauncherBuilder.h src/internal/QmitkDicomDataEventPublisher.h src/internal/DicomEventHandler.h src/internal/QmitkDicomPreferencePage.h ) # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench set(CACHED_RESOURCE_FILES resources/dicom.svg plugin.xml ) # list of Qt .qrc files which contain additional resources # specific to this plugin set(QRC_FILES resources/dicom.qrc ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.dicom/src/internal/QmitkDicomPreferencePage.ui b/Plugins/org.mitk.gui.qt.dicom/src/internal/QmitkDicomPreferencePage.ui deleted file mode 100644 index fac0326e9c..0000000000 --- a/Plugins/org.mitk.gui.qt.dicom/src/internal/QmitkDicomPreferencePage.ui +++ /dev/null @@ -1,492 +0,0 @@ - - - QmitkSegmentationControls - - - - 0 - 0 - 237 - 591 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - MS Shell Dlg 2 - 8 - 50 - false - false - false - false - - - - QmitkSegmentation - - - - QLayout::SetMinimumSize - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 0 - 0 - - - - Data Selection - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - QLayout::SetMinimumSize - - - 4 - - - - - - 0 - 0 - - - - Patient Image - - - - - - - - 0 - 0 - - - - Segmentation - - - - - - - - 0 - 0 - - - - Create a new segmentation - - - ... - - - - :/segmentation/btnNew.png:/segmentation/btnNew.png - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - - - - - - - 0 - 0 - - - - - - - - - 200 - 0 - 0 - - - - - - - 200 - 0 - 0 - - - - - - - 200 - 0 - 0 - - - - - - - - - 200 - 0 - 0 - - - - - - - 200 - 0 - 0 - - - - - - - 200 - 0 - 0 - - - - - - - - - 84 - 82 - 78 - - - - - - - 84 - 82 - 78 - - - - - - - 84 - 82 - 78 - - - - - - - - - 50 - false - - - - Please load an image! - - - true - - - - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - QTabWidget::tab-bar { alignment: middle; } - - - QTabWidget::North - - - QTabWidget::Triangular - - - 0 - - - - - 0 - 0 - - - - Qt::LeftToRight - - - false - - - 2D Tools - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - 0 - 0 - - - - 3D Tools - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - QmitkDataStorageComboBox - QComboBox -
QmitkDataStorageComboBox.h
-
- - QmitkToolSelectionBox - QWidget -
QmitkToolSelectionBox.h
-
- - QmitkSlicesInterpolator - QWidget -
QmitkSlicesInterpolator.h
-
- - QmitkToolGUIArea - QWidget -
QmitkToolGUIArea.h
-
-
- - QmitkToolGUIArea.h - QmitkToolSelectionBox.h - QmitkSlicesInterpolator.h - - - - - -
diff --git a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp index 2a3a0f3090..8400fd4978 100644 --- a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp @@ -1,374 +1,376 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Blueberry #include #include #include // mitk #include #include #include // Qt #include #include #include #include #include "QmitkDicomInspectorView.h" const std::string QmitkDicomInspectorView::VIEW_ID = "org.mitk.gui.qt.dicominspector"; QmitkDicomInspectorView::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { } QmitkDicomInspectorView::QmitkDicomInspectorView() : m_RenderWindowPart(nullptr) , m_PendingSliceChangedEvent(false) , m_SelectedNode(nullptr) - , m_SelectedTimeStep(0) + , m_SelectedTimePoint(0.) , m_CurrentSelectedZSlice(0) { m_SelectedPosition.Fill(0.0); } QmitkDicomInspectorView::~QmitkDicomInspectorView() { this->RemoveAllObservers(); } void QmitkDicomInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } } } void QmitkDicomInspectorView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { m_RenderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); } void QmitkDicomInspectorView::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDicomInspectorView::OnCurrentSelectionChanged); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } bool QmitkDicomInspectorView::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_RenderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { auto cmdSliceEvent = itk::SimpleMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdTimeEvent = itk::SimpleMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver(itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); } ++i; result = result && sliceNavController; } return result; } void QmitkDicomInspectorView::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); } void QmitkDicomInspectorView::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end();) { ObserverMapType::const_iterator delPos = pos++; if (nullptr == deletedPart || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } } void QmitkDicomInspectorView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; m_SelectedData = nullptr; UpdateData(); return; } if (nodes.front() != this->m_SelectedNode) { // node is selected, create DICOM tag table m_SelectedNode = nodes.front(); m_SelectedData = this->m_SelectedNode->GetData(); m_SelectedNodeTime.Modified(); UpdateData(); OnSliceChangedDelayed(); } } void QmitkDicomInspectorView::OnSliceChanged() { // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } } void QmitkDicomInspectorView::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { auto sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); } void QmitkDicomInspectorView::ValidateAndSetCurrentPosition() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); - unsigned int currentSelectedTimeStep = GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); + const auto currentSelectedTimePoint = GetRenderWindowPart()->GetSelectedTimePoint(); if (m_SelectedPosition != currentSelectedPosition - || m_SelectedTimeStep != currentSelectedTimeStep + || m_SelectedTimePoint != currentSelectedTimePoint || m_SelectedNodeTime > m_CurrentPositionTime) { - // the current position has been changed or the selected node has been changed since - // the last position validation -> check position + // the current position has been changed, the selected node has been changed since + // the last position validation or the current time position has been changed -> check position m_SelectedPosition = currentSelectedPosition; - m_SelectedTimeStep = currentSelectedTimeStep; + m_SelectedTimePoint = currentSelectedTimePoint; m_CurrentPositionTime.Modified(); m_ValidSelectedPosition = false; if (m_SelectedData.IsNull()) { return; } - mitk::BaseGeometry::Pointer geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimeStep( - m_SelectedTimeStep); + mitk::BaseGeometry::Pointer geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimePoint(m_SelectedTimePoint); // check for invalid time step if (geometry.IsNull()) { geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_ValidSelectedPosition = geometry->IsInside(m_SelectedPosition); itk::Index<3> index; geometry->WorldToIndex(m_SelectedPosition, index); m_CurrentSelectedZSlice = index[2]; } } void QmitkDicomInspectorView::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; ValidateAndSetCurrentPosition(); m_Controls.tableTags->setEnabled(m_ValidSelectedPosition); if (m_SelectedNode.IsNotNull()) { RenderTable(); } } void QmitkDicomInspectorView::RenderTable() { assert(nullptr != m_RenderWindowPart); - // configure fit information + const auto timeStep = (m_SelectedData.IsNull()) ? 0 : m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); + unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString( - element.second.prop->GetValue(m_SelectedTimeStep, m_CurrentSelectedZSlice, true, true))); + element.second.prop->GetValue(timeStep, m_CurrentSelectedZSlice, true, true))); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateData() { QStringList headers; m_Controls.tableTags->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_Tags.clear(); if (m_SelectedData.IsNotNull()) { for (const auto& element : *(m_SelectedData->GetPropertyList()->GetMap())) { if (element.first.find("DICOM") == 0) { std::istringstream stream(element.first); std::string token; std::getline(stream, token, '.'); //drop the DICOM suffix std::getline(stream, token, '.'); //group id unsigned long dcmgroup = std::stoul(token, nullptr, 16); std::getline(stream, token, '.'); //element id unsigned long dcmelement = std::stoul(token, nullptr, 16); TagInfo info(mitk::DICOMTag(dcmgroup, dcmelement), dynamic_cast(element.second.GetPointer())); m_Tags.insert(std::make_pair(element.first, info)); } } } m_Controls.tableTags->setRowCount(m_Tags.size()); unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(element.second.tag.GetGroup(), 16)); m_Controls.tableTags->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(element.second.tag.GetElement(), 16)); m_Controls.tableTags->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.tag.GetName())); m_Controls.tableTags->setItem(rowIndex, 2, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.prop->GetValue())); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateLabels() { if (m_SelectedData.IsNull()) { m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); } else { + const auto timeStep = m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); + if (m_ValidSelectedPosition) { - m_Controls.timePointValueLabel->setText(QString::number(m_SelectedTimeStep)); + m_Controls.timePointValueLabel->setText(QString::number(timeStep) + QStringLiteral("(")+ QString::number(m_SelectedTimePoint/1000.) + QStringLiteral(" [s])")); m_Controls.sliceNumberValueLabel->setText(QString::number(m_CurrentSelectedZSlice)); } else { m_Controls.timePointValueLabel->setText(QString("outside data geometry")); m_Controls.sliceNumberValueLabel->setText(QString("outside data geometry")); } } } void QmitkDicomInspectorView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } diff --git a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.h b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.h index 8746fa6126..e030b29a58 100644 --- a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.h +++ b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.h @@ -1,155 +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 QmitkDicomInspectorView_h #define QmitkDicomInspectorView_h #include "ui_QmitkDicomInspectorViewControls.h" // Blueberry #include // mitk DICOMReader module #include #include // mitk gui common plugin #include // mitk gui qt common plugin #include #include /** * @brief View class to inspect all DICOM tags available for the data of a node. */ class QmitkDicomInspectorView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: QmitkDicomInspectorView(); ~QmitkDicomInspectorView() override; static const std::string VIEW_ID; void SetFocus() override { }; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; protected: void CreateQtPartControl(QWidget* parent) override; /** @brief Initializes and sets the observers that are used to monitor changes in the selected position or time point in order to actualize the view*/ bool InitObservers(); /** @brief Removes all observers of the specific deleted slice navigation controller.*/ void RemoveObservers(const mitk::SliceNavigationController* deletedSlicer); /** @brief Removes all observers of the deletedPart. If null pointer is passed all observers will be removed.*/ void RemoveAllObservers(mitk::IRenderWindowPart* deletedPart = nullptr); /** @brief Called by the selection widget when the selection has changed.*/ void OnCurrentSelectionChanged(QList nodes); /** @brief Calls OnSliceChangedDelayed so the event isn't triggered multiple times.*/ void OnSliceChanged(); void OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/); /** @brief Sets m_currentSelectedPosition to the current selection and validates if this position is valid * for the input image of the currently selected fit. If it is valid, m_validSelectedPosition is set to true. * If the fit, his input image or geometry is not specified, it will also handled as invalid.*/ void ValidateAndSetCurrentPosition(); private Q_SLOTS: /** @brief Updates the current slice and time is correctly displayed.*/ void OnSliceChangedDelayed(); private: void RenderTable(); /** (Re-)initializes the headers of the data table.*/ void UpdateData(); void UpdateLabels(); void SetAsSelectionListener(bool checked); Ui::QmitkDicomInspectorViewControls m_Controls; mitk::IRenderWindowPart* m_RenderWindowPart; std::unique_ptr m_SelectionServiceConnector; /** Needed for observing the events for when a slice or time step is changed.*/ bool m_PendingSliceChangedEvent; /** Helper structure to manage the registered observer events.*/ struct ObserverInfo { mitk::SliceNavigationController* controller; int observerTag; std::string renderWindowName; mitk::IRenderWindowPart* renderWindowPart; ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part); }; typedef std::multimap ObserverMapType; ObserverMapType m_ObserverMap; /** @brief Currently selected node for the DICOM information.*/ mitk::DataNode::ConstPointer m_SelectedNode; /** @brief Base data of the currently selected node.*/ mitk::BaseData::ConstPointer m_SelectedData; /** @brief Valid selected position in the inspector.*/ mitk::Point3D m_SelectedPosition; /** @brief Indicates if the currently selected position is valid for the currently selected data.*/ bool m_ValidSelectedPosition; - unsigned int m_SelectedTimeStep; + mitk::TimePointType m_SelectedTimePoint; itk::IndexValueType m_CurrentSelectedZSlice; /*************************************/ /* Members for visualizing the model */ itk::TimeStamp m_SelectedNodeTime; itk::TimeStamp m_CurrentPositionTime; /**Helper structure to manage the registered observer events.*/ struct TagInfo { mitk::DICOMTag tag; mitk::DICOMProperty::ConstPointer prop; TagInfo(const mitk::DICOMTag& aTag, mitk::DICOMProperty::ConstPointer aProp) : tag(aTag) , prop(aProp) { }; }; typedef std::map TagMapType; TagMapType m_Tags; }; #endif // QmitkDicomInspectorView_h diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index 22e9fb3954..84835e20fd 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,484 +1,494 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageCropperView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.imageSelectionWidget->SetNodePredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; } void QmitkImageCropperView::OnImageSelectionChanged(QList) { bool rotationEnabled = false; auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::ImageIOBase::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } - + if (nullptr == imageNode->GetData()) + { + return; + } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Shape"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); - int timeStep = renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); - auto imageGeometry = static_cast(imageNode->GetData()->GetGeometry(timeStep)); + const auto timePoint = renderWindowPart->GetSelectedTimePoint(); + const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); boundingBoxNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("handle size factor", mitk::DoubleProperty::New(1.0 / 40.0)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } -mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(mitk::BaseGeometry::Pointer geometry) +mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); - boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()); + boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); - int timeStep = renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); + const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } + if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); + return; + } + const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); + auto image = dynamic_cast(imageNode->GetData()); auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { QString imageName; if (mask) { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) auto labelsetImageInput = dynamic_cast(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); croppedImageNode->SetProperty("layer", mitk::IntProperty::New(99)); // arbitrary, copied from segmentation functionality if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(cutter->GetOutput()); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { m_Controls.labelWarningRotation->setVisible(false); m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; } diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.h b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.h index 7bd77de7de..ba8aadb464 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.h +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.h @@ -1,90 +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 QmitkImageCropperView_h #define QmitkImageCropperView_h #include #include #include "ui_QmitkImageCropperViewControls.h" class QmitkImageCropperView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; QmitkImageCropperView(QObject *parent = nullptr); ~QmitkImageCropperView() override; void CreateQtPartControl(QWidget *parent) override; void SetFocus() override { }; protected Q_SLOTS: /*! * @brief Updates current selection of the image to crop */ void OnImageSelectionChanged(QList nodes); /*! * @brief Updates current selection of the bounding object */ void OnBoundingBoxSelectionChanged(QList nodes); /*! * @brief Creates a new bounding object */ void OnCreateNewBoundingBox(); /*! * @brief Whenever Crop button is pressed, issue a cropping action */ void OnCropping(); /*! * @brief Whenever Mask button is pressed, issue a masking action */ void OnMasking(); /*! * @brief Sets the scalar value for outside pixels in case of masking */ void OnSliderValueChanged(int slidervalue); private: void CreateBoundingShapeInteractor(bool rotationEnabled); // initializes a new bounding shape using the selected image geometry. - mitk::Geometry3D::Pointer InitializeWithImageGeometry(mitk::BaseGeometry::Pointer geometry); + mitk::Geometry3D::Pointer InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const; void ProcessImage(bool crop); void SetDefaultGUI(); QString AdaptBoundingObjectName(const QString& name) const; QWidget* m_ParentWidget; // interactor for moving and scaling the cuboid mitk::BoundingShapeInteractor::Pointer m_BoundingShapeInteractor; // cropping parameter mitk::ScalarType m_CropOutsideValue; Ui::QmitkImageCropperViewControls m_Controls; }; #endif // QmitkImageCropperView_h diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp index 86ba3b923a..2d38d0f4f1 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp @@ -1,304 +1,306 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Blueberry #include #include // Mitk #include #include #include #include "mitkRegVisPropertyTags.h" #include "mitkMatchPointPropertyTags.h" #include "mitkRegEvaluationObject.h" #include "mitkRegistrationHelper.h" #include "mitkRegEvaluationMapper2D.h" #include // Qmitk #include "QmitkRenderWindow.h" #include "QmitkMatchPointRegistrationEvaluator.h" // Qt #include #include #include const std::string QmitkMatchPointRegistrationEvaluator::VIEW_ID = "org.mitk.views.matchpoint.registration.evaluator"; const std::string QmitkMatchPointRegistrationEvaluator::HelperNodeName = "RegistrationEvaluationHelper"; QmitkMatchPointRegistrationEvaluator::QmitkMatchPointRegistrationEvaluator() - : m_Parent(nullptr), m_activeEvaluation(false), m_currentSelectedTimeStep(0) + : m_Parent(nullptr), m_activeEvaluation(false), m_currentSelectedTimePoint(0.) { m_currentSelectedPosition.Fill(0.0); } QmitkMatchPointRegistrationEvaluator::~QmitkMatchPointRegistrationEvaluator() { if (this->m_selectedEvalNode.IsNotNull() && this->GetDataStorage().IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } } void QmitkMatchPointRegistrationEvaluator::SetFocus() { } void QmitkMatchPointRegistrationEvaluator::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); } void QmitkMatchPointRegistrationEvaluator::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; this->m_Controls.registrationNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.registrationNodeSelector->SetSelectionIsOptional(true); this->m_Controls.movingNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.movingNodeSelector->SetSelectionIsOptional(false); this->m_Controls.targetNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.targetNodeSelector->SetSelectionIsOptional(false); this->m_Controls.registrationNodeSelector->SetInvalidInfo("Select valid registration."); this->m_Controls.registrationNodeSelector->SetEmptyInfo("Assuming identity. Select registration to change."); this->m_Controls.registrationNodeSelector->SetPopUpTitel("Select registration."); this->m_Controls.registrationNodeSelector->SetPopUpHint("Select a registration object that should be evaluated. If no registration is selected, identity will be assumed for evaluation."); this->m_Controls.movingNodeSelector->SetInvalidInfo("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpTitel("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpHint("Select the moving image for the evaluation. This is the image that will be mapped by the registration."); this->m_Controls.targetNodeSelector->SetInvalidInfo("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpTitel("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpHint("Select the target image for the evaluation."); + this->m_Controls.checkAutoSelect->setChecked(true); this->ConfigureNodePredicates(); connect(m_Controls.pbEval, SIGNAL(clicked()), this, SLOT(OnEvalBtnPushed())); connect(m_Controls.pbStop, SIGNAL(clicked()), this, SLOT(OnStopBtnPushed())); connect(m_Controls.evalSettings, SIGNAL(SettingsChanged(mitk::DataNode*)), this, SLOT(OnSettingsChanged(mitk::DataNode*))); connect(m_Controls.registrationNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); connect(m_Controls.movingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); connect(m_Controls.targetNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); m_selectedEvalNode = this->GetDataStorage()->GetNamedNode(HelperNodeName); this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkMatchPointRegistrationEvaluator::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkMatchPointRegistrationEvaluator::ConfigureNodePredicates() { this->m_Controls.registrationNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::RegNodePredicate()); this->m_Controls.movingNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); this->m_Controls.targetNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); } void QmitkMatchPointRegistrationEvaluator::CheckInputs() { if (!m_activeEvaluation) { + bool autoSelectInput = m_Controls.checkAutoSelect->isChecked() && this->m_spSelectedRegNode != this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_spSelectedRegNode = this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_spSelectedMovingNode = this->m_Controls.movingNodeSelector->GetSelectedNode(); this->m_spSelectedTargetNode = this->m_Controls.targetNodeSelector->GetSelectedNode(); - if (this->m_spSelectedMovingNode.IsNull() && this->m_spSelectedRegNode.IsNotNull()) + if (this->m_spSelectedRegNode.IsNotNull() && (this->m_spSelectedMovingNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_spSelectedRegNode->GetData()->GetProperty(mitk::Prop_RegAlgMovingData); if (uidProp) { //search for the moving node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer movingNode = this->GetDataStorage()->GetNode(predicate); if (movingNode.IsNotNull()) { this->m_spSelectedMovingNode = movingNode; QmitkSingleNodeSelectionWidget::NodeList selection({ movingNode }); this->m_Controls.movingNodeSelector->SetCurrentSelection(selection); } } } - if (this->m_spSelectedTargetNode.IsNull() && this->m_spSelectedRegNode.IsNotNull()) + if (this->m_spSelectedRegNode.IsNotNull() && (this->m_spSelectedTargetNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_spSelectedRegNode->GetData()->GetProperty(mitk::Prop_RegAlgTargetData); if (uidProp) { //search for the target node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer targetNode = this->GetDataStorage()->GetNode(predicate); if (targetNode.IsNotNull()) { this->m_spSelectedTargetNode = targetNode; QmitkSingleNodeSelectionWidget::NodeList selection({ targetNode }); this->m_Controls.targetNodeSelector->SetCurrentSelection(selection); } } } } } void QmitkMatchPointRegistrationEvaluator::OnNodeSelectionChanged(QList /*nodes*/) { this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::NodeRemoved(const mitk::DataNode* node) { if (node == this->m_spSelectedMovingNode || node == this->m_spSelectedRegNode || node == this->m_spSelectedTargetNode || node == this->m_selectedEvalNode) { if (node == this->m_selectedEvalNode) { this->m_selectedEvalNode = nullptr; } this->OnStopBtnPushed(); MITK_INFO << "Stopped current MatchPoint evaluation session, because at least one relevant node was removed from storage."; } } void QmitkMatchPointRegistrationEvaluator::ConfigureControls() { //config settings widget this->m_Controls.evalSettings->setVisible(m_activeEvaluation); this->m_Controls.pbEval->setEnabled(this->m_spSelectedMovingNode.IsNotNull() && this->m_spSelectedTargetNode.IsNotNull()); this->m_Controls.pbEval->setVisible(!m_activeEvaluation); this->m_Controls.pbStop->setVisible(m_activeEvaluation); this->m_Controls.registrationNodeSelector->setEnabled(!m_activeEvaluation); this->m_Controls.movingNodeSelector->setEnabled(!m_activeEvaluation); this->m_Controls.targetNodeSelector->setEnabled(!m_activeEvaluation); } void QmitkMatchPointRegistrationEvaluator::OnSliceChanged() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); - unsigned int currentSelectedTimeStep = GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); + auto currentTimePoint = GetRenderWindowPart()->GetSelectedTimePoint(); if (m_currentSelectedPosition != currentSelectedPosition - || m_currentSelectedTimeStep != currentSelectedTimeStep + || m_currentSelectedTimePoint != currentTimePoint || m_selectedNodeTime > m_currentPositionTime) { //the current position has been changed or the selected node has been changed since the last position validation -> check position m_currentSelectedPosition = currentSelectedPosition; - m_currentSelectedTimeStep = currentSelectedTimeStep; + m_currentSelectedTimePoint = currentTimePoint; m_currentPositionTime.Modified(); if (this->m_selectedEvalNode.IsNotNull()) { this->m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalCurrentPosition, mitk::GenericProperty::New(currentSelectedPosition)); } } } void QmitkMatchPointRegistrationEvaluator::OnSettingsChanged(mitk::DataNode*) { this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationEvaluator::OnEvalBtnPushed() { //reinit view mitk::RenderingManager::GetInstance()->InitializeViews(m_spSelectedTargetNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); mitk::RegEvaluationObject::Pointer regEval = mitk::RegEvaluationObject::New(); mitk::MAPRegistrationWrapper::Pointer reg; if (m_spSelectedRegNode.IsNotNull()) { reg = dynamic_cast(this->m_spSelectedRegNode->GetData()); } else { //generate a dymme reg to use reg = mitk::GenerateIdentityRegistration3D(); } regEval->SetRegistration(reg); regEval->SetTargetNode(this->m_spSelectedTargetNode); regEval->SetMovingNode(this->m_spSelectedMovingNode); if (this->m_selectedEvalNode.IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } this->m_selectedEvalNode = mitk::DataNode::New(); this->m_selectedEvalNode->SetData(regEval); mitk::RegEvaluationMapper2D::SetDefaultProperties(this->m_selectedEvalNode); this->m_selectedEvalNode->SetName(HelperNodeName); this->m_selectedEvalNode->SetBoolProperty("helper object", true); this->GetDataStorage()->Add(this->m_selectedEvalNode); this->m_Controls.evalSettings->SetNode(this->m_selectedEvalNode); this->OnSliceChanged(); this->GetRenderWindowPart()->RequestUpdate(); this->m_activeEvaluation = true; this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationEvaluator::OnStopBtnPushed() { this->m_activeEvaluation = false; if (this->m_selectedEvalNode.IsNotNull()) { this->GetDataStorage()->Remove(this->m_selectedEvalNode); } this->m_selectedEvalNode = nullptr; this->m_Controls.evalSettings->SetNode(this->m_selectedEvalNode); this->CheckInputs(); this->ConfigureControls(); this->GetRenderWindowPart()->RequestUpdate(); } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h index 099f91ad58..5aaa24643b 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h @@ -1,116 +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 __Q_MITK_MATCHPOINT_REGISTRATION_EVALUATOR_H #define __Q_MITK_MATCHPOINT_REGISTRATION_EVALUATOR_H #include #include #include #include "ui_QmitkMatchPointRegistrationEvaluator.h" /*! \brief QmitkMatchPointRegistrationEvaluator \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkFunctionality \ingroup ${plugin_target}_internal */ class QmitkMatchPointRegistrationEvaluator : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; /** * Creates smartpointer typedefs */ berryObjectMacro(QmitkMatchPointRegistrationEvaluator); QmitkMatchPointRegistrationEvaluator(); ~QmitkMatchPointRegistrationEvaluator() override; void CreateQtPartControl(QWidget *parent) override; protected slots: /// \brief Called when the user clicks the GUI button void OnEvalBtnPushed(); void OnStopBtnPushed(); void OnSettingsChanged(mitk::DataNode*); void OnSliceChanged(); void OnNodeSelectionChanged(QList nodes); protected: void NodeRemoved(const mitk::DataNode* node) override; void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; Ui::MatchPointRegistrationEvaluatorControls m_Controls; private: QWidget *m_Parent; void Error(QString msg); /** * Checks if appropriated nodes are selected in the data manager. If nodes are selected, * they are stored m_spSelectedRegNode, m_spSelectedInputNode and m_spSelectedRefNode. * They are also checked for vadility and stored in m_ValidInput,... . * It also sets the info lables accordingly.*/ void CheckInputs(); /** * Updates the state of controls regarding to selected eval object.*/ void ConfigureControls(); /** Configure the node selectors predicates according to the selected algorithm. */ void ConfigureNodePredicates(); mitk::DataNode::Pointer m_selectedEvalNode; QmitkSliceNavigationListener m_SliceChangeListener; itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentPositionTime; bool m_activeEvaluation; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; - /** @brief indicates if the currently selected position is valid for the currently selected fit. - * This it is within the input image */ - unsigned int m_currentSelectedTimeStep; + /** @brief currently selected timepoint*/ + mitk::TimePointType m_currentSelectedTimePoint; mitk::DataNode::Pointer m_spSelectedRegNode; mitk::DataNode::Pointer m_spSelectedMovingNode; mitk::DataNode::Pointer m_spSelectedTargetNode; static const std::string HelperNodeName; }; #endif // MatchPoint_h diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.ui b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.ui index c9ef2b1e0e..f9b68eb898 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.ui +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.ui @@ -1,160 +1,173 @@ MatchPointRegistrationEvaluatorControls 0 0 379 694 5 5 5 5 5 Registration: 0 40 + + + + If checked, the target and the moving image will be selected (if available) together with a selected registration. + + + Autoselect images by registration + + + true + + + Moving image: 0 40 Target image: 0 40 <html><head/><body><p>Starts the mapping of the input image with the current settings.</p></body></html> Start evaluation Stop evaluation 0 0 250 10 Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
QmitkRegEvalSettingsWidget QWidget
QmitkRegEvalSettingsWidget.h
5 5 true true true
diff --git a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.cpp b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.cpp index 4ac853520c..1205c3929e 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.cpp @@ -1,492 +1,495 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Blueberry #include #include // Mitk #include #include #include #include "mitkRegVisPropertyTags.h" #include "mitkMatchPointPropertyTags.h" #include "mitkRegEvaluationObject.h" #include "mitkRegistrationHelper.h" #include "mitkRegEvaluationMapper2D.h" #include #include #include // Qmitk #include "QmitkRenderWindow.h" #include "QmitkMatchPointRegistrationManipulator.h" #include // Qt #include #include #include #include //MatchPoint #include #include #include #include #include #include #include const std::string QmitkMatchPointRegistrationManipulator::VIEW_ID = "org.mitk.views.matchpoint.registration.manipulator"; const std::string QmitkMatchPointRegistrationManipulator::HelperNodeName = "RegistrationManipulationEvaluationHelper"; QmitkMatchPointRegistrationManipulator::QmitkMatchPointRegistrationManipulator() : m_Parent(nullptr), m_activeManipulation(false), - m_currentSelectedTimeStep(0), m_internalUpdate(false) + m_currentSelectedTimePoint(0.), m_internalUpdate(false) { m_currentSelectedPosition.Fill(0.0); } QmitkMatchPointRegistrationManipulator::~QmitkMatchPointRegistrationManipulator() { if (this->m_EvalNode.IsNotNull() && this->GetDataStorage().IsNotNull()) { this->GetDataStorage()->Remove(this->m_EvalNode); } } void QmitkMatchPointRegistrationManipulator::SetFocus() { } void QmitkMatchPointRegistrationManipulator::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); } void QmitkMatchPointRegistrationManipulator::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; this->m_Controls.registrationNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.registrationNodeSelector->SetSelectionIsOptional(false); this->m_Controls.movingNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.movingNodeSelector->SetSelectionIsOptional(false); this->m_Controls.targetNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.targetNodeSelector->SetSelectionIsOptional(false); this->m_Controls.registrationNodeSelector->SetInvalidInfo("Select base registration."); this->m_Controls.registrationNodeSelector->SetPopUpTitel("Select registration."); this->m_Controls.registrationNodeSelector->SetPopUpHint("Select a registration object that should be used as starting point for the manual manipulation."); this->m_Controls.movingNodeSelector->SetInvalidInfo("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpTitel("Select moving image."); this->m_Controls.movingNodeSelector->SetPopUpHint("Select the moving image for the evaluation. This is the image that will be mapped by the registration."); this->m_Controls.targetNodeSelector->SetInvalidInfo("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpTitel("Select target image."); this->m_Controls.targetNodeSelector->SetPopUpHint("Select the target image for the evaluation."); + this->m_Controls.checkAutoSelect->setChecked(true); this->ConfigureNodePredicates(); connect(m_Controls.pbStart, SIGNAL(clicked()), this, SLOT(OnStartBtnPushed())); connect(m_Controls.pbCancel, SIGNAL(clicked()), this, SLOT(OnCancelBtnPushed())); connect(m_Controls.pbStore, SIGNAL(clicked()), this, SLOT(OnStoreBtnPushed())); connect(m_Controls.evalSettings, SIGNAL(SettingsChanged(mitk::DataNode*)), this, SLOT(OnSettingsChanged(mitk::DataNode*))); connect(m_Controls.radioSelectedReg, SIGNAL(toggled(bool)), this, SLOT(OnRegSourceChanged())); connect(m_Controls.comboCenter, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCenterTypeChanged(int))); connect(m_Controls.manipulationWidget, SIGNAL(RegistrationChanged(map::core::RegistrationBase*)), this, SLOT(OnRegistrationChanged())); connect(m_Controls.registrationNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationManipulator::OnNodeSelectionChanged); connect(m_Controls.movingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationManipulator::OnNodeSelectionChanged); connect(m_Controls.targetNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointRegistrationManipulator::OnNodeSelectionChanged); this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); m_Controls.radioNewReg->setChecked(true); m_EvalNode = this->GetDataStorage()->GetNamedNode(HelperNodeName); this->CheckInputs(); this->StopSession(); this->ConfigureControls(); } void QmitkMatchPointRegistrationManipulator::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); } void QmitkMatchPointRegistrationManipulator::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkMatchPointRegistrationManipulator::ConfigureNodePredicates() { this->m_Controls.registrationNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::RegNodePredicate()); this->m_Controls.movingNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); this->m_Controls.targetNodeSelector->SetNodePredicate(mitk::MITKRegistrationHelper::ImageNodePredicate()); } void QmitkMatchPointRegistrationManipulator::CheckInputs() { if (!m_activeManipulation) { + bool autoSelectInput = m_Controls.checkAutoSelect->isChecked() && this->m_SelectedPreRegNode != this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_SelectedPreRegNode = this->m_Controls.registrationNodeSelector->GetSelectedNode(); this->m_SelectedMovingNode = this->m_Controls.movingNodeSelector->GetSelectedNode(); this->m_SelectedTargetNode = this->m_Controls.targetNodeSelector->GetSelectedNode(); if (this->m_SelectedPreRegNode.IsNotNull()) { mitk::MAPRegistrationWrapper* regWrapper = dynamic_cast(m_SelectedPreRegNode->GetData()); if (regWrapper) { this->m_SelectedPreReg = dynamic_cast(regWrapper->GetRegistration()); } } - if (this->m_SelectedMovingNode.IsNull() && this->m_SelectedPreRegNode.IsNotNull()) + if (this->m_SelectedPreRegNode.IsNotNull() && (this->m_SelectedMovingNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_SelectedPreRegNode->GetData()->GetProperty(mitk::Prop_RegAlgMovingData); if (uidProp) { //search for the moving node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer movingNode = this->GetDataStorage()->GetNode(predicate); if (movingNode.IsNotNull()) { this->m_SelectedMovingNode = movingNode; QmitkSingleNodeSelectionWidget::NodeList selection({ movingNode }); this->m_Controls.movingNodeSelector->SetCurrentSelection(selection); } } } - if (this->m_SelectedTargetNode.IsNull() && this->m_SelectedPreRegNode.IsNotNull()) + if (this->m_SelectedPreRegNode.IsNotNull() && (this->m_SelectedTargetNode.IsNull() || autoSelectInput)) { mitk::BaseProperty* uidProp = m_SelectedPreRegNode->GetData()->GetProperty(mitk::Prop_RegAlgTargetData); if (uidProp) { //search for the target node mitk::NodePredicateDataProperty::Pointer predicate = mitk::NodePredicateDataProperty::New(mitk::Prop_UID, uidProp); mitk::DataNode::Pointer targetNode = this->GetDataStorage()->GetNode(predicate); if (targetNode.IsNotNull()) { this->m_SelectedTargetNode = targetNode; QmitkSingleNodeSelectionWidget::NodeList selection({ targetNode }); this->m_Controls.targetNodeSelector->SetCurrentSelection(selection); } } } } } void QmitkMatchPointRegistrationManipulator::OnRegSourceChanged() { this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationManipulator::OnNodeSelectionChanged(QList /*nodes*/) { this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationManipulator::NodeRemoved(const mitk::DataNode* node) { if (node == this->m_SelectedMovingNode || node == this->m_SelectedTargetNode || node == this->m_EvalNode) { if (node == this->m_EvalNode) { this->m_EvalNode = nullptr; } if (this->m_activeManipulation) { MITK_INFO << "Stopped current MatchPoint manual registration session, because at least one relevant node was removed from storage."; } this->OnCancelBtnPushed(); } } void QmitkMatchPointRegistrationManipulator::ConfigureControls() { if (!m_activeManipulation) { QString name = "ManuelRegistration"; if (m_SelectedPreRegNode.IsNotNull()) { name = QString::fromStdString(m_SelectedPreRegNode->GetName()) + " manual refined"; } this->m_Controls.lbNewRegName->setText(name); } //config settings widget this->m_Controls.groupReg->setEnabled(!m_activeManipulation); this->m_Controls.pbStart->setEnabled(this->m_SelectedMovingNode.IsNotNull() && this->m_SelectedTargetNode.IsNotNull() && !m_activeManipulation && (this->m_Controls.radioNewReg->isChecked() || this->m_SelectedPreReg.IsNotNull())); this->m_Controls.lbNewRegName->setEnabled(m_activeManipulation); this->m_Controls.checkMapEntity->setEnabled(m_activeManipulation); this->m_Controls.tabWidget->setEnabled(m_activeManipulation); this->m_Controls.pbCancel->setEnabled(m_activeManipulation); this->m_Controls.pbStore->setEnabled(m_activeManipulation); this->m_Controls.registrationNodeSelector->setEnabled(!m_activeManipulation && this->m_Controls.radioSelectedReg->isChecked()); + this->m_Controls.checkAutoSelect->setEnabled(!m_activeManipulation && this->m_Controls.radioSelectedReg->isChecked()); this->m_Controls.movingNodeSelector->setEnabled(!m_activeManipulation); this->m_Controls.targetNodeSelector->setEnabled(!m_activeManipulation); } void QmitkMatchPointRegistrationManipulator::InitSession() { if (this->m_Controls.radioNewReg->isChecked()) { //init to map the image centers auto movingCenter = m_SelectedMovingNode->GetData()->GetTimeGeometry()->GetCenterInWorld(); auto targetCenter = m_SelectedTargetNode->GetData()->GetTimeGeometry()->GetCenterInWorld(); this->m_Controls.manipulationWidget->Initialize(movingCenter, targetCenter); } else { //use selected pre registration as baseline m_Controls.manipulationWidget->Initialize(m_SelectedPreReg); } this->m_CurrentRegistration = m_Controls.manipulationWidget->GetInterimRegistration(); this->m_CurrentRegistrationWrapper = mitk::MAPRegistrationWrapper::New(m_CurrentRegistration); this->m_Controls.comboCenter->setCurrentIndex(0); this->OnCenterTypeChanged(0); //reinit view mitk::RenderingManager::GetInstance()->InitializeViews(m_SelectedTargetNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); //generate evaluation node mitk::RegEvaluationObject::Pointer regEval = mitk::RegEvaluationObject::New(); regEval->SetRegistration(this->m_CurrentRegistrationWrapper); regEval->SetTargetNode(this->m_SelectedTargetNode); regEval->SetMovingNode(this->m_SelectedMovingNode); this->m_EvalNode = mitk::DataNode::New(); this->m_EvalNode->SetData(regEval); mitk::RegEvaluationMapper2D::SetDefaultProperties(this->m_EvalNode); this->m_EvalNode->SetName(HelperNodeName); this->m_EvalNode->SetBoolProperty("helper object", true); this->GetDataStorage()->Add(this->m_EvalNode); this->m_Controls.evalSettings->SetNode(this->m_EvalNode); this->m_activeManipulation = true; } void QmitkMatchPointRegistrationManipulator::StopSession() { this->m_activeManipulation = false; if (this->m_EvalNode.IsNotNull()) { this->GetDataStorage()->Remove(this->m_EvalNode); } this->m_EvalNode = nullptr; this->m_CurrentRegistration = nullptr; this->m_CurrentRegistrationWrapper = nullptr; m_Controls.manipulationWidget->Initialize(); } void QmitkMatchPointRegistrationManipulator::OnRegistrationChanged() { if (this->m_EvalNode.IsNotNull()) { this->m_EvalNode->Modified(); } if (this->m_CurrentRegistrationWrapper.IsNotNull()) { this->m_CurrentRegistrationWrapper->Modified(); } this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationManipulator::OnSliceChanged() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); - unsigned int currentSelectedTimeStep = GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); + auto currentTimePoint = GetRenderWindowPart()->GetSelectedTimePoint(); if (m_currentSelectedPosition != currentSelectedPosition - || m_currentSelectedTimeStep != currentSelectedTimeStep + || m_currentSelectedTimePoint != currentTimePoint || m_selectedNodeTime > m_currentPositionTime) { //the current position has been changed or the selected node has been changed since the last position validation -> check position m_currentSelectedPosition = currentSelectedPosition; - m_currentSelectedTimeStep = currentSelectedTimeStep; + m_currentSelectedTimePoint = currentTimePoint; m_currentPositionTime.Modified(); if (this->m_EvalNode.IsNotNull()) { this->m_EvalNode->SetProperty(mitk::nodeProp_RegEvalCurrentPosition, mitk::GenericProperty::New(currentSelectedPosition)); } if (m_activeManipulation && m_Controls.comboCenter->currentIndex() == 2) { //update transform with the current position. m_Controls.manipulationWidget->SetCenterOfRotation(m_currentSelectedPosition); } } } void QmitkMatchPointRegistrationManipulator::OnSettingsChanged(mitk::DataNode*) { this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationManipulator::OnStartBtnPushed() { this->InitSession(); this->OnSliceChanged(); this->GetRenderWindowPart()->RequestUpdate(); this->CheckInputs(); this->ConfigureControls(); } void QmitkMatchPointRegistrationManipulator::OnCancelBtnPushed() { this->StopSession(); this->CheckInputs(); this->ConfigureControls(); if (this->GetRenderWindowPart()) { this->GetRenderWindowPart()->RequestUpdate(); } } void QmitkMatchPointRegistrationManipulator::OnStoreBtnPushed() { map::core::RegistrationBase::Pointer newReg = this->m_Controls.manipulationWidget->GenerateRegistration(); auto newRegWrapper = mitk::MAPRegistrationWrapper::New(newReg); mitk::DataNode::Pointer spResultRegistrationNode = mitk::generateRegistrationResultNode( this->m_Controls.lbNewRegName->text().toStdString(), newRegWrapper, "org.mitk::manual_registration", mitk::EnsureUID(m_SelectedMovingNode->GetData()), mitk::EnsureUID(m_SelectedTargetNode->GetData())); this->GetDataStorage()->Add(spResultRegistrationNode); if (m_Controls.checkMapEntity->checkState() == Qt::Checked) { QmitkMappingJob* pMapJob = new QmitkMappingJob(); pMapJob->setAutoDelete(true); pMapJob->m_spInputData = this->m_SelectedMovingNode->GetData(); pMapJob->m_InputDataUID = mitk::EnsureUID(m_SelectedMovingNode->GetData()); pMapJob->m_spRegNode = spResultRegistrationNode; pMapJob->m_doGeometryRefinement = false; pMapJob->m_spRefGeometry = this->m_SelectedTargetNode->GetData()->GetGeometry()->Clone().GetPointer(); pMapJob->m_MappedName = this->m_Controls.lbNewRegName->text().toStdString() + std::string(" mapped moving data"); pMapJob->m_allowUndefPixels = true; pMapJob->m_paddingValue = 100; pMapJob->m_allowUnregPixels = true; pMapJob->m_errorValue = 200; pMapJob->m_InterpolatorLabel = "Linear Interpolation"; pMapJob->m_InterpolatorType = mitk::ImageMappingInterpolator::Linear; connect(pMapJob, SIGNAL(Error(QString)), this, SLOT(Error(QString))); connect(pMapJob, SIGNAL(MapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), this, SLOT(OnMapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), Qt::BlockingQueuedConnection); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pMapJob); } this->StopSession(); this->CheckInputs(); this->ConfigureControls(); this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationManipulator::OnMapResultIsAvailable(mitk::BaseData::Pointer spMappedData, const QmitkMappingJob* job) { mitk::DataNode::Pointer spMappedNode = mitk::generateMappedResultNode(job->m_MappedName, spMappedData, job->GetRegistration()->getRegistrationUID(), job->m_InputDataUID, job->m_doGeometryRefinement, job->m_InterpolatorLabel); this->GetDataStorage()->Add(spMappedNode); this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationManipulator::OnCenterTypeChanged(int index) { ConfigureTransformCenter(index); if (this->m_EvalNode.IsNotNull()) { this->m_EvalNode->Modified(); } if (this->m_CurrentRegistrationWrapper.IsNotNull()) { this->m_CurrentRegistrationWrapper->Modified(); } this->GetRenderWindowPart()->RequestUpdate(); } void QmitkMatchPointRegistrationManipulator::ConfigureTransformCenter(int centerType) { if (centerType == 0) { //image center auto center = m_SelectedMovingNode->GetData()->GetTimeGeometry()->GetCenterInWorld(); m_Controls.manipulationWidget->SetCenterOfRotationIsRelativeToTarget(false); m_Controls.manipulationWidget->SetCenterOfRotation(center); } else if (centerType == 1) { //world origin mitk::Point3D center; center.Fill(0.0); m_Controls.manipulationWidget->SetCenterOfRotationIsRelativeToTarget(false); m_Controls.manipulationWidget->SetCenterOfRotation(center); } else { //current selected point m_Controls.manipulationWidget->SetCenterOfRotationIsRelativeToTarget(true); m_Controls.manipulationWidget->SetCenterOfRotation(m_currentSelectedPosition); } } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.h b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.h index 1c3e0abf8f..79786b709f 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.h +++ b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.h @@ -1,150 +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 __Q_MITK_MATCHPOINT_REGISTRATION_MANIPULATOR_H #define __Q_MITK_MATCHPOINT_REGISTRATION_MANIPULATOR_H #include #include #include #include #include #include "ui_QmitkMatchPointRegistrationManipulator.h" class QmitkMappingJob; /*! \brief QmitkMatchPointRegistrationManipulator \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkFunctionality \ingroup ${plugin_target}_internal */ class QmitkMatchPointRegistrationManipulator : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; /** * Creates smartpointer typedefs */ berryObjectMacro(QmitkMatchPointRegistrationManipulator); QmitkMatchPointRegistrationManipulator(); ~QmitkMatchPointRegistrationManipulator() override; void CreateQtPartControl(QWidget *parent) override; protected slots: /// \brief Called when the user clicks the GUI button void OnStartBtnPushed(); void OnCancelBtnPushed(); void OnStoreBtnPushed(); void OnSettingsChanged(mitk::DataNode*); void OnRegSourceChanged(); void OnNodeSelectionChanged(QList nodes); void OnRegistrationChanged(); void OnCenterTypeChanged(int); void OnSliceChanged(); void OnMapResultIsAvailable(mitk::BaseData::Pointer spMappedData, const QmitkMappingJob* job); void Error(QString msg); protected: void NodeRemoved(const mitk::DataNode* node) override; void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; Ui::MatchPointRegistrationManipulatorControls m_Controls; private: QWidget *m_Parent; /** Methods returns a list of all eval nodes in the data manager.*/ QList GetEvalNodes(); /** * Checks if appropriated nodes are selected in the data manager. If nodes are selected, * they are stored m_spSelectedRegNode, m_spSelectedInputNode and m_spSelectedRefNode. * They are also checked for validity.*/ void CheckInputs(); /** * Updates the state of controls regarding to the state of the view and it objects.*/ void ConfigureControls(); /** Configure the node selectors predicates according to the selected algorithm. */ void ConfigureNodePredicates(); /** Initialize the state of the view, so the manipulation can start.*/ void InitSession(); /** Stops session, removes all obsolete members (e.g. RegEvalObject). After that the view is in a valid but inactive state.*/ void StopSession(); void ConfigureTransformCenter(int centerType); mitk::DataNode::Pointer m_EvalNode; QmitkSliceNavigationListener m_SliceChangeListener; itk::TimeStamp m_selectedNodeTime; itk::TimeStamp m_currentPositionTime; bool m_activeManipulation; /** @brief currently valid selected position in the inspector*/ mitk::Point3D m_currentSelectedPosition; - /** @brief indicates if the currently selected position is valid for the currently selected fit. - * This it is within the input image */ - unsigned int m_currentSelectedTimeStep; + /** @brief currently selected timepoint*/ + mitk::TimePointType m_currentSelectedTimePoint; mitk::DataNode::Pointer m_SelectedPreRegNode; mitk::DataNode::Pointer m_SelectedMovingNode; mitk::DataNode::Pointer m_SelectedTargetNode; mitk::MAPRegistrationWrapper::Pointer m_CurrentRegistrationWrapper; map::core::RegistrationBase::Pointer m_CurrentRegistration; using MAPRegistrationType = map::core::Registration<3, 3>; MAPRegistrationType::Pointer m_SelectedPreReg; bool m_internalUpdate; static const std::string HelperNodeName; }; #endif // MatchPoint_h diff --git a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.ui b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.ui index 573c467250..7bd5e8033a 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.ui +++ b/Plugins/org.mitk.gui.qt.matchpoint.manipulator/src/internal/QmitkMatchPointRegistrationManipulator.ui @@ -1,313 +1,326 @@ MatchPointRegistrationManipulatorControls 0 0 379 983 5 5 5 5 5 Source registration <html><head/><body><p>Select this option to start with a new registration.</p><p>The registration will be pre initialized to map the center of the selected images.</p></body></html> New registration (Center pre initialization) Use the selected registration as bsaeline. The manipulation will be "on top" of the given registration. Selected registration 0 40 + + + + If checked, the target and the moving image will be selected (if available) together with a selected pre registration. + + + Autoselect images by registration + + + true + + + Moving image: 0 40 Target image: 0 40 Starts the manipulation /manual registration. Start manual registration New registration name: Name of the resulting manual registration ManualRegistration Check if a mapped moving image should be generated if the manual registration is confirmed. Generate + store mapped result true 0 Transformation settings 6 6 6 6 Center of rotation: 0 0 Allows to set the center of rotation. Moving image center World origin Current navigator position Evaluation settings 0 0 250 10 Cancel the manual registration and resets the state of the view. Cancel <html><head/><body><p>Confirm the defined registration.</p><p>The registration will be stored (and, if activated, a mapped image will be generated).</p></body></html> Confirm + Store Qt::Vertical 20 40 QmitkRegEvalSettingsWidget QWidget
QmitkRegEvalSettingsWidget.h
QmitkRegistrationManipulationWidget QWidget
QmitkRegistrationManipulationWidget.h
1
QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
5 5 true true true
diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index be6c8c3bdb..5a2bfebebd 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,442 +1,444 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkPlanarFigureMaskGenerator.h" #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); m_Controls.sliderWidget_histogram->setPrefix("Time: "); m_Controls.sliderWidget_histogram->setDecimals(0); m_Controls.sliderWidget_histogram->setVisible(false); m_Controls.sliderWidget_intensityProfile->setPrefix("Time: "); m_Controls.sliderWidget_intensityProfile->setDecimals(0); m_Controls.sliderWidget_intensityProfile->setVisible(false); ResetGUI(); m_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); m_DataGenerator->SetDataStorage(this->GetDataStorage()); m_DataGenerator->SetAutoUpdate(true); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.imageNodesSelector->SetDataStorage(this->GetDataStorage()); m_Controls.imageNodesSelector->SetNodePredicate(mitk::GetImageStatisticsImagePredicate()); m_Controls.imageNodesSelector->SetSelectionCheckFunction(this->CheckForSameGeometry()); m_Controls.imageNodesSelector->SetSelectionIsOptional(false); m_Controls.imageNodesSelector->SetInvalidInfo(QStringLiteral("Please select images for statistics")); m_Controls.imageNodesSelector->SetPopUpTitel(QStringLiteral("Select input images")); m_Controls.roiNodesSelector->SetPopUpHint(QStringLiteral("You may select multiple images for the statistics computation. But all selected images must have the same geometry.")); m_Controls.roiNodesSelector->SetDataStorage(this->GetDataStorage()); m_Controls.roiNodesSelector->SetNodePredicate(this->GenerateROIPredicate()); m_Controls.roiNodesSelector->SetSelectionIsOptional(true); m_Controls.roiNodesSelector->SetEmptyInfo(QStringLiteral("Please select ROIs")); m_Controls.roiNodesSelector->SetPopUpTitel(QStringLiteral("Select ROIs for statistics computation")); m_Controls.roiNodesSelector->SetPopUpHint(QStringLiteral("You may select ROIs (e.g. planar figures, segmentations) that should be used for the statistics computation. The statistics will only computed for the image parts defined by the ROIs.")); CreateConnections(); } void QmitkImageStatisticsView::CreateConnections() { connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::DataGenerationStarted, this, &QmitkImageStatisticsView::OnGenerationStarted); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, this, &QmitkImageStatisticsView::OnGenerationFinished); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, this, &QmitkImageStatisticsView::OnJobError); connect(m_Controls.imageNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageStatisticsView::OnImageSelectionChanged); connect(m_Controls.roiNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageStatisticsView::OnROISelectionChanged); + + connect(m_Controls.sliderWidget_intensityProfile, &ctkSliderWidget::valueChanged, this, &QmitkImageStatisticsView::UpdateIntensityProfile); } void QmitkImageStatisticsView::UpdateIntensityProfile() { m_Controls.groupBox_intensityProfile->setVisible(false); const auto selectedImageNodes = m_Controls.imageNodesSelector->GetSelectedNodes(); const auto selectedROINodes = m_Controls.roiNodesSelector->GetSelectedNodes(); if (selectedImageNodes.size()==1 && selectedROINodes.size()==1) { //only supported for one image and roi currently auto image = dynamic_cast(selectedImageNodes.front()->GetData()); auto maskPlanarFigure = dynamic_cast(selectedROINodes.front()->GetData()); if (maskPlanarFigure != nullptr) { if (!maskPlanarFigure->IsClosed()) { mitk::Image::Pointer inputImage; if (image->GetDimension() == 4) { m_Controls.sliderWidget_intensityProfile->setVisible(true); unsigned int maxTimestep = image->GetTimeSteps(); m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); // Intensity profile can only be calculated on 3D, so extract if 4D mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); timeSelector->SetInput(image); timeSelector->SetTimeNr(currentTimestep); timeSelector->Update(); inputImage = timeSelector->GetOutput(); } else { m_Controls.sliderWidget_intensityProfile->setVisible(false); inputImage = image; } auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, maskPlanarFigure); m_Controls.groupBox_intensityProfile->setVisible(true); m_Controls.widget_intensityProfile->Reset(); m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), "Intensity Profile of " + selectedImageNodes.front()->GetName()); } } } } void QmitkImageStatisticsView::UpdateHistogramWidget() { m_Controls.groupBox_histogram->setVisible(false); const auto selectedImageNodes = m_Controls.imageNodesSelector->GetSelectedNodes(); const auto selectedMaskNodes = m_Controls.roiNodesSelector->GetSelectedNodes(); if (selectedImageNodes.size() == 1 && selectedMaskNodes.size()<=1) { //currently only supported for one image and roi due to histogram widget limitations. auto imageNode = selectedImageNodes.front(); const mitk::DataNode* roiNode = nullptr; const mitk::PlanarFigure* planarFigure = nullptr; if (!selectedMaskNodes.empty()) { roiNode = selectedMaskNodes.front(); planarFigure = dynamic_cast(roiNode->GetData()); } if (planarFigure == nullptr || planarFigure->IsClosed()) { //if a planar figure is not closed, we show the intensity profile instead of the histogram. auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode, true); if (statisticsNode.IsNotNull()) { auto statistics = dynamic_cast(statisticsNode->GetData()); if (statistics) { std::stringstream label; label << "Histogram " << imageNode->GetName(); if (imageNode->GetData()->GetTimeSteps() > 1) { label << "[0]"; } if (roiNode) { label << " with " << roiNode->GetName(); } m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(0), label.str()); } m_Controls.groupBox_histogram->setVisible(statisticsNode.IsNotNull()); } } } } QmitkChartWidget::ColorTheme QmitkImageStatisticsView::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); m_Controls.widget_histogram->SetTheme(GetColorTheme()); } void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { m_Controls.label_currentlyComputingStatistics->setVisible(true); } void QmitkImageStatisticsView::OnGenerationFinished() { m_Controls.label_currentlyComputingStatistics->setVisible(false); mitk::StatusBar::GetInstance()->Clear(); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnJobError(QString error, const QmitkDataGenerationJobBase* /*failedJob*/) { mitk::StatusBar::GetInstance()->DisplayErrorText(error.toStdString().c_str()); MITK_WARN << "Error when calculating statistics: " << error; } void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int nbins) { m_Controls.widget_statistics->SetHistogramNBins(nbins); m_DataGenerator->SetHistogramNBins(nbins); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { auto ignoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; m_Controls.widget_statistics->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); m_DataGenerator->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { auto images = m_Controls.imageNodesSelector->GetSelectedNodesStdVector(); m_Controls.widget_statistics->SetImageNodes(images); m_Controls.widget_statistics->setEnabled(!images.empty()); m_Controls.roiNodesSelector->SetNodePredicate(this->GenerateROIPredicate()); m_DataGenerator->SetAutoUpdate(false); m_DataGenerator->SetImageNodes(images); m_DataGenerator->Generate(); m_DataGenerator->SetAutoUpdate(true); this->UpdateHistogramWidget(); this->UpdateIntensityProfile(); } void QmitkImageStatisticsView::OnROISelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { auto rois = m_Controls.roiNodesSelector->GetSelectedNodesStdVector(); m_Controls.widget_statistics->SetMaskNodes(rois); m_DataGenerator->SetAutoUpdate(false); m_DataGenerator->SetROINodes(rois); m_DataGenerator->Generate(); m_DataGenerator->SetAutoUpdate(true); this->UpdateHistogramWidget(); this->UpdateIntensityProfile(); } void QmitkImageStatisticsView::OnButtonSelectionPressed() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(nullptr, "Select input for the statistic","You may select images and ROIs to compute their statistic. ROIs may be segmentations or planar figures."); dialog->SetDataStorage(GetDataStorage()); dialog->SetSelectionCheckFunction(CheckForSameGeometry()); // set predicates auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); auto isImageOrMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isMaskOrPlanarFigurePredicate, isImagePredicate); dialog->SetNodePredicate(isImageOrMaskOrPlanarFigurePredicate); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetCurrentSelection(m_Controls.imageNodesSelector->GetSelectedNodes()+m_Controls.roiNodesSelector->GetSelectedNodes()); if (dialog->exec()) { auto selectedNodeList = dialog->GetSelectedNodes(); m_Controls.imageNodesSelector->SetCurrentSelection(selectedNodeList); m_Controls.roiNodesSelector->SetCurrentSelection(selectedNodeList); } delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto lambda = [isMaskPredicate](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { auto castedData = dynamic_cast(node->GetData()); if (castedData != nullptr && !isMaskPredicate->CheckNode(node)) { imageNodeData = castedData; break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

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

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

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

"; return ss.str(); } } } return std::string(); }; return lambda; } mitk::NodePredicateBase::Pointer QmitkImageStatisticsView::GenerateROIPredicate() const { auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); mitk::NodePredicateBase::Pointer result = isMaskOrPlanarFigurePredicate.GetPointer(); if(!m_Controls.imageNodesSelector->GetSelectedNodes().empty()) { auto image = m_Controls.imageNodesSelector->GetSelectedNodes().front()->GetData(); auto imageGeoPredicate = mitk::NodePredicateSubGeometry::New(image->GetGeometry()); auto lambda = [image, imageGeoPredicate](const mitk::DataNode* node) { bool sameGeometry = true; if (dynamic_cast(node->GetData()) != nullptr) { sameGeometry = imageGeoPredicate->CheckNode(node); } else { const auto planar2 = dynamic_cast(node->GetData()); if (planar2) { sameGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), image->GetGeometry()); } } return sameGeometry; }; result = mitk::NodePredicateAnd::New(isMaskOrPlanarFigurePredicate, mitk::NodePredicateFunction::New(lambda)).GetPointer(); } return result; } diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp index 49ab5f61b9..88b344ea27 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp @@ -1,1023 +1,1061 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiLabelSegmentationView.h" // blueberry #include #include // mitk #include "mitkApplicationCursor.h" #include "mitkLabelSetImage.h" #include "mitkStatusBar.h" #include "mitkToolManagerProvider.h" #include "mitkInteractionEventObserver.h" #include "mitkPlanePositionManager.h" #include "mitkPluginActivator.h" #include "mitkSegTool2D.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateSubGeometry.h" // Qmitk #include "QmitkNewSegmentationDialog.h" #include "QmitkRenderWindow.h" #include "QmitkSegmentationOrganNamesHandling.cpp" // us #include #include #include #include #include // Qt #include #include #include #include #include #include "tinyxml.h" #include #include const std::string QmitkMultiLabelSegmentationView::VIEW_ID = "org.mitk.views.multilabelsegmentation"; QmitkMultiLabelSegmentationView::QmitkMultiLabelSegmentationView() : m_Parent(nullptr), m_IRenderWindowPart(nullptr), m_ToolManager(nullptr), m_ReferenceNode(nullptr), m_WorkingNode(nullptr), m_AutoSelectionEnabled(false), m_MouseCursorSet(false) { m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isMask = mitk::NodePredicateAnd::New(isBinary, isImage); mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); mitk::NodePredicateDataType::Pointer isDti = mitk::NodePredicateDataType::New("TensorImage"); mitk::NodePredicateDataType::Pointer isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(isMask)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); } QmitkMultiLabelSegmentationView::~QmitkMultiLabelSegmentationView() { // Loose LabelSetConnections OnLooseLabelSetConnection(); } void QmitkMultiLabelSegmentationView::CreateQtPartControl(QWidget *parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls.setupUi(parent); // *------------------------ // * Shortcuts // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls.m_ReferenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls.m_ReferenceNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_ReferenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls.m_WorkingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_WorkingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls.m_ReferenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this,&QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged); connect(m_Controls.m_WorkingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this,&QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * ToolManager // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); m_Controls.m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls.m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); // *------------------------ // * LabelSetWidget // *------------------------ m_Controls.m_LabelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls.m_LabelSetWidget->SetOrganColors(mitk::OrganNamesHandling::GetDefaultOrganColorString()); m_Controls.m_LabelSetWidget->hide(); // *------------------------ // * Interpolation // *------------------------ m_Controls.m_SurfaceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); m_Controls.m_SliceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); connect(m_Controls.m_cbInterpolation, SIGNAL(activated(int)), this, SLOT(OnInterpolationSelectionChanged(int))); m_Controls.m_cbInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->hide(); m_Controls.m_gbInterpolation->hide(); // See T27436 QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' FastMarching2D Correction 'Live Wire'"); QString segTools3D = tr("Threshold 'Two Thresholds' 'Auto Threshold' 'Multiple Otsu'"); std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // *------------------------ // * ToolSelection 2D // *------------------------ m_Controls.m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer2D); m_Controls.m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); // todo: "Correction // 'Live Wire'" m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls.m_ManualToolSelectionBox2D, SIGNAL(ToolSelected(int)), this, SLOT(OnManualTool2DSelected(int))); // *------------------------ // * ToolSelection 3D // *------------------------ m_Controls.m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer3D); m_Controls.m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); // todo add : FastMarching3D RegionGrowing Watershed m_Controls.m_ManualToolSelectionBox3D->SetLayoutColumns(2); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // *------------------------* // * Connect PushButtons (pb) // *------------------------* connect(m_Controls.m_pbNewLabel, SIGNAL(clicked()), this, SLOT(OnNewLabel())); connect(m_Controls.m_pbNewSegmentationSession, SIGNAL(clicked()), this, SLOT(OnNewSegmentationSession())); connect(m_Controls.m_pbShowLabelTable, SIGNAL(toggled(bool)), this, SLOT(OnShowLabelTable(bool))); // *------------------------* // * Connect LabelSetWidget // *------------------------* connect(m_Controls.m_LabelSetWidget, SIGNAL(goToLabel(const mitk::Point3D &)), this, SLOT(OnGoToLabel(const mitk::Point3D &))); connect(m_Controls.m_LabelSetWidget, SIGNAL(resetView()), this, SLOT(OnResetView())); // *------------------------* // * DATA SLECTION WIDGET // *------------------------* m_IRenderWindowPart = this->GetRenderWindowPart(); if (m_IRenderWindowPart) { QList controllers; controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } // this->InitializeListeners(); connect(m_Controls.m_btAddLayer, SIGNAL(clicked()), this, SLOT(OnAddLayer())); connect(m_Controls.m_btDeleteLayer, SIGNAL(clicked()), this, SLOT(OnDeleteLayer())); connect(m_Controls.m_btPreviousLayer, SIGNAL(clicked()), this, SLOT(OnPreviousLayer())); connect(m_Controls.m_btNextLayer, SIGNAL(clicked()), this, SLOT(OnNextLayer())); connect(m_Controls.m_btLockExterior, SIGNAL(toggled(bool)), this, SLOT(OnLockExteriorToggled(bool))); connect(m_Controls.m_cbActiveLayer, SIGNAL(currentIndexChanged(int)), this, SLOT(OnChangeLayer(int))); m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_btLockExterior->setEnabled(false); m_Controls.m_pbShowLabelTable->setEnabled(false); // Make sure the GUI notices if appropriate data is already present on creation m_Controls.m_ReferenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls.m_WorkingNodeSelector->SetAutoSelectNewNodes(true); } void QmitkMultiLabelSegmentationView::Activated() { m_ToolManager->SetReferenceData(m_Controls.m_ReferenceNodeSelector->GetSelectedNode()); m_ToolManager->SetWorkingData(m_Controls.m_WorkingNodeSelector->GetSelectedNode()); } void QmitkMultiLabelSegmentationView::Deactivated() { // Not yet implemented } void QmitkMultiLabelSegmentationView::Visible() { // Not yet implemented } void QmitkMultiLabelSegmentationView::Hidden() { // Not yet implemented } int QmitkMultiLabelSegmentationView::GetSizeFlags(bool width) { if (!width) { return berry::Constants::MIN | berry::Constants::MAX | berry::Constants::FILL; } else { return 0; } } int QmitkMultiLabelSegmentationView::ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) { if (width == false) { return 100; } else { return preferredResult; } } /************************************************************************/ /* protected slots */ /************************************************************************/ void QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); bool isVisible = false; workingNode->GetBoolProperty("visible", isVisible); workingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkMultiLabelSegmentationView::OnNewLabel() { m_ToolManager->ActivateTool(-1); + if (m_ReferenceNode.IsNull()) + { + QMessageBox::information( + m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); + return; + } + + if (nullptr == m_ReferenceNode->GetData()) + { + QMessageBox::information( + m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); + return; + } + mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog(m_Parent); dialog->SetSuggestionList(mitk::OrganNamesHandling::GetDefaultOrganColorString()); dialog->setWindowTitle("New Label"); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } QString segName = dialog->GetSegmentationName(); if (segName.isEmpty()) { segName = "Unnamed"; } workingImage->GetActiveLabelSet()->AddLabel(segName.toStdString(), dialog->GetColor()); UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - mitk::RenderingManager::GetInstance()->InitializeViews(workingNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + this->ReinitializeViews(); } void QmitkMultiLabelSegmentationView::OnShowLabelTable(bool value) { if (value) m_Controls.m_LabelSetWidget->show(); else m_Controls.m_LabelSetWidget->hide(); } void QmitkMultiLabelSegmentationView::OnNewSegmentationSession() { mitk::DataNode *referenceNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); if (!referenceNode) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } m_ToolManager->ActivateTool(-1); mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); assert(referenceImage); + const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + unsigned int imageTimeStep = 0; + if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) + { + imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + } + + auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Generate a static mask?"), tr("The selected image has multiple time steps. You can either generate a simple/static masks resembling the geometry of the first timestep of the image. Or you can generate a dynamic mask that equals the selected image in geometry and number of timesteps; thus a dynamic mask can change over time (e.g. according to the image)."), tr("Yes, generate a static mask"), tr("No, generate a dynamic mask"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); - newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(), 0); + newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); - referenceImage = newImage; + segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); bool ok = false; newName = QInputDialog::getText(m_Parent, "New Segmentation Session", "New name:", QLineEdit::Normal, newName, &ok); if (!ok) { return; } this->WaitCursorOn(); mitk::LabelSetImage::Pointer workingImage = mitk::LabelSetImage::New(); try { - workingImage->Initialize(referenceImage); + workingImage->Initialize(segTemplateImage); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Parent, "New Segmentation Session", "Could not create a new segmentation session.\n"); return; } this->WaitCursorOff(); mitk::DataNode::Pointer workingNode = mitk::DataNode::New(); workingNode->SetData(workingImage); workingNode->SetName(newName.toStdString()); workingImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(referenceNode->GetName().c_str())); workingImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New(newName.toStdString().c_str())); if (!GetDataStorage()->Exists(workingNode)) { GetDataStorage()->Add(workingNode, referenceNode); } m_Controls.m_WorkingNodeSelector->SetCurrentSelectedNode(workingNode); OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_IRenderWindowPart) m_IRenderWindowPart->SetSelectedPosition(pos); } void QmitkMultiLabelSegmentationView::OnResetView() { if (m_IRenderWindowPart) m_IRenderWindowPart->ForceImmediateUpdate(); } void QmitkMultiLabelSegmentationView::OnAddLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); QString question = "Do you really want to add a layer to the current segmentation session?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Add layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) return; try { WaitCursorOn(); workingImage->AddLayer(); WaitCursorOff(); } catch ( mitk::Exception& e ) { WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( m_Controls.m_LabelSetWidget, "Add Layer", "Could not add a new layer. See error log for details.\n"); return; } OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnDeleteLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); if (workingImage->GetNumberOfLayers() < 2) return; QString question = "Do you really want to delete the current layer?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } try { this->WaitCursorOn(); workingImage->RemoveLayer(); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Controls.m_LabelSetWidget, "Delete Layer", "Could not delete the currently active layer. See error log for details.\n"); return; } UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnPreviousLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); OnChangeLayer(workingImage->GetActiveLayer() - 1); } void QmitkMultiLabelSegmentationView::OnNextLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); OnChangeLayer(workingImage->GetActiveLayer() + 1); } void QmitkMultiLabelSegmentationView::OnChangeLayer(int layer) { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); this->WaitCursorOn(); workingImage->SetActiveLayer(layer); this->WaitCursorOff(); UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnDeactivateActiveTool() { m_ToolManager->ActivateTool(-1); } void QmitkMultiLabelSegmentationView::OnLockExteriorToggled(bool checked) { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); workingImage->GetLabel(0)->SetLocked(checked); } void QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged(QList /*nodes*/) { m_ToolManager->ActivateTool(-1); auto refNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); m_ReferenceNode = refNode; m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(refNode->GetData()->GetGeometry())); m_Controls.m_WorkingNodeSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer patientNodes = GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = patientNodes->begin(); iter != patientNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } UpdateControls(); } void QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged(QList /*nodes*/) { m_ToolManager->ActivateTool(-1); if (m_WorkingNode.IsNotNull()) OnLooseLabelSetConnection(); m_WorkingNode = m_Controls.m_WorkingNodeSelector->GetSelectedNode(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { OnEstablishLabelSetConnection(); if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); } UpdateControls(); if (m_WorkingNode.IsNotNull()) { m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - mitk::RenderingManager::GetInstance()->InitializeViews(m_WorkingNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + this->ReinitializeViews(); } } void QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged(int index) { if (index == 1) { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false);//OnToggleWidgetActivation(false); m_Controls.m_swInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->show(); } else if (index == 2) { m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(1); m_Controls.m_swInterpolation->show(); } else { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(2); m_Controls.m_swInterpolation->hide(); } } /************************************************************************/ /* protected */ /************************************************************************/ void QmitkMultiLabelSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Parent && m_WorkingNode.IsNotNull()) { m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); mitk::BoolProperty::Pointer drawOutline = mitk::BoolProperty::New(prefs->GetBool("draw outline", true)); mitk::BoolProperty::Pointer volumeRendering = mitk::BoolProperty::New(prefs->GetBool("volume rendering", false)); mitk::LabelSetImage* labelSetImage; mitk::DataNode* segmentation; // iterate all segmentations (binary (single label) and LabelSetImages) mitk::NodePredicateProperty::Pointer isBinaryPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateOr::Pointer allSegmentationsPredicate = mitk::NodePredicateOr::New(isBinaryPredicate, m_SegmentationPredicate); mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(allSegmentationsPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator it = allSegmentations->begin(); it != allSegmentations->end(); ++it) { segmentation = *it; labelSetImage = dynamic_cast(segmentation->GetData()); if (nullptr != labelSetImage) { // segmentation node is a multi label segmentation segmentation->SetProperty("labelset.contour.active", drawOutline); //segmentation->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); segmentation->SetProperty("volumerendering", volumeRendering); // force render window update to show outline segmentation->GetData()->Modified(); } else if (nullptr != segmentation->GetData()) { // node is actually a 'single label' segmentation, // but its outline property can be set in the 'multi label' segmentation preference page as well bool isBinary = false; segmentation->GetBoolProperty("binary", isBinary); if (isBinary) { segmentation->SetProperty("outline binary", drawOutline); segmentation->SetProperty("outline width", mitk::FloatProperty::New(2.0)); //segmentation->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); segmentation->SetProperty("volumerendering", volumeRendering); // force render window update to show outline segmentation->GetData()->Modified(); } } else { // "interpolation feedback" data nodes have binary flag but don't have a data set. So skip them for now. MITK_INFO << "DataNode " << segmentation->GetName() << " doesn't contain a base data."; } } } } void QmitkMultiLabelSegmentationView::NodeRemoved(const mitk::DataNode *node) { bool isHelperObject(false); node->GetBoolProperty("helper object", isHelperObject); if (isHelperObject) { return; } if (m_ReferenceNode.IsNotNull() && dynamic_cast(node->GetData())) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext *context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService *service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; } } void QmitkMultiLabelSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } mitk::LabelSetImage *workingImage = dynamic_cast(m_WorkingNode->GetData()); assert(workingImage); workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); } void QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } mitk::LabelSetImage *workingImage = dynamic_cast(m_WorkingNode->GetData()); assert(workingImage); // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); } void QmitkMultiLabelSegmentationView::SetFocus() { } void QmitkMultiLabelSegmentationView::UpdateControls() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasValidWorkingNode = workingNode != nullptr; m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_gbInterpolation->setEnabled(false); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_LabelSetWidget->setEnabled(false); m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btLockExterior->setChecked(false); m_Controls.m_btLockExterior->setEnabled(false); m_Controls.m_pbShowLabelTable->setChecked(false); m_Controls.m_pbShowLabelTable->setEnabled(false); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); if (hasValidWorkingNode) { // TODO adapt tool manager so that this check is done there, e.g. convenience function mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); hasValidWorkingNode = workingImage != nullptr; if (hasValidWorkingNode) { m_Controls.m_pbNewLabel->setEnabled(true); m_Controls.m_btLockExterior->setEnabled(true); m_Controls.m_pbShowLabelTable->setEnabled(true); m_Controls.m_gbInterpolation->setEnabled(true); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_LabelSetWidget->setEnabled(true); m_Controls.m_btAddLayer->setEnabled(true); int activeLayer = workingImage->GetActiveLayer(); int numberOfLayers = workingImage->GetNumberOfLayers(); m_Controls.m_cbActiveLayer->blockSignals(true); m_Controls.m_cbActiveLayer->clear(); for (unsigned int lidx = 0; lidx < workingImage->GetNumberOfLayers(); ++lidx) { m_Controls.m_cbActiveLayer->addItem(QString::number(lidx)); } m_Controls.m_cbActiveLayer->setCurrentIndex(activeLayer); m_Controls.m_cbActiveLayer->blockSignals(false); m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btDeleteLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btPreviousLayer->setEnabled(activeLayer > 0); m_Controls.m_btNextLayer->setEnabled(activeLayer != numberOfLayers - 1); m_Controls.m_btLockExterior->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); m_Controls.m_pbShowLabelTable->setChecked(workingImage->GetNumberOfLabels() > 1 /*1st is exterior*/); //MLI TODO //m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithWorkingDataVisible); } } if (hasValidWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->RequestRenderWindowUpdate(mitk::RenderingManager::REQUEST_UPDATE_ALL); } void QmitkMultiLabelSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_IRenderWindowPart != renderWindowPart) { m_IRenderWindowPart = renderWindowPart; m_Parent->setEnabled(true); QList controllers; controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } } void QmitkMultiLabelSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_ToolManager->ActivateTool(-1); m_IRenderWindowPart = nullptr; m_Parent->setEnabled(false); } void QmitkMultiLabelSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkMultiLabelSegmentationView::SetMouseCursor(const us::ModuleResource resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkMultiLabelSegmentationView::InitializeListeners() { if (m_Interactor.IsNull()) { us::Module* module = us::GetModuleContext()->GetModule(); std::vector resources = module->FindResources("/", "*", true); for (std::vector::iterator iter = resources.begin(); iter != resources.end(); ++iter) { MITK_INFO << iter->GetResourcePath(); } m_Interactor = mitk::SegmentationInteractor::New(); if (!m_Interactor->LoadStateMachine("SegmentationInteraction.xml", module)) { MITK_WARN << "Error loading state machine"; } if (!m_Interactor->SetEventConfig("ConfigSegmentation.xml", module)) { MITK_WARN << "Error loading state machine configuration"; } // Register as listener via micro services us::ServiceProperties props; props["name"] = std::string("SegmentationInteraction"); m_ServiceRegistration = us::GetModuleContext()->RegisterService(m_Interactor.GetPointer(), props); } } + +void QmitkMultiLabelSegmentationView::ReinitializeViews() const +{ + if (m_ReferenceNode.IsNotNull() && nullptr != m_ReferenceNode->GetData()) + { + const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + unsigned int imageTimeStep = 0; + if (m_ReferenceNode->GetData()->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) + { + imageTimeStep = m_ReferenceNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + } + + mitk::RenderingManager::GetInstance()->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); + } +} diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h index 345e404897..9d6bc16a0f 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h @@ -1,169 +1,171 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkMultiLabelSegmentationView_h #define QmitkMultiLabelSegmentationView_h #include #include "mitkSegmentationInteractor.h" #include #include "ui_QmitkMultiLabelSegmentationControls.h" // berry #include class QmitkRenderWindow; /** * \ingroup ToolManagerEtAl * \ingroup org_mitk_gui_qt_multilabelsegmentation_internal */ class QmitkMultiLabelSegmentationView : public QmitkAbstractView, public mitk::ILifecycleAwarePart { Q_OBJECT public: static const std::string VIEW_ID; QmitkMultiLabelSegmentationView(); ~QmitkMultiLabelSegmentationView() override; typedef std::map NodeTagMapType; // GUI setup void CreateQtPartControl(QWidget *parent) override; // ILifecycleAwarePart interface public: void Activated() override; void Deactivated() override; void Visible() override; void Hidden() override; virtual int GetSizeFlags(bool width); virtual int ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult); protected slots: // reaction to the shortcut for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the selection of any 2D segmentation tool void OnManualTool2DSelected(int id); // reaction to button "New Label" void OnNewLabel(); // reaction to button "Show Label Table" void OnShowLabelTable(bool value); // reaction to button "New Segmentation Session" void OnNewSegmentationSession(); // reaction to signal "goToLabel" from labelset widget void OnGoToLabel(const mitk::Point3D &pos); void OnResetView(); // reaction to the button "Add Layer" void OnAddLayer(); // reaction to the button "Delete Layer" void OnDeleteLayer(); // reaction to the button "Previous Layer" void OnPreviousLayer(); // reaction to the button "Next Layer" void OnNextLayer(); // reaction to the combobox change "Change Layer" void OnChangeLayer(int); // reaction to the button "Deactive Active Tool" void OnDeactivateActiveTool(); // reaction to the button "Lock exterior" void OnLockExteriorToggled(bool); // reaction to the selection of a new patient (reference) image in the DataStorage combobox void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new Segmentation (working) image in the DataStorage combobox void OnSegmentationSelectionChanged(QList nodes); // reaction to ... void OnInterpolationSelectionChanged(int); protected: // reimplemented from QmitkAbstractView void OnPreferencesChanged(const berry::IBerryPreferences* prefs) override; // reimplemented from QmitkAbstractView void NodeRemoved(const mitk::DataNode* node) override; void OnEstablishLabelSetConnection(); void OnLooseLabelSetConnection(); void SetFocus() override; void UpdateControls(); void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart); void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart); void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource, int hotspotX, int hotspotY); void InitializeListeners(); + void ReinitializeViews() const; + /// \brief the Qt parent of our GUI (NOT of this object) QWidget *m_Parent; /// \brief Qt GUI file Ui::QmitkMultiLabelSegmentationControls m_Controls; mitk::IRenderWindowPart *m_IRenderWindowPart; mitk::ToolManager *m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_AutoSelectionEnabled; bool m_MouseCursorSet; mitk::SegmentationInteractor::Pointer m_Interactor; /** * Reference to the service registration of the observer, * it is needed to unregister the observer on unload. */ us::ServiceRegistration m_ServiceRegistration; }; #endif // QmitkMultiLabelSegmentationView_h diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp index 64c496cda8..87c10cacf2 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp @@ -1,140 +1,140 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkBooleanOperationsWidget.h" #include "../../Common/QmitkDataSelectionWidget.h" #include #include #include static const char* const HelpText = "Select two different masks above"; std::string GetPrefix(mitk::BooleanOperation::Type type) { switch (type) { case mitk::BooleanOperation::Difference: return "DifferenceFrom_"; case mitk::BooleanOperation::Intersection: return "IntersectionWith_"; case mitk::BooleanOperation::Union: return "UnionWith_"; default: assert(false && "Unknown boolean operation type"); return "UNKNOWN_BOOLEAN_OPERATION_WITH_"; } } void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr) { mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetBoolProperty("binary", true); dataNode->SetName(name); dataNode->SetData(segmentation); dataStorage->Add(dataNode, parent); } QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent) : QmitkSegmentationUtilityWidget(timeNavigationController, parent) { m_Controls.setupUi(this); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::MaskPredicate); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::MaskPredicate); m_Controls.dataSelectionWidget->SetHelpText(HelpText); connect(m_Controls.dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); connect(m_Controls.differenceButton, SIGNAL(clicked()), this, SLOT(OnDifferenceButtonClicked())); connect(m_Controls.intersectionButton, SIGNAL(clicked()), this, SLOT(OnIntersectionButtonClicked())); connect(m_Controls.unionButton, SIGNAL(clicked()), this, SLOT(OnUnionButtonClicked())); } QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() { } void QmitkBooleanOperationsWidget::OnSelectionChanged(unsigned int, const mitk::DataNode*) { QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node0 = dataSelectionWidget->GetSelection(0); mitk::DataNode::Pointer node1 = dataSelectionWidget->GetSelection(1); if (node0.IsNotNull() && node1.IsNotNull() && node0 != node1) { dataSelectionWidget->SetHelpText(""); this->EnableButtons(); } else { dataSelectionWidget->SetHelpText(HelpText); this->EnableButtons(false); } } void QmitkBooleanOperationsWidget::EnableButtons(bool enable) { m_Controls.differenceButton->setEnabled(enable); m_Controls.intersectionButton->setEnabled(enable); m_Controls.unionButton->setEnabled(enable); } void QmitkBooleanOperationsWidget::OnDifferenceButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Difference); } void QmitkBooleanOperationsWidget::OnIntersectionButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Intersection); } void QmitkBooleanOperationsWidget::OnUnionButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Union); } void QmitkBooleanOperationsWidget::DoBooleanOperation(mitk::BooleanOperation::Type type) { mitk::SliceNavigationController* timeNavigationController = this->GetTimeNavigationController(); assert(timeNavigationController != nullptr); mitk::Image::Pointer segmentation0 = static_cast(m_Controls.dataSelectionWidget->GetSelection(0)->GetData()); mitk::Image::Pointer segmentation1 = static_cast(m_Controls.dataSelectionWidget->GetSelection(1)->GetData()); mitk::Image::Pointer result; try { - mitk::BooleanOperation booleanOperation(type, segmentation0, segmentation1, timeNavigationController->GetTime()->GetPos()); + mitk::BooleanOperation booleanOperation(type, segmentation0, segmentation1, timeNavigationController->GetSelectedTimePoint()); result = booleanOperation.GetResult(); } catch (const mitk::Exception &exception) { MITK_ERROR << "Boolean operation failed: " << exception.GetDescription(); } assert(result.IsNotNull()); QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; AddToDataStorage( dataSelectionWidget->GetDataStorage(), result, GetPrefix(type) + dataSelectionWidget->GetSelection(1)->GetName(), dataSelectionWidget->GetSelection(0)); } diff --git a/Plugins/org.mitk.gui.qt.pointsetinteraction/files.cmake b/Plugins/org.mitk.gui.qt.pointsetinteraction/files.cmake index 10c9760fe6..0255405a6f 100644 --- a/Plugins/org.mitk.gui.qt.pointsetinteraction/files.cmake +++ b/Plugins/org.mitk.gui.qt.pointsetinteraction/files.cmake @@ -1,35 +1,31 @@ -set(SRC_CPP_FILES - -) - set(INTERNAL_CPP_FILES QmitkPointSetInteractionView.cpp mitkPluginActivator.cpp ) set(UI_FILES src/internal/QmitkPointSetInteractionViewControls.ui ) set(MOC_H_FILES src/internal/mitkPluginActivator.h src/internal/QmitkPointSetInteractionView.h ) set(CACHED_RESOURCE_FILES resources/pointset_interaction.svg plugin.xml ) set(QRC_FILES resources/QmitkPointSetInteractionView.qrc ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cmake b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cmake deleted file mode 100755 index 3734c47189..0000000000 --- a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cmake +++ /dev/null @@ -1,11 +0,0 @@ -# QmitkPointSetInteractionView - -message(STATUS "processing QmitkPointSetInteractionView.cmake") -message(STATUS "APPMOD_CPP before: ${APPMOD_CPP}") - -set( APPMOD_H ${APPMOD_H} QmitkPointSetInteractionView.h ) -set( APPMOD_MOC_H ${APPMOD_MOC_H} QmitkPointSetInteractionView.h ) -set( APPMOD_CPP ${APPMOD_CPP} QmitkPointSetInteractionView.cpp ) -set(APPMOD_FORMS ${APPMOD_FORMS} QmitkPointSetInteractionControls.ui) -message(STATUS "APPMOD_CPP after: ${APPMOD_CPP}") - diff --git a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cpp b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cpp index 1c1d18734f..d63cf1e9d6 100755 --- a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cpp +++ b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.cpp @@ -1,147 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -// Qmitk related includes #include "QmitkPointSetInteractionView.h" -#include "ui_QmitkPointSetInteractionViewControls.h" -#include -#include #include #include -#include -#include -#include +#include +#include +#include +#include #include #include const std::string QmitkPointSetInteractionView::VIEW_ID = "org.mitk.views.pointsetinteraction"; - -QmitkPointSetInteractionView::QmitkPointSetInteractionView( QObject* /*parent*/ ) -: m_Controls(nullptr) +QmitkPointSetInteractionView::QmitkPointSetInteractionView() + : m_Controls(nullptr) { } QmitkPointSetInteractionView::~QmitkPointSetInteractionView() { } -void QmitkPointSetInteractionView::CreateQtPartControl( QWidget *parent ) +void QmitkPointSetInteractionView::CreateQtPartControl(QWidget *parent) { - m_Controls = new Ui::QmitkPointSetInteractionControls; + m_Controls = new Ui::QmitkPointSetInteractionViewControls; m_Controls->setupUi(parent); - m_Controls->m_PbAddPointSet->connect( m_Controls->m_PbAddPointSet, SIGNAL( clicked() ) - , this, SLOT( OnAddPointSetClicked() ) ); - if (mitk::IRenderWindowPart *renderWindowPart = GetRenderWindowPart()) + m_Controls->selectedPointSetWidget->SetDataStorage(GetDataStorage()); + m_Controls->selectedPointSetWidget->SetNodePredicate(mitk::NodePredicateAnd::New( + mitk::TNodePredicateDataType::New(), + mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); + + m_Controls->selectedPointSetWidget->SetSelectionIsOptional(true); + m_Controls->selectedPointSetWidget->SetAutoSelectNewNodes(true); + m_Controls->selectedPointSetWidget->SetEmptyInfo(QString("Please select a point set")); + m_Controls->selectedPointSetWidget->SetPopUpTitel(QString("Select point set")); + + connect(m_Controls->selectedPointSetWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkPointSetInteractionView::OnCurrentSelectionChanged); + + connect(m_Controls->addPointSetPushButton, &QPushButton::clicked, + this, &QmitkPointSetInteractionView::OnAddPointSetClicked); + + if (mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart()) { - // let the point set widget know about the render window part (crosshair updates) RenderWindowPartActivated(renderWindowPart); } } void QmitkPointSetInteractionView::SetFocus() { - m_Controls->m_PbAddPointSet->setFocus(); -} - -void QmitkPointSetInteractionView::OnAddPointSetClicked() -{ - //Ask for the name of the point set - bool ok = false; - QString name = QInputDialog::getText( QApplication::activeWindow() - , tr("Add point set..."), tr("Enter name for the new point set"), QLineEdit::Normal, tr("PointSet"), &ok ); - if ( ! ok || name.isEmpty() ) - return; - - // - //Create a new empty pointset - // - mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); - // - // Create a new data tree node - // - mitk::DataNode::Pointer pointSetNode = mitk::DataNode::New(); - // - // fill the data tree node with the appropriate information - // - pointSetNode->SetData( pointSet ); - pointSetNode->SetProperty( "name", mitk::StringProperty::New( name.toStdString() ) ); - pointSetNode->SetProperty( "opacity", mitk::FloatProperty::New( 1 ) ); - pointSetNode->SetColor( 1.0, 1.0, 0.0 ); - // - // add the node to the ds - // - this->GetDataStorage()->Add(pointSetNode); - - // make new selection and emulate selection for this - berry::IWorkbenchPart::Pointer nullPart; - QList selection; - selection.push_back(pointSetNode); - this->OnSelectionChanged(nullPart, selection); + m_Controls->addPointSetPushButton->setFocus(); } -void QmitkPointSetInteractionView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) +void QmitkPointSetInteractionView::OnCurrentSelectionChanged(QList nodes) { - mitk::DataNode::Pointer selectedNode; - - if(!nodes.empty()) - selectedNode = nodes.front(); - - mitk::PointSet::Pointer pointSet; - - if(selectedNode.IsNotNull()) - pointSet = dynamic_cast(selectedNode->GetData()); - - if (pointSet.IsNotNull()) + if (nodes.empty() || nodes.front().IsNull()) { - m_SelectedPointSetNode = selectedNode; - m_Controls->m_CurrentPointSetLabel->setText(QString::fromStdString(selectedNode->GetName())); - m_Controls->m_PointListWidget->SetPointSetNode(selectedNode); + m_Controls->poinSetListWidget->SetPointSetNode(nullptr); + return; } - else + + auto selectedPointSet = dynamic_cast(nodes.front()->GetData()); + if (nullptr == selectedPointSet) { - m_Controls->m_CurrentPointSetLabel->setText(tr("None")); - m_Controls->m_PointListWidget->SetPointSetNode(nullptr); + m_Controls->poinSetListWidget->SetPointSetNode(nullptr); + return; } + + m_Controls->poinSetListWidget->SetPointSetNode(nodes.front()); } -void QmitkPointSetInteractionView::NodeChanged( const mitk::DataNode* node ) +void QmitkPointSetInteractionView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { - if(node == m_SelectedPointSetNode && m_Controls->m_CurrentPointSetLabel->text().toStdString() != node->GetName()) + if (nullptr != m_Controls) { - m_Controls->m_CurrentPointSetLabel->setText(QString::fromStdString(node->GetName())); + m_Controls->poinSetListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); + m_Controls->poinSetListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); + m_Controls->poinSetListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); } } -void QmitkPointSetInteractionView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) +void QmitkPointSetInteractionView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { - if(m_Controls) + if (nullptr != m_Controls) { - m_Controls->m_PointListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); - m_Controls->m_PointListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); - m_Controls->m_PointListWidget->AddSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); + m_Controls->poinSetListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); + m_Controls->poinSetListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); + m_Controls->poinSetListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); } } -void QmitkPointSetInteractionView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) +void QmitkPointSetInteractionView::OnAddPointSetClicked() { - if(m_Controls) + // ask for the name of the point set + bool ok = false; + QString name = QInputDialog::getText(QApplication::activeWindow(), + tr("Add point set..."), tr("Enter name for the new point set"), QLineEdit::Normal, tr("PointSet"), &ok); + if (!ok || name.isEmpty()) { - m_Controls->m_PointListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); - m_Controls->m_PointListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); - m_Controls->m_PointListWidget->RemoveSliceNavigationController(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); + return; } + + mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); + mitk::DataNode::Pointer pointSetNode = mitk::DataNode::New(); + pointSetNode->SetData(pointSet); + pointSetNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); + pointSetNode->SetProperty("opacity", mitk::FloatProperty::New(1)); + pointSetNode->SetColor(1.0, 1.0, 0.0); + this->GetDataStorage()->Add(pointSetNode); + + m_Controls->selectedPointSetWidget->SetCurrentSelectedNode(pointSetNode); } diff --git a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.h b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.h index 2087d208e8..769d6dc3ab 100755 --- a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.h +++ b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionView.h @@ -1,59 +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. ============================================================================*/ -#if !defined(QmitkPointSetInteraction_H__INCLUDED) -#define QmitkPointSetInteraction_H__INCLUDED +#ifndef QMITKPOINTSETINTERACTIONVIEW_H +#define QMITKPOINTSETINTERACTIONVIEW_H -#include -#include -#include -#include -#include +#include "ui_QmitkPointSetInteractionViewControls.h" + +// mitk core #include -namespace Ui -{ -class QmitkPointSetInteractionControls; -}; +// org mitk gui common plugin +#include + +// org mitk gui qt common plugin +#include -/*! -\brief QmitkPointSetInteractionView +/** +* @brief +* +* */ class QmitkPointSetInteractionView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: + static const std::string VIEW_ID; - QmitkPointSetInteractionView(QObject *parent=nullptr); + QmitkPointSetInteractionView(); ~QmitkPointSetInteractionView() override; - - void CreateQtPartControl(QWidget *parent) override; - - /// - /// Sets the focus to an internal widget. - /// void SetFocus() override; - void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList& nodes) override; - void NodeChanged(const mitk::DataNode* node) override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; -protected slots: + +private Q_SLOT: + + void OnCurrentSelectionChanged(QList nodes); void OnAddPointSetClicked(); -protected: - Ui::QmitkPointSetInteractionControls * m_Controls; - mitk::WeakPointer m_SelectedPointSetNode; + +private: + + void CreateQtPartControl(QWidget *parent) override; + + Ui::QmitkPointSetInteractionViewControls* m_Controls; + }; -#endif // !defined(QmitkPointSetInteraction_H__INCLUDED) + +#endif // QMITKPOINTSETINTERACTIONVIEW_H diff --git a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionViewControls.ui b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionViewControls.ui index 9f398951c2..e25b3ed02b 100755 --- a/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pointsetinteraction/src/internal/QmitkPointSetInteractionViewControls.ui @@ -1,179 +1,91 @@ - QmitkPointSetInteractionControls - + QmitkPointSetInteractionViewControls + 0 0 - 376 - 580 + 300 + 600 0 0 - QmitkPointSetInteractionView + PointSet Interaction - - 6 - - - 9 - - - 9 - - - 9 - - - 9 - - - - 2 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Selected point set: - - - - - + + + - none + Selected point set - - - - - - 2 - - - 2 - - - 2 - - - 2 - - - 2 - - - + + - + 0 0 + + + 0 + 40 + + + + + + Add new point set 0 0 - - - 50 - false - false - - Current pointset - - 2 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - 0 - 0 - - - + - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+
QmitkPointListWidget QWidget
QmitkPointListWidget.h
- - mitkDataNode.h - mitkPointSet.h -
diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index b60fa47186..299883d318 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,894 +1,903 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSegTool2D.h" #include "mitkStatusBar.h" #include "QmitkNewSegmentationDialog.h" #include #include #include #include "QmitkSegmentationView.h" #include #include "mitkVtkResliceInterpolationProperty.h" #include "mitkApplicationCursor.h" #include "mitkSegmentationObjectFactory.h" #include "mitkPluginActivator.h" #include "mitkCameraController.h" #include "mitkLabelSetImage.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateSubGeometry.h" #include #include "usModuleResource.h" #include "usModuleResourceStream.h" //micro service to get the ToolManager instance #include "mitkToolManagerProvider.h" #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_MouseCursorSet(false) , m_DataSelectionChanged(false) { mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); mitk::NodePredicateDataType::Pointer isDti = mitk::NodePredicateDataType::New("TensorImage"); mitk::NodePredicateDataType::Pointer isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_IsNotAHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true))); m_IsOfTypeImagePredicate = mitk::NodePredicateAnd::New(validImages, m_IsNotAHelperObject); mitk::NodePredicateProperty::Pointer isBinaryPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer isNotBinaryPredicate = mitk::NodePredicateNot::New(isBinaryPredicate); mitk::NodePredicateAnd::Pointer isABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isBinaryPredicate); mitk::NodePredicateAnd::Pointer isNotABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isNotBinaryPredicate); m_IsASegmentationImagePredicate = mitk::NodePredicateOr::New(isABinaryImagePredicate, mitk::TNodePredicateDataType::New()); m_IsAPatientImagePredicate = mitk::NodePredicateAnd::New(isNotABinaryImagePredicate, mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New())); } QmitkSegmentationView::~QmitkSegmentationView() { if (m_Controls) { SetToolSelectionBoxesEnabled(false); // deactivate all tools mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); SetToolManagerSelection(nullptr, nullptr); } delete m_Controls; } void QmitkSegmentationView::NewNodeObjectsGenerated(mitk::ToolManager::DataVectorType* nodes) { if (!nodes) return; mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); if (!toolManager) return; for (mitk::ToolManager::DataVectorType::iterator iter = nodes->begin(); iter != nodes->end(); ++iter) { this->FireNodeSelected( *iter ); // only last iteration meaningful, multiple generated objects are not taken into account here } } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part if (m_Controls) { mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_Controls->m_SlicesInterpolator->SetDataStorage(this->GetDataStorage()); QList controllers; controllers.push_back(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->m_SlicesInterpolator->Initialize(toolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Controls != nullptr) { bool slimView = prefs->GetBool("slim view", false); m_Controls->m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls->m_ManualToolSelectionBox3D->SetShowNames(!slimView); m_Controls->btnNewSegmentation->setToolButtonStyle(slimView ? Qt::ToolButtonIconOnly : Qt::ToolButtonTextOnly); } auto autoSelectionEnabled = prefs->GetBool("auto selection", true); m_Controls->patImageSelector->SetAutoSelectNewNodes(autoSelectionEnabled); m_Controls->segImageSelector->SetAutoSelectNewNodes(autoSelectionEnabled); this->ForceDisplayPreferencesUponAllImages(); } void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer node = mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { - mitk::Image::ConstPointer image = dynamic_cast(node->GetData()); - if (image.IsNotNull()) + mitk::Image::ConstPointer referenceImage = dynamic_cast(node->GetData()); + if (referenceImage.IsNotNull()) { - if (image->GetDimension() > 1) + if (referenceImage->GetDimension() > 1) { // ask about the name and organ type of the new segmentation QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog(m_Parent); // needs a QWidget as parent, "this" is not QWidget QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString();; dialog->SetSuggestionList(organColors); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { // user clicked cancel or pressed Esc or something similar return; } - if (image->GetDimension() > 3) + const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + unsigned int imageTimeStep = 0; + if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) + { + imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + } + + auto segTemplateImage = referenceImage; + if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Generate a static mask?"),tr("The selected image has multiple time steps. You can either generate a simple/static masks resembling the geometry of the first timestep of the image. Or you can generate a dynamic mask that equals the selected image in geometry and number of timesteps; thus a dynamic mask can change over time (e.g. according to the image)."), tr("Yes, generate a static mask"), tr("No, generate a dynamic mask"), QString(), 0,0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); - selector->SetInput(image); + selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); - const auto refTimeGeometry = image->GetTimeGeometry(); + const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); - newTimeGeometry->SetTimeStepGeometry(image->GetGeometry(), 0); + newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); - image = newImage; + segTemplateImage = newImage; } } // ask the user about an organ type and name, add this information to the image's (!) propertylist // create a new image of the same dimensions and smallest possible pixel type mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); mitk::Tool* firstTool = toolManager->GetToolById(0); if (firstTool) { try { std::string newNodeName = dialog->GetSegmentationName().toStdString(); if (newNodeName.empty()) { newNodeName = "no_name"; } - mitk::DataNode::Pointer emptySegmentation = firstTool->CreateEmptySegmentationNode(image, newNodeName, dialog->GetColor()); + mitk::DataNode::Pointer emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); // initialize showVolume to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); if (!emptySegmentation) { return; // could be aborted by user } mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); // escape ';' here (replace by '\;'), see longer comment above QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->Put("Organ-Color-List", stringForStorage); this->GetPreferences()->Flush(); if (mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)->SetSelected(false); } emptySegmentation->SetSelected(true); this->GetDataStorage()->Add(emptySegmentation, node); // add as a child, because the segmentation "derives" from the original m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); - mitk::RenderingManager::GetInstance()->InitializeViews(emptySegmentation->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + mitk::RenderingManager::GetInstance()->InitializeViews(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); } catch (const std::bad_alloc&) { QMessageBox::warning(nullptr, tr("Create new segmentation"), tr("Could not allocate memory for new segmentation")); } } } else { QMessageBox::information(nullptr, tr("Segmentation"), tr("Segmentation is currently not supported for 2D images")); } } } else { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a patient image is selected..."; } } void QmitkSegmentationView::OnVisiblePropertyChanged() { this->CheckRenderingState(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode *node) { if (!m_IsASegmentationImagePredicate->CheckNode(node)) { return; } itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnVisiblePropertyChanged); m_WorkingDataObserverTags.insert(std::pair(const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); ApplyDisplayOptions(const_cast(node)); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_IsASegmentationImagePredicate->CheckNode(node)) { //First of all remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations(node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; if ((mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0) == node) && m_Controls->patImageSelector->GetSelectedNode().IsNotNull()) { this->SetToolManagerSelection(mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetReferenceData(0), nullptr); this->UpdateWarningLabel(tr("Select or create a segmentation")); } mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } mitk::DataNode* tempNode = const_cast(node); //Remove observer if one was registered auto finding = m_WorkingDataObserverTags.find(tempNode); if (finding != m_WorkingDataObserverTags.end()) { node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); m_WorkingDataObserverTags.erase(tempNode); } } void QmitkSegmentationView::OnPatientSelectionChanged(QList nodes) { if(! nodes.empty()) { this->UpdateWarningLabel(""); auto node = nodes.first(); auto segPredicate = mitk::NodePredicateAnd::New(m_IsASegmentationImagePredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(node->GetData()->GetGeometry())); m_Controls->segImageSelector->SetNodePredicate(segPredicate); mitk::DataNode* segNode = m_Controls->segImageSelector->GetSelectedNode(); this->SetToolManagerSelection(node, segNode); if (segNode) { //Doing this we can assure that the segmentation is always visible if the segmentation and the patient image are //loaded separately int layer(10); node->GetIntProperty("layer", layer); layer++; segNode->SetProperty("layer", mitk::IntProperty::New(layer)); this->CheckRenderingState(); } else { this->SetToolSelectionBoxesEnabled( false ); this->UpdateWarningLabel(tr("Select or create a segmentation")); } } else { m_Controls->segImageSelector->SetNodePredicate(m_IsASegmentationImagePredicate); this->UpdateWarningLabel(tr("Please select an image!")); this->SetToolSelectionBoxesEnabled( false ); } } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { if (nodes.empty()) { this->UpdateWarningLabel(tr("Select or create a segmentation")); this->SetToolSelectionBoxesEnabled( false ); return; } auto refNode = m_Controls->patImageSelector->GetSelectedNode(); auto segNode = nodes.front(); if (!refNode) { this->UpdateWarningLabel(tr("Please select the matching patient image!")); this->SetToolSelectionBoxesEnabled(false); this->SetToolManagerSelection(nullptr, segNode); return; } this->CheckRenderingState(); if ( m_Controls->lblSegmentationWarnings->isVisible()) // "this->CheckRenderingState()" caused a warning. we do not need to go any further return; this->SetToolManagerSelection(refNode, segNode); if (segNode) { //Doing this we can assure that the segmenation is always visible if the segmentation and the patient image are //loaded separately int layer(10); refNode->GetIntProperty("layer", layer); layer++; segNode->SetProperty("layer", mitk::IntProperty::New(layer)); } else { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("Select or create a segmentation")); } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); if (!renderWindowPart || !segNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer())) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); this->SetToolSelectionBoxesEnabled( false ); } } void QmitkSegmentationView::OnShowMarkerNodes (bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetTools().size(); for(unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetToolById(i)); if (manualSegmentationTool) { if(state == true) { manualSegmentationTool->SetShowMarkerNodes( true ); } else { manualSegmentationTool->SetShowMarkerNodes( false ); } } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode *node) { QmitkRenderWindow* selectedRenderWindow = nullptr; QmitkRenderWindow* axialRenderWindow = GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow("axial"); QmitkRenderWindow* sagittalRenderWindow = GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow("sagittal"); QmitkRenderWindow* coronalRenderWindow = GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow("coronal"); QmitkRenderWindow* _3DRenderWindow = GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, _3DRenderWindow->GetRenderer())) { selectedRenderWindow = _3DRenderWindow; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t+1).c_str())-1; { ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); } selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes) { if (nodes.size() != 0) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } } void QmitkSegmentationView::OnTabWidgetChanged(int id) { //always disable tools on tab changed mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); //2D Tab ID = 0 //3D Tab ID = 1 if (id == 0) { //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox3D->hide(); m_Controls->m_ManualToolSelectionBox2D->show(); //Deactivate possible active tool //TODO Remove possible visible interpolations -> Maybe changes in SlicesInterpolator } else { //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox2D->hide(); m_Controls->m_ManualToolSelectionBox3D->show(); //Deactivate possible active tool } } void QmitkSegmentationView::SetToolManagerSelection(mitk::DataNode* referenceData, mitk::DataNode* workingData) { // called as a result of new BlueBerry selections // tells the ToolManager for manual segmentation about new selections // updates GUI information about what the user should select mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); toolManager->SetReferenceData(const_cast(referenceData)); toolManager->SetWorkingData(const_cast(workingData)); m_Controls->btnNewSegmentation->setEnabled(referenceData != nullptr); } void QmitkSegmentationView::ForceDisplayPreferencesUponAllImages() { if (!m_Parent) { return; } // check all images and segmentations in DataStorage: // (items in brackets are implicitly done by previous steps) // 1. // if a reference image is selected, // show the reference image // and hide all other images (orignal and segmentation), // (and hide all segmentations of the other original images) // and show all the reference's segmentations // if no reference image is selected, do do nothing // // 2. // if a segmentation is selected, // show it // (and hide all all its siblings (childs of the same parent, incl, nullptr parent)) // if no segmentation is selected, do nothing if (!m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); mitk::DataNode::Pointer referenceData = toolManager->GetReferenceData(0); mitk::DataNode::Pointer workingData = toolManager->GetWorkingData(0); // 1. if (referenceData.IsNotNull()) { // iterate all images mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_IsASegmentationImagePredicate); for ( mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; // apply display preferences ApplyDisplayOptions(node); // set visibility node->SetVisibility(node == referenceData); } } // 2. if (workingData.IsNotNull()) workingData->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (!node) { return; } mitk::BoolProperty::Pointer drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); mitk::BoolProperty::Pointer volumeRendering = mitk::BoolProperty::New(GetPreferences()->GetBool("volume rendering", false)); mitk::LabelSetImage* labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is actually a multi label segmentation, // but its outline property can be set in the 'single label' segmentation preference page as well node->SetProperty("labelset.contour.active", drawOutline); //node->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); node->SetProperty("volumerendering", volumeRendering); // force render window update to show outline node->GetData()->Modified(); } else { // node is a 'single label' segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); //node->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); node->SetProperty("volumerendering", volumeRendering); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::CheckRenderingState() { mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); mitk::DataNode* workingNode = m_Controls->segImageSelector->GetSelectedNode(); if (!workingNode) { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("Select or create a segmentation")); return; } bool selectedNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!selectedNodeIsVisible) { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image if aligned with the worldgeometry * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry* worldGeo = this->GetRenderWindowPart()->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (workingNode && worldGeo) { const mitk::BaseGeometry* workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* worldGeo = this->GetRenderWindowPart()->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), workingNode); this->SetToolSelectionBoxesEnabled(true); this->UpdateWarningLabel(""); return; } } this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), nullptr); this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) m_Controls->lblSegmentationWarnings->hide(); else m_Controls->lblSegmentationWarnings->show(); m_Controls->lblSegmentationWarnings->setText("" + text + ""); } void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->patImageSelector->SetDataStorage(GetDataStorage()); m_Controls->patImageSelector->SetNodePredicate(m_IsAPatientImagePredicate); m_Controls->patImageSelector->SetSelectionIsOptional(false); m_Controls->patImageSelector->SetInvalidInfo("Select an image."); m_Controls->patImageSelector->SetPopUpTitel("Select an image."); m_Controls->patImageSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); UpdateWarningLabel(tr("Please select an image")); if (m_Controls->patImageSelector->GetSelectedNode().IsNotNull()) { UpdateWarningLabel(tr("Select or create a new segmentation")); } m_Controls->segImageSelector->SetDataStorage(GetDataStorage()); m_Controls->segImageSelector->SetNodePredicate(m_IsASegmentationImagePredicate); m_Controls->segImageSelector->SetSelectionIsOptional(false); m_Controls->segImageSelector->SetInvalidInfo("Select a segmentation."); m_Controls->segImageSelector->SetPopUpTitel("Select a segmentation."); m_Controls->segImageSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); if (m_Controls->segImageSelector->GetSelectedNode().IsNotNull()) { UpdateWarningLabel(""); } mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); assert(toolManager); toolManager->SetDataStorage(*(GetDataStorage())); toolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Correction Paint Wipe 'Region Growing' Fill Erase 'Live Wire' '2D Fast Marching'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Fast Marching 3D' 'Region Growing 3D' Watershed Picking"); std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = toolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // all part of open source MITK m_Controls->m_ManualToolSelectionBox2D->setEnabled(true); m_Controls->m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer2D); m_Controls->m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); //setup 3D Tools m_Controls->m_ManualToolSelectionBox3D->setEnabled(true); m_Controls->m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer3D); //specify tools to be added to 3D Tool area m_Controls->m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox3D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox3D->hide(); m_Controls->m_ManualToolSelectionBox2D->show(); // update the list of segmentations toolManager->NewNodeObjectsGenerated += mitk::MessageDelegate1(this, &QmitkSegmentationView::NewNodeObjectsGenerated); // create signal/slot connections connect(m_Controls->patImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnPatientSelectionChanged); connect(m_Controls->segImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); connect(m_Controls->btnNewSegmentation, &QToolButton::clicked, this, &QmitkSegmentationView::CreateNewSegmentation); connect(m_Controls->tabWidgetSegmentationTools, &QTabWidget::currentChanged, this, &QmitkSegmentationView::OnTabWidgetChanged); connect(m_Controls->m_SlicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); // set callback function for already existing nodes (images & segmentations) mitk::DataStorage::SetOfObjects::ConstPointer allImages = GetDataStorage()->GetSubset(m_IsOfTypeImagePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnVisiblePropertyChanged); m_WorkingDataObserverTags.insert(std::pair(node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); } itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::CheckRenderingState); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), m_Controls->segImageSelector->GetSelectedNode()); m_RenderWindowPart = GetRenderWindowPart(); if (m_RenderWindowPart) { RenderWindowPartActivated(m_RenderWindowPart); } //Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->patImageSelector->SetAutoSelectNewNodes(true); m_Controls->segImageSelector->SetAutoSelectNewNodes(true); } void QmitkSegmentationView::SetFocus() { m_Controls->btnNewSegmentation->setFocus(); } void QmitkSegmentationView::OnManualTool2DSelected(int id) { if (id >= 0) { std::string text = "Active Tool: \""; mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); text += toolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = toolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } else { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); } } void QmitkSegmentationView::ResetMouseCursor() { if ( m_MouseCursorSet ) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor( const us::ModuleResource& resource, int hotspotX, int hotspotY ) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::SetToolSelectionBoxesEnabled(bool status) { if (status) { m_Controls->m_ManualToolSelectionBox2D->RecreateButtons(); m_Controls->m_ManualToolSelectionBox3D->RecreateButtons(); } m_Controls->m_ManualToolSelectionBox2D->setEnabled(status); m_Controls->m_ManualToolSelectionBox3D->setEnabled(status); m_Controls->m_SlicesInterpolator->setEnabled(status); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp index d66b78dc14..abb337ba5b 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp @@ -1,141 +1,141 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkBooleanOperationsWidget.h" #include "../../Common/QmitkDataSelectionWidget.h" #include #include #include #include static const char* const HelpText = "Select two different segmentations above"; static std::string GetPrefix(mitk::BooleanOperation::Type type) { switch (type) { case mitk::BooleanOperation::Difference: return "DifferenceFrom_"; case mitk::BooleanOperation::Intersection: return "IntersectionWith_"; case mitk::BooleanOperation::Union: return "UnionWith_"; default: assert(false && "Unknown boolean operation type"); return "UNKNOWN_BOOLEAN_OPERATION_WITH_"; } } static void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr) { auto dataNode = mitk::DataNode::New(); dataNode->SetName(name); dataNode->SetData(segmentation); dataStorage->Add(dataNode, parent); } QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent) : QmitkSegmentationUtilityWidget(timeNavigationController, parent) { m_Controls.setupUi(this); m_Controls.dataSelectionWidget->AddDataSelection("", "Select 1st segmentation", "Select 1st segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); m_Controls.dataSelectionWidget->AddDataSelection("", "Select 2nd segmentation", "Select 2nd segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); m_Controls.dataSelectionWidget->SetHelpText(HelpText); connect(m_Controls.dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); connect(m_Controls.differenceButton, SIGNAL(clicked()), this, SLOT(OnDifferenceButtonClicked())); connect(m_Controls.intersectionButton, SIGNAL(clicked()), this, SLOT(OnIntersectionButtonClicked())); connect(m_Controls.unionButton, SIGNAL(clicked()), this, SLOT(OnUnionButtonClicked())); } QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() { } void QmitkBooleanOperationsWidget::OnSelectionChanged(unsigned int, const mitk::DataNode*) { auto dataSelectionWidget = m_Controls.dataSelectionWidget; auto nodeA = dataSelectionWidget->GetSelection(0); auto nodeB = dataSelectionWidget->GetSelection(1); if (nodeA.IsNotNull() && nodeB.IsNotNull() && nodeA != nodeB) { dataSelectionWidget->SetHelpText(""); this->EnableButtons(); } else { dataSelectionWidget->SetHelpText(HelpText); this->EnableButtons(false); } } void QmitkBooleanOperationsWidget::EnableButtons(bool enable) { m_Controls.differenceButton->setEnabled(enable); m_Controls.intersectionButton->setEnabled(enable); m_Controls.unionButton->setEnabled(enable); } void QmitkBooleanOperationsWidget::OnDifferenceButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Difference); } void QmitkBooleanOperationsWidget::OnIntersectionButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Intersection); } void QmitkBooleanOperationsWidget::OnUnionButtonClicked() { this->DoBooleanOperation(mitk::BooleanOperation::Union); } void QmitkBooleanOperationsWidget::DoBooleanOperation(mitk::BooleanOperation::Type type) { auto timeNavigationController = this->GetTimeNavigationController(); assert(timeNavigationController != nullptr); mitk::Image::Pointer segmentationA = dynamic_cast(m_Controls.dataSelectionWidget->GetSelection(0)->GetData()); mitk::Image::Pointer segmentationB = dynamic_cast(m_Controls.dataSelectionWidget->GetSelection(1)->GetData()); mitk::Image::Pointer result; try { - mitk::BooleanOperation booleanOperation(type, segmentationA, segmentationB, timeNavigationController->GetTime()->GetPos()); + mitk::BooleanOperation booleanOperation(type, segmentationA, segmentationB, timeNavigationController->GetSelectedTimePoint()); result = booleanOperation.GetResult(); assert(result.IsNotNull()); auto dataSelectionWidget = m_Controls.dataSelectionWidget; AddToDataStorage( dataSelectionWidget->GetDataStorage(), result, GetPrefix(type) + dataSelectionWidget->GetSelection(1)->GetName(), dataSelectionWidget->GetSelection(0)); } catch (const mitk::Exception& exception) { MITK_ERROR << "Boolean operation failed: " << exception.GetDescription(); QMessageBox::information(nullptr, "Boolean operation failed", exception.GetDescription()); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp index eb5efa0705..59ff902954 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp @@ -1,226 +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 "QmitkContourModelToImageWidget.h" #include "mitkImage.h" #include "../../Common/QmitkDataSelectionWidget.h" #include #include #include #include #include #include #include static const char* const HelpText = "Select a image and a contour(set)"; class QmitkContourModelToImageWidgetPrivate { public: QmitkContourModelToImageWidgetPrivate(); ~QmitkContourModelToImageWidgetPrivate(); /** @brief Check if selections is valid. */ void SelectionControl( unsigned int index, const mitk::DataNode* selection); /** @brief Enable buttons if data selction is valid. */ void EnableButtons(bool enable = true); /** @brief Does the actual contour filling */ - mitk::Image::Pointer FillContourModelSetIntoImage(mitk::Image *image, mitk::ContourModelSet *contourSet, unsigned int timeStep); + mitk::Image::Pointer FillContourModelSetIntoImage(mitk::Image *image, mitk::ContourModelSet *contourSet, mitk::TimePointType timePoint); Ui::QmitkContourModelToImageWidgetControls m_Controls; QFutureWatcher m_Watcher; }; QmitkContourModelToImageWidgetPrivate::QmitkContourModelToImageWidgetPrivate() { } QmitkContourModelToImageWidgetPrivate::~QmitkContourModelToImageWidgetPrivate() { } void QmitkContourModelToImageWidgetPrivate::EnableButtons(bool enable) { m_Controls.btnProcess->setEnabled(enable); } void QmitkContourModelToImageWidgetPrivate::SelectionControl(unsigned int index, const mitk::DataNode* /*selection*/) { QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node = dataSelectionWidget->GetSelection(index); dataSelectionWidget->SetHelpText(""); this->EnableButtons(); } -mitk::Image::Pointer QmitkContourModelToImageWidgetPrivate::FillContourModelSetIntoImage(mitk::Image* image, mitk::ContourModelSet* contourSet, unsigned int timeStep) +mitk::Image::Pointer QmitkContourModelToImageWidgetPrivate::FillContourModelSetIntoImage(mitk::Image* image, mitk::ContourModelSet* contourSet, mitk::TimePointType timePoint) { // Use mitk::ContourModelSetToImageFilter to fill the ContourModelSet into the image mitk::ContourModelSetToImageFilter::Pointer contourFiller = mitk::ContourModelSetToImageFilter::New(); + auto timeStep = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); contourFiller->SetTimeStep(timeStep); contourFiller->SetImage(image); contourFiller->SetInput(contourSet); contourFiller->MakeOutputBinaryOn(); - contourFiller->Update(); + mitk::Image::Pointer result = nullptr; + + try + { + contourFiller->Update(); + result = contourFiller->GetOutput(); + } + catch (const std::exception & e) + { + MITK_ERROR << "Error while converting contour model. "<< e.what(); + } + catch (...) + { + MITK_ERROR << "Unknown error while converting contour model."; + } - mitk::Image::Pointer result = contourFiller->GetOutput(); if (result.IsNull()) { MITK_ERROR<<"Could not write the selected contours into the image!"; } result->DisconnectPipeline(); return result; } void QmitkContourModelToImageWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode* selection) { Q_D(QmitkContourModelToImageWidget); QmitkDataSelectionWidget* dataSelectionWidget = d->m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node0 = dataSelectionWidget->GetSelection(0); mitk::DataNode::Pointer node1 = dataSelectionWidget->GetSelection(1); if (node0.IsNull() || node1.IsNull() ) { d->EnableButtons(false); dataSelectionWidget->SetHelpText(HelpText); } else { d->SelectionControl(index, selection); } } void QmitkContourModelToImageWidget::OnProcessingFinished() { // Called when processing finished // Adding the result to the data storage Q_D(QmitkContourModelToImageWidget); // Adding the result to the data storage mitk::Image::Pointer result = d->m_Watcher.result(); if (result.IsNotNull()) { QmitkDataSelectionWidget* dataSelectionWidget = d->m_Controls.dataSelectionWidget; mitk::DataNode::Pointer imageNode = dataSelectionWidget->GetSelection(0); mitk::DataNode::Pointer contourNode = dataSelectionWidget->GetSelection(1); mitk::DataNode::Pointer filled = mitk::DataNode::New(); std::stringstream stream; stream << imageNode->GetName(); stream << "_"; stream << contourNode->GetName(); filled->SetName(stream.str()); filled->SetData(result); dataSelectionWidget->GetDataStorage()->Add(filled, imageNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { MITK_ERROR<<"Error filling contours into an image!"; } d->EnableButtons(); } void QmitkContourModelToImageWidget::OnProcessPressed() { Q_D(QmitkContourModelToImageWidget); QmitkDataSelectionWidget* dataSelectionWidget = d->m_Controls.dataSelectionWidget; mitk::DataNode::Pointer imageNode = dataSelectionWidget->GetSelection(0); mitk::DataNode::Pointer contourNode = dataSelectionWidget->GetSelection(1); // Check if data nodes are valid if(imageNode.IsNull() || contourNode.IsNull() ) { MITK_ERROR << "Selection does not contain valid data"; QMessageBox::information( this, "Contour To Image", "Selection does not contain valid data, please select a binary image and a contour(set)", QMessageBox::Ok ); d->m_Controls.btnProcess->setEnabled(false); return; } mitk::Image::Pointer image = static_cast(imageNode->GetData()); // Check if the image is valid if (image.IsNull()) { MITK_ERROR<<"Error writing contours into image! Invalid image data selected!"; return; } - unsigned int timeStep = this->GetTimeNavigationController()->GetTime()->GetPos(); + const auto timePoint = this->GetTimeNavigationController()->GetSelectedTimePoint(); + if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_ERROR << "Error writing contours into image! Currently selected time point is not supported by selected image data."; + return; + } // Check if the selected contours are valid mitk::ContourModelSet::Pointer contourSet; mitk::ContourModel::Pointer contour = dynamic_cast(contourNode->GetData()); if (contour.IsNotNull()) { contourSet = mitk::ContourModelSet::New(); contourSet->AddContourModel(contour); } else { contourSet = static_cast(contourNode->GetData()); if (contourSet.IsNull()) { MITK_ERROR<<"Error writing contours into binary image! Invalid contour data selected!"; return; } } //Disable Buttons during calculation and initialize Progressbar d->EnableButtons(false); // Start the computation in a background thread - QFuture< mitk::Image::Pointer > future = QtConcurrent::run(d, &QmitkContourModelToImageWidgetPrivate::FillContourModelSetIntoImage, image, contourSet, timeStep); + QFuture< mitk::Image::Pointer > future = QtConcurrent::run(d, &QmitkContourModelToImageWidgetPrivate::FillContourModelSetIntoImage, image, contourSet, timePoint); d->m_Watcher.setFuture(future); } QmitkContourModelToImageWidget::QmitkContourModelToImageWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent) : QmitkSegmentationUtilityWidget(timeNavigationController, parent), d_ptr(new QmitkContourModelToImageWidgetPrivate()) { Q_D(QmitkContourModelToImageWidget); // Set up UI d->m_Controls.setupUi(this); d->m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ImageAndSegmentationPredicate); d->m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ContourModelPredicate); d->m_Controls.dataSelectionWidget->SetHelpText(HelpText); d->EnableButtons(false); // Create connections connect (d->m_Controls.btnProcess, SIGNAL(pressed()), this, SLOT(OnProcessPressed())); connect(d->m_Controls.dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); connect(&d->m_Watcher, SIGNAL(finished()), this, SLOT(OnProcessingFinished())); if( d->m_Controls.dataSelectionWidget->GetSelection(0).IsNotNull() && d->m_Controls.dataSelectionWidget->GetSelection(1).IsNotNull() ) { OnSelectionChanged(0, d->m_Controls.dataSelectionWidget->GetSelection(0)); } } QmitkContourModelToImageWidget::~QmitkContourModelToImageWidget() { }