diff --git a/CMake/BuildConfigurations/Default.cmake b/CMake/BuildConfigurations/Default.cmake index 08e48e4bfd..7ec36d8732 100644 --- a/CMake/BuildConfigurations/Default.cmake +++ b/CMake/BuildConfigurations/Default.cmake @@ -1,25 +1,24 @@ set(MITK_CONFIG_PACKAGES ACVD Qt5 BLUEBERRY ) set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.mitkworkbench.intro org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.mxnmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization - org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper org.mitk.gui.qt.pixelvalue ) diff --git a/CMake/BuildConfigurations/mitkNavigationModules.cmake b/CMake/BuildConfigurations/mitkNavigationModules.cmake index d6881034ab..a5eb033617 100644 --- a/CMake/BuildConfigurations/mitkNavigationModules.cmake +++ b/CMake/BuildConfigurations/mitkNavigationModules.cmake @@ -1,35 +1,34 @@ message(STATUS "Configuring MITK Navigation Modules Build") set(MITK_CONFIG_PACKAGES ACVD Qt5 BLUEBERRY ) # Enable open cv and open igt link, which is a necessary configuration set(MITK_USE_OpenCV ON CACHE BOOL "MITK Use OpenCV Library" FORCE) set(MITK_USE_OpenIGTLink ON CACHE BOOL "MITK Use OpenIGTLink Library" FORCE) # Enable default plugins and the navigation modules set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization - org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.registration org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper org.mitk.gui.qt.igttracking org.mitk.gui.qt.openigtlink org.mitk.gui.qt.ultrasound org.mitk.gui.qt.tofutil ) diff --git a/CMake/Whitelists/FlowBenchSegmentation.cmake b/CMake/Whitelists/FlowBenchSegmentation.cmake index c0eedb2dfb..7c7a9ea489 100644 --- a/CMake/Whitelists/FlowBenchSegmentation.cmake +++ b/CMake/Whitelists/FlowBenchSegmentation.cmake @@ -1,54 +1,53 @@ set(enabled_modules Core CppMicroServices DICOM DICOMPM DataTypesExt AlgorithmsExt DICOMQI Multilabel SceneSerializationBase DICOMPMIO DICOMImageIO ContourModel DICOMSegIO LegacyGL MapperExt SceneSerialization LegacyIO IOExt MultilabelIO AppUtil QtWidgets QtWidgetsExt Segmentation SegmentationUI PlanarFigure Annotation SurfaceInterpolation GraphAlgorithms ImageExtraction ImageStatistics ) set(enabled_plugins org.blueberry.core.commands org.blueberry.core.expressions org.blueberry.core.runtime org.blueberry.ui.qt org.blueberry.ui.qt.help org.blueberry.ui.qt.log org.mitk.core.services org.mitk.gui.common org.mitk.gui.qt.application org.mitk.gui.qt.common org.mitk.gui.qt.datamanager org.mitk.gui.qt.ext org.mitk.gui.qt.flow.segmentation org.mitk.gui.qt.flowapplication org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.stdmultiwidgeteditor -org.mitk.planarfigure ) diff --git a/CMakeExternals/CTK.cmake b/CMakeExternals/CTK.cmake index a14e888731..93716a6ca6 100644 --- a/CMakeExternals/CTK.cmake +++ b/CMakeExternals/CTK.cmake @@ -1,102 +1,78 @@ #----------------------------------------------------------------------------- # CTK #----------------------------------------------------------------------------- if(MITK_USE_CTK) # Sanity checks if(DEFINED CTK_DIR AND NOT EXISTS ${CTK_DIR}) message(FATAL_ERROR "CTK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj CTK) set(proj_DEPENDENCIES DCMTK) set(CTK_DEPENDS ${proj}) if(NOT DEFINED CTK_DIR) set(ctk_optional_cache_args ) - if(MITK_USE_Python3) - list(APPEND ctk_optional_cache_args - -DCTK_LIB_Scripting/Python/Widgets:BOOL=ON - -DCTK_ENABLE_Python_Wrapping:BOOL=OFF - -DCTK_APP_ctkSimplePythonShell:BOOL=OFF - "-DPYTHON_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIRS}" - "-DPYTHON_LIBRARY:FILEPATH=${Python3_LIBRARY_RELEASE}" - ) - else() - list(APPEND ctk_optional_cache_args - -DCTK_LIB_Scripting/Python/Widgets:BOOL=OFF - -DCTK_ENABLE_Python_Wrapping:BOOL=OFF - -DCTK_APP_ctkSimplePythonShell:BOOL=OFF - -DDCMTK_CMAKE_DEBUG_POSTFIX:STRING=d - ) - endif() if(CTEST_USE_LAUNCHERS) list(APPEND ctk_optional_cache_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() FOREACH(type RUNTIME ARCHIVE LIBRARY) IF(DEFINED CTK_PLUGIN_${type}_OUTPUT_DIRECTORY) LIST(APPEND mitk_optional_cache_args -DCTK_PLUGIN_${type}_OUTPUT_DIRECTORY:PATH=${CTK_PLUGIN_${type}_OUTPUT_DIRECTORY}) ENDIF() ENDFOREACH() mitk_query_custom_ep_vars() - set(pythonqt_location_args - "-DPythonQt_GIT_REPOSITORY:STRING=https://github.com/MITK/PythonQt.git" - -DPythonQt_REVISION_TAG:STRING=patched-10-patched - ) - ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} GIT_REPOSITORY https://github.com/MITK/CTK.git - GIT_TAG ec816cbb-patched + GIT_TAG 37aff99226ed936b930b1ef07df046877771ea03 # branch: qt-6 UPDATE_COMMAND "" INSTALL_COMMAND "" CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${ctk_optional_cache_args} # The CTK PluginFramework cannot cope with # a non-empty CMAKE_DEBUG_POSTFIX for the plugin # libraries yet. -DCMAKE_DEBUG_POSTFIX:STRING= -DCTK_QT_VERSION:STRING=5 - -DQt5_DIR=${Qt5_DIR} - -DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE} - -DCTK_BUILD_QTDESIGNER_PLUGINS:BOOL=ON - -DCTK_LIB_CommandLineModules/Backend/LocalProcess:BOOL=ON - -DCTK_LIB_CommandLineModules/Frontend/QtGui:BOOL=ON + "-DQt5_DIR=${Qt5_DIR}" + "-DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE}" -DCTK_LIB_PluginFramework:BOOL=ON -DCTK_LIB_DICOM/Widgets:BOOL=ON -DCTK_LIB_XNAT/Core:BOOL=ON -DCTK_PLUGIN_org.commontk.eventadmin:BOOL=ON -DCTK_PLUGIN_org.commontk.configadmin:BOOL=ON - -DDCMTK_DIR:PATH=${DCMTK_DIR} - ${pythonqt_location_args} + -DCTK_USE_SYSTEM_DCMTK:BOOL=ON + "-DDCMTK_ROOT:PATH=${ep_prefix}" ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) ExternalProject_Get_Property(${proj} binary_dir) set(CTK_DIR ${binary_dir}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/ITK.cmake b/CMakeExternals/ITK.cmake index 59b448cc79..4ea3b132db 100644 --- a/CMakeExternals/ITK.cmake +++ b/CMakeExternals/ITK.cmake @@ -1,90 +1,90 @@ #----------------------------------------------------------------------------- # ITK #----------------------------------------------------------------------------- # Sanity checks if(DEFINED ITK_DIR AND NOT EXISTS ${ITK_DIR}) message(FATAL_ERROR "ITK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj ITK) set(proj_DEPENDENCIES GDCM) if(MITK_USE_OpenCV) list(APPEND proj_DEPENDENCIES OpenCV) endif() if(MITK_USE_HDF5) list(APPEND proj_DEPENDENCIES HDF5) endif() set(ITK_DEPENDS ${proj}) if(NOT DEFINED ITK_DIR) set(additional_cmake_args -DUSE_WRAP_ITK:BOOL=OFF) list(APPEND additional_cmake_args -DITKV4_COMPATIBILITY:BOOL=OFF -DITK_LEGACY_REMOVE:BOOL=ON ) if(MITK_USE_OpenCV) list(APPEND additional_cmake_args -DModule_ITKVideoBridgeOpenCV:BOOL=ON -DOpenCV_DIR:PATH=${OpenCV_DIR} "-DCMAKE_CONFIGURATION_TYPES:STRING=Debug$Release" ) endif() # Keep the behaviour of ITK 4.3 which by default turned on ITK Review # see MITK bug #17338 list(APPEND additional_cmake_args -DModule_ITKReview:BOOL=ON -DModule_ITKOpenJPEG:BOOL=ON # for 4.7, the OpenJPEG is needed by review but the variable must be set -DModule_IsotropicWavelets:BOOL=ON ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_cmake_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} UPDATE_COMMAND "" - GIT_REPOSITORY https://github.com/MITK/ITK.git - GIT_TAG v5.3.0-patched + GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITK.git + GIT_TAG 80446b1c18fde0aaca3eecab6e1e7a30d015ac2c # tag: v5.4rc02 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_cmake_args} -DITK_SKIP_PATH_LENGTH_CHECKS:BOOL=ON -DBUILD_EXAMPLES:BOOL=OFF -DITK_USE_SYSTEM_GDCM:BOOL=ON -DGDCM_DIR:PATH=${GDCM_DIR} -DITK_USE_SYSTEM_HDF5:BOOL=ON -DHDF5_DIR:PATH=${HDF5_DIR} -DModule_GrowCut:BOOL=ON ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(ITK_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() diff --git a/CMakeExternals/cpprestsdk.cmake b/CMakeExternals/cpprestsdk.cmake index ea888903a9..3c172eab55 100644 --- a/CMakeExternals/cpprestsdk.cmake +++ b/CMakeExternals/cpprestsdk.cmake @@ -1,41 +1,41 @@ set(proj cpprestsdk) set(proj_DEPENDENCIES Boost ZLIB) if(MITK_USE_${proj}) set(${proj}_DEPENDS ${proj}) if(DEFINED ${proj}_DIR AND NOT EXISTS ${${proj}_DIR}) message(FATAL_ERROR "${proj}_DIR variable is defined but corresponds to non-existing directory!") endif() if(NOT DEFINED ${proj}_DIR) set(cmake_cache_args ${ep_common_cache_args} -DBUILD_SAMPLES:BOOL=OFF -DBUILD_TESTS:BOOL=OFF -DWERROR:BOOL=OFF ) if(OPENSSL_ROOT_DIR) list(APPEND cmake_cache_args -DOPENSSL_ROOT_DIR:PATH=${OPENSSL_ROOT_DIR} ) endif() ExternalProject_Add(${proj} - GIT_REPOSITORY https://github.com/microsoft/cpprestsdk.git - GIT_TAG v2.10.18 + GIT_REPOSITORY https://github.com/MITK/cpprestsdk.git + GIT_TAG v2.10.19-patched SOURCE_SUBDIR Release CMAKE_ARGS "-DBoost_DIR:PATH=${Boost_DIR}" ${ep_common_args} CMAKE_CACHE_ARGS ${cmake_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} DEPENDS ${proj_DEPENDENCIES} ) set(${proj}_DIR ${ep_prefix}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index f3bc388b79..c5392b79a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1422 +1,1424 @@ #[[ When increasing the minimum required version, check if Boost_ADDITIONAL_VERSIONS in CMake/PackageDepends/MITK_Boost_Config.cmake can be removed. See the first long comment in CMakeExternals/Boost.cmake for details. ]] set(MITK_CMAKE_MINIMUM_REQUIRED_VERSION 3.18) cmake_minimum_required(VERSION ${MITK_CMAKE_MINIMUM_REQUIRED_VERSION}) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND CMAKE_VERSION VERSION_LESS 3.19.2) message(FATAL_ERROR "\ CMake v${CMAKE_VERSION} is defective [1]. \ Please either downgrade to v3.18 or upgrade to at least v3.19.2.\n\ [1] https://gitlab.kitware.com/cmake/cmake/-/issues/21529") endif() #----------------------------------------------------------------------------- # Policies #----------------------------------------------------------------------------- #[[ T28060 https://cmake.org/cmake/help/v3.18/policy/CMP0091.html https://cmake.org/cmake/help/v3.18/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html We pass CMP0091 to all external projects as command-line argument: -DCMAKE_POLICY_DEFAULT_CMP0091:STRING=OLD ]] cmake_policy(SET CMP0091 OLD) if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) # https://cmake.org/cmake/help/v3.24/policy/CMP0135.html endif() #----------------------------------------------------------------------------- # Superbuild Option - Enabled by default #----------------------------------------------------------------------------- option(MITK_USE_SUPERBUILD "Build MITK and the projects it depends on via SuperBuild.cmake." ON) if(MITK_USE_SUPERBUILD) project(MITK-superbuild) set(MITK_SOURCE_DIR ${PROJECT_SOURCE_DIR}) set(MITK_BINARY_DIR ${PROJECT_BINARY_DIR}) else() project(MITK VERSION 2023.12.99) include_directories(SYSTEM ${MITK_SUPERBUILD_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # MITK Extension Feature #----------------------------------------------------------------------------- set(MITK_EXTENSION_DIRS "" CACHE STRING "") unset(MITK_ABSOLUTE_EXTENSION_DIRS) foreach(MITK_EXTENSION_DIR ${MITK_EXTENSION_DIRS}) get_filename_component(MITK_ABSOLUTE_EXTENSION_DIR "${MITK_EXTENSION_DIR}" ABSOLUTE) list(APPEND MITK_ABSOLUTE_EXTENSION_DIRS "${MITK_ABSOLUTE_EXTENSION_DIR}") endforeach() set(MITK_DIR_PLUS_EXTENSION_DIRS "${MITK_SOURCE_DIR}" ${MITK_ABSOLUTE_EXTENSION_DIRS}) #----------------------------------------------------------------------------- # Update CMake module path #----------------------------------------------------------------------------- set(MITK_CMAKE_DIR ${MITK_SOURCE_DIR}/CMake) set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_CMAKE_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMake") if(EXISTS "${MITK_CMAKE_EXTENSION_DIR}") list(APPEND CMAKE_MODULE_PATH "${MITK_CMAKE_EXTENSION_DIR}") endif() endforeach() #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- # Standard CMake macros include(FeatureSummary) include(CTest) include(CMakeParseArguments) include(FindPackageHandleStandardArgs) # MITK macros include(mitkFunctionGetGccVersion) include(mitkFunctionCheckCompilerFlags) include(mitkFunctionSuppressWarnings) # includes several functions include(mitkMacroEmptyExternalProject) include(mitkFunctionEnableBuildConfiguration) include(mitkFunctionWhitelists) include(mitkFunctionAddExternalProject) include(mitkFunctionAddLibrarySearchPaths) SUPPRESS_VC_DEPRECATED_WARNINGS() #----------------------------------------------------------------------------- # Set a default build type if none was specified #----------------------------------------------------------------------------- if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified.") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionGetGccVersion(${CMAKE_CXX_COMPILER} GCC_VERSION) else() set(GCC_VERSION 0) endif() set(MITK_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS 0) set(CMAKE_CXX_STANDARD ${MITK_CXX_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED 1) # This is necessary to avoid problems with compile feature checks. # CMAKE_CXX_STANDARD seems to only set the -std=c++ flag for targets. # However, compile flag checks also need to be done with -std=c++. # The MITK_CXX_FLAG variable is also used for external projects # build during the MITK super-build. mitkFunctionCheckCompilerFlags("-std=c++${MITK_CXX_STANDARD}" MITK_CXX${MITK_CXX_STANDARD}_FLAG) #----------------------------------------------------------------------------- # Warn if source or build path is too long #----------------------------------------------------------------------------- if(WIN32) set(_src_dir_length_max 50) set(_bin_dir_length_max 50) if(MITK_USE_SUPERBUILD) set(_src_dir_length_max 34) # _src_dir_length_max - strlen(ep/src/ITK-build) set(_bin_dir_length_max 40) # _bin_dir_length_max - strlen(MITK-build) endif() string(LENGTH "${MITK_SOURCE_DIR}" _src_n) string(LENGTH "${MITK_BINARY_DIR}" _bin_n) # The warnings should be converted to errors if(_src_n GREATER _src_dir_length_max) message(WARNING "MITK source code directory path length is too long (${_src_n} > ${_src_dir_length_max})." "Please move the MITK source code directory to a directory with a shorter path." ) endif() if(_bin_n GREATER _bin_dir_length_max) message(WARNING "MITK build directory path length is too long (${_bin_n} > ${_bin_dir_length_max})." "Please move the MITK build directory to a directory with a shorter path." ) endif() endif() #----------------------------------------------------------------------------- # Additional MITK Options (also shown during superbuild) #----------------------------------------------------------------------------- # ----------------------------------------- # General build options option(BUILD_SHARED_LIBS "Build MITK with shared libraries" ON) option(WITH_COVERAGE "Enable/Disable coverage" OFF) option(BUILD_TESTING "Test the project" ON) option(MITK_FAST_TESTING "Disable long-running tests like packaging" OFF) option(MITK_XVFB_TESTING "Execute test drivers through xvfb-run" OFF) option(MITK_BUILD_ALL_APPS "Build all MITK applications" OFF) option(MITK_BUILD_EXAMPLES "Build the MITK Examples" OFF) mark_as_advanced( MITK_XVFB_TESTING MITK_FAST_TESTING MITK_BUILD_ALL_APPS ) #----------------------------------------------------------------------------- # Set UI testing flags #----------------------------------------------------------------------------- if(MITK_XVFB_TESTING) set(MITK_XVFB_TESTING_COMMAND "xvfb-run" "--auto-servernum" CACHE STRING "Command and options to test through Xvfb") mark_as_advanced(MITK_XVFB_TESTING_COMMAND) endif(MITK_XVFB_TESTING) # ----------------------------------------- # Other options set(MITK_CUSTOM_REVISION_DESC "" CACHE STRING "Override MITK revision description") mark_as_advanced(MITK_CUSTOM_REVISION_DESC) set_property(GLOBAL PROPERTY MITK_EXTERNAL_PROJECTS "") include(CMakeExternals/ExternalProjectList.cmake) foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_CMAKE_EXTERNALS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMakeExternals") if(EXISTS "${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake") include("${MITK_CMAKE_EXTERNALS_EXTENSION_DIR}/ExternalProjectList.cmake") endif() endforeach() # ----------------------------------------- # Other MITK_USE_* options not related to # external projects build via the # MITK superbuild option(MITK_USE_BLUEBERRY "Build the BlueBerry platform" ON) option(MITK_USE_OpenCL "Use OpenCL GPU-Computing library" OFF) option(MITK_USE_OpenMP "Use OpenMP" OFF) option(MITK_USE_Python3 "Use Python 3" OFF) #----------------------------------------------------------------------------- # Build configurations #----------------------------------------------------------------------------- set(_buildConfigs "Custom") file(GLOB _buildConfigFiles CMake/BuildConfigurations/*.cmake) foreach(_buildConfigFile ${_buildConfigFiles}) get_filename_component(_buildConfigFile ${_buildConfigFile} NAME_WE) list(APPEND _buildConfigs ${_buildConfigFile}) endforeach() foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) file(GLOB _extBuildConfigFiles "${MITK_EXTENSION_DIR}/CMake/BuildConfigurations/*.cmake") foreach(_extBuildConfigFile ${_extBuildConfigFiles}) get_filename_component(_extBuildConfigFile "${_extBuildConfigFile}" NAME_WE) list(APPEND _buildConfigs "${_extBuildConfigFile}") endforeach() list(REMOVE_DUPLICATES _buildConfigs) endforeach() set(MITK_BUILD_CONFIGURATION "Custom" CACHE STRING "Use pre-defined MITK configurations") set_property(CACHE MITK_BUILD_CONFIGURATION PROPERTY STRINGS ${_buildConfigs}) mitkFunctionEnableBuildConfiguration() mitkFunctionCreateWhitelistPaths(MITK) mitkFunctionFindWhitelists(MITK) # ----------------------------------------- # Qt version related variables option(MITK_USE_Qt5 "Use Qt 5 library" ON) if(MITK_USE_Qt5) if(WIN32) set(MITK_QT5_MINIMUM_VERSION 5.12.9) else() set(MITK_QT5_MINIMUM_VERSION 5.12) endif() set(MITK_QT5_COMPONENTS Concurrent OpenGL PrintSupport Script Sql Svg Widgets Xml XmlPatterns WebEngineWidgets UiTools Help LinguistTools) if(APPLE) list(APPEND MITK_QT5_COMPONENTS DBus) elseif(UNIX) list(APPEND MITK_QT5_COMPONENTS X11Extras) endif() # Hint at default install locations of Qt if(NOT Qt5_DIR) if(MSVC) set(_dir_candidates "C:/Qt") if(CMAKE_GENERATOR MATCHES "^Visual Studio [0-9]+ ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") elseif(CMAKE_GENERATOR MATCHES "Ninja") include(mitkFunctionGetMSVCVersion) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_PRODUCT_NAME MATCHES "^Visual Studio ([0-9]+)") set(_compilers "msvc${CMAKE_MATCH_1}") endif() endif() if(_compilers MATCHES "[0-9]+") if (CMAKE_MATCH_0 EQUAL 2022) list(APPEND _compilers "msvc2019" "msvc2017") # Binary compatible elseif (CMAKE_MATCH_0 EQUAL 2019) list(APPEND _compilers "msvc2017") # Binary compatible endif() endif() else() set(_dir_candidates ~/Qt) if(APPLE) set(_compilers clang) else() list(APPEND _dir_candidates /opt/Qt) set(_compilers gcc) endif() endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) foreach(_compiler ${_compilers}) list(APPEND _compilers64 "${_compiler}_64") endforeach() set(_compilers ${_compilers64}) endif() foreach(_dir_candidate ${_dir_candidates}) get_filename_component(_dir_candidate ${_dir_candidate} REALPATH) foreach(_compiler ${_compilers}) set(_glob_expression "${_dir_candidate}/5.*/${_compiler}") file(GLOB _hints ${_glob_expression}) list(SORT _hints) list(APPEND MITK_QT5_HINTS ${_hints}) endforeach() endforeach() endif() find_package(Qt5 ${MITK_QT5_MINIMUM_VERSION} COMPONENTS ${MITK_QT5_COMPONENTS} REQUIRED HINTS ${MITK_QT5_HINTS}) endif() # ----------------------------------------- # Custom dependency logic if(WIN32 AND Qt5_DIR) set(_dir_candidate "${Qt5_DIR}/../../../../../Tools/OpenSSL/Win_x64") get_filename_component(_dir_candidate ${_dir_candidate} ABSOLUTE) if(EXISTS "${_dir_candidate}") set(OPENSSL_ROOT_DIR "${_dir_candidate}") endif() endif() find_package(OpenSSL) option(MITK_USE_SYSTEM_Boost "Use the system Boost" OFF) set(MITK_USE_Boost_LIBRARIES "" CACHE STRING "A semi-colon separated list of required Boost libraries") if(MITK_USE_cpprestsdk) if(NOT OpenSSL_FOUND) set(openssl_message "Could not find OpenSSL (dependency of C++ REST SDK).\n") if(UNIX) if(APPLE) set(openssl_message "${openssl_message}Please install it using your favorite package management " "system (i.e. Homebrew or MacPorts).\n") else() set(openssl_message "${openssl_message}Please install the dev package of OpenSSL (i.e. libssl-dev).\n") endif() else() set(openssl_message "${openssl_message}Please either install Win32 OpenSSL:\n" " https://slproweb.com/products/Win32OpenSSL.html\n" "Or use the Qt Maintenance tool to install:\n" " Developer and Designer Tools > OpenSSL Toolkit > OpenSSL 64-bit binaries\n") endif() set(openssl_message "${openssl_message}If it still cannot be found, you can hint CMake to find OpenSSL by " "adding/setting the OPENSSL_ROOT_DIR variable to the root directory of an " "OpenSSL installation. Make sure to clear variables of partly found " "versions of OpenSSL before, or they will be mixed up.") message(FATAL_ERROR ${openssl_message}) endif() list(APPEND MITK_USE_Boost_LIBRARIES date_time regex system) if(UNIX) list(APPEND MITK_USE_Boost_LIBRARIES atomic chrono filesystem random thread) endif() list(REMOVE_DUPLICATES MITK_USE_Boost_LIBRARIES) set(MITK_USE_Boost_LIBRARIES ${MITK_USE_Boost_LIBRARIES} CACHE STRING "A semi-colon separated list of required Boost libraries" FORCE) endif() if(MITK_USE_Python3) set(MITK_USE_ZLIB ON CACHE BOOL "" FORCE) - if(APPLE AND CMAKE_FRAMEWORK_PATH AND CMAKE_FRAMEWORK_PATH MATCHES "python3\\.?([0-9]+)") - find_package(Python3 3.${CMAKE_MATCH_1} EXACT REQUIRED COMPONENTS Interpreter Development NumPy) + if(APPLE) + set(python3_mininum_version 3.11) else() - find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy) + set(python3_mininum_version 3.8) endif() + find_package(Python3 ${python3_mininum_version} REQUIRED COMPONENTS Interpreter Development NumPy) + if(WIN32) string(REPLACE "\\" "/" Python3_STDARCH "${Python3_STDARCH}") string(REPLACE "\\" "/" Python3_STDLIB "${Python3_STDLIB}") string(REPLACE "\\" "/" Python3_SITELIB "${Python3_SITELIB}") endif() endif() if(BUILD_TESTING AND NOT MITK_USE_CppUnit) message("> Forcing MITK_USE_CppUnit to ON because BUILD_TESTING=ON") set(MITK_USE_CppUnit ON CACHE BOOL "Use CppUnit for unit tests" FORCE) endif() if(MITK_USE_BLUEBERRY) option(MITK_BUILD_ALL_PLUGINS "Build all MITK plugins" OFF) mark_as_advanced(MITK_BUILD_ALL_PLUGINS) if(NOT MITK_USE_CTK) message("> Forcing MITK_USE_CTK to ON because of MITK_USE_BLUEBERRY") set(MITK_USE_CTK ON CACHE BOOL "Use CTK in MITK" FORCE) endif() endif() #----------------------------------------------------------------------------- # Pixel type multiplexing #----------------------------------------------------------------------------- # Customize the default pixel types for multiplex macros set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") mark_as_advanced(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES MITK_ACCESSBYITK_DIMENSIONS ) # consistency checks if(NOT MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES) set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES) set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES) set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "itk::RGBPixel, itk::RGBAPixel" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros" FORCE) endif() if(NOT MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) string(REPLACE "," ";" _integral_types ${MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES}) string(REPLACE "," ";" _floating_types ${MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES}) foreach(_scalar_type ${_integral_types} ${_floating_types}) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}itk::VariableLengthVector<${_scalar_type}>,") endforeach() string(LENGTH "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" _length) math(EXPR _length "${_length} - 1") string(SUBSTRING "${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES}" 0 ${_length} MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES) set(MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES ${MITK_ACCESSBYITK_VECTOR_PIXEL_TYPES} CACHE STRING "List of vector pixel types used in AccessByItk and InstantiateAccessFunction macros for itk::VectorImage types" FORCE) endif() if(NOT MITK_ACCESSBYITK_DIMENSIONS) set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") endif() find_package(Git REQUIRED) #----------------------------------------------------------------------------- # Superbuild script #----------------------------------------------------------------------------- if(MITK_USE_SUPERBUILD) include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake") # Print configuration summary message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL) return() endif() #***************************************************************************** #**************************** END OF SUPERBUILD **************************** #***************************************************************************** #----------------------------------------------------------------------------- # Organize MITK targets in folders #----------------------------------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(MITK_ROOT_FOLDER "MITK" CACHE STRING "") mark_as_advanced(MITK_ROOT_FOLDER) #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- include(WriteBasicConfigVersionFile) include(CheckCXXSourceCompiles) include(GenerateExportHeader) include(mitkFunctionAddManifest) include(mitkFunctionAddCustomModuleTest) include(mitkFunctionCheckModuleDependencies) include(mitkFunctionCompileSnippets) include(mitkFunctionConfigureVisualStudioUserProjectFile) include(mitkFunctionCreateBlueBerryApplication) include(mitkFunctionCreateCommandLineApp) include(mitkFunctionCreateModule) include(mitkFunctionCreatePlugin) include(mitkFunctionCreateProvisioningFile) include(mitkFunctionGetLibrarySearchPaths) include(mitkFunctionGetVersion) include(mitkFunctionGetVersionDescription) include(mitkFunctionInstallAutoLoadModules) include(mitkFunctionInstallCTKPlugin) include(mitkFunctionInstallProvisioningFiles) include(mitkFunctionInstallThirdPartyCTKPlugins) include(mitkFunctionOrganizeSources) include(mitkFunctionUseModules) if( ${MITK_USE_MatchPoint} ) include(mitkFunctionCreateMatchPointDeployedAlgorithm) endif() include(mitkMacroConfigureItkPixelTypes) include(mitkMacroCreateExecutable) include(mitkMacroCreateModuleTests) include(mitkMacroGenerateToolsLibrary) include(mitkMacroGetLinuxDistribution) include(mitkMacroGetPMDPlatformString) include(mitkMacroInstall) include(mitkMacroInstallHelperApp) include(mitkMacroInstallTargets) include(mitkMacroMultiplexPicType) # Deprecated include(mitkMacroCreateCTKPlugin) #----------------------------------------------------------------------------- # Global CMake variables #----------------------------------------------------------------------------- if(NOT DEFINED CMAKE_DEBUG_POSTFIX) # We can't do this yet because the CTK Plugin Framework # cannot cope with a postfix yet. #set(CMAKE_DEBUG_POSTFIX d) endif() #----------------------------------------------------------------------------- # Output directories. #----------------------------------------------------------------------------- set(_default_LIBRARY_output_dir lib) set(_default_RUNTIME_output_dir bin) set(_default_ARCHIVE_output_dir lib) foreach(type LIBRARY RUNTIME ARCHIVE) # Make sure the directory exists if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY AND NOT EXISTS ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) message("Creating directory MITK_CMAKE_${type}_OUTPUT_DIRECTORY: ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") file(MAKE_DIRECTORY "${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}") endif() if(MITK_CMAKE_${type}_OUTPUT_DIRECTORY) set(CMAKE_${type}_OUTPUT_DIRECTORY ${MITK_CMAKE_${type}_OUTPUT_DIRECTORY}) else() set(CMAKE_${type}_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${_default_${type}_output_dir}) set(MITK_CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY}) endif() set(CMAKE_${type}_OUTPUT_DIRECTORY ${CMAKE_${type}_OUTPUT_DIRECTORY} CACHE INTERNAL "Output directory for ${type} files.") mark_as_advanced(CMAKE_${type}_OUTPUT_DIRECTORY) endforeach() #----------------------------------------------------------------------------- # Set MITK specific options and variables (NOT available during superbuild) #----------------------------------------------------------------------------- if(OpenSSL_FOUND AND WIN32) #[[ On Windows, CMake is able to locate the link libraries for OpenSSL but it does not look for the corresponding DLLs that we need to copy to our binary directories and include in packaging. Setting these paths manually is cumbersome so we try to use a simple heuristic to automatically set them: - Based on the link libraries (usually located in a lib folder), try to find the "../bin" binary directory. - Use the base file names of the link libraries to find corresponding DLLs like "*.dll", that usually are named like "-1_1-x64.dll" or similar. ]] set(openssl_ssl_dll "") set(openssl_crypto_dll "") if(OPENSSL_SSL_LIBRARY AND EXISTS "${OPENSSL_SSL_LIBRARY}") get_filename_component(openssl_bin_dir "${OPENSSL_SSL_LIBRARY}" DIRECTORY) get_filename_component(openssl_bin_dir "${openssl_bin_dir}" DIRECTORY) set(openssl_bin_dir "${openssl_bin_dir}/bin") if(EXISTS "${openssl_bin_dir}") get_filename_component(openssl_ssl_basename "${OPENSSL_SSL_LIBRARY}" NAME_WE) file(GLOB openssl_ssl_dll "${openssl_bin_dir}/${openssl_ssl_basename}*.dll") list(LENGTH openssl_ssl_dll num_findings) if(num_findings GREATER 1) set(openssl_ssl_dll "") endif() get_filename_component(openssl_crypto_basename "${OPENSSL_CRYPTO_LIBRARY}" NAME_WE) file(GLOB openssl_crypto_dll "${openssl_bin_dir}/${openssl_crypto_basename}*.dll") list(LENGTH openssl_crypto_dll num_findings) if(num_findings GREATER 1) set(openssl_crypto_dll "") endif() endif() endif() set(MITK_OPENSSL_SSL_DLL "${openssl_ssl_dll}" CACHE FILEPATH "") if(DEFINED CACHE{MITK_OPENSSL_SSL_DLL} AND NOT MITK_OPENSSL_SSL_DLL AND openssl_ssl_dll) set(MITK_OPENSSL_SSL_DLL "${openssl_ssl_dll}" CACHE FILEPATH "" FORCE) endif() set(MITK_OPENSSL_CRYPTO_DLL "${openssl_crypto_dll}" CACHE FILEPATH "") if(DEFINED CACHE{MITK_OPENSSL_CRYPTO_DLL} AND NOT MITK_OPENSSL_CRYPTO_DLL AND openssl_crypto_dll) set(MITK_OPENSSL_CRYPTO_DLL "${openssl_crypto_dll}" CACHE FILEPATH "" FORCE) endif() if(MITK_OPENSSL_SSL_DLL AND EXISTS "${MITK_OPENSSL_SSL_DLL}" AND MITK_OPENSSL_CRYPTO_DLL AND EXISTS "${MITK_OPENSSL_CRYPTO_DLL}") foreach(config_type ${CMAKE_CONFIGURATION_TYPES}) execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${MITK_BINARY_DIR}/bin/${config_type}") configure_file("${MITK_OPENSSL_SSL_DLL}" "${MITK_BINARY_DIR}/bin/${config_type}/" COPYONLY) configure_file("${MITK_OPENSSL_CRYPTO_DLL}" "${MITK_BINARY_DIR}/bin/${config_type}/" COPYONLY) endforeach() MITK_INSTALL(FILES "${MITK_OPENSSL_SSL_DLL}" "${MITK_OPENSSL_CRYPTO_DLL}" ) endif() endif() # Look for optional Doxygen package find_package(Doxygen) option(BLUEBERRY_DEBUG_SMARTPOINTER "Enable code for debugging smart pointers" OFF) mark_as_advanced(BLUEBERRY_DEBUG_SMARTPOINTER) # Ask the user to show the console window for applications option(MITK_SHOW_CONSOLE_WINDOW "Use this to enable or disable the console window when starting MITK GUI Applications" ON) mark_as_advanced(MITK_SHOW_CONSOLE_WINDOW) if(NOT MITK_FAST_TESTING) if(MITK_CTEST_SCRIPT_MODE STREQUAL "Continuous" OR MITK_CTEST_SCRIPT_MODE STREQUAL "Experimental") set(MITK_FAST_TESTING ON) endif() endif() if(NOT UNIX) set(MITK_WIN32_FORCE_STATIC "STATIC" CACHE INTERNAL "Use this variable to always build static libraries on non-unix platforms") endif() if(MITK_BUILD_ALL_PLUGINS) set(MITK_BUILD_ALL_PLUGINS_OPTION "FORCE_BUILD_ALL") endif() # Configure pixel types used for ITK image access multiplexing mitkMacroConfigureItkPixelTypes() # Configure module naming conventions set(MITK_MODULE_NAME_REGEX_MATCH "^[A-Z].*$") set(MITK_MODULE_NAME_REGEX_NOT_MATCH "^[Mm][Ii][Tt][Kk].*$") set(MITK_DEFAULT_MODULE_NAME_PREFIX "Mitk") set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) set(MITK_MODULE_NAME_DEFAULTS_TO_DIRECTORY_NAME 1) #----------------------------------------------------------------------------- # Get MITK version info #----------------------------------------------------------------------------- mitkFunctionGetVersion(${MITK_SOURCE_DIR} MITK) mitkFunctionGetVersionDescription(${MITK_SOURCE_DIR} MITK) # MITK_VERSION set(MITK_VERSION_STRING "${MITK_VERSION_MAJOR}.${MITK_VERSION_MINOR}.${MITK_VERSION_PATCH}") if(MITK_VERSION_PATCH STREQUAL "99") set(MITK_VERSION_STRING "${MITK_VERSION_STRING}-${MITK_REVISION_SHORTID}") endif() #----------------------------------------------------------------------------- # Installation preparation # # These should be set before any MITK install macros are used #----------------------------------------------------------------------------- # on macOS all BlueBerry plugins get copied into every # application bundle (.app directory) specified here if(MITK_USE_BLUEBERRY AND APPLE) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") set(MITK_APPS "") include("${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 1 option_name) list(GET target_info_list 0 app_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) set(MACOSX_BUNDLE_NAMES ${MACOSX_BUNDLE_NAMES} Mitk${app_name}) endif() endforeach() endif() endforeach() endif() #----------------------------------------------------------------------------- # Set coverage Flags #----------------------------------------------------------------------------- if(WITH_COVERAGE) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(coverage_flags "-g -fprofile-arcs -ftest-coverage -O0 -DNDEBUG") set(COVERAGE_CXX_FLAGS ${coverage_flags}) set(COVERAGE_C_FLAGS ${coverage_flags}) endif() endif() #----------------------------------------------------------------------------- # MITK C/CXX Flags #----------------------------------------------------------------------------- set(MITK_C_FLAGS "${COVERAGE_C_FLAGS}") set(MITK_C_FLAGS_DEBUG ) set(MITK_C_FLAGS_RELEASE ) set(MITK_CXX_FLAGS "${COVERAGE_CXX_FLAGS} ${MITK_CXX${MITK_CXX_STANDARD}_FLAG}") set(MITK_CXX_FLAGS_DEBUG ) set(MITK_CXX_FLAGS_RELEASE ) set(MITK_EXE_LINKER_FLAGS ) set(MITK_SHARED_LINKER_FLAGS ) if(WIN32) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DWIN32_LEAN_AND_MEAN -DNOMINMAX") mitkFunctionCheckCompilerFlags("/wd4005" MITK_CXX_FLAGS) # warning C4005: macro redefinition mitkFunctionCheckCompilerFlags("/wd4231" MITK_CXX_FLAGS) # warning C4231: nonstandard extension used : 'extern' before template explicit instantiation # the following line should be removed after fixing bug 17637 mitkFunctionCheckCompilerFlags("/wd4316" MITK_CXX_FLAGS) # warning C4316: object alignment on heap mitkFunctionCheckCompilerFlags("/wd4180" MITK_CXX_FLAGS) # warning C4180: qualifier applied to function type has no meaning mitkFunctionCheckCompilerFlags("/wd4251" MITK_CXX_FLAGS) # warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' endif() if(APPLE) set(MITK_CXX_FLAGS "${MITK_CXX_FLAGS} -DGL_SILENCE_DEPRECATION") # Apple deprecated OpenGL in macOS 10.14 endif() if(NOT MSVC_VERSION) foreach(_flag -Wall -Wextra -Wpointer-arith -Winvalid-pch -Wcast-align -Wwrite-strings -Wno-error=gnu -Wno-error=unknown-pragmas # The strict-overflow warning is generated by ITK template code -Wno-error=strict-overflow -Woverloaded-virtual -Wstrict-null-sentinel #-Wold-style-cast #-Wsign-promo -Wno-deprecated-copy -Wno-array-bounds -Wno-cast-function-type -Wno-maybe-uninitialized -Wno-error=stringop-overread -fdiagnostics-show-option ) mitkFunctionCheckCAndCXXCompilerFlags(${_flag} MITK_C_FLAGS MITK_CXX_FLAGS) endforeach() endif() if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE) mitkFunctionCheckCompilerFlags("-Wl,--no-undefined" MITK_SHARED_LINKER_FLAGS) mitkFunctionCheckCompilerFlags("-Wl,--as-needed" MITK_SHARED_LINKER_FLAGS) endif() if(CMAKE_COMPILER_IS_GNUCXX) mitkFunctionCheckCAndCXXCompilerFlags("-fstack-protector-all" MITK_C_FLAGS MITK_CXX_FLAGS) set(MITK_CXX_FLAGS_RELEASE "-U_FORTIFY_SOURCES -D_FORTIFY_SOURCE=2 ${MITK_CXX_FLAGS_RELEASE}") endif() set(MITK_MODULE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) set(MITK_EXE_LINKER_FLAGS ${MITK_SHARED_LINKER_FLAGS}) #----------------------------------------------------------------------------- # MITK Packages #----------------------------------------------------------------------------- set(MITK_MODULES_PACKAGE_DEPENDS_DIR ${MITK_SOURCE_DIR}/CMake/PackageDepends) set(MODULES_PACKAGE_DEPENDS_DIRS ${MITK_MODULES_PACKAGE_DEPENDS_DIR}) foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_PACKAGE_DEPENDS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/CMake/PackageDepends") if(EXISTS "${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}") list(APPEND MODULES_PACKAGE_DEPENDS_DIRS "${MITK_PACKAGE_DEPENDS_EXTENSION_DIR}") endif() endforeach() if(NOT MITK_USE_SYSTEM_Boost) set(Boost_NO_SYSTEM_PATHS 1) endif() set(Boost_USE_MULTITHREADED 1) set(Boost_USE_STATIC_LIBS 0) set(Boost_USE_STATIC_RUNTIME 0) set(Boost_ADDITIONAL_VERSIONS 1.74 1.74.0) # We need this later for a DCMTK workaround set(_dcmtk_dir_orig ${DCMTK_DIR}) # This property is populated at the top half of this file get_property(MITK_EXTERNAL_PROJECTS GLOBAL PROPERTY MITK_EXTERNAL_PROJECTS) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(MITK_USE_${ep} AND _package) if(_components) find_package(${_package} COMPONENTS ${_components} REQUIRED CONFIG) else() # Prefer config mode first because it finds external # Config.cmake files pointed at by _DIR variables. # Otherwise, existing Find.cmake files could fail. if(DEFINED ${_package}_DIR) #we store the information because it will be overwritten by find_package #and would get lost for all EPs that use on Find.cmake instead of config #files. set(_temp_EP_${_package}_dir ${${_package}_DIR}) endif(DEFINED ${_package}_DIR) find_package(${_package} QUIET CONFIG) string(TOUPPER "${_package}" _package_uc) if(NOT (${_package}_FOUND OR ${_package_uc}_FOUND)) if(DEFINED _temp_EP_${_package}_dir) set(${_package}_DIR ${_temp_EP_${_package}_dir} CACHE PATH "externaly set dir of the package ${_package}" FORCE) endif(DEFINED _temp_EP_${_package}_dir) find_package(${_package} REQUIRED) endif() endif() endif() endforeach() # Ensure that the MITK CMake module path comes first set(CMAKE_MODULE_PATH ${MITK_CMAKE_DIR} ${CMAKE_MODULE_PATH} ) if(MITK_USE_DCMTK) if(${_dcmtk_dir_orig} MATCHES "${MITK_EXTERNAL_PROJECT_PREFIX}.*") # Help our FindDCMTK.cmake script find our super-build DCMTK set(DCMTK_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) else() # Use the original value set(DCMTK_DIR ${_dcmtk_dir_orig}) endif() endif() if(MITK_USE_DCMQI) # Due to the preferred CONFIG mode in find_package calls above, # the DCMQIConfig.cmake file is read, which does not provide useful # package information. We explictly need MODULE mode to find DCMQI. # Help our FindDCMQI.cmake script find our super-build DCMQI set(DCMQI_DIR ${MITK_EXTERNAL_PROJECT_PREFIX}) find_package(DCMQI REQUIRED) endif() if(MITK_USE_OpenIGTLink) link_directories(${OpenIGTLink_LIBRARY_DIRS}) endif() if(MITK_USE_OpenCL) find_package(OpenCL REQUIRED) endif() if(MITK_USE_OpenMP) find_package(OpenMP REQUIRED COMPONENTS CXX) else() find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) elseif(APPLE AND OpenMP_libomp_LIBRARY AND NOT OpenMP_CXX_LIB_NAMES) set(OpenMP_CXX_LIB_NAMES libomp CACHE STRING "" FORCE) get_filename_component(openmp_lib_dir "${OpenMP_libomp_LIBRARY}" DIRECTORY) set(openmp_include_dir "${openmp_lib_dir}/../include") if(EXISTS "${openmp_include_dir}") get_filename_component(openmp_include_dir "${openmp_include_dir}" REALPATH) set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${openmp_include_dir}" CACHE STRING "" FORCE) find_package(OpenMP QUIET COMPONENTS CXX) if(OpenMP_FOUND) set(MITK_USE_OpenMP ON CACHE BOOL "" FORCE) endif() endif() endif() endif() # Qt support if(MITK_USE_Qt5) find_package(Qt5Core ${MITK_QT5_MINIMUM_VERSION} REQUIRED) # at least Core required get_target_property(_qmake_exec Qt5::qmake LOCATION) execute_process(COMMAND ${_qmake_exec} -query QT_INSTALL_BINS RESULT_VARIABLE _result OUTPUT_VARIABLE QT_BINARY_DIR ERROR_VARIABLE _error ) string(STRIP "${QT_BINARY_DIR}" QT_BINARY_DIR) if(_result OR NOT EXISTS "${QT_BINARY_DIR}") message(FATAL_ERROR "Could not determine Qt binary directory: ${_result} ${QT_BINARY_DIR} ${_error}") endif() find_program(QT_HELPGENERATOR_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5 qhelpgenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_COLLECTIONGENERATOR_EXECUTABLE NAMES qcollectiongenerator qcollectiongenerator-qt5 qcollectiongenerator5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_ASSISTANT_EXECUTABLE NAMES assistant assistant-qt5 assistant5 PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) find_program(QT_XMLPATTERNS_EXECUTABLE NAMES xmlpatterns PATHS ${QT_BINARY_DIR} NO_DEFAULT_PATH ) mark_as_advanced(QT_HELPGENERATOR_EXECUTABLE QT_COLLECTIONGENERATOR_EXECUTABLE QT_ASSISTANT_EXECUTABLE QT_XMLPATTERNS_EXECUTABLE ) if(MITK_USE_BLUEBERRY) option(BLUEBERRY_USE_QT_HELP "Enable support for integrating plugin documentation into Qt Help" ${DOXYGEN_FOUND}) mark_as_advanced(BLUEBERRY_USE_QT_HELP) # Sanity checks for in-application BlueBerry plug-in help generation if(BLUEBERRY_USE_QT_HELP) set(_force_blueberry_use_qt_help_to_off 0) if(NOT DOXYGEN_FOUND) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen was not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(DOXYGEN_FOUND AND DOXYGEN_VERSION VERSION_LESS 1.8.7) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because Doxygen version 1.8.7 or newer not found.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_HELPGENERATOR_EXECUTABLE) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because QT_HELPGENERATOR_EXECUTABLE is empty.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT MITK_USE_Qt5) message("> Forcing BLUEBERRY_USE_QT_HELP to OFF because MITK_USE_Qt5 is OFF.") set(_force_blueberry_use_qt_help_to_off 1) endif() if(NOT QT_XMLPATTERNS_EXECUTABLE) message("You have enabled Qt Help support, but QT_XMLPATTERNS_EXECUTABLE is empty") set(_force_blueberry_use_qt_help_to_off 1) endif() if(_force_blueberry_use_qt_help_to_off) set(BLUEBERRY_USE_QT_HELP OFF CACHE BOOL "Enable support for integrating plugin documentation into Qt Help" FORCE) endif() endif() if(BLUEBERRY_QT_HELP_REQUIRED AND NOT BLUEBERRY_USE_QT_HELP) message(FATAL_ERROR "BLUEBERRY_USE_QT_HELP is required to be set to ON") endif() endif() endif() #----------------------------------------------------------------------------- # Testing #----------------------------------------------------------------------------- if(BUILD_TESTING) # Configuration for the CMake-generated test driver set(CMAKE_TESTDRIVER_EXTRA_INCLUDES "#include ") set(CMAKE_TESTDRIVER_BEFORE_TESTMAIN " try {") set(CMAKE_TESTDRIVER_AFTER_TESTMAIN " } catch (const std::exception& e) { fprintf(stderr, \"%s\\n\", e.what()); return EXIT_FAILURE; } catch (...) { printf(\"Exception caught in the test driver\\n\"); return EXIT_FAILURE; }") set(MITK_TEST_OUTPUT_DIR "${MITK_BINARY_DIR}/test_output") if(NOT EXISTS ${MITK_TEST_OUTPUT_DIR}) file(MAKE_DIRECTORY ${MITK_TEST_OUTPUT_DIR}) endif() # Test the package target include(mitkPackageTest) endif() configure_file(mitkTestingConfig.h.in ${MITK_BINARY_DIR}/mitkTestingConfig.h) #----------------------------------------------------------------------------- # MITK_SUPERBUILD_BINARY_DIR #----------------------------------------------------------------------------- # If MITK_SUPERBUILD_BINARY_DIR isn't defined, it means MITK is *NOT* build using Superbuild. # In that specific case, MITK_SUPERBUILD_BINARY_DIR should default to MITK_BINARY_DIR if(NOT DEFINED MITK_SUPERBUILD_BINARY_DIR) set(MITK_SUPERBUILD_BINARY_DIR ${MITK_BINARY_DIR}) endif() #----------------------------------------------------------------------------- # Set C/CXX and linker flags for MITK code #----------------------------------------------------------------------------- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MITK_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${MITK_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MITK_CXX_FLAGS_RELEASE}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MITK_C_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${MITK_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${MITK_C_FLAGS_RELEASE}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MITK_EXE_LINKER_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${MITK_SHARED_LINKER_FLAGS}") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${MITK_MODULE_LINKER_FLAGS}") #----------------------------------------------------------------------------- # Add subdirectories #----------------------------------------------------------------------------- add_subdirectory(Utilities) add_subdirectory(Modules) include("${CMAKE_CURRENT_SOURCE_DIR}/Modules/ModuleList.cmake") mitkFunctionWhitelistModules(MITK MITK_MODULES) set(MITK_ROOT_FOLDER_BACKUP "${MITK_ROOT_FOLDER}") foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) get_filename_component(MITK_ROOT_FOLDER "${MITK_EXTENSION_DIR}" NAME) set(MITK_MODULES_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Modules") if(EXISTS "${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake") set(MITK_MODULES "") include("${MITK_MODULES_EXTENSION_DIR}/ModuleList.cmake") foreach(mitk_module ${MITK_MODULES}) add_subdirectory("${MITK_MODULES_EXTENSION_DIR}/${mitk_module}" "Modules/${mitk_module}") endforeach() endif() set(MITK_MODULE_NAME_PREFIX ${MITK_DEFAULT_MODULE_NAME_PREFIX}) endforeach() set(MITK_ROOT_FOLDER "${MITK_ROOT_FOLDER_BACKUP}") add_subdirectory(Wrapping) set(MITK_DOXYGEN_OUTPUT_DIR "${PROJECT_BINARY_DIR}/Documentation/Doxygen" CACHE PATH "Output directory for doxygen generated documentation.") if(MITK_USE_BLUEBERRY) include("${CMAKE_CURRENT_SOURCE_DIR}/Plugins/PluginList.cmake") mitkFunctionWhitelistPlugins(MITK MITK_PLUGINS) set(mitk_plugins_fullpath "") foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath Plugins/${mitk_plugin}) endforeach() set(MITK_PLUGIN_REGEX_LIST "") foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_PLUGINS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Plugins") if(EXISTS "${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake") set(MITK_PLUGINS "") include("${MITK_PLUGINS_EXTENSION_DIR}/PluginList.cmake") foreach(mitk_plugin ${MITK_PLUGINS}) list(APPEND mitk_plugins_fullpath "${MITK_PLUGINS_EXTENSION_DIR}/${mitk_plugin}") endforeach() endif() endforeach() if(EXISTS ${MITK_PRIVATE_MODULES}/PluginList.cmake) include(${MITK_PRIVATE_MODULES}/PluginList.cmake) foreach(mitk_plugin ${MITK_PRIVATE_PLUGINS}) list(APPEND mitk_plugins_fullpath ${MITK_PRIVATE_MODULES}/${mitk_plugin}) endforeach() endif() if(MITK_BUILD_EXAMPLES) include("${CMAKE_CURRENT_SOURCE_DIR}/Examples/Plugins/PluginList.cmake") set(mitk_example_plugins_fullpath ) foreach(mitk_example_plugin ${MITK_EXAMPLE_PLUGINS}) list(APPEND mitk_example_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) list(APPEND mitk_plugins_fullpath Examples/Plugins/${mitk_example_plugin}) endforeach() endif() # Specify which plug-ins belong to this project macro(GetMyTargetLibraries all_target_libraries varname) set(re_ctkplugin_mitk "^org_mitk_[a-zA-Z0-9_]+$") set(re_ctkplugin_bb "^org_blueberry_[a-zA-Z0-9_]+$") set(_tmp_list) list(APPEND _tmp_list ${all_target_libraries}) ctkMacroListFilter(_tmp_list re_ctkplugin_mitk re_ctkplugin_bb MITK_PLUGIN_REGEX_LIST OUTPUT_VARIABLE ${varname}) endmacro() # Get infos about application directories and build options set(mitk_apps_fullpath "") foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") set(MITK_APPS "") include("${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") foreach(mitk_app ${MITK_APPS}) # extract option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) if(${option_name}) list(APPEND mitk_apps_fullpath "${MITK_APPLICATIONS_EXTENSION_DIR}/${directory_name}^^${option_name}") endif() endforeach() endif() endforeach() if (mitk_plugins_fullpath) ctkMacroSetupPlugins(${mitk_plugins_fullpath} BUILD_OPTION_PREFIX MITK_BUILD_ APPS ${mitk_apps_fullpath} BUILD_ALL ${MITK_BUILD_ALL_PLUGINS} COMPACT_OPTIONS) endif() set(MITK_PLUGIN_USE_FILE "${MITK_BINARY_DIR}/MitkPluginUseFile.cmake") if(${PROJECT_NAME}_PLUGIN_LIBRARIES) ctkFunctionGeneratePluginUseFile(${MITK_PLUGIN_USE_FILE}) else() file(REMOVE ${MITK_PLUGIN_USE_FILE}) set(MITK_PLUGIN_USE_FILE ) endif() endif() #----------------------------------------------------------------------------- # Documentation #----------------------------------------------------------------------------- set(MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS) set(MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS "${MITK_DOXYGEN_ADDITIONAL_INPUT_DIRS} \"${MITK_EXTENSION_DIR}\"") set(MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS "${MITK_DOXYGEN_ADDITIONAL_IMAGE_PATHS} \"${MITK_EXTENSION_DIR}\"") endforeach() if(DOXYGEN_FOUND) add_subdirectory(Documentation) endif() #----------------------------------------------------------------------------- # Installation #----------------------------------------------------------------------------- # set MITK cpack variables # These are the default variables, which can be overwritten ( see below ) include(mitkSetupCPack) set(use_default_config ON) set(ALL_MITK_APPS "") set(activated_apps_no 0) foreach(MITK_EXTENSION_DIR ${MITK_DIR_PLUS_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") set(MITK_APPS "") include("${MITK_APPLICATIONS_EXTENSION_DIR}/AppList.cmake") foreach(mitk_app ${MITK_APPS}) string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 directory_name) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) list(APPEND ALL_MITK_APPS "${MITK_EXTENSION_DIR}/Applications/${directory_name}^^${option_name}^^${executable_name}") if(${option_name} OR MITK_BUILD_ALL_APPS) MATH(EXPR activated_apps_no "${activated_apps_no} + 1") endif() endforeach() endif() endforeach() list(LENGTH ALL_MITK_APPS app_count) if(app_count EQUAL 1 AND (activated_apps_no EQUAL 1 OR MITK_BUILD_ALL_APPS)) # Corner case if there is only one app in total set(use_project_cpack ON) elseif(activated_apps_no EQUAL 1 AND NOT MITK_BUILD_ALL_APPS) # Only one app is enabled (no "build all" flag set) set(use_project_cpack ON) else() # Less or more then one app is enabled set(use_project_cpack OFF) endif() foreach(mitk_app ${ALL_MITK_APPS}) # extract target_dir and option_name string(REPLACE "^^" "\\;" target_info ${mitk_app}) set(target_info_list ${target_info}) list(GET target_info_list 0 target_dir) list(GET target_info_list 1 option_name) list(GET target_info_list 2 executable_name) # check if the application is enabled if(${option_name} OR MITK_BUILD_ALL_APPS) # check whether application specific configuration files will be used if(use_project_cpack) # use files if they exist if(EXISTS "${target_dir}/CPackOptions.cmake") include("${target_dir}/CPackOptions.cmake") endif() if(EXISTS "${target_dir}/CPackConfig.cmake.in") set(CPACK_PROJECT_CONFIG_FILE "${target_dir}/CPackConfig.cmake") configure_file(${target_dir}/CPackConfig.cmake.in ${CPACK_PROJECT_CONFIG_FILE} @ONLY) set(use_default_config OFF) endif() endif() # add link to the list list(APPEND CPACK_CREATE_DESKTOP_LINKS "${executable_name}") endif() endforeach() # if no application specific configuration file was used, use default if(use_default_config) configure_file(${MITK_SOURCE_DIR}/MITKCPackOptions.cmake.in ${MITK_BINARY_DIR}/MITKCPackOptions.cmake @ONLY) set(CPACK_PROJECT_CONFIG_FILE "${MITK_BINARY_DIR}/MITKCPackOptions.cmake") endif() # include CPack model once all variables are set include(CPack) # Additional installation rules include(mitkInstallRules) #----------------------------------------------------------------------------- # Last configuration steps #----------------------------------------------------------------------------- # ---------------- Export targets ----------------- set(MITK_EXPORTS_FILE "${MITK_BINARY_DIR}/MitkExports.cmake") file(REMOVE ${MITK_EXPORTS_FILE}) set(targets_to_export) get_property(module_targets GLOBAL PROPERTY MITK_MODULE_TARGETS) if(module_targets) list(APPEND targets_to_export ${module_targets}) endif() if(MITK_USE_BLUEBERRY) if(MITK_PLUGIN_LIBRARIES) list(APPEND targets_to_export ${MITK_PLUGIN_LIBRARIES}) endif() endif() export(TARGETS ${targets_to_export} APPEND FILE ${MITK_EXPORTS_FILE}) set(MITK_EXPORTED_TARGET_PROPERTIES ) foreach(target_to_export ${targets_to_export}) get_target_property(autoload_targets ${target_to_export} MITK_AUTOLOAD_TARGETS) if(autoload_targets) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_TARGETS \"${autoload_targets}\")") endif() get_target_property(autoload_dir ${target_to_export} MITK_AUTOLOAD_DIRECTORY) if(autoload_dir) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_AUTOLOAD_DIRECTORY \"${autoload_dir}\")") endif() get_target_property(deprecated_module ${target_to_export} MITK_MODULE_DEPRECATED_SINCE) if(deprecated_module) set(MITK_EXPORTED_TARGET_PROPERTIES "${MITK_EXPORTED_TARGET_PROPERTIES} set_target_properties(${target_to_export} PROPERTIES MITK_MODULE_DEPRECATED_SINCE \"${deprecated_module}\")") endif() endforeach() # ---------------- External projects ----------------- get_property(MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS_CONFIG GLOBAL PROPERTY MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS) set(MITK_CONFIG_EXTERNAL_PROJECTS ) #string(REPLACE "^^" ";" _mitk_external_projects ${MITK_EXTERNAL_PROJECTS}) foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} set(MITK_USE_${ep} ${MITK_USE_${ep}}) set(MITK_${ep}_DIR \"${${ep}_DIR}\") set(MITK_${ep}_COMPONENTS ${_components}) ") endforeach() foreach(ep ${MITK_EXTERNAL_PROJECTS}) get_property(_package GLOBAL PROPERTY MITK_${ep}_PACKAGE) get_property(_components GLOBAL PROPERTY MITK_${ep}_COMPONENTS) if(_components) set(_components_arg COMPONENTS \${_components}) else() set(_components_arg) endif() if(_package) set(MITK_CONFIG_EXTERNAL_PROJECTS "${MITK_CONFIG_EXTERNAL_PROJECTS} if(MITK_USE_${ep}) set(${ep}_DIR \${MITK_${ep}_DIR}) if(MITK_${ep}_COMPONENTS) mitkMacroFindDependency(${_package} COMPONENTS \${MITK_${ep}_COMPONENTS}) else() mitkMacroFindDependency(${_package}) endif() endif()") endif() endforeach() # ---------------- Tools ----------------- configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactory.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolExtensionITKFactoryLoader.cpp.in ${MITK_BINARY_DIR}/ToolExtensionITKFactoryLoader.cpp.in COPYONLY) configure_file(${MITK_SOURCE_DIR}/CMake/ToolGUIExtensionITKFactory.cpp.in ${MITK_BINARY_DIR}/ToolGUIExtensionITKFactory.cpp.in COPYONLY) # ---------------- Configure files ----------------- configure_file(mitkVersion.h.in ${MITK_BINARY_DIR}/mitkVersion.h) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) set(IPFUNC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ipFunc) set(UTILITIES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Utilities) configure_file(mitkConfig.h.in ${MITK_BINARY_DIR}/mitkConfig.h) configure_file(MITKConfig.cmake.in ${MITK_BINARY_DIR}/MITKConfig.cmake @ONLY) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake VERSION ${MITK_VERSION_STRING} COMPATIBILITY AnyNewerVersion) #----------------------------------------------------------------------------- # MITK Applications #----------------------------------------------------------------------------- # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Applications) if(MSVC AND TARGET MitkWorkbench) set_directory_properties(PROPERTIES VS_STARTUP_PROJECT MitkWorkbench) endif() foreach(MITK_EXTENSION_DIR ${MITK_ABSOLUTE_EXTENSION_DIRS}) set(MITK_APPLICATIONS_EXTENSION_DIR "${MITK_EXTENSION_DIR}/Applications") if(EXISTS "${MITK_APPLICATIONS_EXTENSION_DIR}/CMakeLists.txt") add_subdirectory("${MITK_APPLICATIONS_EXTENSION_DIR}" "Applications") endif() endforeach() #----------------------------------------------------------------------------- # MITK Examples #----------------------------------------------------------------------------- if(MITK_BUILD_EXAMPLES) # This must come after MITKConfig.h was generated, since applications # might do a find_package(MITK REQUIRED). add_subdirectory(Examples) endif() #----------------------------------------------------------------------------- # Print configuration summary #----------------------------------------------------------------------------- message("\n\n") feature_summary( DESCRIPTION "------- FEATURE SUMMARY FOR ${PROJECT_NAME} -------" WHAT ALL ) diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox index 8304a11c44..215faaecb1 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox @@ -1,74 +1,73 @@ /** \page PluginListPage MITK Plugin Manuals The plugins and bundles provide much of the extended functionality of MITK. Each encapsulates a solution to a problem and associated features. This way one can easily assemble the necessary capabilites for a workflow without adding a lot of bloat, by combining plugins as needed.
  • \subpage org_mitk_views_basicimageprocessing
  • \subpage org_mitk_views_datamanager
  • \subpage org_mitk_editors_dicombrowser
  • \subpage org_mitk_views_dicominspector
  • \subpage org_mitk_views_imagecropper
  • \subpage org_mitk_views_imagenavigator
  • \subpage org_mitk_views_pixelvalue
  • \subpage org_blueberry_views_logview
  • \subpage org_mitk_views_matchpoint_algorithm_browser
  • \subpage org_mitk_views_matchpoint_algorithm_control
  • \subpage org_mitk_views_matchpoint_evaluator
  • \subpage org_mitk_views_matchpoint_framereg
  • \subpage org_mitk_views_matchpoint_manipulator
  • \subpage org_mitk_views_matchpoint_mapper
  • \subpage org_mitk_views_matchpoint_visualizer
  • \subpage org_mitk_gui_qt_measurementtoolbox
    • \subpage org_mitk_views_measurement
    • \subpage org_mitk_views_imagestatistics
  • \subpage org_mitk_views_moviemaker
  • \subpage org_mitk_views_pointsetinteraction
  • \subpage org_mitk_views_python
  • \subpage org_mitk_views_remeshing
  • \subpage org_mitk_views_screenshotmaker
  • Segmentation
    • \subpage org_mitk_views_segmentation
    • \subpage org_mitk_views_segmentationutilities
    • \subpage org_mitk_views_segmentationtasklist
  • \subpage org_mitk_editors_stdmultiwidget
  • \subpage org_mitk_editors_mxnmultiwidget
  • \subpage org_mitk_views_deformableclippingplane
  • \subpage org_mitk_views_viewnavigator
  • \subpage org_mitk_views_volumevisualization
  • \subpage org_mitk_views_properties
  • \subpage org_mitk_gui_qt_flowapplication
  • \subpage org_mitk_gui_qt_flow_segmentation
  • \subpage org_mitk_gui_qt_aicpregistration
  • \subpage org_mitk_gui_qt_cest
  • -
  • \subpage org_mitk_views_cmdlinemodules
  • \subpage org_mitk_views_pharmacokinetics_concentration_mri
  • \subpage org_mitk_views_pharmacokinetics_mri
  • \subpage org_mitk_views_pharmacokinetics_pet
  • \subpage org_mitk_gui_qt_examples
  • \subpage org_mitk_gui_qt_geometrytools
  • \subpage org_mitk_gui_qt_igtexample
  • \subpage org_mitk_gui_qt_igttracking
  • \subpage org_mitk_views_igttrackingsemiautomaticmeasurement
  • \subpage org_mitk_views_fit_demo
  • \subpage org_mitk_views_fit_genericfitting
  • \subpage org_mitk_views_fit_inspector
  • \subpage org_mitkexamplesopencv
  • \subpage org_mitk_gui_qt_overlaymanager
  • \subpage org_mitk_gui_qt_preprocessing_resampling
  • \subpage org_mitk_views_pharmacokinetics_curvedescriptor
  • \subpage org_mitk_views_pharmacokinetics_simulation
  • \subpage org_surfacematerialeditor
  • \subpage org_toftutorial
  • \subpage org_blueberry_ui_qt_objectinspector
  • \subpage org_mitk_gui_qt_ultrasound
  • \subpage org_mitk_gui_qt_xnat
*/ diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md index 693917cc85..3d03b00901 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/SupportedPlatforms.md @@ -1,55 +1,56 @@ Supported Platforms {#SupportedPlatformsPage} =================== MITK is a cross-platform framework that is available for the following platforms: - Windows - Linux - macOS Supported Platforms Details --------------------------- The MITK team provides support for the most frequently used platforms and continuously runs testing procedures to ensure compatibility. Due to the large amount of possible combinations of operating systems and compiler versions, we divide platform support into two test categories: Tier 1 and Tier 2. Although MITK may be built on a broader range of platform-compiler combinations, only a subset of these are actively supported by the MITK development team. In general, only 64-bit builds are supported. Tier 1 Platforms ---------------- All Tier 1 platforms are continuously tested by our unit test suite and other internal testing procedures. Errors or bugs discovered in these platforms are prioritized and corrected as soon as possible. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- -| Microsoft Windows 10 | Visual Studio 2019 (latest update) +| Microsoft Windows 10 | Visual Studio 2022 | Linux Ubuntu 22.04 | Default GCC version | Linux Ubuntu 20.04 | Default GCC version Tier 2 Platforms ---------------- Tier 2 platforms may or may not be tested on a regular basis. Some Tier 2 platforms are used by individual members of the MITK development team on a daily basis and some only receive occasional testing. While we strive to support these platforms, MITK users should note that errors may be present in released versions as well as in the current master branch. | Platform | Compilers | ----------------------------------- | -------------------------------------------------- -| Microsoft Windows 10 | Visual Studio 2022 (latest update) -| Apple macOS 11 "Big Sur" | Default Apple Clang version -| Apple macOS 10.15 "Catalina" | Default Apple Clang version +| Microsoft Windows 11 | Visual Studio 2022 +| Microsoft Windows 10 | Visual Studio 2019 +| Apple macOS 13 Ventura | Default Apple Clang version +| Apple macOS 12 Monterey | Default Apple Clang version All platforms not listed above are not officially supported by the MITK team. However, we will happily accept contributions to improve support for other platforms as long as we have the hardware and capacity for maintenance. CI Build Clients ---------------- To get an overview of currently tested platforms, see the build reports on our CDash site. diff --git a/Modules/AppUtil/src/mitkBaseApplication.cpp b/Modules/AppUtil/src/mitkBaseApplication.cpp index 91201a6414..6f9e3a72d2 100644 --- a/Modules/AppUtil/src/mitkBaseApplication.cpp +++ b/Modules/AppUtil/src/mitkBaseApplication.cpp @@ -1,905 +1,927 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { - void outputQtMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) + void outputImportantQtMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) + { + auto message = msg.toStdString(); + + switch (type) + { + case QtWarningMsg: + MITK_WARN << message; + break; + + case QtCriticalMsg: + MITK_ERROR << message; + break; + + case QtFatalMsg: + MITK_ERROR << message; + abort(); + + default: + break; + } + } + + void outputQtMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg) { auto message = msg.toStdString(); switch (type) { case QtDebugMsg: MITK_DEBUG << message; break; case QtInfoMsg: MITK_INFO << message; break; case QtWarningMsg: - MITK_WARN << message; - break; + [[fallthrough]]; case QtCriticalMsg: - MITK_ERROR << message; - break; + [[fallthrough]]; case QtFatalMsg: - MITK_ERROR << message; - abort(); + outputImportantQtMessage(type, context, msg); + break; default: MITK_INFO << message; break; } } } namespace mitk { const QString BaseApplication::ARG_APPLICATION = "BlueBerry.application"; const QString BaseApplication::ARG_CLEAN = "BlueBerry.clean"; const QString BaseApplication::ARG_CONSOLELOG = "BlueBerry.consoleLog"; const QString BaseApplication::ARG_DEBUG = "BlueBerry.debug"; const QString BaseApplication::ARG_FORCE_PLUGIN_INSTALL = "BlueBerry.forcePlugins"; const QString BaseApplication::ARG_HOME = "BlueBerry.home"; const QString BaseApplication::ARG_NEWINSTANCE = "BlueBerry.newInstance"; const QString BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING = "BlueBerry.noLazyRegistryCacheLoading"; const QString BaseApplication::ARG_NO_REGISTRY_CACHE = "BlueBerry.noRegistryCache"; const QString BaseApplication::ARG_PLUGIN_CACHE = "BlueBerry.plugin_cache_dir"; const QString BaseApplication::ARG_PLUGIN_DIRS = "BlueBerry.plugin_dirs"; const QString BaseApplication::ARG_PRELOAD_LIBRARY = "BlueBerry.preloadLibrary"; const QString BaseApplication::ARG_PRODUCT = "BlueBerry.product"; const QString BaseApplication::ARG_PROVISIONING = "BlueBerry.provisioning"; const QString BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE = "BlueBerry.registryMultiLanguage"; const QString BaseApplication::ARG_SPLASH_IMAGE = "BlueBerry.splashscreen"; const QString BaseApplication::ARG_STORAGE_DIR = "BlueBerry.storageDir"; const QString BaseApplication::ARG_XARGS = "xargs"; const QString BaseApplication::ARG_LOG_QT_MESSAGES = "Qt.logMessages"; const QString BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET = "Segmentation.labelSetPreset"; const QString BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS = "Segmentation.labelSuggestions"; const QString BaseApplication::PROP_APPLICATION = "blueberry.application"; const QString BaseApplication::PROP_FORCE_PLUGIN_INSTALL = BaseApplication::ARG_FORCE_PLUGIN_INSTALL; const QString BaseApplication::PROP_NEWINSTANCE = BaseApplication::ARG_NEWINSTANCE; const QString BaseApplication::PROP_NO_LAZY_REGISTRY_CACHE_LOADING = BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING; const QString BaseApplication::PROP_NO_REGISTRY_CACHE = BaseApplication::ARG_NO_REGISTRY_CACHE; const QString BaseApplication::PROP_PRODUCT = "blueberry.product"; const QString BaseApplication::PROP_REGISTRY_MULTI_LANGUAGE = BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE; class SplashCloserCallback : public QRunnable { public: SplashCloserCallback(QSplashScreen* splashscreen) : m_Splashscreen(splashscreen) { } void run() override { this->m_Splashscreen->close(); } private: QSplashScreen *m_Splashscreen; // Owned by BaseApplication::Impl }; struct BaseApplication::Impl { ctkProperties m_FWProps; QCoreApplication *m_QApp; int m_Argc; char **m_Argv; #ifdef Q_OS_MAC std::vector m_Argv_macOS; #endif QString m_AppName; QString m_OrgaName; QString m_OrgaDomain; bool m_SingleMode; bool m_SafeMode; QSplashScreen *m_Splashscreen; SplashCloserCallback *m_SplashscreenClosingCallback; bool m_LogQtMessages; QStringList m_PreloadLibs; QString m_ProvFile; Impl(int argc, char **argv) : m_Argc(argc), m_Argv(argv), #ifdef Q_OS_MAC m_Argv_macOS(), #endif m_SingleMode(false), m_SafeMode(true), m_Splashscreen(nullptr), m_SplashscreenClosingCallback(nullptr), m_LogQtMessages(false) { #ifdef Q_OS_MAC /* On macOS the process serial number is passed as an command line argument (-psn_) in certain circumstances. This option causes a Poco exception. We remove it, if present. */ m_Argv_macOS.reserve(argc + 1); const char psn[] = "-psn"; for (int i = 0; i < argc; ++i) { if (0 == strncmp(argv[i], psn, sizeof(psn) - 1)) continue; m_Argv_macOS.push_back(argv[i]); } m_Argv_macOS.push_back(nullptr); m_Argc = static_cast(m_Argv_macOS.size() - 1); m_Argv = m_Argv_macOS.data(); #endif } ~Impl() { delete m_SplashscreenClosingCallback; delete m_Splashscreen; delete m_QApp; } QVariant getProperty(const QString &property) const { auto iter = m_FWProps.find(property); return m_FWProps.end() != iter ? iter.value() : QVariant(); } void handleBooleanOption(const std::string &name, const std::string &) { if (ARG_LOG_QT_MESSAGES.toStdString() == name) { m_LogQtMessages = true; return; } auto fwKey = QString::fromStdString(name); // Translate some keys to proper framework properties if (ARG_CONSOLELOG == fwKey) fwKey = ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG; // For all other options we use the command line option name as the // framework property key. m_FWProps[fwKey] = true; } void handlePreloadLibraryOption(const std::string &, const std::string &value) { m_PreloadLibs.push_back(QString::fromStdString(value)); } void handleClean(const std::string &, const std::string &) { m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT; } void initializeCTKPluginFrameworkProperties(Poco::Util::LayeredConfiguration &configuration) { // Add all configuration key/value pairs as framework properties Poco::Util::LayeredConfiguration::Keys keys; Poco::Util::LayeredConfiguration::Keys keyStack; configuration.keys(keyStack); std::vector keyChain; while (!keyStack.empty()) { const auto currSubKey = keyStack.back(); if (!keyChain.empty() && keyChain.back() == currSubKey) { keyChain.pop_back(); keyStack.pop_back(); continue; } Poco::Util::LayeredConfiguration::Keys subKeys; configuration.keys(currSubKey, subKeys); if (subKeys.empty()) { std::string finalKey; keyStack.pop_back(); for (const auto& key : keyChain) finalKey += key + '.'; finalKey += currSubKey; keys.push_back(finalKey); } else { keyChain.push_back(currSubKey); for (const auto& key : subKeys) keyStack.push_back(key); } } for (const auto& key : keys) { if (configuration.hasProperty(key)) { // .ini and command line options overwrite already inserted keys auto qKey = QString::fromStdString(key); m_FWProps[qKey] = QString::fromStdString(configuration.getString(key)); } } } void parseProvisioningFile(const QString &filePath) { // Skip parsing if the file path is empty if (filePath.isEmpty()) return; auto consoleLog = this->getProperty(ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG).toBool(); // Read initial plugins from a provisioning file QFileInfo provFile(filePath); QStringList pluginsToStart; if (provFile.exists()) { MITK_INFO(consoleLog) << "Using provisioning file: " << qPrintable(provFile.absoluteFilePath()); ProvisioningInfo provInfo(provFile.absoluteFilePath()); // It can still happen that the encoding is not compatible with the fromUtf8 function (i.e. when // manipulating the LANG variable). The QStringList in provInfo is empty then. if (provInfo.getPluginDirs().empty()) { MITK_ERROR << "Cannot search for provisioning file, the retrieved directory list is empty.\n" << "This can happen if there are some special non-ASCII characters in the install path."; } else { for(const auto& pluginPath : provInfo.getPluginDirs()) ctkPluginFrameworkLauncher::addSearchPath(pluginPath); auto pluginUrlsToStart = provInfo.getPluginsToStart(); for (const auto& url : qAsConst(pluginUrlsToStart)) pluginsToStart.push_back(url.toString()); } } else { MITK_INFO(consoleLog) << "Provisionig file does not exist."; } if (!pluginsToStart.isEmpty()) { m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS] = pluginsToStart; // Use transient start with declared activation policy (this helps when the provisioning file // changes and some plug-ins should not be installed in the application any more). ctkPlugin::StartOptions startOptions(ctkPlugin::START_TRANSIENT | ctkPlugin::START_ACTIVATION_POLICY); m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS_START_OPTIONS] = static_cast(startOptions); } } }; BaseApplication::BaseApplication(int argc, char **argv) : Application(), d(new Impl(argc, argv)) { } BaseApplication::~BaseApplication() { delete d; } void BaseApplication::printHelp(const std::string &, const std::string &) { Poco::Util::HelpFormatter help(this->options()); help.setAutoIndent(); help.setCommand(this->commandName()); help.format(std::cout); exit(EXIT_OK); } void BaseApplication::setApplicationName(const QString &name) { if (nullptr != qApp) qApp->setApplicationName(name); d->m_AppName = name; } QString BaseApplication::getApplicationName() const { return nullptr != qApp ? qApp->applicationName() : d->m_AppName; } void BaseApplication::setOrganizationName(const QString &name) { if (nullptr != qApp) qApp->setOrganizationName(name); d->m_OrgaName = name; } QString BaseApplication::getOrganizationName() const { return nullptr != qApp ? qApp->organizationName() : d->m_OrgaName; } void BaseApplication::setOrganizationDomain(const QString &domain) { if (nullptr != qApp) qApp->setOrganizationDomain(domain); d->m_OrgaDomain = domain; } QString BaseApplication::getOrganizationDomain() const { return nullptr != qApp ? qApp->organizationDomain() : d->m_OrgaDomain; } void BaseApplication::setSingleMode(bool singleMode) { if (nullptr != qApp) return; d->m_SingleMode = singleMode; } bool BaseApplication::getSingleMode() const { return d->m_SingleMode; } void BaseApplication::setSafeMode(bool safeMode) { if (nullptr != qApp && nullptr == d->m_QApp) return; d->m_SafeMode = safeMode; nullptr == d->m_QApp && getSingleMode() ? static_cast(d->m_QApp)->setSafeMode(safeMode) : static_cast(d->m_QApp)->setSafeMode(safeMode); } bool BaseApplication::getSafeMode() const { return d->m_SafeMode; } void BaseApplication::setPreloadLibraries(const QStringList &libraryBaseNames) { d->m_PreloadLibs = libraryBaseNames; } QStringList BaseApplication::getPreloadLibraries() const { return d->m_PreloadLibs; } void BaseApplication::setProvisioningFilePath(const QString &filePath) { d->m_ProvFile = filePath; } QString BaseApplication::getProvisioningFilePath() const { auto provFilePath = d->m_ProvFile; // A null QString means look up a default provisioning file if (provFilePath.isNull() && nullptr != qApp) { QFileInfo appFilePath(QCoreApplication::applicationFilePath()); QDir basePath(QCoreApplication::applicationDirPath()); auto provFileName = appFilePath.baseName() + ".provisioning"; QFileInfo provFile(basePath.absoluteFilePath(provFileName)); #ifdef Q_OS_MAC /* * On macOS, if started from the build directory, the .provisioning file is located at: * * The executable path is: * * In this case we have to cdUp threetimes. * * During packaging the MitkWorkbench.provisioning file is placed at the same * level like the executable. Nothing has to be done. */ if (!provFile.exists()) { basePath.cdUp(); basePath.cdUp(); basePath.cdUp(); provFile = basePath.absoluteFilePath(provFileName); } #endif if (provFile.exists()) { provFilePath = provFile.absoluteFilePath(); } #ifdef CMAKE_INTDIR else { basePath.cdUp(); provFile.setFile(basePath.absoluteFilePath(provFileName)); if (provFile.exists()) provFilePath = provFile.absoluteFilePath(); } #endif } return provFilePath; } void BaseApplication::initializeQt() { if (nullptr != qApp) return; #ifdef Q_OS_LINUX qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--single-process"); // See T29332 #endif // If parameters have been set before, we have to store them to hand them // through to the application auto appName = this->getApplicationName(); auto orgName = this->getOrganizationName(); auto orgDomain = this->getOrganizationDomain(); // Create a QCoreApplication instance this->getQApplication(); // Provide parameters to QCoreApplication this->setApplicationName(appName); this->setOrganizationName(orgName); this->setOrganizationDomain(orgDomain); - if (d->m_LogQtMessages) - qInstallMessageHandler(outputQtMessage); + qInstallMessageHandler(!d->m_LogQtMessages + ? outputImportantQtMessage + : outputQtMessage); QWebEngineUrlScheme qtHelpScheme("qthelp"); qtHelpScheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed); QWebEngineUrlScheme::registerScheme(qtHelpScheme); } void BaseApplication::initialize(Poco::Util::Application &self) { // 1. Call the super-class method Poco::Util::Application::initialize(self); // 2. Initialize the Qt framework (by creating a QCoreApplication) this->initializeQt(); // 3. Seed the random number generator, once at startup. QTime time = QTime::currentTime(); qsrand((uint)time.msec()); // 4. Load the "default" configuration, which involves parsing // an optional .ini file and parsing any // command line arguments this->loadConfiguration(); // 5. Add configuration data from the command line and the // optional .ini file as CTK plugin // framework properties. d->initializeCTKPluginFrameworkProperties(this->config()); // 6. Initialize splash screen if an image path is provided // in the .ini file this->initializeSplashScreen(qApp); // 7. Set the custom CTK Plugin Framework storage directory QString storageDir = this->getCTKFrameworkStorageDir(); if (!storageDir.isEmpty()) { d->m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storageDir; // Initialize core service preferences at the exact same location as their predecessor BlueBerry preferences mitk::CoreServicePointer preferencesService(mitk::CoreServices::GetPreferencesService()); preferencesService->InitializeStorage(storageDir.toStdString() + "/data/3/prefs.xml"); } // 8. Set the library search paths and the pre-load library property this->initializeLibraryPaths(); auto preloadLibs = this->getPreloadLibraries(); if (!preloadLibs.isEmpty()) d->m_FWProps[ctkPluginConstants::FRAMEWORK_PRELOAD_LIBRARIES] = preloadLibs; // 9. Initialize the CppMicroServices library. // The initializeCppMicroServices() method reuses the // FRAMEWORK_STORAGE property, so we call it after the // getCTKFrameworkStorageDir method. this->initializeCppMicroServices(); // 10. Parse the (optional) provisioning file and set the // correct framework properties. d->parseProvisioningFile(this->getProvisioningFilePath()); // 11. Set the CTK Plugin Framework properties ctkPluginFrameworkLauncher::setFrameworkProperties(d->m_FWProps); } void BaseApplication::uninitialize() { auto pfw = this->getFramework(); if (pfw) { pfw->stop(); // Wait for up to 10 seconds for the CTK plugin framework to stop pfw->waitForStop(10000); } Poco::Util::Application::uninitialize(); } int BaseApplication::getArgc() const { return d->m_Argc; } char **BaseApplication::getArgv() const { return d->m_Argv; } QString BaseApplication::getCTKFrameworkStorageDir() const { QString storageDir; if (this->getSingleMode()) { // This function checks if an instance is already running and either sends a message to // it containing the command line arguments or checks if a new instance was forced by // providing the BlueBerry.newInstance command line argument. In the latter case, a path // to a temporary directory for the new application's storage directory is returned. storageDir = handleNewAppInstance(static_cast(d->m_QApp), d->m_Argc, d->m_Argv, ARG_NEWINSTANCE); } if (storageDir.isEmpty()) { // This is a new instance and no other instance is already running. We specify the // storage directory here (this is the same code as in berryInternalPlatform.cpp) // so that we can re-use the location for the persistent data location of the // the CppMicroServices library. // Append a hash value of the absolute path of the executable to the data location. // This allows to start the same application from different build or install trees. storageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + this->getOrganizationName() + "/" + this->getApplicationName() + '_'; storageDir += QString::number(qHash(QCoreApplication::applicationDirPath())) + "/"; } return storageDir; } void BaseApplication::initializeCppMicroServices() { auto storageDir = this->getProperty(ctkPluginConstants::FRAMEWORK_STORAGE).toString(); if (!storageDir.isEmpty()) us::ModuleSettings::SetStoragePath((storageDir + "us" + QDir::separator()).toStdString()); } QCoreApplication *BaseApplication::getQApplication() const { if (nullptr == qApp) { vtkLogger::SetStderrVerbosity(vtkLogger::VERBOSITY_WARNING); vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0); auto defaultFormat = QVTKOpenGLNativeWidget::defaultFormat(); defaultFormat.setSamples(0); QSurfaceFormat::setDefaultFormat(defaultFormat); #ifdef Q_OS_OSX QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); d->m_QApp = this->getSingleMode() ? static_cast(new QmitkSingleApplication(d->m_Argc, d->m_Argv, this->getSafeMode())) : static_cast(new QmitkSafeApplication(d->m_Argc, d->m_Argv, this->getSafeMode())); } return qApp; } void BaseApplication::initializeLibraryPaths() { QStringList suffixes; suffixes << "plugins"; #ifdef Q_OS_WINDOWS suffixes << "bin/plugins"; #ifdef CMAKE_INTDIR suffixes << "bin/" CMAKE_INTDIR "/plugins"; #endif #else suffixes << "lib/plugins"; #ifdef CMAKE_INTDIR suffixes << "lib/" CMAKE_INTDIR "/plugins"; #endif #endif #ifdef Q_OS_MAC suffixes << "../../plugins"; #endif // We add a couple of standard library search paths for plug-ins QDir appDir(QCoreApplication::applicationDirPath()); // Walk one directory up and add bin and lib sub-dirs; this might be redundant appDir.cdUp(); for (const auto& suffix : qAsConst(suffixes)) ctkPluginFrameworkLauncher::addSearchPath(appDir.absoluteFilePath(suffix)); } int BaseApplication::main(const std::vector &args) { // Start the plugin framework and all installed plug-ins according to their auto-start setting QStringList arguments; for (auto const &arg : args) arguments.push_back(QString::fromStdString(arg)); if (nullptr != d->m_Splashscreen) { // A splash screen is displayed. Create the closing callback. d->m_SplashscreenClosingCallback = new SplashCloserCallback(d->m_Splashscreen); } return ctkPluginFrameworkLauncher::run(d->m_SplashscreenClosingCallback, QVariant::fromValue(arguments)).toInt(); } void BaseApplication::defineOptions(Poco::Util::OptionSet &options) { Poco::Util::Option helpOption("help", "h", "print this help text"); helpOption.callback(Poco::Util::OptionCallback(this, &BaseApplication::printHelp)); options.addOption(helpOption); Poco::Util::Option newInstanceOption(ARG_NEWINSTANCE.toStdString(), "", "forces a new instance of this application"); newInstanceOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(newInstanceOption); Poco::Util::Option cleanOption(ARG_CLEAN.toStdString(), "", "cleans the plugin cache"); cleanOption.callback(Poco::Util::OptionCallback(d, &Impl::handleClean)); options.addOption(cleanOption); Poco::Util::Option productOption(ARG_PRODUCT.toStdString(), "", "the id of the product to be launched"); productOption.argument("").binding(PROP_PRODUCT.toStdString()); options.addOption(productOption); Poco::Util::Option appOption(ARG_APPLICATION.toStdString(), "", "the id of the application extension to be executed"); appOption.argument("").binding(PROP_APPLICATION.toStdString()); options.addOption(appOption); Poco::Util::Option provOption(ARG_PROVISIONING.toStdString(), "", "the location of a provisioning file"); provOption.argument("").binding(ARG_PROVISIONING.toStdString()); options.addOption(provOption); Poco::Util::Option storageDirOption(ARG_STORAGE_DIR.toStdString(), "", "the location for storing persistent application data"); storageDirOption.argument("").binding(ctkPluginConstants::FRAMEWORK_STORAGE.toStdString()); options.addOption(storageDirOption); Poco::Util::Option consoleLogOption(ARG_CONSOLELOG.toStdString(), "", "log messages to the console"); consoleLogOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(consoleLogOption); Poco::Util::Option debugOption(ARG_DEBUG.toStdString(), "", "enable debug mode"); debugOption.argument("", false).binding(ctkPluginFrameworkLauncher::PROP_DEBUG.toStdString()); options.addOption(debugOption); Poco::Util::Option forcePluginOption(ARG_FORCE_PLUGIN_INSTALL.toStdString(), "", "force installing plug-ins with same symbolic name"); forcePluginOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(forcePluginOption); Poco::Util::Option preloadLibsOption(ARG_PRELOAD_LIBRARY.toStdString(), "", "preload a library"); preloadLibsOption.argument("") .repeatable(true) .callback(Poco::Util::OptionCallback(d, &Impl::handlePreloadLibraryOption)); options.addOption(preloadLibsOption); Poco::Util::Option noRegistryCacheOption(ARG_NO_REGISTRY_CACHE.toStdString(), "", "do not use a cache for the registry"); noRegistryCacheOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noRegistryCacheOption); Poco::Util::Option noLazyRegistryCacheLoadingOption(ARG_NO_LAZY_REGISTRY_CACHE_LOADING.toStdString(), "", "do not use lazy cache loading for the registry"); noLazyRegistryCacheLoadingOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noLazyRegistryCacheLoadingOption); Poco::Util::Option registryMultiLanguageOption(ARG_REGISTRY_MULTI_LANGUAGE.toStdString(), "", "enable multi-language support for the registry"); registryMultiLanguageOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(registryMultiLanguageOption); Poco::Util::Option splashScreenOption(ARG_SPLASH_IMAGE.toStdString(), "", "optional picture to use as a splash screen"); splashScreenOption.argument("").binding(ARG_SPLASH_IMAGE.toStdString()); options.addOption(splashScreenOption); Poco::Util::Option xargsOption(ARG_XARGS.toStdString(), "", "Extended argument list"); xargsOption.argument("").binding(ARG_XARGS.toStdString()); options.addOption(xargsOption); Poco::Util::Option logQtMessagesOption(ARG_LOG_QT_MESSAGES.toStdString(), "", "log Qt messages"); logQtMessagesOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(logQtMessagesOption); Poco::Util::Option labelSetPresetOption(ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), "", "use this label set preset for new segmentations"); labelSetPresetOption.argument("").binding(ARG_SEGMENTATION_LABELSET_PRESET.toStdString()); options.addOption(labelSetPresetOption); Poco::Util::Option labelSuggestionsOption(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), "", "use this list of predefined suggestions for segmentation labels"); labelSuggestionsOption.argument("").binding(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString()); options.addOption(labelSuggestionsOption); Poco::Util::Application::defineOptions(options); } QSharedPointer BaseApplication::getFramework() const { return ctkPluginFrameworkLauncher::getPluginFramework(); } ctkPluginContext *BaseApplication::getFrameworkContext() const { auto framework = getFramework(); return framework ? framework->getPluginContext() : nullptr; } void BaseApplication::initializeSplashScreen(QCoreApplication * application) const { auto pixmapFileNameProp = d->getProperty(ARG_SPLASH_IMAGE); if (!pixmapFileNameProp.isNull()) { auto pixmapFileName = pixmapFileNameProp.toString(); QFileInfo checkFile(pixmapFileName); if (checkFile.exists() && checkFile.isFile()) { QPixmap pixmap(checkFile.absoluteFilePath()); d->m_Splashscreen = new QSplashScreen(pixmap, Qt::WindowStaysOnTopHint); d->m_Splashscreen->show(); application->processEvents(); } } } QHash BaseApplication::getFrameworkProperties() const { return d->m_FWProps; } int BaseApplication::run() { this->init(d->m_Argc, d->m_Argv); return Application::run(); } void BaseApplication::setProperty(const QString &property, const QVariant &value) { d->m_FWProps[property] = value; } QVariant BaseApplication::getProperty(const QString &property) const { return d->getProperty(property); } void BaseApplication::installTranslator(QTranslator* translator) { this->getQApplication()->installTranslator(translator); } bool BaseApplication::isRunning() { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->isRunning(); mitkThrow() << "Method not implemented."; } void BaseApplication::sendMessage(const QByteArray msg) { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->sendMessage(msg); mitkThrow() << "Method not implemented."; } } diff --git a/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx b/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx index dfd8c8383b..a744a463a1 100644 --- a/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx +++ b/Modules/Classification/CLUtilities/include/itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.hxx @@ -1,396 +1,394 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ /*========================================================================= * * Copyright Insight Software Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef __itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter_hxx #define __itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter_hxx #include "itkEnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter.h" #include "itkConstNeighborhoodIterator.h" #include "itkNeighborhood.h" #include "vnl/vnl_math.h" #include "itkMacro.h" #include "itkRescaleIntensityImageFilter.h" #include "itkMaskImageFilter.h" #include "itkLabelStatisticsImageFilter.h" #include "itkScalarConnectedComponentImageFilter.h" #include "itkRelabelComponentImageFilter.h" #include "itkCastImageFilter.h" #include namespace itk { namespace Statistics { template EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter() : m_NumberOfBinsPerAxis( itkGetStaticConstMacro( DefaultBinsPerAxis ) ), m_Min( NumericTraits::NonpositiveMin() ), m_Max( NumericTraits::max() ), m_MinDistance( NumericTraits::ZeroValue() ), m_MaxDistance( NumericTraits::max() ), m_InsidePixelValue( NumericTraits::OneValue() ) { this->SetNumberOfRequiredInputs( 1 ); this->SetNumberOfRequiredOutputs( 1 ); const unsigned int measurementVectorSize = 1; this->ProcessObject::SetNthOutput( 0, this->MakeOutput( 0 ) ); this->ProcessObject::SetNthOutput( 1, this->MakeOutput( 1 ) ); HistogramType *output = const_cast( this->GetOutput() ); output->SetMeasurementVectorSize( measurementVectorSize ); m_siMatrix = new double[m_NumberOfBinsPerAxis]; for(unsigned int i = 0; i < m_NumberOfBinsPerAxis; i++) { m_siMatrix[i] = 0; } this->m_LowerBound.SetSize( measurementVectorSize ); this->m_UpperBound.SetSize( measurementVectorSize ); this->m_LowerBound[0] = this->m_Min; this->m_LowerBound[1] = this->m_MinDistance; this->m_UpperBound[0] = this->m_Max; this->m_UpperBound[1] = this->m_MaxDistance; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetOffset( const OffsetType offset ) { OffsetVectorPointer offsetVector = OffsetVector::New(); offsetVector->push_back( offset ); this->SetOffsets( offsetVector ); MITK_WARN << "We now have " << this->GetOffsets()->size() << " offsets in matrixgenerator"; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::AddOffsets( const std::vector _offsets ) { OffsetVectorPointer offsetVector = OffsetVector::New(); - typename OffsetVector::ConstIterator offsets; - //MITK_WARN << "We have " << this->GetOffsets()->size() << " offsets!"; for( std::size_t i = 0; i < _offsets.size(); i++) { offsetVector->push_back(_offsets[i]); auto k = _offsets[i]; this->NormalizeOffsetDirection(k); offsetVector->push_back(k); } this->SetOffsets( offsetVector ); MITK_WARN << "We now have " << this->GetOffsets()->size() << " offsets in matrixgenerator"; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetInput( const ImageType *image ) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput( 0, const_cast( image ) ); } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetMaskImage( const ImageType *image ) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput( 1, const_cast( image ) ); } template const TImageType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetInput() const { if( this->GetNumberOfInputs() < 1 ) { return ITK_NULLPTR; } return static_cast( this->ProcessObject::GetInput( 0 ) ); } template const TImageType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetMaskImage() const { if( this->GetNumberOfInputs() < 2 ) { return ITK_NULLPTR; } return static_cast( this->ProcessObject::GetInput( 1 ) ); } template const typename EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter::HistogramType * EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetOutput() const { const HistogramType *output = static_cast( this->ProcessObject::GetOutput( 0 ) ); return output; } template double* EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GetSiMatrix() const { return m_siMatrix; } template typename EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter::DataObjectPointer EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::MakeOutput( DataObjectPointerArraySizeType itkNotUsed( idx ) ) { return HistogramType::New().GetPointer(); } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::GenerateData() { HistogramType *output = static_cast( this->ProcessObject::GetOutput( 0 ) ); const ImageType * inputImage = this->GetInput(); const ImageType * maskImage = this->GetMaskImage(); // First, create an appropriate histogram with the right number of bins // and mins and maxes correct for the image type. typename HistogramType::SizeType size( output->GetMeasurementVectorSize() ); size.Fill( this->m_NumberOfBinsPerAxis ); this->m_LowerBound[0] = this->m_Min; this->m_LowerBound[1] = this->m_MinDistance; this->m_UpperBound[0] = this->m_Max; this->m_UpperBound[1] = this->m_MaxDistance; output->Initialize( size, this->m_LowerBound, this->m_UpperBound ); MeasurementVectorType run( output->GetMeasurementVectorSize() ); typename HistogramType::IndexType hIndex; //Cast the image to a float image - with no respect to the incoming image //to prevent some non-templated itk issues typedef itk::Image FloatImageType; typedef itk::CastImageFilter CastFilterType; typename CastFilterType::Pointer caster = CastFilterType::New(); caster->SetInput(inputImage); caster->Update(); typename FloatImageType::Pointer floatImage = caster->GetOutput(); //Cast the mask to an unsigned short image - with no respect to the incomimg maskimage //to prevent some non-templated itk issues typedef unsigned short LabelPixelType; typedef itk::Image LabelImageType; typedef itk::CastImageFilter MaskCastFilterType; typename MaskCastFilterType::Pointer maskCaster = MaskCastFilterType::New(); maskCaster->SetInput(maskImage); maskCaster->Update(); //Set all values out of the mask to nans. typedef itk::MaskImageFilter< FloatImageType, LabelImageType, FloatImageType > MaskFilterType; typename MaskFilterType::Pointer maskFilter = MaskFilterType::New(); maskFilter->SetInput(floatImage); maskFilter->SetMaskImage(maskCaster->GetOutput()); maskFilter->SetOutsideValue( std::numeric_limits::quiet_NaN()); maskFilter->Update(); FloatImageType::Pointer floatImageMasked = maskFilter->GetOutput(); typedef ConstNeighborhoodIterator NeighborhoodIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill( 1 ); NeighborhoodIteratorType neighborIt( radius, inputImage, inputImage->GetRequestedRegion() ); for( neighborIt.GoToBegin(); !neighborIt.IsAtEnd(); ++neighborIt ) { const PixelType centerPixelIntensity = neighborIt.GetCenterPixel(); IndexType centerIndex = neighborIt.GetIndex(); FloatImageType::IndexType cIndex; cIndex[0] = centerIndex[0]; cIndex[1] = centerIndex[1]; cIndex[2] = centerIndex[2]; float centerFloatPixel = floatImageMasked->GetPixel(cIndex); int px = 0; PixelType sum = 0.0; bool canCalculate = true; typename OffsetVector::ConstIterator offsets; for( offsets = this->GetOffsets()->Begin(); offsets != this->GetOffsets()->End(); offsets++ ) { OffsetType offset = offsets.Value(); IndexType index; index = centerIndex + offset; if(!inputImage->GetRequestedRegion().IsInside(index)) { canCalculate = false; break; } PixelType offsetPixel = inputImage->GetPixel(index); FloatImageType::IndexType fIndex; fIndex[0] = index[0]; fIndex[1] = index[1]; fIndex[2] = index[2]; float floatPixel = floatImageMasked->GetPixel(fIndex); //We have a nan here if(floatPixel != floatPixel || centerFloatPixel!= centerFloatPixel) { canCalculate = false; break; } sum += offsetPixel; px++; } //If we have a nan in the neighbourhood, continue if(!canCalculate) continue; PixelType mean = sum / px; double si = std::abs(mean-centerPixelIntensity); run[0] = centerPixelIntensity; //Check for NaN and inf if(run[0] == run[0] && !std::isinf(std::abs(run[0]))) { output->GetIndex( run, hIndex ); output->IncreaseFrequencyOfIndex( hIndex, 1 ); m_siMatrix[hIndex[0]] += si; } //MITK_WARN << " -> In this round we added: " << centerIndex << " with value " << centerPixelIntensity << " and si = " << si; //MITK_WARN << " -> Values are now siMatrix["< Values are now niMatrix: " << output->GetFrequency(hIndex) << "/" << run; } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetPixelValueMinMax( PixelType min, PixelType max ) { if( this->m_Min != min || this->m_Max != max ) { itkDebugMacro( "setting Min to " << min << "and Max to " << max ); this->m_Min = min; this->m_Max = max; this->Modified(); } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::SetDistanceValueMinMax( RealType min, RealType max ) { if( this->m_MinDistance != min || this->m_MaxDistance != max ) { itkDebugMacro( "setting MinDistance to " << min << "and MaxDistance to " << max ); this->m_MinDistance = min; this->m_MaxDistance = max; this->Modified(); } } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::PrintSelf( std::ostream& os, Indent indent ) const { Superclass::PrintSelf( os,indent ); os << indent << "Offsets: " << this->GetOffsets() << std::endl; os << indent << "Min: " << this->m_Min << std::endl; os << indent << "Max: " << this->m_Max << std::endl; os << indent << "Min distance: " << this->m_MinDistance << std::endl; os << indent << "Max distance: " << this->m_MaxDistance << std::endl; os << indent << "NumberOfBinsPerAxis: " << this->m_NumberOfBinsPerAxis << std::endl; os << indent << "InsidePixelValue: " << this->m_InsidePixelValue << std::endl; } template void EnhancedScalarImageToNeighbourhoodGreyLevelDifferenceMatrixFilter ::NormalizeOffsetDirection(OffsetType &offset) { //MITK_WARN <<" -> NGTDM old offset = " << offset; itkDebugMacro("old offset = " << offset << std::endl); int sign = 1; bool metLastNonZero = false; for (int i = offset.GetOffsetDimension()-1; i>=0; i--) { if (metLastNonZero) { offset[i] *= sign; } else if (offset[i] != 0) { sign = (offset[i] > 0 ) ? 1 : -1; metLastNonZero = true; offset[i] *= sign; } } //MITK_WARN << " ->NGTDM new offset = " << offset; itkDebugMacro("new offset = " << offset << std::endl); } } // end of namespace Statistics } // end of namespace itk #endif diff --git a/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp b/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp index 3b4130bc5c..026f64a6d2 100644 --- a/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp +++ b/Modules/Classification/CLUtilities/src/GlobalImageFeatures/mitkGIFVolumetricDensityStatistics.cpp @@ -1,515 +1,513 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // MITK #include #include #include #include #include // ITK #include #include #include #include // VTK #include #include #include #include #include #include #include #include // STL #include #include // Eigen #include struct GIFVolumetricDensityStatisticsParameters { double volume; mitk::FeatureID id; }; template void CalculateVolumeDensityStatistic(const itk::Image* itkImage, const mitk::Image* mask, GIFVolumetricDensityStatisticsParameters params, mitk::GIFVolumetricDensityStatistics::FeatureListType & featureList) { typedef itk::Image ImageType; typedef itk::Image MaskType; double volume = params.volume; typename MaskType::Pointer maskImage = MaskType::New(); mitk::CastToItkImage(mask, maskImage); itk::ImageRegionConstIteratorWithIndex imgA(itkImage, itkImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex imgB(itkImage, itkImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex maskA(maskImage, maskImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex maskB(maskImage, maskImage->GetLargestPossibleRegion()); double moranA = 0; double moranB = 0; double geary = 0; double Nv = 0; double w_ij = 0; double mean = 0; typename ImageType::PointType pointA; typename ImageType::PointType pointB; while (!imgA.IsAtEnd()) { if (maskA.Get() > 0) { Nv += 1; mean += imgA.Get(); } ++imgA; ++maskA; } mean /= Nv; imgA.GoToBegin(); maskA.GoToBegin(); while (!imgA.IsAtEnd()) { if (maskA.Get() > 0) { imgB.GoToBegin(); maskB.GoToBegin(); while (!imgB.IsAtEnd()) { if ((imgA.GetIndex() == imgB.GetIndex()) || (maskB.Get() < 1)) { ++imgB; ++maskB; continue; } itkImage->TransformIndexToPhysicalPoint(maskA.GetIndex(), pointA); itkImage->TransformIndexToPhysicalPoint(maskB.GetIndex(), pointB); double w = 1 / pointA.EuclideanDistanceTo(pointB); moranA += w*(imgA.Get() - mean)* (imgB.Get() - mean); geary += w * (imgA.Get() - imgB.Get()) * (imgA.Get() - imgB.Get()); w_ij += w; ++imgB; ++maskB; } moranB += (imgA.Get() - mean)* (imgA.Get() - mean); } ++imgA; ++maskA; } MITK_INFO << "Volume: " << volume; MITK_INFO << " Mean: " << mean; featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume integrated intensity"), volume* mean)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume Moran's I index"), Nv / w_ij * moranA / moranB)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume Geary's C measure"), ( Nv -1 ) / 2 / w_ij * geary/ moranB)); } void calculateMOBB(vtkPointSet *pointset, double &volume, double &surface) { volume = std::numeric_limits::max(); for (int cellID = 0; cellID < pointset->GetNumberOfCells(); ++cellID) { auto cell = pointset->GetCell(cellID); for (int edgeID = 0; edgeID < 3; ++edgeID) { auto edge = cell->GetEdge(edgeID); double pA[3], pB[3]; double pAA[3], pBB[3]; vtkSmartPointer transform = vtkSmartPointer::New(); transform->PostMultiply(); pointset->GetPoint(edge->GetPointId(0), pA); pointset->GetPoint(edge->GetPointId(1), pB); double angleZ = std::atan2((- pA[2] + pB[2]) ,(pA[1] - pB[1])); angleZ *= 180 / vnl_math::pi; if (pA[2] == pB[2]) angleZ = 0; transform->RotateX(angleZ); transform->TransformPoint(pA, pAA); transform->TransformPoint(pB, pBB); double angleY = std::atan2((pAA[1] -pBB[1]) ,-(pAA[0] - pBB[0])); angleY *= 180 / vnl_math::pi; if (pAA[1] == pBB[1]) angleY = 0; transform->RotateZ(angleY); double p0[3]; pointset->GetPoint(edge->GetPointId(0), p0); double curMinX = std::numeric_limits::max(); double curMaxX = std::numeric_limits::lowest(); double curMinY = std::numeric_limits::max(); double curMaxY = std::numeric_limits::lowest(); double curMinZ = std::numeric_limits::max(); double curMaxZ = std::numeric_limits::lowest(); for (int pointID = 0; pointID < pointset->GetNumberOfPoints(); ++pointID) { double p[3]; double p2[3]; pointset->GetPoint(pointID, p); p[0] -= p0[0]; p[1] -= p0[1]; p[2] -= p0[2]; transform->TransformPoint(p, p2); curMinX = std::min(p2[0], curMinX); curMaxX = std::max(p2[0], curMaxX); curMinY = std::min(p2[1], curMinY); curMaxY = std::max(p2[1], curMaxY); curMinZ = std::min(p2[2], curMinZ); curMaxZ = std::max(p2[2], curMaxZ); } if ((curMaxX - curMinX)*(curMaxY - curMinY)*(curMaxZ - curMinZ) < volume) { volume = (curMaxX - curMinX)*(curMaxY - curMinY)*(curMaxZ - curMinZ); surface = (curMaxX - curMinX)*(curMaxX - curMinX) + (curMaxY - curMinY)*(curMaxY - curMinY) + (curMaxZ - curMinZ)*(curMaxZ - curMinZ); surface *= 2; } } } } void calculateMEE(vtkPointSet *pointset, double &vol, double &surf, double tolerance=0.0001) { // Inspired by https://github.com/smdabdoub/ProkaryMetrics/blob/master/calc/fitting.py int numberOfPoints = pointset->GetNumberOfPoints(); int dimension = 3; Eigen::MatrixXd points(3, numberOfPoints); Eigen::MatrixXd Q(3+1, numberOfPoints); double p[3]; std::cout << "Initialize Q " << std::endl; for (int i = 0; i < numberOfPoints; ++i) { pointset->GetPoint(i, p); points(0, i) = p[0]; points(1, i) = p[1]; points(2, i) = p[2]; Q(0, i) = p[0]; Q(1, i) = p[1]; Q(2, i) = p[2]; Q(3, i) = 1.0; } - int count = 1; double error = 1; Eigen::VectorXd u_vector(numberOfPoints); u_vector.fill(1.0 / numberOfPoints); Eigen::DiagonalMatrix u = u_vector.asDiagonal(); Eigen::VectorXd ones(dimension + 1); ones.fill(1); Eigen::MatrixXd Ones = ones.asDiagonal(); // Khachiyan Algorithm while (error > tolerance) { auto Qt = Q.transpose(); Eigen::MatrixXd X = Q*u*Qt; Eigen::FullPivHouseholderQR qr(X); Eigen::MatrixXd Xi = qr.solve(Ones); Eigen::MatrixXd M = Qt * Xi * Q; double maximumValue = M(0, 0); int maximumPosition = 0; for (int i = 0; i < numberOfPoints; ++i) { if (maximumValue < M(i, i)) { maximumValue = M(i, i); maximumPosition = i; } } double stepsize = (maximumValue - dimension - 1) / ((dimension + 1) * (maximumValue - 1)); Eigen::DiagonalMatrix new_u = (1.0 - stepsize) * u; new_u.diagonal()[maximumPosition] = (new_u.diagonal())(maximumPosition) + stepsize; - ++count; error = (new_u.diagonal() - u.diagonal()).norm(); u.diagonal() = new_u.diagonal(); } // U = u Eigen::MatrixXd Ai = points * u * points.transpose() - points * u *(points * u).transpose(); Eigen::FullPivHouseholderQR qr(Ai); Eigen::VectorXd ones2(dimension); ones2.fill(1); Eigen::MatrixXd Ones2 = ones2.asDiagonal(); Eigen::MatrixXd A = qr.solve(Ones2)*1.0/dimension; Eigen::JacobiSVD svd(A); double c = 1 / sqrt(svd.singularValues()[0]); double b = 1 / sqrt(svd.singularValues()[1]); double a = 1 / sqrt(svd.singularValues()[2]); double V = 4 * vnl_math::pi*a*b*c / 3; double ad_mvee= 0; double alpha = std::sqrt(1 - b*b / a / a); double beta = std::sqrt(1 - c*c / a / a); for (int i = 0; i < 20; ++i) { ad_mvee += 4 * vnl_math::pi*a*b*(alpha*alpha + beta*beta) / (2 * alpha*beta) * (std::pow(alpha*beta, i)) / (1 - 4 * i*i); } vol = V; surf = ad_mvee; } mitk::GIFVolumetricDensityStatistics::GIFVolumetricDensityStatistics() { SetLongName("volume-density"); SetShortName("volden"); SetFeatureClassName("Morphological Density"); } void mitk::GIFVolumetricDensityStatistics::AddArguments(mitkCommandLineParser& parser) const { std::string name = GetOptionPrefix(); parser.addArgument(GetLongName(), name, mitkCommandLineParser::Bool, "Use Volume-Density Statistic", "calculates volume density based features", us::Any()); } mitk::AbstractGlobalImageFeature::FeatureListType mitk::GIFVolumetricDensityStatistics::DoCalculateFeatures(const Image* image, const Image* mask) { FeatureListType featureList; if (image->GetDimension() < 3) { MITK_INFO << "Skipped calculating volumetric density features; only 3D images are supported ...."; return featureList; } MITK_INFO << "Start calculating volumetric density features ...."; vtkSmartPointer mesher = vtkSmartPointer::New(); vtkSmartPointer stats = vtkSmartPointer::New(); vtkSmartPointer stats2 = vtkSmartPointer::New(); auto nonconstVtkData = const_cast(mask->GetVtkImageData()); mesher->SetInputData(nonconstVtkData); mesher->SetValue(0, 0.5); stats->SetInputConnection(mesher->GetOutputPort()); stats->Update(); vtkSmartPointer delaunay = vtkSmartPointer< vtkDelaunay3D >::New(); delaunay->SetInputConnection(mesher->GetOutputPort()); delaunay->SetAlpha(0); delaunay->Update(); vtkSmartPointer geometryFilter = vtkSmartPointer::New(); geometryFilter->SetInputConnection(delaunay->GetOutputPort()); geometryFilter->Update(); stats2->SetInputConnection(geometryFilter->GetOutputPort()); stats2->Update(); double vol_mvee; double surf_mvee; calculateMEE(mesher->GetOutput(), vol_mvee, surf_mvee); double vol_mobb; double surf_mobb; calculateMOBB(geometryFilter->GetOutput(), vol_mobb, surf_mobb); double pi = vnl_math::pi; double meshVolume = stats->GetVolume(); double meshSurf = stats->GetSurfaceArea(); GIFVolumetricDensityStatisticsParameters params; params.volume = meshVolume; params.id = this->CreateTemplateFeatureID(); AccessByItk_3(image, CalculateVolumeDensityStatistic, mask, params, featureList); //Calculate center of mass shift int xx = mask->GetDimensions()[0]; int yy = mask->GetDimensions()[1]; int zz = mask->GetDimensions()[2]; double xd = mask->GetGeometry()->GetSpacing()[0]; double yd = mask->GetGeometry()->GetSpacing()[1]; double zd = mask->GetGeometry()->GetSpacing()[2]; int minimumX = xx; int maximumX = 0; int minimumY = yy; int maximumY = 0; int minimumZ = zz; int maximumZ = 0; vtkSmartPointer dataset1Arr = vtkSmartPointer::New(); vtkSmartPointer dataset2Arr = vtkSmartPointer::New(); vtkSmartPointer dataset3Arr = vtkSmartPointer::New(); dataset1Arr->SetNumberOfComponents(1); dataset2Arr->SetNumberOfComponents(1); dataset3Arr->SetNumberOfComponents(1); dataset1Arr->SetName("M1"); dataset2Arr->SetName("M2"); dataset3Arr->SetName("M3"); vtkSmartPointer dataset1ArrU = vtkSmartPointer::New(); vtkSmartPointer dataset2ArrU = vtkSmartPointer::New(); vtkSmartPointer dataset3ArrU = vtkSmartPointer::New(); dataset1ArrU->SetNumberOfComponents(1); dataset2ArrU->SetNumberOfComponents(1); dataset3ArrU->SetNumberOfComponents(1); dataset1ArrU->SetName("M1"); dataset2ArrU->SetName("M2"); dataset3ArrU->SetName("M3"); vtkSmartPointer points = vtkSmartPointer< vtkPoints >::New(); for (int x = 0; x < xx; x++) { for (int y = 0; y < yy; y++) { for (int z = 0; z < zz; z++) { itk::Image::IndexType index; index[0] = x; index[1] = y; index[2] = z; mitk::ScalarType pxImage; mitk::ScalarType pxMask; mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, image->GetChannelDescriptor().GetPixelType(), image, image->GetVolumeData(), index, pxImage, 0); mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, mask->GetChannelDescriptor().GetPixelType(), mask, mask->GetVolumeData(), index, pxMask, 0); //Check if voxel is contained in segmentation if (pxMask > 0) { minimumX = std::min(x, minimumX); minimumY = std::min(y, minimumY); minimumZ = std::min(z, minimumZ); maximumX = std::max(x, maximumX); maximumY = std::max(y, maximumY); maximumZ = std::max(z, maximumZ); points->InsertNextPoint(x * xd, y * yd, z * zd); if (pxImage == pxImage) { dataset1Arr->InsertNextValue(x * xd); dataset2Arr->InsertNextValue(y * yd); dataset3Arr->InsertNextValue(z * zd); } } } } } vtkSmartPointer datasetTable = vtkSmartPointer::New(); datasetTable->AddColumn(dataset1Arr); datasetTable->AddColumn(dataset2Arr); datasetTable->AddColumn(dataset3Arr); vtkSmartPointer pcaStatistics = vtkSmartPointer::New(); pcaStatistics->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, datasetTable); pcaStatistics->SetColumnStatus("M1", 1); pcaStatistics->SetColumnStatus("M2", 1); pcaStatistics->SetColumnStatus("M3", 1); pcaStatistics->RequestSelectedColumns(); pcaStatistics->SetDeriveOption(true); pcaStatistics->Update(); vtkSmartPointer eigenvalues = vtkSmartPointer::New(); pcaStatistics->GetEigenvalues(eigenvalues); std::vector eigen_val(3); eigen_val[2] = eigenvalues->GetValue(0); eigen_val[1] = eigenvalues->GetValue(1); eigen_val[0] = eigenvalues->GetValue(2); double major = 2 * sqrt(eigen_val[2]); double minor = 2 * sqrt(eigen_val[1]); double least = 2 * sqrt(eigen_val[0]); double alpha = std::sqrt(1 - minor * minor / major / major); double beta = std::sqrt(1 - least * least / major / major); double a = (maximumX - minimumX + 1) * xd; double b = (maximumY - minimumY + 1) * yd; double c = (maximumZ - minimumZ + 1) * zd; double vd_aabb = meshVolume / (a * b * c); double ad_aabb = meshSurf / (2 * a * b + 2 * a * c + 2 * b * c); double vd_aee = 3 * meshVolume / (4.0 * pi * major * minor * least); double ad_aee = 0; for (int i = 0; i < 20; ++i) { ad_aee += 4 * pi * major * minor * (alpha * alpha + beta * beta) / (2 * alpha * beta) * (std::pow(alpha * beta, i)) / (1 - 4 * i * i); } ad_aee = meshSurf / ad_aee; double vd_ch = meshVolume / stats2->GetVolume(); double ad_ch = meshSurf / stats2->GetSurfaceArea(); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density axis-aligned bounding box"), vd_aabb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density axis-aligned bounding box"), ad_aabb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density oriented minimum bounding box"), meshVolume / vol_mobb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density oriented minimum bounding box"), meshSurf / surf_mobb)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density approx. enclosing ellipsoid"), vd_aee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density approx. enclosing ellipsoid"), ad_aee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density approx. minimum volume enclosing ellipsoid"), meshVolume / vol_mvee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density approx. minimum volume enclosing ellipsoid"), meshSurf / surf_mvee)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Volume density convex hull"), vd_ch)); featureList.push_back(std::make_pair(mitk::CreateFeatureID(params.id, "Surface density convex hull"), ad_ch)); MITK_INFO << "Finished calculating volumetric density features...."; return featureList; } mitk::AbstractGlobalImageFeature::FeatureListType mitk::GIFVolumetricDensityStatistics::CalculateFeatures(const Image* image, const Image* mask, const Image*) { return Superclass::CalculateFeatures(image, mask); } diff --git a/Modules/Core/test/mitkGeometry3DTest.cpp b/Modules/Core/test/mitkGeometry3DTest.cpp index c4395e1a28..a699e40249 100644 --- a/Modules/Core/test/mitkGeometry3DTest.cpp +++ b/Modules/Core/test/mitkGeometry3DTest.cpp @@ -1,622 +1,620 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkGeometry3D.h" #include #include #include "mitkInteractionConst.h" #include "mitkRotationOperation.h" #include #include #include "mitkTestingMacros.h" #include #include bool testGetAxisVectorVariants(mitk::Geometry3D *geometry) { int direction; for (direction = 0; direction < 3; ++direction) { mitk::Vector3D frontToBack(0.); switch (direction) { case 0: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(true, false, false); break; // 7-3 case 1: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(false, true, false); break; // 7-5 case 2: frontToBack = geometry->GetCornerPoint(false, false, false) - geometry->GetCornerPoint(false, false, true); break; // 7-2 } std::cout << "Testing GetAxisVector(int) vs GetAxisVector(bool, bool, bool): "; if (mitk::Equal(geometry->GetAxisVector(direction), frontToBack) == false) { std::cout << "[FAILED]" << std::endl; return false; } std::cout << "[PASSED]" << std::endl; } return true; } bool testGetAxisVectorExtent(mitk::Geometry3D *geometry) { int direction; for (direction = 0; direction < 3; ++direction) { if (mitk::Equal(geometry->GetAxisVector(direction).GetNorm(), geometry->GetExtentInMM(direction)) == false) { std::cout << "[FAILED]" << std::endl; return false; } std::cout << "[PASSED]" << std::endl; } return true; } // a part of the test requires axis-parallel coordinates int testIndexAndWorldConsistency(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems: "); mitk::Point3D origin = geometry3d->GetOrigin(); mitk::Point3D dummy; MITK_TEST_OUTPUT(<< " Testing index->world->index conversion consistency"); geometry3d->WorldToIndex(origin, dummy); geometry3d->IndexToWorld(dummy, dummy); MITK_TEST_CONDITION_REQUIRED(dummy == origin, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin, mitk::Point3D)==(0,0,0)"); mitk::Point3D globalOrigin; mitk::FillVector3D(globalOrigin, 0, 0, 0); mitk::Point3D originContinuousIndex; geometry3d->WorldToIndex(origin, originContinuousIndex); MITK_TEST_CONDITION_REQUIRED(originContinuousIndex == globalOrigin, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin, itk::Index)==(0,0,0)"); itk::Index<3> itkindex; geometry3d->WorldToIndex(origin, itkindex); itk::Index<3> globalOriginIndex; mitk::vtk2itk(globalOrigin, globalOriginIndex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin-0.5*spacing, itk::Index)==(0,0,0)"); mitk::Vector3D halfSpacingStep = geometry3d->GetSpacing() * 0.5; mitk::Matrix3D rotation; mitk::Point3D originOffCenter = origin - halfSpacingStep; geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin+0.5*spacing-eps, itk::Index)==(0,0,0)"); originOffCenter = origin + halfSpacingStep; originOffCenter -= mitk::Vector(0.0001); geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(origin+0.5*spacing, itk::Index)==(1,1,1)"); originOffCenter = origin + halfSpacingStep; itk::Index<3> global111; mitk::FillVector3D(global111, 1, 1, 1); geometry3d->WorldToIndex(originOffCenter, itkindex); MITK_TEST_CONDITION_REQUIRED(itkindex == global111, ""); MITK_TEST_OUTPUT(<< " Testing WorldToIndex(GetCenter())==BoundingBox.GetCenter: "); mitk::Point3D center = geometry3d->GetCenter(); mitk::Point3D centerContIndex; geometry3d->WorldToIndex(center, centerContIndex); mitk::BoundingBox::ConstPointer boundingBox = geometry3d->GetBoundingBox(); mitk::BoundingBox::PointType centerBounds = boundingBox->GetCenter(); // itk assumes corner based geometry. If our geometry is center based (imageGoe == true), everything needs to be // shifted if (geometry3d->GetImageGeometry()) { centerBounds[0] -= 0.5; centerBounds[1] -= 0.5; centerBounds[2] -= 0.5; } MITK_TEST_CONDITION_REQUIRED(mitk::Equal(centerContIndex, centerBounds), ""); MITK_TEST_OUTPUT(<< " Testing GetCenter()==IndexToWorld(BoundingBox.GetCenter): "); center = geometry3d->GetCenter(); mitk::Point3D centerBoundsInWorldCoords; geometry3d->IndexToWorld(centerBounds, centerBoundsInWorldCoords); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(center, centerBoundsInWorldCoords), ""); return EXIT_SUCCESS; } int testIndexAndWorldConsistencyForVectors(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems for vectors: "); mitk::Vector3D xAxisMM = geometry3d->GetAxisVector(0); mitk::Vector3D xAxisContinuousIndex; mitk::Vector3D xAxisContinuousIndexDeprecated; mitk::Point3D p, pIndex, origin; origin = geometry3d->GetOrigin(); p[0] = xAxisMM[0]; p[1] = xAxisMM[1]; p[2] = xAxisMM[2]; geometry3d->WorldToIndex(p, pIndex); geometry3d->WorldToIndex(xAxisMM, xAxisContinuousIndexDeprecated); geometry3d->WorldToIndex(xAxisMM, xAxisContinuousIndex); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == pIndex[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == pIndex[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == pIndex[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == xAxisContinuousIndexDeprecated[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == xAxisContinuousIndexDeprecated[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == xAxisContinuousIndexDeprecated[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[0] == pIndex[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[1] == pIndex[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[2] == pIndex[2], ""); geometry3d->IndexToWorld(xAxisContinuousIndex, xAxisContinuousIndex); geometry3d->IndexToWorld(xAxisContinuousIndexDeprecated, xAxisContinuousIndexDeprecated); geometry3d->IndexToWorld(pIndex, p); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex == xAxisMM, ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[0] == p[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[1] == p[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndex[2] == p[2], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated == xAxisMM, ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[0] == p[0], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[1] == p[1], ""); MITK_TEST_CONDITION_REQUIRED(xAxisContinuousIndexDeprecated[2] == p[2], ""); return EXIT_SUCCESS; } int testIndexAndWorldConsistencyForIndex(mitk::Geometry3D *geometry3d) { MITK_TEST_OUTPUT(<< "Testing consistency of index and world coordinate systems: "); // creating testing data itk::Index<4> itkIndex4, itkIndex4b; itk::Index<3> itkIndex3, itkIndex3b; itk::Index<2> itkIndex2, itkIndex2b; itk::Index<3> mitkIndex, mitkIndexb; itkIndex4[0] = itkIndex4[1] = itkIndex4[2] = itkIndex4[3] = 4; itkIndex3[0] = itkIndex3[1] = itkIndex3[2] = 6; itkIndex2[0] = itkIndex2[1] = 2; mitkIndex[0] = mitkIndex[1] = mitkIndex[2] = 13; // check for consistency mitk::Point3D point; geometry3d->IndexToWorld(itkIndex2, point); geometry3d->WorldToIndex(point, itkIndex2b); MITK_TEST_CONDITION_REQUIRED(((itkIndex2b[0] == itkIndex2[0]) && (itkIndex2b[1] == itkIndex2[1])), "Testing itk::index<2> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(itkIndex3, point); geometry3d->WorldToIndex(point, itkIndex3b); MITK_TEST_CONDITION_REQUIRED( ((itkIndex3b[0] == itkIndex3[0]) && (itkIndex3b[1] == itkIndex3[1]) && (itkIndex3b[2] == itkIndex3[2])), "Testing itk::index<3> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(itkIndex4, point); geometry3d->WorldToIndex(point, itkIndex4b); MITK_TEST_CONDITION_REQUIRED(((itkIndex4b[0] == itkIndex4[0]) && (itkIndex4b[1] == itkIndex4[1]) && (itkIndex4b[2] == itkIndex4[2]) && (itkIndex4b[3] == 0)), "Testing itk::index<3> for IndexToWorld/WorldToIndex consistency"); geometry3d->IndexToWorld(mitkIndex, point); geometry3d->WorldToIndex(point, mitkIndexb); MITK_TEST_CONDITION_REQUIRED( ((mitkIndexb[0] == mitkIndex[0]) && (mitkIndexb[1] == mitkIndex[1]) && (mitkIndexb[2] == mitkIndex[2])), "Testing mitk::Index for IndexToWorld/WorldToIndex consistency"); return EXIT_SUCCESS; } #include int testItkImageIsCenterBased() { MITK_TEST_OUTPUT(<< "Testing whether itk::Image coordinates are center-based."); typedef itk::Image ItkIntImage3D; ItkIntImage3D::Pointer itkintimage = ItkIntImage3D::New(); ItkIntImage3D::SizeType size; size.Fill(10); mitk::Point3D origin; mitk::FillVector3D(origin, 2, 3, 7); itkintimage->Initialize(); itkintimage->SetRegions(size); itkintimage->SetOrigin(origin); std::cout << "[PASSED]" << std::endl; MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToContinuousIndex(origin)==(0,0,0)"); mitk::Point3D globalOrigin; mitk::FillVector3D(globalOrigin, 0, 0, 0); - itk::ContinuousIndex originContinuousIndex; - itkintimage->TransformPhysicalPointToContinuousIndex(origin, originContinuousIndex); + auto originContinuousIndex = itkintimage->TransformPhysicalPointToContinuousIndex(origin); MITK_TEST_CONDITION_REQUIRED(originContinuousIndex == globalOrigin, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin)==(0,0,0)"); - itk::Index<3> itkindex; - itkintimage->TransformPhysicalPointToIndex(origin, itkindex); + itk::Index<3> itkindex = itkintimage->TransformPhysicalPointToIndex(origin); itk::Index<3> globalOriginIndex; mitk::vtk2itk(globalOrigin, globalOriginIndex); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin-0.5*spacing)==(0,0,0)"); mitk::Vector3D halfSpacingStep = itkintimage->GetSpacing() * 0.5; mitk::Matrix3D rotation; mitk::Point3D originOffCenter = origin - halfSpacingStep; - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT( << " Testing itk::Image::TransformPhysicalPointToIndex(origin+0.5*spacing-eps, itk::Index)==(0,0,0)"); originOffCenter = origin + halfSpacingStep; originOffCenter -= mitk::Vector(0.0001); - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == globalOriginIndex, ""); MITK_TEST_OUTPUT(<< " Testing itk::Image::TransformPhysicalPointToIndex(origin+0.5*spacing, itk::Index)==(1,1,1)"); originOffCenter = origin + halfSpacingStep; itk::Index<3> global111; mitk::FillVector3D(global111, 1, 1, 1); - itkintimage->TransformPhysicalPointToIndex(originOffCenter, itkindex); + itkindex = itkintimage->TransformPhysicalPointToIndex(originOffCenter); MITK_TEST_CONDITION_REQUIRED(itkindex == global111, ""); MITK_TEST_OUTPUT(<< "=> Yes, itk::Image coordinates are center-based."); return EXIT_SUCCESS; } int testGeometry3D(bool imageGeometry) { // Build up a new image Geometry mitk::Geometry3D::Pointer geometry3d = mitk::Geometry3D::New(); float bounds[] = {-10.0, 17.0, -12.0, 188.0, 13.0, 211.0}; MITK_TEST_OUTPUT(<< "Initializing"); geometry3d->Initialize(); MITK_TEST_OUTPUT(<< "Setting ImageGeometry to " << imageGeometry); geometry3d->SetImageGeometry(imageGeometry); MITK_TEST_OUTPUT(<< "Setting bounds by SetFloatBounds(): " << bounds); geometry3d->SetFloatBounds(bounds); MITK_TEST_OUTPUT(<< "Testing AxisVectors"); if (testGetAxisVectorVariants(geometry3d) == false) return EXIT_FAILURE; if (testGetAxisVectorExtent(geometry3d) == false) return EXIT_FAILURE; MITK_TEST_OUTPUT(<< "Creating an AffineTransform3D transform"); mitk::AffineTransform3D::MatrixType matrix; matrix.SetIdentity(); matrix(1, 1) = 2; mitk::AffineTransform3D::Pointer transform; transform = mitk::AffineTransform3D::New(); transform->SetMatrix(matrix); MITK_TEST_OUTPUT(<< "Testing a SetIndexToWorldTransform"); geometry3d->SetIndexToWorldTransform(transform); MITK_TEST_OUTPUT(<< "Testing correctness of value returned by GetSpacing"); const mitk::Vector3D &spacing1 = geometry3d->GetSpacing(); mitk::Vector3D expectedSpacing; expectedSpacing.Fill(1.0); expectedSpacing[1] = 2; if (mitk::Equal(spacing1, expectedSpacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } MITK_TEST_OUTPUT(<< "Testing a Compose(transform)"); geometry3d->Compose(transform); MITK_TEST_OUTPUT(<< "Testing correctness of value returned by GetSpacing"); const mitk::Vector3D &spacing2 = geometry3d->GetSpacing(); expectedSpacing[1] = 4; if (mitk::Equal(spacing2, expectedSpacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } MITK_TEST_OUTPUT(<< "Testing correctness of SetSpacing"); mitk::Vector3D newspacing; mitk::FillVector3D(newspacing, 1.5, 2.5, 3.5); geometry3d->SetSpacing(newspacing); const mitk::Vector3D &spacing3 = geometry3d->GetSpacing(); if (mitk::Equal(spacing3, newspacing) == false) { MITK_TEST_OUTPUT(<< " [FAILED]"); return EXIT_FAILURE; } // Separate Test function for Index and World consistency testIndexAndWorldConsistency(geometry3d); testIndexAndWorldConsistencyForVectors(geometry3d); testIndexAndWorldConsistencyForIndex(geometry3d); MITK_TEST_OUTPUT(<< "Testing a rotation of the geometry"); double angle = 35.0; mitk::Vector3D rotationVector; mitk::FillVector3D(rotationVector, 1, 0, 0); mitk::Point3D center = geometry3d->GetCenter(); auto op = new mitk::RotationOperation(mitk::OpROTATE, center, rotationVector, angle); geometry3d->ExecuteOperation(op); MITK_TEST_OUTPUT(<< "Testing mitk::GetRotation() and success of rotation"); mitk::Matrix3D rotation; mitk::GetRotation(geometry3d, rotation); mitk::Vector3D voxelStep = rotation * newspacing; mitk::Vector3D voxelStepIndex; geometry3d->WorldToIndex(voxelStep, voxelStepIndex); mitk::Vector3D expectedVoxelStepIndex; expectedVoxelStepIndex.Fill(1); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(voxelStepIndex, expectedVoxelStepIndex), ""); delete op; std::cout << "[PASSED]" << std::endl; MITK_TEST_OUTPUT(<< "Testing that ImageGeometry is still " << imageGeometry); MITK_TEST_CONDITION_REQUIRED(geometry3d->GetImageGeometry() == imageGeometry, ""); // Test if the translate function moves the origin correctly. mitk::Point3D oldOrigin = geometry3d->GetOrigin(); // use some random values for translation mitk::Vector3D translationVector; translationVector.SetElement(0, 17.5f); translationVector.SetElement(1, -32.3f); translationVector.SetElement(2, 4.0f); // compute ground truth mitk::Point3D tmpResult = geometry3d->GetOrigin() + translationVector; geometry3d->Translate(translationVector); MITK_TEST_CONDITION(mitk::Equal(geometry3d->GetOrigin(), tmpResult), "Testing if origin was translated."); translationVector *= -1; // vice versa geometry3d->Translate(translationVector); MITK_TEST_CONDITION(mitk::Equal(geometry3d->GetOrigin(), oldOrigin), "Testing if the translation could be done vice versa."); return EXIT_SUCCESS; } int testGeometryAfterCasting() { // Epsilon. Allowed difference for rotationvalue float eps = 0.0001; // Cast ITK and MITK images and see if geometry stays typedef itk::Image Image2DType; typedef itk::Image Image3DType; // Create 3D ITK Image from Scratch, cast to 3D MITK image, compare Geometries Image3DType::Pointer image3DItk = Image3DType::New(); Image3DType::RegionType myRegion; Image3DType::SizeType mySize; Image3DType::IndexType myIndex; Image3DType::SpacingType mySpacing; Image3DType::DirectionType myDirection, rotMatrixX, rotMatrixY, rotMatrixZ; mySpacing[0] = 31; mySpacing[1] = 0.1; mySpacing[2] = 2.9; myIndex[0] = -15; myIndex[1] = 15; myIndex[2] = 12; mySize[0] = 10; mySize[1] = 2; mySize[2] = 5; myRegion.SetSize(mySize); myRegion.SetIndex(myIndex); image3DItk->SetSpacing(mySpacing); image3DItk->SetRegions(myRegion); image3DItk->Allocate(); image3DItk->FillBuffer(0); myDirection.SetIdentity(); rotMatrixX.SetIdentity(); rotMatrixY.SetIdentity(); rotMatrixZ.SetIdentity(); mitk::Image::Pointer mitkImage; // direction [row] [column] MITK_TEST_OUTPUT(<< "Casting a rotated 3D ITK Image to a MITK Image and check if Geometry is still same"); for (double rotX = 0; rotX < itk::Math::pi * 2; rotX += itk::Math::pi * 0.4) { // Set Rotation X rotMatrixX[1][1] = cos(rotX); rotMatrixX[1][2] = -sin(rotX); rotMatrixX[2][1] = sin(rotX); rotMatrixX[2][2] = cos(rotX); for (double rotY = 0; rotY < itk::Math::pi * 2; rotY += itk::Math::pi * 0.3) { // Set Rotation Y rotMatrixY[0][0] = cos(rotY); rotMatrixY[0][2] = sin(rotY); rotMatrixY[2][0] = -sin(rotY); rotMatrixY[2][2] = cos(rotY); for (double rotZ = 0; rotZ < itk::Math::pi * 2; rotZ += itk::Math::pi * 0.2) { // Set Rotation Z rotMatrixZ[0][0] = cos(rotZ); rotMatrixZ[0][1] = -sin(rotZ); rotMatrixZ[1][0] = sin(rotZ); rotMatrixZ[1][1] = cos(rotZ); // Multiply matrizes myDirection = myDirection * rotMatrixX * rotMatrixY * rotMatrixZ; image3DItk->SetDirection(myDirection); mitk::CastToMitkImage(image3DItk, mitkImage); const mitk::AffineTransform3D::MatrixType &matrix = mitkImage->GetGeometry()->GetIndexToWorldTransform()->GetMatrix(); for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { double mitkValue = matrix[row][col] / mitkImage->GetGeometry()->GetSpacing()[col]; double itkValue = myDirection[row][col]; double diff = mitkValue - itkValue; // if you decrease this value, you can see that there might be QUITE high inaccuracy!!! if (diff > eps) // need to check, how exact it SHOULD be .. since it is NOT EXACT! { std::cout << "Had a difference of : " << diff; std::cout << "Error: Casting altered Geometry!"; std::cout << "ITK Matrix:\n" << myDirection; std::cout << "Mitk Matrix (With Spacing):\n" << matrix; std::cout << "Mitk Spacing: " << mitkImage->GetGeometry()->GetSpacing(); MITK_TEST_CONDITION_REQUIRED(false == true, ""); return false; } } } } } } // Create 2D ITK Image from Scratch, cast to 2D MITK image, compare Geometries Image2DType::Pointer image2DItk = Image2DType::New(); Image2DType::RegionType myRegion2D; Image2DType::SizeType mySize2D; Image2DType::IndexType myIndex2D; Image2DType::SpacingType mySpacing2D; Image2DType::DirectionType myDirection2D, rotMatrix; mySpacing2D[0] = 31; mySpacing2D[1] = 0.1; myIndex2D[0] = -15; myIndex2D[1] = 15; mySize2D[0] = 10; mySize2D[1] = 2; myRegion2D.SetSize(mySize2D); myRegion2D.SetIndex(myIndex2D); image2DItk->SetSpacing(mySpacing2D); image2DItk->SetRegions(myRegion2D); image2DItk->Allocate(); image2DItk->FillBuffer(0); myDirection2D.SetIdentity(); rotMatrix.SetIdentity(); // direction [row] [column] MITK_TEST_OUTPUT(<< "Casting a rotated 2D ITK Image to a MITK Image and check if Geometry is still same"); for (double rotTheta = 0; rotTheta < itk::Math::pi * 2; rotTheta += itk::Math::pi * 0.2) { // Set Rotation rotMatrix[0][0] = cos(rotTheta); rotMatrix[0][1] = -sin(rotTheta); rotMatrix[1][0] = sin(rotTheta); rotMatrix[1][1] = cos(rotTheta); // Multiply matrizes myDirection2D = myDirection2D * rotMatrix; image2DItk->SetDirection(myDirection2D); mitk::CastToMitkImage(image2DItk, mitkImage); const mitk::AffineTransform3D::MatrixType &matrix = mitkImage->GetGeometry()->GetIndexToWorldTransform()->GetMatrix(); // Compare MITK and ITK matrix for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { double mitkValue = matrix[row][col] / mitkImage->GetGeometry()->GetSpacing()[col]; if ((row == 2) && (col == row)) { if (mitkValue != 1) { MITK_TEST_OUTPUT(<< "After casting a 2D ITK to 3D MITK images, MITK matrix values for 0|2, 1|2, 2|0, 2|1 " "MUST be 0 and value for 2|2 must be 1"); return false; } } else if ((row == 2) || (col == 2)) { if (mitkValue != 0) { MITK_TEST_OUTPUT(<< "After casting a 2D ITK to 3D MITK images, MITK matrix values for 0|2, 1|2, 2|0, 2|1 " "MUST be 0 and value for 2|2 must be 1"); return false; } } else { double itkValue = myDirection2D[row][col]; double diff = mitkValue - itkValue; // if you decrease this value, you can see that there might be QUITE high inaccuracy!!! if (diff > eps) // need to check, how exact it SHOULD be .. since it is NOT EXACT! { std::cout << "Had a difference of : " << diff; std::cout << "Error: Casting altered Geometry!"; std::cout << "ITK Matrix:\n" << myDirection2D; std::cout << "Mitk Matrix (With Spacing):\n" << matrix; std::cout << "Mitk Spacing: " << mitkImage->GetGeometry()->GetSpacing(); MITK_TEST_CONDITION_REQUIRED(false == true, ""); return false; } } } } } // THIS WAS TESTED: // 2D ITK -> 2D MITK, // 3D ITK -> 3D MITK, // Still need to test: 2D MITK Image with ADDITIONAL INFORMATION IN MATRIX -> 2D ITK // 1. Possibility: 3x3 MITK matrix can be converted without loss into 2x2 ITK matrix // 2. Possibility: 3x3 MITK matrix can only be converted with loss into 2x2 ITK matrix // .. before implementing this, we wait for further development in geometry classes (e.g. Geoemtry3D::SetRotation(..)) return EXIT_SUCCESS; } int mitkGeometry3DTest(int /*argc*/, char * /*argv*/ []) { MITK_TEST_BEGIN(mitkGeometry3DTest); int result; MITK_TEST_CONDITION_REQUIRED((result = testItkImageIsCenterBased()) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running main part of test with ImageGeometry = false"); MITK_TEST_CONDITION_REQUIRED((result = testGeometry3D(false)) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running main part of test with ImageGeometry = true"); MITK_TEST_CONDITION_REQUIRED((result = testGeometry3D(true)) == EXIT_SUCCESS, ""); MITK_TEST_OUTPUT(<< "Running test to see if Casting MITK to ITK and the other way around destroys geometry"); MITK_TEST_CONDITION_REQUIRED((result = testGeometryAfterCasting()) == EXIT_SUCCESS, ""); MITK_TEST_END(); return EXIT_SUCCESS; } diff --git a/Modules/CppMicroServices/core/src/module/usModuleContext.cpp b/Modules/CppMicroServices/core/src/module/usModuleContext.cpp index 1f92098b8b..44df19c0e2 100644 --- a/Modules/CppMicroServices/core/src/module/usModuleContext.cpp +++ b/Modules/CppMicroServices/core/src/module/usModuleContext.cpp @@ -1,196 +1,196 @@ /*============================================================================ Library: CppMicroServices Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================================================================*/ #include "usModuleContext.h" #include "usModuleRegistry.h" #include "usModulePrivate.h" #include "usModuleSettings.h" #include "usCoreModuleContext_p.h" #include "usServiceRegistry_p.h" #include "usServiceReferenceBasePrivate.h" #include US_BEGIN_NAMESPACE class ModuleContextPrivate { public: ModuleContextPrivate(ModulePrivate* module) : module(module) {} ModulePrivate* module; }; ModuleContext::ModuleContext(ModulePrivate* module) : d(new ModuleContextPrivate(module)) {} ModuleContext::~ModuleContext() { delete d; } Module* ModuleContext::GetModule() const { return d->module->q; } Module* ModuleContext::GetModule(long id) const { return d->module->coreCtx->moduleHooks.FilterModule(this, ModuleRegistry::GetModule(id)); } Module* ModuleContext::GetModule(const std::string& name) { return ModuleRegistry::GetModule(name); } std::vector ModuleContext::GetModules() const { std::vector modules = ModuleRegistry::GetModules(); d->module->coreCtx->moduleHooks.FilterModules(this, modules); return modules; } ServiceRegistrationU ModuleContext::RegisterService(const InterfaceMap& service, const ServiceProperties& properties) { return d->module->coreCtx->services.RegisterService(d->module, service, properties); } std::vector ModuleContext::GetServiceReferences(const std::string& clazz, const std::string& filter) { std::vector result; std::vector refs; d->module->coreCtx->services.Get(clazz, filter, d->module, refs); for (std::vector::const_iterator iter = refs.begin(); iter != refs.end(); ++iter) { result.push_back(ServiceReferenceU(*iter)); } return result; } ServiceReferenceU ModuleContext::GetServiceReference(const std::string& clazz) { return d->module->coreCtx->services.Get(d->module, clazz); } void* ModuleContext::GetService(const ServiceReferenceBase& reference) { if (!reference) { throw std::invalid_argument("Default constructed ServiceReference is not a valid input to GetService()"); } return reference.d->GetService(d->module->q); } InterfaceMap ModuleContext::GetService(const ServiceReferenceU& reference) { if (!reference) { throw std::invalid_argument("Default constructed ServiceReference is not a valid input to GetService()"); } return reference.d->GetServiceInterfaceMap(d->module->q); } bool ModuleContext::UngetService(const ServiceReferenceBase& reference) { ServiceReferenceBase ref = reference; return ref.d->UngetService(d->module->q, true); } void ModuleContext::AddServiceListener(const ServiceListener& delegate, const std::string& filter) { d->module->coreCtx->listeners.AddServiceListener(this, delegate, nullptr, filter); } void ModuleContext::RemoveServiceListener(const ServiceListener& delegate) { d->module->coreCtx->listeners.RemoveServiceListener(this, delegate, nullptr); } void ModuleContext::AddModuleListener(const ModuleListener& delegate) { d->module->coreCtx->listeners.AddModuleListener(this, delegate, nullptr); } void ModuleContext::RemoveModuleListener(const ModuleListener& delegate) { d->module->coreCtx->listeners.RemoveModuleListener(this, delegate, nullptr); } void ModuleContext::AddServiceListener(const ServiceListener& delegate, void* data, const std::string &filter) { d->module->coreCtx->listeners.AddServiceListener(this, delegate, data, filter); } void ModuleContext::RemoveServiceListener(const ServiceListener& delegate, void* data) { d->module->coreCtx->listeners.RemoveServiceListener(this, delegate, data); } void ModuleContext::AddModuleListener(const ModuleListener& delegate, void* data) { d->module->coreCtx->listeners.AddModuleListener(this, delegate, data); } void ModuleContext::RemoveModuleListener(const ModuleListener& delegate, void* data) { d->module->coreCtx->listeners.RemoveModuleListener(this, delegate, data); } std::string ModuleContext::GetDataFile(const std::string &filename) const { // compute the module storage path #ifdef US_PLATFORM_WINDOWS static const char separator = '\\'; #else static const char separator = '/'; #endif std::string baseStoragePath = ModuleSettings::GetStoragePath(); if (baseStoragePath.empty()) return std::string(); if (baseStoragePath != d->module->baseStoragePath) { d->module->baseStoragePath = baseStoragePath; d->module->storagePath.clear(); } if (d->module->storagePath.empty()) { char buf[50]; - sprintf(buf, "%ld", d->module->info.id); + snprintf(buf, sizeof(buf), "%ld", d->module->info.id); d->module->storagePath = baseStoragePath + separator + buf + "_" + d->module->info.name + separator; } return d->module->storagePath + filename; } US_END_NAMESPACE diff --git a/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp b/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp index b4f4bf51bd..1fa4d4d681 100644 --- a/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp +++ b/Modules/CppMicroServices/core/src/service/usServiceListeners.cpp @@ -1,334 +1,329 @@ /*============================================================================ Library: CppMicroServices Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================================================================*/ #include "usUtils_p.h" #include "usServiceListeners_p.h" #include "usServiceReferenceBasePrivate.h" #include "usCoreModuleContext_p.h" #include "usModule.h" #include "usModuleContext.h" US_BEGIN_NAMESPACE const int ServiceListeners::OBJECTCLASS_IX = 0; const int ServiceListeners::SERVICE_ID_IX = 1; ServiceListeners::ServiceListeners(CoreModuleContext* coreCtx) : coreCtx(coreCtx) { hashedServiceKeys.push_back(ServiceConstants::OBJECTCLASS()); hashedServiceKeys.push_back(ServiceConstants::SERVICE_ID()); } void ServiceListeners::AddServiceListener(ModuleContext* mc, const ServiceListenerEntry::ServiceListener& listener, void* data, const std::string& filter) { US_UNUSED(Lock(this)); ServiceListenerEntry sle(mc, listener, data, filter); RemoveServiceListener_unlocked(sle); serviceSet.insert(sle); coreCtx->serviceHooks.HandleServiceListenerReg(sle); CheckSimple(sle); } void ServiceListeners::RemoveServiceListener(ModuleContext* mc, const ServiceListenerEntry::ServiceListener& listener, void* data) { ServiceListenerEntry entryToRemove(mc, listener, data); US_UNUSED(Lock(this)); RemoveServiceListener_unlocked(entryToRemove); } void ServiceListeners::RemoveServiceListener_unlocked(const ServiceListenerEntry& entryToRemove) { ServiceListenerEntries::const_iterator it = serviceSet.find(entryToRemove); if (it != serviceSet.end()) { it->SetRemoved(true); coreCtx->serviceHooks.HandleServiceListenerUnreg(*it); RemoveFromCache(*it); serviceSet.erase(it); } } void ServiceListeners::AddModuleListener(ModuleContext* mc, const ModuleListener& listener, void* data) { MutexLock lock(moduleListenerMapMutex); ModuleListenerMap::value_type::second_type& listeners = moduleListenerMap[mc]; if (std::find_if(listeners.begin(), listeners.end(), std::bind(ModuleListenerCompare(), std::make_pair(listener, data), std::placeholders::_1)) == listeners.end()) { listeners.push_back(std::make_pair(listener, data)); } } void ServiceListeners::RemoveModuleListener(ModuleContext* mc, const ModuleListener& listener, void* data) { MutexLock lock(moduleListenerMapMutex); moduleListenerMap[mc].remove_if(std::bind(ModuleListenerCompare(), std::make_pair(listener, data), std::placeholders::_1)); } void ServiceListeners::ModuleChanged(const ModuleEvent& evt) { ModuleListenerMap filteredModuleListeners; coreCtx->moduleHooks.FilterModuleEventReceivers(evt, filteredModuleListeners); for(ModuleListenerMap::iterator iter = filteredModuleListeners.begin(), end = filteredModuleListeners.end(); iter != end; ++iter) { for (ModuleListenerMap::mapped_type::iterator iter2 = iter->second.begin(), end2 = iter->second.end(); iter2 != end2; ++iter2) { try { (iter2->first)(evt); } catch (const std::exception& e) { US_WARN << "Module listener threw an exception: " << e.what(); } } } } void ServiceListeners::RemoveAllListeners(ModuleContext* mc) { { US_UNUSED(Lock(this)); for (ServiceListenerEntries::iterator it = serviceSet.begin(); it != serviceSet.end(); ) { if (it->GetModuleContext() == mc) { RemoveFromCache(*it); serviceSet.erase(it++); } else { ++it; } } } { MutexLock lock(moduleListenerMapMutex); moduleListenerMap.erase(mc); } } void ServiceListeners::HooksModuleStopped(ModuleContext* mc) { US_UNUSED(Lock(this)); std::vector entries; for (ServiceListenerEntries::iterator it = serviceSet.begin(); it != serviceSet.end(); ) { if (it->GetModuleContext() == mc) { entries.push_back(*it); } } coreCtx->serviceHooks.HandleServiceListenerUnreg(entries); } void ServiceListeners::ServiceChanged(ServiceListenerEntries& receivers, const ServiceEvent& evt) { ServiceListenerEntries matchBefore; ServiceChanged(receivers, evt, matchBefore); } void ServiceListeners::ServiceChanged(ServiceListenerEntries& receivers, const ServiceEvent& evt, ServiceListenerEntries& matchBefore) { - int n = 0; - if (!matchBefore.empty()) { for (ServiceListenerEntries::const_iterator l = receivers.begin(); l != receivers.end(); ++l) { matchBefore.erase(*l); } } for (ServiceListenerEntries::const_iterator l = receivers.begin(); l != receivers.end(); ++l) { if (!l->IsRemoved()) { try { - ++n; l->CallDelegate(evt); } catch (...) { US_WARN << "Service listener" << " in " << l->GetModuleContext()->GetModule()->GetName() << " threw an exception!"; } } } - - //US_DEBUG << "Notified " << n << " listeners"; } void ServiceListeners::GetMatchingServiceListeners(const ServiceEvent& evt, ServiceListenerEntries& set, bool lockProps) { US_UNUSED(Lock(this)); // Filter the original set of listeners ServiceListenerEntries receivers = serviceSet; coreCtx->serviceHooks.FilterServiceEventReceivers(evt, receivers); // Check complicated or empty listener filters for (std::list::const_iterator sse = complicatedListeners.begin(); sse != complicatedListeners.end(); ++sse) { if (receivers.count(*sse) == 0) continue; const LDAPExpr& ldapExpr = sse->GetLDAPExpr(); if (ldapExpr.IsNull() || ldapExpr.Evaluate(evt.GetServiceReference().d->GetProperties(), false)) { set.insert(*sse); } } //US_DEBUG << "Added " << set.size() << " out of " << n // << " listeners with complicated filters"; // Check the cache const std::vector c(any_cast > (evt.GetServiceReference().d->GetProperty(ServiceConstants::OBJECTCLASS(), lockProps))); for (std::vector::const_iterator objClass = c.begin(); objClass != c.end(); ++objClass) { AddToSet(set, receivers, OBJECTCLASS_IX, *objClass); } long service_id = any_cast(evt.GetServiceReference().d->GetProperty(ServiceConstants::SERVICE_ID(), lockProps)); std::stringstream ss; ss << service_id; AddToSet(set, receivers, SERVICE_ID_IX, ss.str()); } std::vector ServiceListeners::GetListenerInfoCollection() const { US_UNUSED(Lock(this)); std::vector result; result.reserve(serviceSet.size()); for (ServiceListenerEntries::const_iterator iter = serviceSet.begin(), iterEnd = serviceSet.end(); iter != iterEnd; ++iter) { result.push_back(*iter); } return result; } void ServiceListeners::RemoveFromCache(const ServiceListenerEntry& sle) { if (!sle.GetLocalCache().empty()) { for (std::size_t i = 0; i < hashedServiceKeys.size(); ++i) { CacheType& keymap = cache[i]; std::vector& l = sle.GetLocalCache()[i]; for (std::vector::const_iterator it = l.begin(); it != l.end(); ++it) { std::list& sles = keymap[*it]; sles.remove(sle); if (sles.empty()) { keymap.erase(*it); } } } } else { complicatedListeners.remove(sle); } } void ServiceListeners::CheckSimple(const ServiceListenerEntry& sle) { if (sle.GetLDAPExpr().IsNull()) { complicatedListeners.push_back(sle); } else { LDAPExpr::LocalCache local_cache; if (sle.GetLDAPExpr().IsSimple(hashedServiceKeys, local_cache, false)) { sle.GetLocalCache() = local_cache; for (std::size_t i = 0; i < hashedServiceKeys.size(); ++i) { for (std::vector::const_iterator it = local_cache[i].begin(); it != local_cache[i].end(); ++it) { std::list& sles = cache[i][*it]; sles.push_back(sle); } } } else { //US_DEBUG << "Too complicated filter: " << sle.GetFilter(); complicatedListeners.push_back(sle); } } } void ServiceListeners::AddToSet(ServiceListenerEntries& set, const ServiceListenerEntries& receivers, int cache_ix, const std::string& val) { std::list& l = cache[cache_ix][val]; if (!l.empty()) { //US_DEBUG << hashedServiceKeys[cache_ix] << " matches " << l.size(); for (std::list::const_iterator entry = l.begin(); entry != l.end(); ++entry) { if (receivers.count(*entry)) { set.insert(*entry); } } } else { //US_DEBUG << hashedServiceKeys[cache_ix] << " matches none"; } } US_END_NAMESPACE diff --git a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp index 19a910e832..a24947359c 100644 --- a/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOM/src/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,622 +1,628 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG #define ENABLE_TIMING #include #include #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMGDCMTagScanner.h" std::mutex mitk::DICOMITKSeriesGDCMReader::s_LocaleMutex; mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( unsigned int decimalPlacesForOrientation, bool simpleVolumeImport ) : DICOMFileReader() , m_FixTiltByShearing(m_DefaultFixTiltByShearing) , m_SimpleVolumeReading( simpleVolumeImport ) , m_DecimalPlacesForOrientation( decimalPlacesForOrientation ) , m_ExternalCache(false) { this->EnsureMandatorySortersArePresent( decimalPlacesForOrientation, simpleVolumeImport ); } mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( const DICOMITKSeriesGDCMReader& other ) : DICOMFileReader( other ) , m_FixTiltByShearing( other.m_FixTiltByShearing) , m_SortingResultInProgress( other.m_SortingResultInProgress ) , m_Sorter( other.m_Sorter ) , m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) , m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) , m_ReplacedCLocales( other.m_ReplacedCLocales ) , m_ReplacedCinLocales( other.m_ReplacedCinLocales ) , m_DecimalPlacesForOrientation( other.m_DecimalPlacesForOrientation ) , m_TagCache( other.m_TagCache ) , m_ExternalCache(other.m_ExternalCache) { } mitk::DICOMITKSeriesGDCMReader::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader:: operator=( const DICOMITKSeriesGDCMReader& other ) { if ( this != &other ) { DICOMFileReader::operator =( other ); this->m_FixTiltByShearing = other.m_FixTiltByShearing; this->m_SortingResultInProgress = other.m_SortingResultInProgress; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); this->m_ReplacedCLocales = other.m_ReplacedCLocales; this->m_ReplacedCinLocales = other.m_ReplacedCinLocales; this->m_DecimalPlacesForOrientation = other.m_DecimalPlacesForOrientation; this->m_TagCache = other.m_TagCache; } return *this; } bool mitk::DICOMITKSeriesGDCMReader::operator==( const DICOMFileReader& other ) const { if ( const auto* otherSelf = dynamic_cast( &other ) ) { if ( this->m_FixTiltByShearing == otherSelf->m_FixTiltByShearing && *( this->m_EquiDistantBlocksSorter ) == *( otherSelf->m_EquiDistantBlocksSorter ) && ( fabs( this->m_DecimalPlacesForOrientation - otherSelf->m_DecimalPlacesForOrientation ) < eps ) ) { // test sorters for equality if ( this->m_Sorter.size() != otherSelf->m_Sorter.size() ) return false; auto mySorterIter = this->m_Sorter.cbegin(); auto oSorterIter = otherSelf->m_Sorter.cbegin(); for ( ; mySorterIter != this->m_Sorter.cend() && oSorterIter != otherSelf->m_Sorter.cend(); ++mySorterIter, ++oSorterIter ) { if ( !( **mySorterIter == **oSorterIter ) ) return false; // this sorter differs } // nothing differs ==> all is equal return true; } else { return false; } } else { return false; } } void mitk::DICOMITKSeriesGDCMReader::SetFixTiltByShearing( bool on ) { this->Modified(); m_FixTiltByShearing = on; } bool mitk::DICOMITKSeriesGDCMReader::GetFixTiltByShearing() const { return m_FixTiltByShearing; } void mitk::DICOMITKSeriesGDCMReader::SetAcceptTwoSlicesGroups( bool accept ) const { this->Modified(); m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups( accept ); } bool mitk::DICOMITKSeriesGDCMReader::GetAcceptTwoSlicesGroups() const { return m_EquiDistantBlocksSorter->GetAcceptTwoSlicesGroups(); } void mitk::DICOMITKSeriesGDCMReader::InternalPrintConfiguration( std::ostream& os ) const { unsigned int sortIndex( 1 ); for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sortIndex, ++sorterIter ) { os << "Sorting step " << sortIndex << ":" << std::endl; ( *sorterIter )->PrintConfiguration( os, " " ); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration( os, " " ); } std::string mitk::DICOMITKSeriesGDCMReader::GetActiveLocale() { return setlocale( LC_NUMERIC, nullptr ); } void mitk::DICOMITKSeriesGDCMReader::PushLocale() const { s_LocaleMutex.lock(); std::string currentCLocale = setlocale( LC_NUMERIC, nullptr ); m_ReplacedCLocales.push( currentCLocale ); setlocale( LC_NUMERIC, "C" ); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue( l ); s_LocaleMutex.unlock(); } void mitk::DICOMITKSeriesGDCMReader::PopLocale() const { s_LocaleMutex.lock(); if ( !m_ReplacedCLocales.empty() ) { setlocale( LC_NUMERIC, m_ReplacedCLocales.top().c_str() ); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if ( !m_ReplacedCinLocales.empty() ) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } s_LocaleMutex.unlock(); } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::Condense3DBlocks( SortingBlockList& input ) { return input; // to be implemented differently by sub-classes } #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) #define timeStart( part ) timer.Start( part ); #define timeStop( part ) timer.Stop( part ); #else #define timeStart( part ) #define timeStop( part ) #endif void mitk::DICOMITKSeriesGDCMReader::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timeStart( "Reset" ); this->ClearOutputs(); timeStop( "Reset" ); // prepare initial sorting (== list of input files) const StringList inputFilenames = this->GetInputFiles(); timeStart( "Check input for DCM" ); if ( inputFilenames.empty() || !this->CanHandleFile( inputFilenames.front() ) // first || !this->CanHandleFile( inputFilenames.back() ) // last || !this->CanHandleFile( inputFilenames[inputFilenames.size() / 2] ) // roughly central file ) { // TODO a read-as-many-as-possible fallback could be implemented here MITK_DEBUG << "Reader unable to process files.."; return; } timeStop( "Check input for DCM" ); // scan files for sorting-relevant tags if ( m_TagCache.IsNull() || ( m_TagCache->GetMTime()GetMTime() && !m_ExternalCache )) { timeStart( "Tag scanning" ); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); filescanner->SetInputFiles( inputFilenames ); filescanner->AddTagPaths( this->GetTagsOfInterest() ); PushLocale(); filescanner->Scan(); PopLocale(); m_TagCache = filescanner->GetScanCache(); // keep alive and make accessible to sub-classes timeStop("Tag scanning"); } else { // ensure that the tag cache contains our required tags AND files and has scanned! } m_SortingResultInProgress.clear(); m_SortingResultInProgress.push_back(m_TagCache->GetFrameInfoList()); // sort and split blocks as configured timeStart( "Sorting frames" ); unsigned int sorterIndex = 0; for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIndex, ++sorterIter ) { std::stringstream ss; ss << "Sorting step " << sorterIndex; timeStart( ss.str().c_str() ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex, *sorterIter, m_SortingResultInProgress ); timeStop( ss.str().c_str() ); } if ( !m_SimpleVolumeReading ) { // a last extra-sorting step: ensure equidistant slices timeStart( "EquiDistantBlocksSorter" ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress ); timeStop( "EquiDistantBlocksSorter" ); } timeStop( "Sorting frames" ); timeStart( "Condensing 3D blocks" ); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timeStop( "Condensing 3D blocks" ); // provide final result as output timeStart( "Output" ); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for ( auto blockIter = m_SortingResultInProgress.cbegin(); blockIter != m_SortingResultInProgress.cend(); ++o, ++blockIter ) { const DICOMDatasetAccessingImageFrameList& gdcmFrameInfoList = *blockIter; assert( !gdcmFrameInfoList.empty() ); // reverse frames if necessary // update tilt information from absolute last sorting const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput( 0 ) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert( !frameList.empty() ); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because // SetImageFrameList will trigger reading of lots of interesting // tags! block.SetAdditionalTagsOfInterest( GetAdditionalTagsOfInterest() ); block.SetTagLookupTableToPropertyFunctor( GetTagLookupTableToPropertyFunctor() ); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel( block.GetSOPClassUID() ) ); this->SetOutput( o, block ); } timeStop( "Output" ); #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; #endif } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input ) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex << " '"; #if defined( MBILOG_ENABLE_DEBUG ) sorter->PrintConfiguration( ss ); #endif ss << "'"; nextStepSorting.clear(); MITK_DEBUG << "================================================================================"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << input.size() << " groups input"; +#if defined( MBILOG_ENABLE_DEBUG ) unsigned int groupIndex = 0; +#endif - for ( auto blockIter = input.cbegin(); blockIter != input.cend(); ++groupIndex, ++blockIter ) + for ( auto blockIter = input.cbegin(); blockIter != input.cend(); +#if defined( MBILOG_ENABLE_DEBUG ) + ++groupIndex, +#endif + ++blockIter ) { const DICOMDatasetAccessingImageFrameList& gdcmInfoFrameList = *blockIter; const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmInfoFrameList ); #if defined( MBILOG_ENABLE_DEBUG ) MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for ( auto oi = datasetList.cbegin(); oi != datasetList.cend(); ++oi ) { MITK_DEBUG << " INPUT : " << ( *oi )->GetFilenameIfAvailable(); } #endif sorter->SetInput( datasetList ); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for ( unsigned int b = 0; b < numberOfResultingBlocks; ++b ) { const DICOMDatasetList blockResult = sorter->GetOutput( b ); for ( auto oi = blockResult.cbegin(); oi != blockResult.cend(); ++oi ) { MITK_DEBUG << " OUTPUT(" << b << ") :" << ( *oi )->GetFilenameIfAvailable(); } DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( blockResult ); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader::GetReaderImplementationLevel( const std::string sopClassUID ) { if ( sopClassUID.empty() ) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSName gdcmType = static_cast((gdcm::UIDs::TSType)uidKnowledge); switch ( gdcmType ) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for ( unsigned int o = 0; o < numberOfOutputs; ++o ) { success &= this->LoadMitkImageForOutput( o ); } return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor( DICOMImageBlockDescriptor& block ) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); ITKDICOMSeriesReaderHelper::StringContainer filenames; filenames.reserve( frames.size() ); for ( auto frameIter = frames.cbegin(); frameIter != frames.cend(); ++frameIter ) { filenames.push_back( ( *frameIter )->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; bool success( true ); try { mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); } catch ( const std::exception& e ) { success = false; MITK_ERROR << "Exception during image loading: " << e.what(); } PopLocale(); return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForOutput( unsigned int o ) { DICOMImageBlockDescriptor& block = this->InternalGetOutput( o ); return this->LoadMitkImageForImageBlockDescriptor( block ); } bool mitk::DICOMITKSeriesGDCMReader::CanHandleFile( const std::string& filename ) { return ITKDICOMSeriesReaderHelper::CanHandleFile( filename ); } void mitk::DICOMITKSeriesGDCMReader::AddSortingElement( DICOMDatasetSorter* sorter, bool atFront ) { assert( sorter ); if ( atFront ) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } this->Modified(); } mitk::DICOMITKSeriesGDCMReader::ConstSorterList mitk::DICOMITKSeriesGDCMReader::GetFreelyConfiguredSortingElements() const { std::list result; unsigned int sortIndex( 0 ); for ( auto sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter ) { if ( sortIndex > 0 ) // ignore first element (see EnsureMandatorySortersArePresent) { result.push_back( ( *sorterIter ).GetPointer() ); } } return result; } void mitk::DICOMITKSeriesGDCMReader::EnsureMandatorySortersArePresent( unsigned int decimalPlacesForOrientation, bool simpleVolumeImport ) { DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(decimalPlacesForOrientation) ); // Image Orientation (Patient) splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness if ( simpleVolumeImport ) { MITK_DEBUG << "Simple volume reading: ignoring number of frames"; } else { splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames } this->AddSortingElement( splitter, true ); // true = at front if ( m_EquiDistantBlocksSorter.IsNull() ) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if ( m_NormalDirectionConsistencySorter.IsNull() ) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffsetToAdaptive( double fractionOfInterSliceDistance ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive( fractionOfInterSliceDistance ); this->Modified(); } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffset( double millimeters ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset( millimeters ); this->Modified(); } double mitk::DICOMITKSeriesGDCMReader::GetToleratedOriginError() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->GetToleratedOriginOffset(); } bool mitk::DICOMITKSeriesGDCMReader::IsToleratedOriginOffsetAbsolute() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->IsToleratedOriginOffsetAbsolute(); } double mitk::DICOMITKSeriesGDCMReader::GetDecimalPlacesForOrientation() const { return m_DecimalPlacesForOrientation; } mitk::DICOMTagCache::Pointer mitk::DICOMITKSeriesGDCMReader::GetTagCache() const { return m_TagCache; } void mitk::DICOMITKSeriesGDCMReader::SetTagCache( const DICOMTagCache::Pointer& tagCache ) { m_TagCache = tagCache; m_ExternalCache = tagCache.IsNotNull(); } mitk::DICOMTagPathList mitk::DICOMITKSeriesGDCMReader::GetTagsOfInterest() const { DICOMTagPathList completeList; // check all configured sorters for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIter ) { assert( sorterIter->IsNotNull() ); const DICOMTagList tags = ( *sorterIter )->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); } // check our own forced sorters DICOMTagList tags = m_EquiDistantBlocksSorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); tags = m_NormalDirectionConsistencySorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); // add the tags for DICOMImageBlockDescriptor tags = DICOMImageBlockDescriptor::GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); const AdditionalTagsMapType tagList = GetAdditionalTagsOfInterest(); for ( auto iter = tagList.cbegin(); iter != tagList.cend(); ++iter ) { completeList.push_back( iter->first ) ; } return completeList; } diff --git a/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp index d84f0914dc..1f93af9f62 100644 --- a/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp +++ b/Modules/DICOM/src/mitkDICOMTagBasedSorter.cpp @@ -1,596 +1,601 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDICOMTagBasedSorter.h" #include #include mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(unsigned int precision) :m_Precision(precision) { } mitk::DICOMTagBasedSorter::CutDecimalPlaces ::CutDecimalPlaces(const CutDecimalPlaces& other) :m_Precision(other.m_Precision) { } std::string mitk::DICOMTagBasedSorter::CutDecimalPlaces ::operator()(const std::string& input) const { // be a bit tolerant for tags such as image orientation orientation, let only the first few digits matter (https://phabricator.mitk.org/T12263) // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers std::ostringstream resultString; resultString.str(std::string()); resultString.clear(); resultString.setf(std::ios::fixed, std::ios::floatfield); resultString.precision(m_Precision); std::stringstream ss(input); ss.str(input); ss.clear(); std::string item; double number(0); std::istringstream converter(item); while (std::getline(ss, item, '\\')) { converter.str(item); converter.clear(); if (converter >> number && converter.eof()) { // converted to double resultString << number; } else { // did not convert to double resultString << item; // just paste the unmodified string } if (!ss.eof()) { resultString << "\\"; } } return resultString.str(); } mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter::CutDecimalPlaces ::Clone() const { return new CutDecimalPlaces(*this); } unsigned int mitk::DICOMTagBasedSorter::CutDecimalPlaces ::GetPrecision() const { return m_Precision; } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter() :DICOMDatasetSorter() ,m_StrictSorting(m_DefaultStrictSorting) ,m_ExpectDistanceOne(m_DefaultExpectDistanceOne) { } mitk::DICOMTagBasedSorter ::~DICOMTagBasedSorter() { for(auto ti = m_TagValueProcessor.cbegin(); ti != m_TagValueProcessor.cend(); ++ti) { delete ti->second; } } mitk::DICOMTagBasedSorter ::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) :DICOMDatasetSorter(other) ,m_DistinguishingTags( other.m_DistinguishingTags ) ,m_SortCriterion( other.m_SortCriterion ) ,m_StrictSorting( other.m_StrictSorting ) ,m_ExpectDistanceOne( other.m_ExpectDistanceOne ) { for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } mitk::DICOMTagBasedSorter& mitk::DICOMTagBasedSorter ::operator=(const DICOMTagBasedSorter& other) { if (this != &other) { DICOMDatasetSorter::operator=(other); m_DistinguishingTags = other.m_DistinguishingTags; m_SortCriterion = other.m_SortCriterion; m_StrictSorting = other.m_StrictSorting; m_ExpectDistanceOne = other.m_ExpectDistanceOne; for(auto ti = other.m_TagValueProcessor.cbegin(); ti != other.m_TagValueProcessor.cend(); ++ti) { m_TagValueProcessor[ti->first] = ti->second->Clone(); } } return *this; } bool mitk::DICOMTagBasedSorter ::operator==(const DICOMDatasetSorter& other) const { if (const auto* otherSelf = dynamic_cast(&other)) { if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false; if (this->m_ExpectDistanceOne != otherSelf->m_ExpectDistanceOne) return false; bool allTagsPresentAndEqual(true); if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size()) return false; for (auto myTag = this->m_DistinguishingTags.cbegin(); myTag != this->m_DistinguishingTags.cend(); ++myTag) { allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.cbegin(), otherSelf->m_DistinguishingTags.cend(), *myTag ) != otherSelf->m_DistinguishingTags.cend()); // other contains this tags // since size is equal, we don't need to check the inverse } if (!allTagsPresentAndEqual) return false; if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull()) { return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion); } else { return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull(); } } else { return false; } } void mitk::DICOMTagBasedSorter ::PrintConfiguration(std::ostream& os, const std::string& indent) const { os << indent << "Tag based sorting " << "(strict=" << (m_StrictSorting?"true":"false") << ", expectDistanceOne=" << (m_ExpectDistanceOne?"true":"false") << "):" << std::endl; for (auto tagIter = m_DistinguishingTags.begin(); tagIter != m_DistinguishingTags.end(); ++tagIter) { os << indent << " Split on "; tagIter->Print(os); os << std::endl; } DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer(); while (crit.IsNotNull()) { os << indent << " Sort by "; crit->Print(os); os << std::endl; crit = crit->GetSecondaryCriterion(); } } void mitk::DICOMTagBasedSorter ::SetStrictSorting(bool strict) { m_StrictSorting = strict; } bool mitk::DICOMTagBasedSorter ::GetStrictSorting() const { return m_StrictSorting; } void mitk::DICOMTagBasedSorter ::SetExpectDistanceOne(bool strict) { m_ExpectDistanceOne = strict; } bool mitk::DICOMTagBasedSorter ::GetExpectDistanceOne() const { return m_ExpectDistanceOne; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetTagsOfInterest() { DICOMTagList allTags = m_DistinguishingTags; if (m_SortCriterion.IsNotNull()) { const DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); allTags.insert( allTags.end(), sortingRelevantTags.cbegin(), sortingRelevantTags.cend() ); // append } return allTags; } mitk::DICOMTagList mitk::DICOMTagBasedSorter ::GetDistinguishingTags() const { return m_DistinguishingTags; } const mitk::DICOMTagBasedSorter::TagValueProcessor* mitk::DICOMTagBasedSorter ::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const { auto loc = m_TagValueProcessor.find(tag); if (loc != m_TagValueProcessor.cend()) { return loc->second; } else { return nullptr; } } void mitk::DICOMTagBasedSorter ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) { m_DistinguishingTags.push_back(tag); m_TagValueProcessor[tag] = tagValueProcessor; } void mitk::DICOMTagBasedSorter ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) { m_SortCriterion = criterion; } mitk::DICOMSortCriterion::ConstPointer mitk::DICOMTagBasedSorter ::GetSortCriterion() const { return m_SortCriterion; } void mitk::DICOMTagBasedSorter ::Sort() { // 1. split // 2. sort each group GroupIDToListType groups = this->SplitInputGroups(); GroupIDToListType& sortedGroups = this->SortGroups( groups ); // 3. define output this->SetNumberOfOutputs(sortedGroups.size()); unsigned int outputIndex(0); for (auto groupIter = sortedGroups.cbegin(); groupIter != sortedGroups.cend(); ++outputIndex, ++groupIter) { this->SetOutput(outputIndex, groupIter->second); } } std::string mitk::DICOMTagBasedSorter ::BuildGroupID( DICOMDatasetAccess* dataset ) { // just concatenate all tag values assert(dataset); std::stringstream groupID; groupID << "g"; for (auto tagIter = m_DistinguishingTags.cbegin(); tagIter != m_DistinguishingTags.cend(); ++tagIter) { groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags DICOMDatasetFinding rawTagValue = dataset->GetTagValueAsString(*tagIter); std::string processedTagValue; if ( m_TagValueProcessor[*tagIter] != nullptr && rawTagValue.isValid) { processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue.value); } else { processedTagValue = rawTagValue.value; } groupID << processedTagValue; } // shorten ID? return groupID.str(); } mitk::DICOMTagBasedSorter::GroupIDToListType mitk::DICOMTagBasedSorter ::SplitInputGroups() { DICOMDatasetList input = GetInput(); // copy GroupIDToListType listForGroupID; for (auto dsIter = input.cbegin(); dsIter != input.cend(); ++dsIter) { DICOMDatasetAccess* dataset = *dsIter; assert(dataset); std::string groupID = this->BuildGroupID( dataset ); MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; listForGroupID[groupID].push_back(dataset); } MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; return listForGroupID; } mitk::DICOMTagBasedSorter::GroupIDToListType& mitk::DICOMTagBasedSorter ::SortGroups(GroupIDToListType& groups) { if (m_SortCriterion.IsNotNull()) { /* Three steps here: 1. sort within each group - this may result in orders such as 1 2 3 4 6 7 8 10 12 13 14 2. create new groups by enforcing consecutive order within each group - resorts above example like 1 2 3 4 ; 6 7 8 ; 10 ; 12 13 14 3. sort all of the groups (not WITHIN each group) by their first frame - if earlier "distinguish" steps created groups like 6 7 8 ; 1 2 3 4 ; 10, then this step would sort them like 1 2 3 4 ; 6 7 8 ; 10 */ // Step 1: sort within the groups // for each output // sort by all configured tags, use secondary tags when equal or empty // make configurable: // - sorting order (ascending, descending) // - sort numerically // - ... ? +#ifdef MBILOG_ENABLE_DEBUG unsigned int groupIndex(0); +#endif for (auto gIter = groups.begin(); gIter != groups.end(); - ++groupIndex, ++gIter) +#ifdef MBILOG_ENABLE_DEBUG + ++groupIndex, +#endif + ++gIter) { DICOMDatasetList& dsList = gIter->second; #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; for (auto oi = dsList.begin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } #endif // #ifdef MBILOG_ENABLE_DEBUG std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); #ifdef MBILOG_ENABLE_DEBUG MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for (auto oi = dsList.cbegin(); oi != dsList.cend(); ++oi) { MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; #endif // MBILOG_ENABLE_DEBUG } GroupIDToListType consecutiveGroups; if (m_StrictSorting) { // Step 2: create new groups by enforcing consecutive order within each group unsigned int groupIndex(0); for (auto gIter = groups.begin(); gIter != groups.end(); ++gIter) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupIndex++; DICOMDatasetList& dsList = gIter->second; DICOMDatasetAccess* previousDS(nullptr); unsigned int dsIndex(0); double constantDistance(0.0); bool constantDistanceInitialized(false); for (auto dataset = dsList.cbegin(); dataset != dsList.cend(); ++dsIndex, ++dataset) { if (dsIndex >0) // ignore the first dataset, we cannot check any distances yet.. { // for the second and every following dataset: // let the sorting criterion calculate a "distance" // if the distance is not 1, split off a new group! const double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset); if (constantDistanceInitialized) { if (fabs(currentDistance - constantDistance) < fabs(constantDistance * 0.01)) // ok, deviation of up to 1% of distance is tolerated { // nothing to do, just ok MITK_DEBUG << "Checking currentDistance==" << currentDistance << ": small enough"; } //else if (currentDistance < mitk::eps) // close enough to 0 else { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (current distance " << currentDistance << ", constant distance " << constantDistance << ")"; // split! this is done by simply creating a new group (key) groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } } else { // second slice: learn about the expected distance! // heuristic: if distance is an integer, we check for a special case: // if the distance is integer and not 1/-1, then we assume // a missing slice right after the first slice // ==> split off slices // in all other cases: second dataset at this position, no need to split already, we are still learning about the images // addition to the above: when sorting by imagepositions, a distance other than 1 between the first two slices is // not unusual, actually expected... then we should not split if (m_ExpectDistanceOne) { if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0) // exact comparison. An integer should not be expressed as 1.000000000000000000000000001! { MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (special case: expected distance 1 exactly)"; groupKey.str(std::string()); groupKey.clear(); groupKey << std::setfill('0') << std::setw(6) << groupIndex++; } } MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance; constantDistance = currentDistance; constantDistanceInitialized = true; } } consecutiveGroups[groupKey.str()].push_back(*dataset); previousDS = *dataset; } } } else { consecutiveGroups = groups; } // Step 3: sort all of the groups (not WITHIN each group) by their first frame /* build a list-1 of datasets with the first dataset one of each group sort this list-1 build a new result list-2: - iterate list-1, for each dataset - find the group that contains this dataset - add this group as the next element to list-2 return list-2 as the sorted output */ DICOMDatasetList firstSlices; for (auto gIter = consecutiveGroups.cbegin(); gIter != consecutiveGroups.cend(); ++gIter) { assert(!gIter->second.empty()); firstSlices.push_back(gIter->second.front()); } std::sort( firstSlices.begin(), firstSlices.end(), ParameterizedDatasetSort( m_SortCriterion ) ); GroupIDToListType sortedResultBlocks; unsigned int groupKeyValue(0); for (auto firstSlice = firstSlices.cbegin(); firstSlice != firstSlices.cend(); ++firstSlice) { for (auto gIter = consecutiveGroups.cbegin(); gIter != consecutiveGroups.cend(); ++groupKeyValue, ++gIter) { if (gIter->second.front() == *firstSlice) { std::stringstream groupKey; groupKey << std::setfill('0') << std::setw(6) << groupKeyValue; // try more than 999,999 groups and you are doomed (your application already is) sortedResultBlocks[groupKey.str()] = gIter->second; } } } groups = sortedResultBlocks; } #ifdef MBILOG_ENABLE_DEBUG unsigned int groupIndex( 0 ); for ( auto gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter ) { DICOMDatasetList& dsList = gIter->second; MITK_DEBUG << " --------------------------------------------------------------------------------"; MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; for ( auto oi = dsList.begin(); oi != dsList.end(); ++oi ) { MITK_DEBUG << " OUTPUT : " << ( *oi )->GetFilenameIfAvailable(); } MITK_DEBUG << " --------------------------------------------------------------------------------"; } #endif // MBILOG_ENABLE_DEBUG return groups; } mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion) :m_SortCriterion(criterion) { } bool mitk::DICOMTagBasedSorter::ParameterizedDatasetSort ::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) { assert(left); assert(right); assert(m_SortCriterion.IsNotNull()); return m_SortCriterion->IsLeftBeforeRight(left, right); } diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx index 1e4a4c2756..edaba4950e 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx @@ -1,897 +1,889 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __itkShortestPathImageFilter_txx #define __itkShortestPathImageFilter_txx #include "itkShortestPathImageFilter.h" #include "mitkMemoryUtilities.h" #include #include #include #include namespace itk { // Constructor (initialize standard values) template ShortestPathImageFilter::ShortestPathImageFilter() : m_Nodes(nullptr), m_Graph_NumberOfNodes(0), m_Graph_fullNeighbors(false), m_useCostFunction(true), m_FullNeighborsMode(false), m_MakeOutputImage(true), m_StoreVectorOrder(false), m_CalcAllDistances(false), multipleEndPoints(false), m_ActivateTimeOut(false), m_Initialized(false) { m_endPoints.clear(); m_endPointsClosed.clear(); if (m_MakeOutputImage) { this->SetNumberOfRequiredOutputs(1); this->SetNthOutput(0, OutputImageType::New()); } } template ShortestPathImageFilter::~ShortestPathImageFilter() { delete[] m_Nodes; } template inline typename ShortestPathImageFilter::IndexType ShortestPathImageFilter::NodeToCoord(NodeNumType node) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; IndexType coord; if (dim == 2) { coord[1] = node / size[0]; coord[0] = node % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1])) { coord[0] = 0; coord[1] = 0; } } if (dim == 3) { coord[2] = node / (size[0] * size[1]); coord[1] = (node % (size[0] * size[1])) / size[0]; coord[0] = (node % (size[0] * size[1])) % size[0]; if (((unsigned long)coord[0] >= size[0]) || ((unsigned long)coord[1] >= size[1]) || ((unsigned long)coord[2] >= size[2])) { coord[0] = 0; coord[1] = 0; coord[2] = 0; } } return coord; } template inline typename itk::NodeNumType ShortestPathImageFilter::CoordToNode( IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; NodeNumType node = 0; if (dim == 2) { node = (coord[1] * size[0]) + coord[0]; } if (dim == 3) { node = (coord[2] * size[0] * size[1]) + (coord[1] * size[0]) + coord[0]; } if ((m_Graph_NumberOfNodes > 0) && (node >= m_Graph_NumberOfNodes)) { /*MITK_INFO << "WARNING! Coordinates outside image!"; MITK_INFO << "Coords = " << coord ; MITK_INFO << "ImageDim = " << dim ; MITK_INFO << "RequestedRegionSize = " << size ;*/ node = 0; } return node; } template inline bool ShortestPathImageFilter::CoordIsInBounds(IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; if (dim == 2) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1])) { return true; } } if (dim == 3) { if ((coord[0] >= 0) && ((unsigned long)coord[0] < size[0]) && (coord[1] >= 0) && ((unsigned long)coord[1] < size[1]) && (coord[2] >= 0) && ((unsigned long)coord[2] < size[2])) { return true; } } return false; } template inline std::vector ShortestPathImageFilter::GetNeighbors( unsigned int nodeNum, bool FullNeighbors) { // returns a vector of nodepointers.. these nodes are the neighbors int dim = InputImageType::ImageDimension; IndexType Coord = NodeToCoord(nodeNum); IndexType NeighborCoord; std::vector nodeList; int neighborDistance = 1; // if i increase that, i might not hit the endnote // maybe use itkNeighborhoodIterator here, might be faster if (dim == 2) { // N4 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N8 NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } if (dim == 3) { // N6 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N26 // Middle Slice NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] - neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice (Diagonal) NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice(Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] - neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] + neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1] + neighborDistance; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0] - neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2] + neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } return nodeList; } template void ShortestPathImageFilter::SetStartIndex( const typename TInputImageType::IndexType &StartIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_StartIndex[i] = StartIndex[i]; } m_Graph_StartNode = CoordToNode(m_StartIndex); // MITK_INFO << "StartIndex = " << StartIndex; // MITK_INFO << "StartNode = " << m_Graph_StartNode; m_Initialized = false; } template void ShortestPathImageFilter::SetEndIndex( const typename TInputImageType::IndexType &EndIndex) { for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { m_EndIndex[i] = EndIndex[i]; } m_Graph_EndNode = CoordToNode(m_EndIndex); // MITK_INFO << "EndNode = " << m_Graph_EndNode; } template void ShortestPathImageFilter::AddEndIndex( const typename TInputImageType::IndexType &index) { // ONLY FOR MULTIPLE END POINTS SEARCH IndexType newEndIndex; for (unsigned int i = 0; i < TInputImageType::ImageDimension; ++i) { newEndIndex[i] = index[i]; } m_endPoints.push_back(newEndIndex); SetEndIndex(m_endPoints[0]); multipleEndPoints = true; } template inline double ShortestPathImageFilter::getEstimatedCostsToTarget( const typename TInputImageType::IndexType &a) { // Returns the minimal possible costs for a path from "a" to targetnode. itk::Vector v; v[0] = m_EndIndex[0] - a[0]; v[1] = m_EndIndex[1] - a[1]; v[2] = m_EndIndex[2] - a[2]; return m_CostFunction->GetMinCost() * v.GetNorm(); } template void ShortestPathImageFilter::InitGraph() { if (!m_Initialized) { // Clean up previous stuff CleanUp(); // Calc Number of nodes auto imageDimensions = TInputImageType::ImageDimension; const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); m_Graph_NumberOfNodes = 1; for (NodeNumType i = 0; i < imageDimensions; ++i) m_Graph_NumberOfNodes = m_Graph_NumberOfNodes * size[i]; // Initialize mainNodeList with that number m_Nodes = new ShortestPathNode[m_Graph_NumberOfNodes]; // Initialize each node in nodelist for (NodeNumType i = 0; i < m_Graph_NumberOfNodes; i++) { m_Nodes[i].distAndEst = -1; m_Nodes[i].distance = -1; m_Nodes[i].prevNode = -1; m_Nodes[i].mainListIndex = i; m_Nodes[i].closed = false; } m_Initialized = true; } // In the beginning, the Startnode needs a distance of 0 m_Nodes[m_Graph_StartNode].distance = 0; m_Nodes[m_Graph_StartNode].distAndEst = 0; // initalize cost function m_CostFunction->Initialize(); } template void ShortestPathImageFilter::StartShortestPathSearch() { // Setup Timer clock_t startAll = clock(); clock_t stopAll = clock(); // init variables double durationAll = 0; bool timeout = false; NodeNumType mainNodeListIndex = 0; DistanceType curNodeDistance = 0; - NodeNumType numberOfNodesChecked = 0; // Create Multimap (tree structure for fast searching) std::multimap myMap; std::pair::iterator, std::multimap::iterator> ret; std::multimap::iterator it; // At first, only startNote is discovered. myMap.insert( std::pair(m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode])); // While there are discovered Nodes, pick the one with lowest distance, // update its neighbors and eventually delete it from the discovered Nodes list. while (!myMap.empty()) { - numberOfNodesChecked++; - /*if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) - { - MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() - << "\n"; - }*/ - // Get element with lowest score mainNodeListIndex = myMap.begin()->second->mainListIndex; curNodeDistance = myMap.begin()->second->distance; myMap.begin()->second->closed = true; // close it // Debug: // MITK_INFO << "INFO: size " << myMap.size(); /* for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ // Kicks out element with lowest score myMap.erase(myMap.begin()); // if wanted, store vector order if (m_StoreVectorOrder) { m_VectorOrder.push_back(mainNodeListIndex); } // Check neighbors std::vector neighborNodes = GetNeighbors(mainNodeListIndex, m_Graph_fullNeighbors); for (NodeNumType i = 0; i < neighborNodes.size(); i++) { if (neighborNodes[i]->closed) continue; // this nodes is already closed, go to next neighbor IndexType coordCurNode = NodeToCoord(mainNodeListIndex); IndexType coordNeighborNode = NodeToCoord(neighborNodes[i]->mainListIndex); // calculate the new Distance to the current neighbor double newDistance = curNodeDistance + (m_CostFunction->GetCost(coordCurNode, coordNeighborNode)); // if it is shorter than any yet known path to this neighbor, than the current path is better. Save that! if ((newDistance < neighborNodes[i]->distance) || (neighborNodes[i]->distance == -1)) { // if that neighbornode is not in discoverednodeList yet, Push it there and update if (neighborNodes[i]->distance == -1) { neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); /* MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ } // or if is already in discoverednodelist, update else { /* it = myMap.find(neighborNodes[i]->distAndEst); if (it == myMap.end() ) { MITK_INFO << "Nothing!"; // look further for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; } } } */ // 1st : find and delete old element bool found = false; ret = myMap.equal_range(neighborNodes[i]->distAndEst); if ((ret.first == ret.second)) { /*+++++++++++++ no exact match +++++++++++++*/ // MITK_INFO << "No exact match!"; // if this happens, you are screwed /* MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; if (ret.first != myMap.end() ) { it = ret.first; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; ++it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; --it; --it; MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; } // look if that ID is found in the map for (it = myMap.begin(); it != myMap.end(); ++it) { if ((*it).second->mainListIndex == lookForId) { MITK_INFO << "But it is there!!!"; MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; } } */ } else { for (it = ret.first; it != ret.second; ++it) { if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) { found = true; myMap.erase(it); /* MITK_INFO << "INFO: size " << myMap.size(); MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; MITK_INFO << "INFO: size " << myMap.size(); for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } */ break; } } } if (!found) { // MITK_INFO << "Could not find it! :("; continue; } // 2nd: update and insert new element neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; // myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); myMap.insert(std::pair(m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex])); // MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << // m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; // MITK_INFO << "INFO: size " << myMap.size(); /*for (it = myMap.begin(); it != myMap.end(); ++it) { MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; }*/ } } } // finished with checking all neighbors. // Check Timeout, if activated if (m_ActivateTimeOut) { stopAll = clock(); durationAll = (double)(stopAll - startAll) / CLOCKS_PER_SEC; if (durationAll >= 30) { // MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; timeout = true; } } // Check end criteria: // For multiple points if (multipleEndPoints) { // super slow, make it faster for (unsigned int i = 0; i < m_endPoints.size(); i++) { if (CoordToNode(m_endPoints[i]) == mainNodeListIndex) { m_endPointsClosed.push_back(NodeToCoord(mainNodeListIndex)); m_endPoints.erase(m_endPoints.begin() + i); if (m_endPoints.empty()) { // Finished! break return; } if (m_Graph_EndNode == mainNodeListIndex) { // set new end SetEndIndex(m_endPoints[0]); } } } } // if single end point, then end, if this one is reached or timeout happened. else if ((mainNodeListIndex == m_Graph_EndNode || timeout) && !m_CalcAllDistances) { /*if (m_StoreVectorOrder) MITK_INFO << "Number of Nodes checked: " << m_VectorOrder.size() ;*/ return; } } } template void ShortestPathImageFilter::MakeOutputs() { // MITK_INFO << "Make Output"; if (m_MakeOutputImage) { OutputImagePointer output0 = this->GetOutput(0); output0->SetRegions(this->GetInput()->GetLargestPossibleRegion()); output0->Allocate(); OutputImageIteratorType shortestPathImageIt(output0, output0->GetRequestedRegion()); // Create ShortestPathImage (Output 0) for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) { // First intialize with background color shortestPathImageIt.Set(BACKGROUND); } if (!multipleEndPoints) // Only one path was calculated { for (unsigned int i = 0; i < m_VectorPath.size(); i++) { shortestPathImageIt.SetIndex(m_VectorPath[i]); shortestPathImageIt.Set(FOREGROUND); } } else // multiple pathes has been calculated, draw all { for (unsigned int i = 0; i < m_MultipleVectorPaths.size(); i++) { for (unsigned int j = 0; j < m_MultipleVectorPaths[i].size(); j++) { shortestPathImageIt.SetIndex(m_MultipleVectorPaths[i][j]); shortestPathImageIt.Set(FOREGROUND); } } } } } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetVectorOrderImage() { // Create Vector Order Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); OutputImageIteratorType vectorOrderImageIt(image, image->GetRequestedRegion()); // MITK_INFO << "GetVectorOrderImage"; for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) { // First intialize with background color vectorOrderImageIt.Value() = BACKGROUND; } for (int i = 0; i < m_VectorOrder.size(); i++) { vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i])); vectorOrderImageIt.Set(BACKGROUND + i); } return image; } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter::GetDistanceImage() { // Create Distance Image // Return it OutputImagePointer image = OutputImageType::New(); image->SetRegions(this->GetInput()->GetLargestPossibleRegion()); image->Allocate(); ; OutputImageIteratorType distanceImageIt(image, image->GetRequestedRegion()); // Create Distance Image (Output 1) NodeNumType myNodeNum; for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) { IndexType index = distanceImageIt.GetIndex(); myNodeNum = CoordToNode(index); double newVal = m_Nodes[myNodeNum].distance; distanceImageIt.Set(newVal); } } template std::vector::IndexType> ShortestPathImageFilter::GetVectorPath() { return m_VectorPath; } template std::vector::IndexType>> ShortestPathImageFilter::GetMultipleVectorPaths() { return m_MultipleVectorPaths; } template void ShortestPathImageFilter::MakeShortestPathVector() { // MITK_INFO << "Make ShortestPath Vec"; if (m_useCostFunction == false) { m_VectorPath.push_back(NodeToCoord(m_Graph_StartNode)); m_VectorPath.push_back(NodeToCoord(m_Graph_EndNode)); return; } // single end point if (!multipleEndPoints) { // fill m_VectorPath with the Shortest Path m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = m_Graph_EndNode; while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); } // Multiple end end points and pathes else { for (unsigned int i = 0; i < m_endPointsClosed.size(); i++) { m_VectorPath.clear(); // Go backwards from endnote to startnode NodeNumType prevNode = CoordToNode(m_endPointsClosed[i]); while (prevNode != m_Graph_StartNode) { m_VectorPath.push_back(NodeToCoord(prevNode)); prevNode = m_Nodes[prevNode].prevNode; } m_VectorPath.push_back(NodeToCoord(prevNode)); // reverse it std::reverse(m_VectorPath.begin(), m_VectorPath.end()); // push_back m_MultipleVectorPaths.push_back(m_VectorPath); } } } template void ShortestPathImageFilter::CleanUp() { m_VectorOrder.clear(); m_VectorPath.clear(); // TODO: if multiple Path, clear all multiple Paths if (m_Nodes) delete[] m_Nodes; } template void ShortestPathImageFilter::GenerateData() { // Build Graph InitGraph(); // Calc Shortest Parth StartShortestPathSearch(); // Fill Shortest Path MakeShortestPathVector(); // Make Outputs MakeOutputs(); } template void ShortestPathImageFilter::PrintSelf(std::ostream &os, Indent indent) const { Superclass::PrintSelf(os, indent); } } /* end namespace itk */ #endif // __itkShortestPathImageFilter_txx diff --git a/Modules/IOExt/Internal/mitkParRecFileReader.cpp b/Modules/IOExt/Internal/mitkParRecFileReader.cpp index 975e0bff4a..c76470e697 100644 --- a/Modules/IOExt/Internal/mitkParRecFileReader.cpp +++ b/Modules/IOExt/Internal/mitkParRecFileReader.cpp @@ -1,280 +1,280 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkParRecFileReader.h" #include #include #ifdef __GNUC__ #define stricmp strcasecmp #endif void mitk::ParRecFileReader::GenerateOutputInformation() { mitk::Image::Pointer output = this->GetOutput(); if ((output->IsInitialized()) && (this->GetMTime() <= m_ReadHeaderTime.GetMTime())) return; itkDebugMacro(<< "Reading PAR file for GenerateOutputInformation()" << m_FileName); // Check to see if we can read the file given the name or prefix // if (m_FileName == "" && m_FilePrefix == "") { throw itk::ImageFileReaderException(__FILE__, __LINE__, "One of FileName or FilePrefix must be non-empty"); } m_RecFileName = ""; if (m_FileName != "") { int extPos = m_FileName.find_last_of("."); if (extPos >= -1) { const char *ext = m_FileName.c_str() + extPos + 1; if (stricmp(ext, "par") == 0) m_RecFileName = m_FileName.substr(0, extPos); else m_RecFileName = m_FileName; } else m_RecFileName = m_FileName; m_RecFileName.append(".rec"); bool headerRead = false; bool signedCharType = true; unsigned int dimension = 0; unsigned int dimensions[4] = {0, 0, 1, 1}; float sliceThickness = 0.0; float sliceGap = 0.0; float sliceSpacing = 0.0; mitk::Vector3D thickness; thickness.Fill(1.0); mitk::Vector3D gap; gap.Fill(0.0); mitk::Vector3D spacing; FILE *f; f = fopen(m_FileName.c_str(), "r"); if (f != nullptr) { while (!feof(f)) { char s[300], *p; - char *ignored = fgets(s, 200, f); - ++ignored; + if (fgets(s, 200, f) == nullptr) + throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not read header"); if (strstr(s, "Max. number of cardiac phases")) { p = strchr(s, ':') + 1; dimensions[3] = atoi(p); if (dimensions[3] > 1) dimension = 4; } else if (strstr(s, "Max. number of slices/locations")) { p = strchr(s, ':') + 1; dimensions[2] = atoi(p); if (dimension == 0) { if (dimensions[2] > 1) dimension = 3; else dimension = 2; } } else if (strstr(s, "Image pixel size")) { p = strchr(s, ':') + 1; int bpe = atoi(p); if (bpe != 8) signedCharType = false; } else if (strstr(s, "Recon resolution")) { p = s + strcspn(s, "0123456789"); sscanf(p, "%u %u", dimensions, dimensions + 1); } else if (strstr(s, "FOV (ap,fh,rl) [mm]")) { p = s + strcspn(s, "0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%lf %lf %lf", &thickness[0], &thickness[1], &thickness[2]); } else if (strstr(s, "Slice thickness [mm]")) { p = s + strcspn(s, "0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%f", &sliceThickness); } else if (strstr(s, "Slice gap [mm]")) { p = s + strcspn(s, "-0123456789"); mitk::LocaleSwitch localeSwitch("C"); sscanf(p, "%f", &sliceGap); } } fclose(f); // C:\home\ivo\data\coronaries\ucsf-wholeheart-2.par sliceSpacing = sliceThickness + sliceGap; if ((dimension > 0) && (dimensions[0] > 0) && (dimensions[1] > 0) && (sliceThickness > 0) && (sliceSpacing > 0)) { headerRead = true; if (fabs(thickness[0] / dimensions[2] - sliceSpacing) < 0.0001) thickness[0] = thickness[1]; else if (fabs(thickness[1] / dimensions[2] - sliceSpacing) < 0.0001) thickness[1] = thickness[0]; thickness[2] = sliceSpacing; thickness[0] /= dimensions[0]; thickness[1] /= dimensions[1]; spacing = thickness + gap; } } if (headerRead == false) { itk::ImageFileReaderException e(__FILE__, __LINE__); std::ostringstream msg; msg << " Could not read file " << m_FileName.c_str(); e.SetDescription(msg.str().c_str()); throw e; return; } // define types mitk::PixelType SCType = mitk::MakeScalarPixelType(); mitk::PixelType SSType = mitk::MakeScalarPixelType(); if (signedCharType) output->Initialize(SCType, dimension, dimensions); else output->Initialize(SSType, dimension, dimensions); output->GetSlicedGeometry()->SetSpacing(spacing); // output->GetSlicedGeometry()->SetPlaneGeometry(mitk::Image::BuildStandardPlanePlaneGeometry(output->GetSlicedGeometry(), // dimensions).GetPointer(), 0); output->GetSlicedGeometry()->SetEvenlySpaced(); } m_ReadHeaderTime.Modified(); } void mitk::ParRecFileReader::GenerateData() { mitk::Image::Pointer output = this->GetOutput(); // Check to see if we can read the file given the name or prefix // if (m_RecFileName == "") { throw itk::ImageFileReaderException(__FILE__, __LINE__, "FileName for rec-file empty"); } if (m_RecFileName != "") { FILE *f = fopen(m_RecFileName.c_str(), "r"); if (f == nullptr) { throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not open rec-file."); } int zstart, zmax; int tstart, tmax; zstart = output->GetRequestedRegion().GetIndex(2); tstart = output->GetRequestedRegion().GetIndex(3); zmax = zstart + output->GetRequestedRegion().GetSize(2); tmax = tstart + output->GetRequestedRegion().GetSize(3); int sliceSize = output->GetDimension(0) * output->GetDimension(1) * output->GetPixelType().GetBpe() / 8; void *data = malloc(sliceSize); bool ignore4Dtopogram = false; { int slicePlusTimeSize = output->GetDimension(0) * output->GetDimension(1) * output->GetDimension(3) * output->GetPixelType().GetBpe() / 8; if (output->GetDimension(3) > 1) ignore4Dtopogram = true; int z, t; for (t = tstart; t < tmax; ++t) for (z = zstart; z < zmax; ++z) { if (ignore4Dtopogram) fseek(f, slicePlusTimeSize * z + (sliceSize + 1) * t, SEEK_SET); else fseek(f, slicePlusTimeSize * z + sliceSize * t, SEEK_SET); - size_t ignored = fread(data, sliceSize, 1, f); - ++ignored; + if(fread(data, sliceSize, 1, f) < 1) + throw itk::ImageFileReaderException(__FILE__, __LINE__, "Could not read slice."); output->SetSlice(data, z, t, 0); } } // else //{ // for(;zSetSlice(data,z,0,0); // } //} free(data); fclose(f); } } bool mitk::ParRecFileReader::CanReadFile(const std::string filename, const std::string /*filePrefix*/, const std::string /*filePattern*/) { // First check the extension if (filename == "") { // MITK_INFO<<"No filename specified."< #include #include #include #include namespace mitk { template void MaskUtilities::SetImage(const ImageType* image) { if (image != m_Image) { m_Image = image; } } template void MaskUtilities::SetMask(const MaskType* mask) { if (mask != m_Mask) { m_Mask = mask; } } template bool MaskUtilities::CheckMaskSanity() { if (m_Mask==nullptr || m_Image==nullptr) { MITK_ERROR << "Set an image and a mask first"; } typedef itk::Image< TPixel, VImageDimension > ImageType; typedef typename ImageType::PointType PointType; typedef typename ImageType::DirectionType DirectionType; bool maskSanity = true; if (m_Mask==nullptr) { MITK_ERROR << "Something went wrong when casting the mitk mask image to an itk mask image. Do the mask and the input image have the same dimension?"; // note to self: We could try to convert say a 2d mask to a 3d mask if the image is 3d. (mask and image dimension have to match.) } // check direction DirectionType imageDirection = m_Image->GetDirection(); DirectionType maskDirection = m_Mask->GetDirection(); for(unsigned int i = 0; i < imageDirection.ColumnDimensions; ++i ) { for(unsigned int j = 0; j < imageDirection.ColumnDimensions; ++j ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if (fabs(differenceDirection) > MASK_SUITABILITY_TOLERANCE_DIRECTION) { maskSanity = false; MITK_INFO << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")"; } } } // check spacing PointType imageSpacing(m_Image->GetSpacing().GetDataPointer()); PointType maskSpacing(m_Mask->GetSpacing().GetDataPointer()); for (unsigned int i = 0; i < VImageDimension; i++) { if ( fabs( maskSpacing[i] - imageSpacing[i] ) > MASK_SUITABILITY_TOLERANCE_COORDINATE ) { maskSanity = false; MITK_INFO << "Spacing of mask and image is not equal. Mask: " << maskSpacing << " image: " << imageSpacing; } } // check alignment // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images - PointType imageOrigin = m_Image->GetOrigin(); PointType maskOrigin = m_Mask->GetOrigin(); - typedef itk::ContinuousIndex ContinousIndexType; - ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; - - m_Image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); - m_Image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); + auto maskOriginContinousIndex = m_Image->template TransformPhysicalPointToContinuousIndex(maskOrigin); for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); // misalignment must be a multiple (int) of spacing in that direction if ( fmod(misalignment,imageSpacing[i]) > MASK_SUITABILITY_TOLERANCE_COORDINATE) { maskSanity = false; MITK_INFO << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << fmod(misalignment,imageSpacing[i]) << ")"; } } // mask must be completely inside image region // Make sure that mask region is contained within image region if ( m_Mask!=nullptr && !m_Image->GetLargestPossibleRegion().IsInside( m_Mask->GetLargestPossibleRegion() ) ) { maskSanity = false; MITK_INFO << "Mask region needs to be inside of image region! (Image region: " << m_Image->GetLargestPossibleRegion() << "; Mask region: " << m_Mask->GetLargestPossibleRegion() << ")"; } return maskSanity; } template typename MaskUtilities::ImageType::ConstPointer MaskUtilities::ExtractMaskImageRegion() { if (m_Mask==nullptr || m_Image==nullptr) { MITK_ERROR << "Set an image and a mask first"; } bool maskSanity = CheckMaskSanity(); if (!maskSanity) { MITK_ERROR << "Mask and image are not compatible"; } typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; typename ImageType::SizeType imageSize = m_Image->GetBufferedRegion().GetSize(); typename ImageType::SizeType maskSize = m_Mask->GetBufferedRegion().GetSize(); typename itk::Image::ConstPointer resultImg; bool maskSmallerImage = false; for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { if ( maskSize[i] < imageSize[i] ) { maskSmallerImage = true; } } if ( maskSmallerImage ) { typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); typename MaskType::PointType maskOrigin = m_Mask->GetOrigin(); typename ImageType::PointType imageOrigin = m_Image->GetOrigin(); typename MaskType::SpacingType maskSpacing = m_Mask->GetSpacing(); typename ImageType::RegionType extractionRegion; typename ImageType::IndexType extractionRegionIndex; for (unsigned int i=0; i < maskOrigin.GetPointDimension(); i++) { extractionRegionIndex[i] = (maskOrigin[i] - imageOrigin[i]) / maskSpacing[i]; } extractionRegion.SetIndex(extractionRegionIndex); extractionRegion.SetSize(m_Mask->GetLargestPossibleRegion().GetSize()); extractImageFilter->SetInput( m_Image ); extractImageFilter->SetExtractionRegion( extractionRegion ); extractImageFilter->SetCoordinateTolerance(MASK_SUITABILITY_TOLERANCE_COORDINATE); extractImageFilter->SetDirectionTolerance(MASK_SUITABILITY_TOLERANCE_DIRECTION); extractImageFilter->Update(); auto extractedImg = extractImageFilter->GetOutput(); extractedImg->SetOrigin(m_Mask->GetOrigin()); extractedImg->SetLargestPossibleRegion(m_Mask->GetLargestPossibleRegion()); extractedImg->SetBufferedRegion(m_Mask->GetBufferedRegion()); resultImg = extractedImg; } else { resultImg = m_Image; } return resultImg; } } #endif diff --git a/Modules/MatchPointRegistration/CMakeLists.txt b/Modules/MatchPointRegistration/CMakeLists.txt index 50eae7426e..e2b6e37ba9 100644 --- a/Modules/MatchPointRegistration/CMakeLists.txt +++ b/Modules/MatchPointRegistration/CMakeLists.txt @@ -1,30 +1,36 @@ mitk_create_module( INCLUDE_DIRS PUBLIC algorithms PRIVATE src/Helper src/Rendering DEPENDS MitkCore MitkSceneSerializationBase MitkMultilabel PACKAGE_DEPENDS PUBLIC MatchPoint PRIVATE VTK|ImagingGeneral+ImagingHybrid ) if(BUILD_TESTING) add_subdirectory(Testing) endif() add_subdirectory(autoload/IO) add_subdirectory(cmdapps) if(TARGET ${MODULE_TARGET}) + if(MSVC) + # Warning 4834 leaks into MITK from MatchPoint (remove after MatchPoint is fixed): + # Discarding return value of function with nodiscard attribute. + target_compile_options(${MODULE_TARGET} PUBLIC /wd4834) + endif() + set(ALG_PROFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/algorithms) include(${MatchPoint_SOURCE_DIR}/CMake/mapFunctionCreateAlgorithmProfile.cmake) file(GLOB ALG_PROFILE_FILES LIST_DIRECTORIES false RELATIVE ${ALG_PROFILE_DIR} "${ALG_PROFILE_DIR}/*.profile") foreach(profile_file ${ALG_PROFILE_FILES}) get_filename_component(profile_name ${profile_file} NAME_WE) message(STATUS "Generate MDRA profile ${profile_name} (from ${profile_file})") CREATE_ALGORITHM_PROFILE(${profile_name} ${ALG_PROFILE_DIR}/${profile_file}) endforeach() add_subdirectory(deployment) endif() diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 48cfd421c1..02b28d5cb8 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1562 +1,1564 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } +const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UnlabeledValue = 0; + mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } this->ReinitMaps(); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledValue); } ls->SetLayer(newLabelSetId); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetActiveLayer() != groupID ? this->GetLayerImage(groupID) : this; if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) { this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 //bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const //{ // auto finding = m_LabelToGroupMap.find(value); // if (m_LabelToGroupMap.end() != finding) // { // return finding->second == groupIndex; // } // return false; //} // //bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const //{ // return index < m_LabelSetContainer.size(); //} bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LabelSetContainer.size(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UnlabeledValue) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; mitk::LabelSetImage::ConstLabelVectorType result; const auto labelValues = m_GroupToLabelMap.find(index)->second; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; mitk::LabelSetImage::LabelVectorType result; const auto labelValues = m_GroupToLabelMap[index]; for (const auto& labelValue : labelValues) { auto* label = this->GetLabel(labelValue); if (label != nullptr) result.emplace_back(label); } return result; } void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); if (labelSet->GetNumberOfLabels() != 0) { for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { if (iter->first != UnlabeledValue) this->AddLabelToMap(iter->first, iter->second, layerID); } } else { m_GroupToLabelMap[layerID] = {}; } } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index bb8cd3a0ac..709bb63434 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,672 +1,672 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; - const static LabelValueType UnlabeledValue = 0; + const static LabelValueType UnlabeledValue; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; using LabelValueVectorType = std::vector; /** * @brief Removes the label with the given value. * The label is removed from the labelset and * the pixel with the value of the label are set to UnlabeledValue. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @brief Removes a whole group including all its labels. * @remark with removing a group all groups with greater index will be reindexed to * close the gap. Hence externaly stored spatial group indices may become invalid. * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre group index must be valid. */ void RemoveGroup(GroupIndexType group); //future declaration (T28524) currently conflicted with old declaration ///** // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ //bool ExistLabel(LabelValueType value) const; ///** // * @brief Checks if a label belongs in a certain spatial group // * @param value the label value // * @param groupIndex Indexp of the spacial group which should be checked for the label // * @return true if the label exists otherwise false // */ //bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; /** * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. * * @param index Group index of the group that should be checked for existance. */ bool ExistGroup(GroupIndexType index) const; bool IsLabelInGroup(LabelValueType value) const; bool IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ GroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the mitk::Label if available otherwise nullptr */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); /** * @brief Returns a vector of all labels located on the specified group. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @return the respective vector of labels. * @pre group index must exist. */ const ConstLabelVectorType GetLabelsInGroup(GroupIndexType index) const; const LabelVectorType GetLabelsInGroup(GroupIndexType index); itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelEventType = Message1; using LabelsEventType = Message1; using GroupEventType = Message1; /** * \brief LabelAdded is emitted whenever a new label has been added. * * Observers should register to this event by calling this->AddLabelAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the added label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelAdded, LabelValueType); /** * \brief LabelModified is emitted whenever a label has been modified. * * A label is modified if either its pixel content was changed, its spatial group or the label instance * information. * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the * case that at least one label was modified (e.g. to avoid getting a signal for each label * individually), use LabelsChanged instead. * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the modified label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelModified, LabelValueType); /** * \brief LabelRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the removed label.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelRemoved, LabelValueType); /** * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). * * In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will * only be sent once (compared to LabelRemoved or LabelModified). * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, * MyObject::MyMethod). * The registered method will be called with the vector of label values of the modified labels.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); /** * \brief GroupAdded is emitted whenever a new group has been added. * * Observers should register to this event by calling this->AddGroupAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupAdded, GroupIndexType); /** * \brief GroupModified is emitted whenever a group has been modified. * * A group is modified if the set of labels associated with it are changed or the group's meta data. * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupModified, GroupIndexType); /** * \brief GroupRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the removed group.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupRemoved, GroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(GroupIndexType groupIndex); void OnGroupModified(GroupIndexType groupIndex); void OnGroupRemoved(GroupIndexType groupIndex); /** Reeinitalizes the internal maps based on the current layer/label content * of the instance. */ void ReinitMaps(); using LabelMapType = std::map; LabelMapType m_LabelMap; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::map; GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; LabelToGroupMapType m_LabelToGroupMap; private: /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(const std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue) const); /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ //[[deprecated("Will be removed with T28524")]] DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ //[[deprecated ("Will be removed with T28524")]] DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a cloned LabelSet to an existing layer * * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership * of the new LabelSet addition. * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values in all layers until the refactoring is done. returns a sorted list of all labels.*/ LabelValueVectorType GetUsedLabelValues() const; //helper function that ensures void RegisterLabelSet(mitk::LabelSet* ls); void ReleaseLabelSet(mitk::LabelSet* ls); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a label value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelSet must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp b/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp index 4805e6b3b6..a3767335f4 100644 --- a/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp +++ b/Modules/PlanarFigure/test/mitkPlanarFigureIOTest.cpp @@ -1,510 +1,509 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkTestingMacros.h" #include "mitkPlanarAngle.h" #include "mitkPlanarCircle.h" #include "mitkPlanarCross.h" #include "mitkPlanarFourPointAngle.h" #include "mitkPlanarLine.h" #include "mitkPlanarPolygon.h" #include "mitkPlanarRectangle.h" #include "mitkPlanarSubdivisionPolygon.h" #include "mitkPlaneGeometry.h" #include "mitkGeometry3D.h" #include "mitkAbstractFileIO.h" #include "mitkFileReaderRegistry.h" #include "mitkFileWriterRegistry.h" #include "mitkIOUtil.h" #include /** \brief Helper class for testing PlanarFigure reader and writer classes. */ class PlanarFigureIOTestClass { public: typedef std::map PlanarFigureMap; typedef std::map PlanarFigureToStreamMap; static PlanarFigureMap CreatePlanarFigures() { PlanarFigureMap planarFigures; // Create PlaneGeometry on which to place the PlanarFigures mitk::PlaneGeometry::Pointer planeGeometry = mitk::PlaneGeometry::New(); planeGeometry->InitializeStandardPlane(100.0, 100.0); // Create a few sample points for PlanarFigure placement mitk::Point2D p0; p0[0] = 20.0; p0[1] = 20.0; mitk::Point2D p1; p1[0] = 80.0; p1[1] = 80.0; mitk::Point2D p2; p2[0] = 90.0; p2[1] = 10.0; mitk::Point2D p3; p3[0] = 10.0; p3[1] = 90.0; // Create PlanarAngle mitk::PlanarAngle::Pointer planarAngle = mitk::PlanarAngle::New(); planarAngle->SetPlaneGeometry(planeGeometry); planarAngle->PlaceFigure(p0); planarAngle->SetCurrentControlPoint(p1); planarAngle->AddControlPoint(p2); planarAngle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarAngle",planarAngle.GetPointer()); // Create PlanarCircle mitk::PlanarCircle::Pointer planarCircle = mitk::PlanarCircle::New(); planarCircle->SetPlaneGeometry(planeGeometry); planarCircle->PlaceFigure(p0); planarCircle->SetCurrentControlPoint(p1); planarCircle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCircle",planarCircle.GetPointer()); // Create PlanarCross mitk::PlanarCross::Pointer planarCross = mitk::PlanarCross::New(); planarCross->SetSingleLineMode(false); planarCross->SetPlaneGeometry(planeGeometry); planarCross->PlaceFigure(p0); planarCross->SetCurrentControlPoint(p1); planarCross->AddControlPoint(p2); planarCross->AddControlPoint(p3); planarCross->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCross",planarCross.GetPointer()); // Create PlanarFourPointAngle mitk::PlanarFourPointAngle::Pointer planarFourPointAngle = mitk::PlanarFourPointAngle::New(); planarFourPointAngle->SetPlaneGeometry(planeGeometry); planarFourPointAngle->PlaceFigure(p0); planarFourPointAngle->SetCurrentControlPoint(p1); planarFourPointAngle->AddControlPoint(p2); planarFourPointAngle->AddControlPoint(p3); planarFourPointAngle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarFourPointAngle",planarFourPointAngle.GetPointer()); // Create PlanarLine mitk::PlanarLine::Pointer planarLine = mitk::PlanarLine::New(); planarLine->SetPlaneGeometry(planeGeometry); planarLine->PlaceFigure(p0); planarLine->SetCurrentControlPoint(p1); planarLine->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarLine",planarLine.GetPointer()); // Create PlanarPolygon mitk::PlanarPolygon::Pointer planarPolygon = mitk::PlanarPolygon::New(); planarPolygon->SetClosed(false); planarPolygon->SetPlaneGeometry(planeGeometry); planarPolygon->PlaceFigure(p0); planarPolygon->SetCurrentControlPoint(p1); planarPolygon->AddControlPoint(p2); planarPolygon->AddControlPoint(p3); planarPolygon->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarPolygon",planarPolygon.GetPointer()); // Create PlanarSubdivisionPolygon mitk::PlanarSubdivisionPolygon::Pointer planarSubdivisionPolygon = mitk::PlanarSubdivisionPolygon::New(); planarSubdivisionPolygon->SetClosed(false); planarSubdivisionPolygon->SetPlaneGeometry(planeGeometry); planarSubdivisionPolygon->PlaceFigure(p0); planarSubdivisionPolygon->SetCurrentControlPoint(p1); planarSubdivisionPolygon->AddControlPoint(p2); planarSubdivisionPolygon->AddControlPoint(p3); planarSubdivisionPolygon->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarSubdivisionPolygon",planarSubdivisionPolygon.GetPointer()); // Create PlanarRectangle mitk::PlanarRectangle::Pointer planarRectangle = mitk::PlanarRectangle::New(); planarRectangle->SetPlaneGeometry(planeGeometry); planarRectangle->PlaceFigure(p0); planarRectangle->SetCurrentControlPoint(p1); planarRectangle->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarRectangle",planarRectangle.GetPointer()); // create preciseGeometry which is using float coordinates mitk::PlaneGeometry::Pointer preciseGeometry = mitk::PlaneGeometry::New(); mitk::Vector3D right; right[0] = 0.0; right[1] = 1.23456; right[2] = 0.0; mitk::Vector3D down; down[0] = 1.23456; down[1] = 0.0; down[2] = 0.0; mitk::Vector3D spacing; spacing[0] = 0.0123456; spacing[1] = 0.0123456; spacing[2] = 1.123456; preciseGeometry->InitializeStandardPlane(right, down, &spacing); // convert points into the precise coordinates mitk::Point2D p0precise; p0precise[0] = p0[0] * spacing[0]; p0precise[1] = p0[1] * spacing[1]; mitk::Point2D p1precise; p1precise[0] = p1[0] * spacing[0]; p1precise[1] = p1[1] * spacing[1]; mitk::Point2D p2precise; p2precise[0] = p2[0] * spacing[0]; p2precise[1] = p2[1] * spacing[1]; mitk::Point2D p3precise; p3precise[0] = p3[0] * spacing[0]; p3precise[1] = p3[1] * spacing[1]; // Now all PlanarFigures are create using the precise Geometry // Create PlanarCross mitk::PlanarCross::Pointer nochncross = mitk::PlanarCross::New(); nochncross->SetSingleLineMode(false); nochncross->SetPlaneGeometry(preciseGeometry); nochncross->PlaceFigure(p0precise); nochncross->SetCurrentControlPoint(p1precise); nochncross->AddControlPoint(p2precise); nochncross->AddControlPoint(p3precise); nochncross->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("nochncross", nochncross.GetPointer()); // Create PlanarAngle mitk::PlanarAngle::Pointer planarAnglePrecise = mitk::PlanarAngle::New(); planarAnglePrecise->SetPlaneGeometry(preciseGeometry); planarAnglePrecise->PlaceFigure(p0precise); planarAnglePrecise->SetCurrentControlPoint(p1precise); planarAnglePrecise->AddControlPoint(p2precise); planarAnglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarAnglePrecise",planarAnglePrecise.GetPointer()); // Create PlanarCircle mitk::PlanarCircle::Pointer planarCirclePrecise = mitk::PlanarCircle::New(); planarCirclePrecise->SetPlaneGeometry(preciseGeometry); planarCirclePrecise->PlaceFigure(p0precise); planarCirclePrecise->SetCurrentControlPoint(p1precise); planarCirclePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarCirclePrecise",planarCirclePrecise.GetPointer()); // Create PlanarFourPointAngle mitk::PlanarFourPointAngle::Pointer planarFourPointAnglePrecise = mitk::PlanarFourPointAngle::New(); planarFourPointAnglePrecise->SetPlaneGeometry(preciseGeometry); planarFourPointAnglePrecise->PlaceFigure(p0precise); planarFourPointAnglePrecise->SetCurrentControlPoint(p1precise); planarFourPointAnglePrecise->AddControlPoint(p2precise); planarFourPointAnglePrecise->AddControlPoint(p3precise); planarFourPointAnglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarFourPointAnglePrecise",planarFourPointAnglePrecise.GetPointer()); // Create PlanarLine mitk::PlanarLine::Pointer planarLinePrecise = mitk::PlanarLine::New(); planarLinePrecise->SetPlaneGeometry(preciseGeometry); planarLinePrecise->PlaceFigure(p0precise); planarLinePrecise->SetCurrentControlPoint(p1precise); planarLinePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarLinePrecise",planarLinePrecise.GetPointer()); // Create PlanarPolygon mitk::PlanarPolygon::Pointer planarPolygonPrecise = mitk::PlanarPolygon::New(); planarPolygonPrecise->SetClosed(false); planarPolygonPrecise->SetPlaneGeometry(preciseGeometry); planarPolygonPrecise->PlaceFigure(p0precise); planarPolygonPrecise->SetCurrentControlPoint(p1precise); planarPolygonPrecise->AddControlPoint(p2precise); planarPolygonPrecise->AddControlPoint(p3precise); planarPolygonPrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarPolygonPrecise",planarPolygonPrecise.GetPointer()); // Create PlanarSubdivisionPolygon mitk::PlanarSubdivisionPolygon::Pointer planarSubdivisionPolygonPrecise = mitk::PlanarSubdivisionPolygon::New(); planarSubdivisionPolygonPrecise->SetClosed(false); planarSubdivisionPolygonPrecise->SetPlaneGeometry(preciseGeometry); planarSubdivisionPolygonPrecise->PlaceFigure(p0precise); planarSubdivisionPolygonPrecise->SetCurrentControlPoint(p1precise); planarSubdivisionPolygonPrecise->AddControlPoint(p2precise); planarSubdivisionPolygonPrecise->AddControlPoint(p3precise); planarSubdivisionPolygonPrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarSubdivisionPolygonPrecise",planarSubdivisionPolygonPrecise.GetPointer()); // Create PlanarRectangle mitk::PlanarRectangle::Pointer planarRectanglePrecise = mitk::PlanarRectangle::New(); planarRectanglePrecise->SetPlaneGeometry(preciseGeometry); planarRectanglePrecise->PlaceFigure(p0precise); planarRectanglePrecise->SetCurrentControlPoint(p1precise); planarRectanglePrecise->GetPropertyList()->SetBoolProperty("initiallyplaced", true); planarFigures.emplace("planarRectanglePrecise",planarRectanglePrecise.GetPointer()); return planarFigures; } static PlanarFigureMap CreateClonedPlanarFigures(PlanarFigureMap original) { PlanarFigureMap copiedPlanarFigures; for (const auto& pf : original) { mitk::PlanarFigure::Pointer copiedFigure = pf.second->Clone(); copiedPlanarFigures[pf.first] = copiedFigure; } return copiedPlanarFigures; } static void VerifyPlanarFigures(PlanarFigureMap &referencePfs, PlanarFigureMap &testPfs) { PlanarFigureMap::iterator it1, it2; int i = 0; for (it1 = referencePfs.begin(); it1 != referencePfs.end(); ++it1) { bool planarFigureFound = false; - int j = 0; + for (it2 = testPfs.begin(); it2 != testPfs.end(); ++it2) { // Compare PlanarFigures (returns false if different types) if (ComparePlanarFigures(it1->second, it2->second)) { planarFigureFound = true; } - ++j; } // Test if (at least) on PlanarFigure of the first type was found in the second list MITK_TEST_CONDITION_REQUIRED(planarFigureFound, "Testing if " << it1->second->GetNameOfClass() << " has a counterpart " << i); ++i; } } static bool ComparePlanarFigures(const mitk::PlanarFigure *referencePf, const mitk::PlanarFigure *testPf) { // Test if PlanarFigures are of same type; otherwise return if (strcmp(referencePf->GetNameOfClass(), testPf->GetNameOfClass()) != 0) { return false; } if (strcmp(referencePf->GetNameOfClass(), "PlanarCross") == 0) { std::cout << "Planar Cross Found" << std::endl; } // Test for equal number of control points if (referencePf->GetNumberOfControlPoints() != testPf->GetNumberOfControlPoints()) { return false; } // Test if all control points are equal for (unsigned int i = 0; i < referencePf->GetNumberOfControlPoints(); ++i) { mitk::Point2D point1 = referencePf->GetControlPoint(i); mitk::Point2D point2 = testPf->GetControlPoint(i); if (point1.EuclideanDistanceTo(point2) >= mitk::eps) { return false; } } // Test for equal number of properties typedef mitk::PropertyList::PropertyMap PropertyMap; const PropertyMap *refProperties = referencePf->GetPropertyList()->GetMap(); const PropertyMap *testProperties = testPf->GetPropertyList()->GetMap(); MITK_INFO << "List 1:"; for (auto i1 = refProperties->begin(); i1 != refProperties->end(); ++i1) { std::cout << i1->first << std::endl; } MITK_INFO << "List 2:"; for (auto i2 = testProperties->begin(); i2 != testProperties->end(); ++i2) { std::cout << i2->first << std::endl; } MITK_INFO << "-------"; //remark test planar figures may have additional properties //(e.g. reader meta information), but they are not relevant //for the test. Only check of all properties of the reference //are present and correct. for (const auto& prop : *refProperties) { auto finding = testProperties->find(prop.first); if (finding == testProperties->end()) { return false; } MITK_INFO << "Comparing " << prop.first << "(" << prop.second->GetValueAsString() << ") and " << finding->first << "(" << finding->second->GetValueAsString() << ")"; // Compare property objects contained in the map entries (see mitk::PropertyList) if (!(*(prop.second) == *(finding->second))) return false; } // Test if Geometry is equal const auto *planeGeometry1 = dynamic_cast(referencePf->GetPlaneGeometry()); const auto *planeGeometry2 = dynamic_cast(testPf->GetPlaneGeometry()); // Test Geometry transform parameters typedef mitk::Geometry3D::TransformType TransformType; const TransformType *affineGeometry1 = planeGeometry1->GetIndexToWorldTransform(); const TransformType::ParametersType ¶meters1 = affineGeometry1->GetParameters(); const TransformType::ParametersType ¶meters2 = planeGeometry2->GetIndexToWorldTransform()->GetParameters(); for (unsigned int i = 0; i < affineGeometry1->GetNumberOfParameters(); ++i) { if (fabs(parameters1.GetElement(i) - parameters2.GetElement(i)) >= mitk::eps) { return false; } } // Test Geometry bounds typedef mitk::Geometry3D::BoundsArrayType BoundsArrayType; const BoundsArrayType &bounds1 = planeGeometry1->GetBounds(); const BoundsArrayType &bounds2 = planeGeometry2->GetBounds(); for (unsigned int i = 0; i < 6; ++i) { if (fabs(bounds1.GetElement(i) - bounds2.GetElement(i)) >= mitk::eps) { return false; }; } // Test Geometry spacing and origin mitk::Vector3D spacing1 = planeGeometry1->GetSpacing(); mitk::Vector3D spacing2 = planeGeometry2->GetSpacing(); if ((spacing1 - spacing2).GetNorm() >= mitk::eps) { return false; } mitk::Point3D origin1 = planeGeometry1->GetOrigin(); mitk::Point3D origin2 = planeGeometry2->GetOrigin(); if (origin1.EuclideanDistanceTo(origin2) >= mitk::eps) { return false; } return true; } static PlanarFigureToStreamMap SerializePlanarFiguresToMemoryBuffers(PlanarFigureMap &planarFigures) { PlanarFigureToStreamMap pfMemoryStreams; for (const auto& pf : planarFigures) { mitk::FileWriterRegistry writerRegistry; auto writers = writerRegistry.GetWriters(pf.second.GetPointer(), ""); std::ostringstream stream; writers[0]->SetOutputStream("",&stream); writers[0]->SetInput(pf.second); writers[0]->Write(); pfMemoryStreams.emplace(pf.first, stream.str()); } return pfMemoryStreams; } static PlanarFigureMap DeserializePlanarFiguresFromMemoryBuffers(PlanarFigureToStreamMap pfMemoryStreams) { // Store them in the list and return it PlanarFigureMap planarFigures; mitk::FileReaderRegistry readerRegistry; std::vector readers = readerRegistry.GetReaders(mitk::FileReaderRegistry::GetMimeTypeForFile("pf")); for (const auto& pfStream : pfMemoryStreams) { std::istringstream stream; stream.str(pfStream.second); readers[0]->SetInput("", &stream); auto pfRead = readers[0]->Read(); MITK_TEST_CONDITION(pfRead.size() == 1, "One planar figure should be read from stream."); auto pf = dynamic_cast(pfRead.front().GetPointer()); MITK_TEST_CONDITION(pf != nullptr, "Loaded data should be a planar figure."); planarFigures.emplace(pfStream.first, pf); } return planarFigures; } }; // end test helper class /** \brief Test for PlanarFigure reader and writer classes. * * The test works as follows: * * First, a number of PlanarFigure objects of different types are created and placed with * various control points. These objects are the serialized to file, read again from file, and * the retrieved objects are compared with their control points, properties, and geometry * information to the original PlanarFigure objects. */ int mitkPlanarFigureIOTest(int /* argc */, char * /*argv*/ []) { MITK_TEST_BEGIN("PlanarFigureIO"); // Create a number of PlanarFigure objects PlanarFigureIOTestClass::PlanarFigureMap originalPlanarFigures = PlanarFigureIOTestClass::CreatePlanarFigures(); // Create a number of cloned planar figures to test the Clone function PlanarFigureIOTestClass::PlanarFigureMap clonedPlanarFigures = PlanarFigureIOTestClass::CreateClonedPlanarFigures(originalPlanarFigures); PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, clonedPlanarFigures); std::map pfFileNameMap; for (const auto& pf : originalPlanarFigures) { std::string filename = mitk::IOUtil::CreateTemporaryFile(pf.first+"_XXXXXX.pf", itksys::SystemTools::GetCurrentWorkingDirectory()); mitk::IOUtil::Save(pf.second, filename); pfFileNameMap.emplace(pf.first, filename); } // Write PlanarFigure objects to memory buffers PlanarFigureIOTestClass::PlanarFigureToStreamMap writersStreams = PlanarFigureIOTestClass::SerializePlanarFiguresToMemoryBuffers(originalPlanarFigures); // Read PlanarFigure objects from temp file PlanarFigureIOTestClass::PlanarFigureMap retrievedPlanarFigures; for (const auto& files : pfFileNameMap) { auto pf = mitk::IOUtil::Load(files.second); retrievedPlanarFigures.emplace(files.first, pf); } // Read PlanarFigure objects from memory buffers PlanarFigureIOTestClass::PlanarFigureMap retrievedPlanarFiguresFromMemory = PlanarFigureIOTestClass::DeserializePlanarFiguresFromMemoryBuffers(writersStreams); // Test if original and retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, retrievedPlanarFigures); // Test if original and memory retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(originalPlanarFigures, retrievedPlanarFiguresFromMemory); // empty the originalPlanarFigures originalPlanarFigures.clear(); // Test if cloned and retrieved PlanarFigure objects are the same PlanarFigureIOTestClass::VerifyPlanarFigures(clonedPlanarFigures, retrievedPlanarFigures); MITK_TEST_END() } diff --git a/Modules/Python/autoload/PythonService/CMakeLists.txt b/Modules/Python/autoload/PythonService/CMakeLists.txt index c0b5d4f8f6..15f1384ec9 100644 --- a/Modules/Python/autoload/PythonService/CMakeLists.txt +++ b/Modules/Python/autoload/PythonService/CMakeLists.txt @@ -1,15 +1,17 @@ mitkFunctionCheckCompilerFlags("-Wno-cpp" CMAKE_CXX_FLAGS) -mitk_create_module(PythonService - INCLUDE_DIRS - PRIVATE src/PythonService - DEPENDS PUBLIC MitkPython - PACKAGE_DEPENDS - PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets - PRIVATE Python3|NumPy - AUTOLOAD_WITH MitkPython -) +if(CTKScriptingPythonCore_INCLUDE_DIRS AND CTKScriptingPythonWidgets_INCLUDE_DIRS) + mitk_create_module(PythonService + INCLUDE_DIRS + PRIVATE src/PythonService + DEPENDS PUBLIC MitkPython + PACKAGE_DEPENDS + PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets + PRIVATE Python3|NumPy + AUTOLOAD_WITH MitkPython + ) +endif() if(TARGET ${MODULE_TARGET}) configure_file(PythonPath.h.in "${CMAKE_CURRENT_BINARY_DIR}/PythonPath.h" @ONLY) endif() diff --git a/Modules/QtPython/CMakeLists.txt b/Modules/QtPython/CMakeLists.txt index 433b7ae4f4..c22ed5a8db 100644 --- a/Modules/QtPython/CMakeLists.txt +++ b/Modules/QtPython/CMakeLists.txt @@ -1,13 +1,15 @@ if(MITK_USE_Python3) mitkFunctionCheckCompilerFlags("/wd4273" CMAKE_CXX_FLAGS) - mitk_create_module( - DEPENDS MitkCore MitkQtWidgets MitkPython - PACKAGE_DEPENDS - PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets + if(CTKScriptingPythonCore_INCLUDE_DIRS AND CTKScriptingPythonWidgets_INCLUDE_DIRS) + mitk_create_module( + DEPENDS MitkCore MitkQtWidgets MitkPython + PACKAGE_DEPENDS + PUBLIC Qt5|Widgets CTK|CTKScriptingPythonCore+CTKScriptingPythonWidgets ) + endif() - if(BUILD_TESTING) + if(BUILD_TESTING AND TARGET ${MODULE_TARGET}) add_subdirectory(Testing) endif() endif() diff --git a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h index c2034d4f64..f5966a2b41 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h +++ b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h @@ -1,148 +1,147 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkDataStorageComboBoxWithSelectNone_h #define QmitkDataStorageComboBoxWithSelectNone_h #include #include "QmitkDataStorageComboBox.h" #include "QmitkCustomVariants.h" #include "mitkDataNode.h" /** * \class QmitkDataStorageComboBoxWithSelectNone * \brief Displays all or a subset (defined by a predicate) of nodes of the Data Storage, * and additionally, index 0 is always "please select", indicating no selection, and will * hence always return a nullptr mitk::DataNode* if asked for the node at index 0. * * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal * \sa QmitkDataStorageComboBox */ class MITKQTWIDGETS_EXPORT QmitkDataStorageComboBoxWithSelectNone : public QmitkDataStorageComboBox { Q_OBJECT Q_PROPERTY(mitkDataNodePtr SelectedNode READ GetSelectedNode WRITE SetSelectedNode) Q_PROPERTY(QString currentValue READ currentValue WRITE setCurrentValue) public: /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ QmitkDataStorageComboBoxWithSelectNone(QWidget* parent = nullptr, bool autoSelectNewNodes=false); /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ QmitkDataStorageComboBoxWithSelectNone( mitk::DataStorage* dataStorage, const mitk::NodePredicateBase* predicate, QWidget* parent = nullptr, bool autoSelectNewNodes = false); /** * \brief Nothing to do. * \see QmitkDataStorageComboBox */ ~QmitkDataStorageComboBoxWithSelectNone() override; /** * \brief Stores the string that will be present on index 0, currently equal to "please select". */ static const QString ZERO_ENTRY_STRING; /** * \brief Searches for a given node, returning the index if found. * \param dataNode an mitk::DataNode, can be nullptr. * \return int -1 if not found, and compared to base class, will add 1 onto the retrieved index. */ int Find(const mitk::DataNode* dataNode) const override; /** * \brief Retrieves the node at a given index, where if index is zero, will always return nullptr. * \param index An integer between 0 and n = number of nodes. * \return mitk::DataNode::Pointer nullptr or a data node pointer. */ mitk::DataNode::Pointer GetNode(int index) const override; /** * \brief Returns the selected DataNode or nullptr if there is none, or the current index is zero. */ mitk::DataNode::Pointer GetSelectedNode() const override; /** * \brief Sets the combo box to the index that contains the specified node, or 0 if the node cannot be found. */ virtual void SetSelectedNode(const mitk::DataNode::Pointer& node); using QmitkDataStorageComboBox::RemoveNode; /** * \brief Removes a node from the ComboBox at a specified index (if the index exists). * Gets called when a DataStorage Remove Event was thrown. */ void RemoveNode(int index) override; using QmitkDataStorageComboBox::SetNode; /** * \brief Set a DataNode in the ComboBox at the specified index (if the index exists). * Internally the method just calls InsertNode(unsigned int) */ void SetNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Get the current file path. */ virtual QString currentValue() const; /** * \brief Set the current file path. */ virtual void setCurrentValue(const QString& path); /** * \brief Set the string that will be present on index 0. */ void SetZeroEntryText(const QString& zeroEntryString); protected: /** * \brief Checks if the given index is within range. */ bool HasIndex(unsigned int index) const; /** * \brief Inserts a new node at the given index, unless index is 0, which is silently ignored. */ void InsertNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Reset function whenever datastorage or predicate changes. */ void Reset() override; private: /** * \brief This should store the current file path of the current image. * * * The reason is so that we can store and retrieve a temporary file name. */ QString m_CurrentPath; }; #endif diff --git a/Modules/QtWidgetsExt/resource/PlanarAngle_48.png b/Modules/QtWidgets/resource/PlanarAngle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarAngle_48.png rename to Modules/QtWidgets/resource/PlanarAngle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarBezierCurve_48.png b/Modules/QtWidgets/resource/PlanarBezierCurve_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarBezierCurve_48.png rename to Modules/QtWidgets/resource/PlanarBezierCurve_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarCircle_48.png b/Modules/QtWidgets/resource/PlanarCircle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarCircle_48.png rename to Modules/QtWidgets/resource/PlanarCircle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarDoubleEllipse_48.png b/Modules/QtWidgets/resource/PlanarDoubleEllipse_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarDoubleEllipse_48.png rename to Modules/QtWidgets/resource/PlanarDoubleEllipse_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarEllipse_48.png b/Modules/QtWidgets/resource/PlanarEllipse_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarEllipse_48.png rename to Modules/QtWidgets/resource/PlanarEllipse_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarFourPointAngle_48.png b/Modules/QtWidgets/resource/PlanarFourPointAngle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarFourPointAngle_48.png rename to Modules/QtWidgets/resource/PlanarFourPointAngle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarLine_48.png b/Modules/QtWidgets/resource/PlanarLine_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarLine_48.png rename to Modules/QtWidgets/resource/PlanarLine_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarPath_48.png b/Modules/QtWidgets/resource/PlanarPath_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarPath_48.png rename to Modules/QtWidgets/resource/PlanarPath_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarPolygon_48.png b/Modules/QtWidgets/resource/PlanarPolygon_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarPolygon_48.png rename to Modules/QtWidgets/resource/PlanarPolygon_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarRectangle_48.png b/Modules/QtWidgets/resource/PlanarRectangle_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarRectangle_48.png rename to Modules/QtWidgets/resource/PlanarRectangle_48.png diff --git a/Modules/QtWidgetsExt/resource/PlanarSubdivisionPolygon_48.png b/Modules/QtWidgets/resource/PlanarSubdivisionPolygon_48.png similarity index 100% rename from Modules/QtWidgetsExt/resource/PlanarSubdivisionPolygon_48.png rename to Modules/QtWidgets/resource/PlanarSubdivisionPolygon_48.png diff --git a/Modules/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index f284857d3e..81f4df1b5e 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,36 +1,47 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg hourglass-half-solid.svg times.svg reset.svg lock.svg unlock.svg invisible.svg visible.svg SegmentationTaskListIcon.svg ROIIcon.svg GeometryDataIcon.svg + PlanarAngle_48.png + PlanarBezierCurve_48.png + PlanarCircle_48.png + PlanarDoubleEllipse_48.png + PlanarEllipse_48.png + PlanarFourPointAngle_48.png + PlanarLine_48.png + PlanarPath_48.png + PlanarPolygon_48.png + PlanarRectangle_48.png + PlanarSubdivisionPolygon_48.png diff --git a/Modules/QtWidgets/src/QmitkIOUtil.cpp b/Modules/QtWidgets/src/QmitkIOUtil.cpp index abda3c7626..053a231c77 100644 --- a/Modules/QtWidgets/src/QmitkIOUtil.cpp +++ b/Modules/QtWidgets/src/QmitkIOUtil.cpp @@ -1,586 +1,586 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkIOUtil.h" #include "mitkCoreServices.h" #include "mitkCustomMimeType.h" #include "mitkFileReaderRegistry.h" #include "mitkFileWriterRegistry.h" #include "mitkIMimeTypeProvider.h" #include "mitkMimeType.h" #include #include #include "QmitkFileReaderOptionsDialog.h" #include "QmitkFileWriterOptionsDialog.h" // QT #include #include #include #include #include // ITK #include #include struct QmitkIOUtil::Impl { struct ReaderOptionsDialogFunctor : public ReaderOptionsFunctorBase { bool operator()(LoadInfo &loadInfo) const override { QmitkFileReaderOptionsDialog dialog(loadInfo); if (dialog.exec() == QDialog::Accepted) { return !dialog.ReuseOptions(); } else { loadInfo.m_Cancel = true; return true; } } }; struct WriterOptionsDialogFunctor : public WriterOptionsFunctorBase { bool operator()(SaveInfo &saveInfo) const override { QmitkFileWriterOptionsDialog dialog(saveInfo); if (dialog.exec() == QDialog::Accepted) { return !dialog.ReuseOptions(); } else { saveInfo.m_Cancel = true; return true; } } }; //! Filename characters that are not valid - depending on the platform (Windows, Posix) static QString s_InvalidFilenameCharacters; //! Return 'true' when: //! - the specified filename contains characters not accepted by the file system (see s_InvalidFilenameCharacters) //! - filename starts or ends in space characters //! //! This test is not exhaustive but just excluding the most common problems. static bool IsIllegalFilename(const QString &fullFilename) { QFileInfo fi(fullFilename); auto filename = fi.fileName(); for (const auto &ch : qAsConst(s_InvalidFilenameCharacters)) { if (filename.contains(ch)) { return true; } } - if (filename.startsWith(' ' || filename.endsWith(' '))) + if (filename.startsWith(' ') || filename.endsWith(' ')) { return true; } return false; } }; // Impl #if defined(_WIN32) || defined(_WIN64) QString QmitkIOUtil::Impl::s_InvalidFilenameCharacters = "<>:\"/\\|?*"; #else QString QmitkIOUtil::Impl::s_InvalidFilenameCharacters = "/"; #endif struct MimeTypeComparison { MimeTypeComparison(const std::string &mimeTypeName) : m_Name(mimeTypeName) {} bool operator()(const mitk::MimeType &mimeType) const { return mimeType.GetName() == m_Name; } const std::string m_Name; }; QString QmitkIOUtil::GetFileOpenFilterString() { QString filters; mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); std::vector categories = mimeTypeProvider->GetCategories(); for (std::vector::iterator cat = categories.begin(); cat != categories.end(); ++cat) { QSet filterExtensions; std::vector mimeTypes = mimeTypeProvider->GetMimeTypesForCategory(*cat); for (std::vector::iterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt) { std::vector extensions = mt->GetExtensions(); for (std::vector::iterator ext = extensions.begin(); ext != extensions.end(); ++ext) { filterExtensions << QString::fromStdString(*ext); } } QString filter = QString::fromStdString(*cat) + " ("; foreach (const QString &extension, filterExtensions) { filter += "*." + extension + " "; } filter = filter.replace(filter.size() - 1, 1, ')'); filters += ";;" + filter; } filters.prepend("All (*)"); return filters; } QList QmitkIOUtil::Load(const QStringList &paths, QWidget *parent) { std::vector loadInfos; foreach (const QString &file, paths) { loadInfos.push_back(LoadInfo(file.toLocal8Bit().constData())); } Impl::ReaderOptionsDialogFunctor optionsCallback; std::string errMsg = Load(loadInfos, nullptr, nullptr, &optionsCallback); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg)); mitkThrow() << errMsg; } QList qResult; for (std::vector::const_iterator iter = loadInfos.begin(), iterEnd = loadInfos.end(); iter != iterEnd; ++iter) { for (const auto &elem : iter->m_Output) { qResult << elem; } } return qResult; } mitk::DataStorage::SetOfObjects::Pointer QmitkIOUtil::Load(const QStringList &paths, mitk::DataStorage &storage, QWidget *parent) { std::vector loadInfos; foreach (const QString &file, paths) { loadInfos.push_back(LoadInfo(file.toLocal8Bit().constData())); } mitk::DataStorage::SetOfObjects::Pointer nodeResult = mitk::DataStorage::SetOfObjects::New(); Impl::ReaderOptionsDialogFunctor optionsCallback; std::string errMsg = Load(loadInfos, nodeResult, &storage, &optionsCallback); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error reading files", QString::fromStdString(errMsg)); } return nodeResult; } QList QmitkIOUtil::Load(const QString &path, QWidget *parent) { QStringList paths; paths << path; return Load(paths, parent); } mitk::DataStorage::SetOfObjects::Pointer QmitkIOUtil::Load(const QString &path, mitk::DataStorage &storage, QWidget *parent) { QStringList paths; paths << path; return Load(paths, storage, parent); } QString QmitkIOUtil::Save(const mitk::BaseData *data, const QString &defaultBaseName, const QString &defaultPath, QWidget *parent, bool setPathProperty) { std::vector dataVector; dataVector.push_back(data); QStringList defaultBaseNames; defaultBaseNames.push_back(defaultBaseName); return Save(dataVector, defaultBaseNames, defaultPath, parent, setPathProperty).back(); } QStringList QmitkIOUtil::Save(const std::vector &data, const QStringList &defaultBaseNames, const QString &defaultPath, QWidget *parent, bool setPathProperty) { QStringList fileNames; QString currentPath = defaultPath; std::vector saveInfos; int counter = 0; for (std::vector::const_iterator dataIter = data.begin(), dataIterEnd = data.end(); dataIter != dataIterEnd; ++dataIter, ++counter) { SaveInfo saveInfo(*dataIter, mitk::MimeType(), std::string()); SaveFilter filters(saveInfo); // If there is only the "__all__" filter string, it means there is no writer for this base data if (filters.Size() < 2) { QMessageBox::warning( parent, "Saving not possible", QString("No writer available for type \"%1\"").arg(QString::fromStdString((*dataIter)->GetNameOfClass()))); continue; } // Construct a default path and file name QString filterString = filters.ToString(); QString selectedFilter = filters.GetDefaultFilter(); QString fileName = currentPath; QString dialogTitle = "Save " + QString::fromStdString((*dataIter)->GetNameOfClass()); if (counter < defaultBaseNames.size()) { dialogTitle += " \"" + defaultBaseNames[counter] + "\""; fileName += QDir::separator() + defaultBaseNames[counter]; // We do not append an extension to the file name by default. The extension // is chosen by the user by either selecting a filter or writing the // extension in the file name himself (in the file save dialog). /* QString defaultExt = filters.GetDefaultExtension(); if (!defaultExt.isEmpty()) { fileName += "." + defaultExt; } */ } // Ask the user for a file name QString nextName = QFileDialog::getSaveFileName(parent, dialogTitle, fileName, filterString, &selectedFilter); if (Impl::IsIllegalFilename(nextName)) { QMessageBox::warning( parent, "Saving not possible", QString("File \"%2\" contains invalid characters.\n\nPlease avoid any of \"%1\"") .arg(Impl::s_InvalidFilenameCharacters.split("", QString::SkipEmptyParts).join(" ")) .arg(nextName)); continue; } if (nextName.isEmpty()) { // We stop asking for further file names, but we still save the // data where the user already confirmed the save dialog. break; } fileName = nextName; std::string stdFileName = fileName.toLocal8Bit().constData(); QFileInfo fileInfo(fileName); currentPath = fileInfo.absolutePath(); QString suffix = fileInfo.completeSuffix(); mitk::MimeType filterMimeType = filters.GetMimeTypeForFilter(selectedFilter); mitk::MimeType selectedMimeType; if (fileInfo.exists() && !fileInfo.isFile()) { QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName)); continue; } // Theoretically, the user could have entered an extension that does not match the selected filter // The extension then has prioritry over the filter // Check if one of the available mime-types match the filename std::vector filterMimeTypes = filters.GetMimeTypes(); for (std::vector::const_iterator mimeTypeIter = filterMimeTypes.begin(), mimeTypeIterEnd = filterMimeTypes.end(); mimeTypeIter != mimeTypeIterEnd; ++mimeTypeIter) { if (mimeTypeIter->MatchesExtension(stdFileName)) { selectedMimeType = *mimeTypeIter; break; } } if (!selectedMimeType.IsValid()) { // The file name either does not contain an extension or the // extension is unknown. // If the file already exists, we stop here because we are unable // to (over)write the file without adding a custom suffix. If the file // does not exist, we add the default extension from the currently // selected filter. If the "All" filter was selected, we only add the // default extensions if the file name itself does not already contain // an extension. if (!fileInfo.exists()) { if (filterMimeType == SaveFilter::ALL_MIMETYPE()) { if (suffix.isEmpty()) { // Use the highest ranked mime-type from the list selectedMimeType = filters.GetDefaultMimeType(); } } else { selectedMimeType = filterMimeType; } if (selectedMimeType.IsValid()) { suffix = QString::fromStdString(selectedMimeType.GetExtensions().front()); fileName += "." + suffix; stdFileName = fileName.toLocal8Bit().constData(); // We changed the file name (added a suffix) so ask in case // the file aready exists. fileInfo = QFileInfo(fileName); if (fileInfo.exists()) { if (!fileInfo.isFile()) { QMessageBox::warning( parent, "Saving not possible", QString("The path \"%1\" is not a file").arg(fileName)); continue; } if (QMessageBox::question( parent, "Replace File", QString("A file named \"%1\" already exists. Do you want to replace it?").arg(fileName)) == QMessageBox::No) { continue; } } } } } if (!selectedMimeType.IsValid()) { // The extension/filename is not valid (no mime-type found), bail out QMessageBox::warning( parent, "Saving not possible", QString("No mime-type available which can handle \"%1\".").arg(fileName)); continue; } if (!QFileInfo(fileInfo.absolutePath()).isWritable()) { QMessageBox::warning(parent, "Saving not possible", QString("The path \"%1\" is not writable").arg(fileName)); continue; } fileNames.push_back(fileName); saveInfo.m_Path = stdFileName; saveInfo.m_MimeType = selectedMimeType; // pre-select the best writer for the chosen mime-type saveInfo.m_WriterSelector.Select(selectedMimeType.GetName()); saveInfos.push_back(saveInfo); } if (!saveInfos.empty()) { Impl::WriterOptionsDialogFunctor optionsCallback; std::string errMsg = Save(saveInfos, &optionsCallback, setPathProperty); if (!errMsg.empty()) { QMessageBox::warning(parent, "Error writing files", QString::fromStdString(errMsg)); mitkThrow() << errMsg; } } return fileNames; } void QmitkIOUtil::SaveBaseDataWithDialog(mitk::BaseData *data, std::string fileName, QWidget * /*parent*/) { Save(data, fileName); } void QmitkIOUtil::SaveSurfaceWithDialog(mitk::Surface::Pointer surface, std::string fileName, QWidget * /*parent*/) { Save(surface, fileName); } void QmitkIOUtil::SaveImageWithDialog(mitk::Image::Pointer image, std::string fileName, QWidget * /*parent*/) { Save(image, fileName); } void QmitkIOUtil::SavePointSetWithDialog(mitk::PointSet::Pointer pointset, std::string fileName, QWidget * /*parent*/) { Save(pointset, fileName); } struct QmitkIOUtil::SaveFilter::Impl { Impl(const mitk::IOUtil::SaveInfo &saveInfo) : m_SaveInfo(saveInfo) { // Add an artifical filter for "All" m_MimeTypes.push_back(ALL_MIMETYPE()); m_FilterStrings.push_back("All (*.*)"); // Get all writers and their mime types for the given base data type // (this is sorted already) std::vector mimeTypes = saveInfo.m_WriterSelector.GetMimeTypes(); for (std::vector::const_reverse_iterator iter = mimeTypes.rbegin(), iterEnd = mimeTypes.rend(); iter != iterEnd; ++iter) { QList filterExtensions; mitk::MimeType mimeType = *iter; std::vector extensions = mimeType.GetExtensions(); for (auto &extension : extensions) { filterExtensions << QString::fromStdString(extension); } if (m_DefaultExtension.isEmpty()) { m_DefaultExtension = QString::fromStdString(extensions.front()); } QString filter = QString::fromStdString(mimeType.GetComment()) + " ("; foreach (const QString &extension, filterExtensions) { filter += "*." + extension + " "; } filter = filter.replace(filter.size() - 1, 1, ')'); m_MimeTypes.push_back(mimeType); m_FilterStrings.push_back(filter); } } const mitk::IOUtil::SaveInfo m_SaveInfo; std::vector m_MimeTypes; QStringList m_FilterStrings; QString m_DefaultExtension; }; mitk::MimeType QmitkIOUtil::SaveFilter::ALL_MIMETYPE() { static mitk::CustomMimeType allMimeType(std::string("__all__")); return mitk::MimeType(allMimeType, -1, -1); } QmitkIOUtil::SaveFilter::SaveFilter(const QmitkIOUtil::SaveFilter &other) : d(new Impl(*other.d)) { } QmitkIOUtil::SaveFilter::SaveFilter(const SaveInfo &saveInfo) : d(new Impl(saveInfo)) { } QmitkIOUtil::SaveFilter &QmitkIOUtil::SaveFilter::operator=(const QmitkIOUtil::SaveFilter &other) { d.reset(new Impl(*other.d)); return *this; } std::vector QmitkIOUtil::SaveFilter::GetMimeTypes() const { return d->m_MimeTypes; } QString QmitkIOUtil::SaveFilter::GetFilterForMimeType(const std::string &mimeType) const { std::vector::const_iterator iter = std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType)); if (iter == d->m_MimeTypes.end()) { return QString(); } int index = static_cast(iter - d->m_MimeTypes.begin()); if (index < 0 || index >= d->m_FilterStrings.size()) { return QString(); } return d->m_FilterStrings[index]; } mitk::MimeType QmitkIOUtil::SaveFilter::GetMimeTypeForFilter(const QString &filter) const { int index = d->m_FilterStrings.indexOf(filter); if (index < 0) { return mitk::MimeType(); } return d->m_MimeTypes[index]; } QString QmitkIOUtil::SaveFilter::GetDefaultFilter() const { if (d->m_FilterStrings.size() > 1) { return d->m_FilterStrings.at(1); } else if (d->m_FilterStrings.size() > 0) { return d->m_FilterStrings.front(); } return QString(); } QString QmitkIOUtil::SaveFilter::GetDefaultExtension() const { return d->m_DefaultExtension; } mitk::MimeType QmitkIOUtil::SaveFilter::GetDefaultMimeType() const { if (d->m_MimeTypes.size() > 1) { return d->m_MimeTypes.at(1); } else if (d->m_MimeTypes.size() > 0) { return d->m_MimeTypes.front(); } return mitk::MimeType(); } QString QmitkIOUtil::SaveFilter::ToString() const { return d->m_FilterStrings.join(";;"); } int QmitkIOUtil::SaveFilter::Size() const { return d->m_FilterStrings.size(); } bool QmitkIOUtil::SaveFilter::IsEmpty() const { return d->m_FilterStrings.isEmpty(); } bool QmitkIOUtil::SaveFilter::ContainsMimeType(const std::string &mimeType) { return std::find_if(d->m_MimeTypes.begin(), d->m_MimeTypes.end(), MimeTypeComparison(mimeType)) != d->m_MimeTypes.end(); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp index 04045da5b0..96b441e343 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutManager.cpp @@ -1,541 +1,541 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiWidgetLayoutManager.h" #include #include #include #include // qt #include #include QmitkMultiWidgetLayoutManager::QmitkMultiWidgetLayoutManager(QmitkAbstractMultiWidget* multiwidget) : QObject(multiwidget) , m_MultiWidget(multiwidget) , m_CurrentRenderWindowWidget(nullptr) { // nothing here } void QmitkMultiWidgetLayoutManager::SetLayoutDesign(LayoutDesign layoutDesign) { if (nullptr == m_MultiWidget) { return; } // retrieve the render window name from the sending render window auto renderWindow = dynamic_cast(QObject::sender()); m_CurrentRenderWindowWidget = m_MultiWidget->GetRenderWindowWidget(renderWindow).get(); switch (layoutDesign) { case LayoutDesign::DEFAULT: { SetDefaultLayout(); break; } case LayoutDesign::ALL_2D_TOP_3D_BOTTOM: { SetAll2DTop3DBottomLayout(); break; } case LayoutDesign::ALL_2D_LEFT_3D_RIGHT: { SetAll2DLeft3DRightLayout(); break; } case LayoutDesign::ONE_BIG: { SetOneBigLayout(); break; } case LayoutDesign::ONLY_2D_HORIZONTAL: { SetOnly2DHorizontalLayout(); break; } case LayoutDesign::ONLY_2D_VERTICAL: { SetOnly2DVerticalLayout(); break; } case LayoutDesign::ONE_TOP_3D_BOTTOM: { SetOneTop3DBottomLayout(); break; } case LayoutDesign::ONE_LEFT_3D_RIGHT: { SetOneLeft3DRightLayout(); break; } case LayoutDesign::ALL_HORIZONTAL: { SetAllHorizontalLayout(); break; } case LayoutDesign::ALL_VERTICAL: { SetAllVerticalLayout(); break; } case LayoutDesign::REMOVE_ONE: { RemoveOneLayout(); break; } case LayoutDesign::NONE: { break; } }; } void QmitkMultiWidgetLayoutManager::SetCurrentRenderWindowWidget(QmitkRenderWindowWidget* renderWindowWidget) { m_CurrentRenderWindowWidget = renderWindowWidget; } void QmitkMultiWidgetLayoutManager::SetDefaultLayout() { - MITK_INFO << "Set default layout" << std::endl; + MITK_DEBUG << "Set default layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSizeRow; for (int row = 0; row < m_MultiWidget->GetRowCount(); ++row) { splitterSizeRow.push_back(1000); QList splitterSizeColumn; auto splitter = new QSplitter(mainSplit); for (int column = 0; column < m_MultiWidget->GetColumnCount(); ++column) { splitterSizeColumn.push_back(1000); auto renderWindowWidget = m_MultiWidget->GetRenderWindowWidget(row, column); splitter->addWidget(renderWindowWidget.get()); renderWindowWidget->show(); } splitter->setSizes(splitterSizeColumn); } mainSplit->setSizes(splitterSizeRow); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::DEFAULT); } } void QmitkMultiWidgetLayoutManager::SetAll2DTop3DBottomLayout() { - MITK_INFO << "Set all 2D top and 3D bottom layout" << std::endl; + MITK_DEBUG << "Set all 2D top and 3D bottom layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); auto subSplit2D = new QSplitter(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { subSplit2D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit2D->setSizes(splitterSize); auto subSplit3D = new QSplitter(mainSplit); splitterSize.clear(); auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(600); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_2D_TOP_3D_BOTTOM); } } void QmitkMultiWidgetLayoutManager::SetAll2DLeft3DRightLayout() { - MITK_INFO << "Set all 2D left and 3D right layout" << std::endl; + MITK_DEBUG << "Set all 2D left and 3D right layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); auto subSplit2D = new QSplitter(Qt::Vertical, mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { subSplit2D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit2D->setSizes(splitterSize); auto subSplit3D = new QSplitter(mainSplit); splitterSize.clear(); auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(600); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_2D_LEFT_3D_RIGHT); } } void QmitkMultiWidgetLayoutManager::SetOneBigLayout() { - MITK_INFO << "Set single 2D layout" << std::endl; + MITK_DEBUG << "Set single 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_BIG); } } void QmitkMultiWidgetLayoutManager::SetOnly2DHorizontalLayout() { - MITK_INFO << "Set only 2D layout" << std::endl; + MITK_DEBUG << "Set only 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONLY_2D_HORIZONTAL); } } void QmitkMultiWidgetLayoutManager::SetOnly2DVerticalLayout() { - MITK_INFO << "Set only 2D layout" << std::endl; + MITK_DEBUG << "Set only 2D layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto all2DRenderWindowWidgets = m_MultiWidget->Get2DRenderWindowWidgets(); for (const auto& renderWindowWidget : all2DRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONLY_2D_VERTICAL); } } void QmitkMultiWidgetLayoutManager::SetOneTop3DBottomLayout() { - MITK_INFO << "Set one top and all 3D bottom layout" << std::endl; + MITK_DEBUG << "Set one top and all 3D bottom layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); auto subSplit3D = new QSplitter(mainSplit); QList splitterSize; auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(1000); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_TOP_3D_BOTTOM); } } void QmitkMultiWidgetLayoutManager::SetOneLeft3DRightLayout() { - MITK_INFO << "Set one left and all 3D right layout" << std::endl; + MITK_DEBUG << "Set one left and all 3D right layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); mainSplit->addWidget(m_CurrentRenderWindowWidget); m_CurrentRenderWindowWidget->show(); auto subSplit3D = new QSplitter(Qt::Vertical, mainSplit); QList splitterSize; auto all3DRenderWindowWidgets = m_MultiWidget->Get3DRenderWindowWidgets(); for (const auto& renderWindowWidget : all3DRenderWindowWidgets) { subSplit3D->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } subSplit3D->setSizes(splitterSize); // set size for main splitter splitterSize.clear(); splitterSize.push_back(1000); splitterSize.push_back(1000); mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ONE_LEFT_3D_RIGHT); } } void QmitkMultiWidgetLayoutManager::SetAllHorizontalLayout() { - MITK_INFO << "Set default layout" << std::endl; + MITK_DEBUG << "Set all horizontal layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto allRenderWindowWidgets = m_MultiWidget->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : allRenderWindowWidgets) { if (nullptr != renderWindowWidget.second) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } } // set size for main splitter mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_HORIZONTAL); } } void QmitkMultiWidgetLayoutManager::SetAllVerticalLayout() { - MITK_INFO << "Set all vertical" << std::endl; + MITK_DEBUG << "Set all vertical layout"; m_MultiWidget->ActivateMenuWidget(false); delete m_MultiWidget->layout(); auto hBoxLayout = new QHBoxLayout(m_MultiWidget); hBoxLayout->setContentsMargins(0, 0, 0, 0); m_MultiWidget->setLayout(hBoxLayout); hBoxLayout->setMargin(0); auto mainSplit = new QSplitter(Qt::Vertical, m_MultiWidget); hBoxLayout->addWidget(mainSplit); QList splitterSize; auto allRenderWindowWidgets = m_MultiWidget->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : allRenderWindowWidgets) { mainSplit->addWidget(renderWindowWidget.second.get()); renderWindowWidget.second->show(); splitterSize.push_back(1000); } // set size for splitter mainSplit->setSizes(splitterSize); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::ALL_VERTICAL); } } void QmitkMultiWidgetLayoutManager::RemoveOneLayout() { - MITK_INFO << "Remove single render window" << std::endl; + MITK_DEBUG << "Remove single render window"; m_MultiWidget->ActivateMenuWidget(false); m_CurrentRenderWindowWidget->hide(); m_MultiWidget->ActivateMenuWidget(true); auto allRenderWindows = m_MultiWidget->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { renderWindow->UpdateLayoutDesignList(LayoutDesign::NONE); } } diff --git a/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp b/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp index ab142499b0..62f41a8e2b 100644 --- a/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp +++ b/Modules/QtWidgets/src/QmitkNodeDescriptorManager.cpp @@ -1,183 +1,218 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include +#include QmitkNodeDescriptorManager* QmitkNodeDescriptorManager::GetInstance() { static QmitkNodeDescriptorManager instance; return &instance; } void QmitkNodeDescriptorManager::Initialize() { auto isImage = mitk::NodePredicateDataType::New("Image"); - AddDescriptor(new QmitkNodeDescriptor(tr("Image"), QString(":/Qmitk/Images_48.png"), isImage, this)); + AddDescriptor(new QmitkNodeDescriptor("Image", ":/Qmitk/Images_48.png", isImage, this)); auto isMultiComponentImage = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateProperty::New("Image.Displayed Component")); - AddDescriptor(new QmitkNodeDescriptor(tr("MultiComponentImage"), QString(": / Qmitk / Images_48.png"), isMultiComponentImage, this)); + AddDescriptor(new QmitkNodeDescriptor("MultiComponentImage", ":/Qmitk/Images_48.png", isMultiComponentImage, this)); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isBinaryImage = mitk::NodePredicateAnd::New(isBinary, isImage); - AddDescriptor(new QmitkNodeDescriptor(tr("ImageMask"), QString(":/Qmitk/Binaerbilder_48.png"), isBinaryImage, this)); + AddDescriptor(new QmitkNodeDescriptor("ImageMask", ":/Qmitk/Binaerbilder_48.png", isBinaryImage, this)); auto isLabelSetImage = mitk::NodePredicateDataType::New("LabelSetImage"); - AddDescriptor(new QmitkNodeDescriptor(tr("LabelSetImage"), QString(":/Qmitk/LabelSetImage_48.png"), isLabelSetImage, this)); + AddDescriptor(new QmitkNodeDescriptor("LabelSetImage", ":/Qmitk/LabelSetImage_48.png", isLabelSetImage, this)); auto segmentationTaskListIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/SegmentationTaskListIcon.svg")); auto isSegmentationTaskList = mitk::NodePredicateDataType::New("SegmentationTaskList"); AddDescriptor(new QmitkNodeDescriptor("SegmentationTaskList", segmentationTaskListIcon, isSegmentationTaskList, this)); auto roiIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/ROIIcon.svg")); auto isROI = mitk::NodePredicateDataType::New("ROI"); AddDescriptor(new QmitkNodeDescriptor("ROI", roiIcon, isROI, this)); auto geometryDataIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/GeometryDataIcon.svg")); auto isGeometryData = mitk::NodePredicateDataType::New("GeometryData"); AddDescriptor(new QmitkNodeDescriptor("GeometryData", geometryDataIcon, isGeometryData, this)); auto isPointSet = mitk::NodePredicateDataType::New("PointSet"); - AddDescriptor(new QmitkNodeDescriptor(tr("PointSet"), QString(":/Qmitk/PointSet_48.png"), isPointSet, this)); + AddDescriptor(new QmitkNodeDescriptor("PointSet", ":/Qmitk/PointSet_48.png", isPointSet, this)); auto isSurface = mitk::NodePredicateDataType::New("Surface"); - AddDescriptor(new QmitkNodeDescriptor(tr("Surface"), QString(":/Qmitk/Surface_48.png"), isSurface, this)); + AddDescriptor(new QmitkNodeDescriptor("Surface", ":/Qmitk/Surface_48.png", isSurface, this)); auto isNotBinary = mitk::NodePredicateNot::New(isBinary); auto isNoneBinaryImage = mitk::NodePredicateAnd::New(isImage, isNotBinary); - AddDescriptor(new QmitkNodeDescriptor(tr("NoneBinaryImage"), QString(":/Qmitk/Images_48.png"), isNoneBinaryImage, this)); + AddDescriptor(new QmitkNodeDescriptor("NoneBinaryImage", ":/Qmitk/Images_48.png", isNoneBinaryImage, this)); + + auto isPlanarLine = mitk::NodePredicateDataType::New("PlanarLine"); + AddDescriptor(new QmitkNodeDescriptor("PlanarLine", ":/Qmitk/PlanarLine_48.png", isPlanarLine, this)); + + auto isPlanarCircle = mitk::NodePredicateDataType::New("PlanarCircle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarCircle", ":/Qmitk/PlanarCircle_48.png", isPlanarCircle, this)); + + auto isPlanarEllipse = mitk::NodePredicateDataType::New("PlanarEllipse"); + AddDescriptor(new QmitkNodeDescriptor("PlanarEllipse", ":/Qmitk/PlanarEllipse_48.png", isPlanarEllipse, this)); + + auto isPlanarAngle = mitk::NodePredicateDataType::New("PlanarAngle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarAngle", ":/Qmitk/PlanarAngle_48.png", isPlanarAngle, this)); + + auto isPlanarFourPointAngle = mitk::NodePredicateDataType::New("PlanarFourPointAngle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarFourPointAngle", ":/Qmitk/PlanarFourPointAngle_48.png", isPlanarFourPointAngle, this)); + + auto isPlanarRectangle = mitk::NodePredicateDataType::New("PlanarRectangle"); + AddDescriptor(new QmitkNodeDescriptor("PlanarRectangle", ":/Qmitk/PlanarRectangle_48.png", isPlanarRectangle, this)); + + auto isPlanarPolygon = mitk::NodePredicateDataType::New("PlanarPolygon"); + AddDescriptor(new QmitkNodeDescriptor("PlanarPolygon", ":/Qmitk/PlanarPolygon_48.png", isPlanarPolygon, this)); + + auto isNotClosedPolygon = mitk::NodePredicateProperty::New("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); + auto isPlanarPath = mitk::NodePredicateAnd::New(isNotClosedPolygon, isPlanarPolygon); + AddDescriptor(new QmitkNodeDescriptor("PlanarPath", ":/Qmitk/PlanarPath_48.png", isPlanarPath, this)); + + auto isPlanarDoubleEllipse = mitk::NodePredicateDataType::New("PlanarDoubleEllipse"); + AddDescriptor(new QmitkNodeDescriptor("PlanarDoubleEllipse", ":/Qmitk/PlanarDoubleEllipse_48.png", isPlanarDoubleEllipse, this)); + + auto isPlanarBezierCurve = mitk::NodePredicateDataType::New("PlanarBezierCurve"); + AddDescriptor(new QmitkNodeDescriptor("PlanarBezierCurve", ":/Qmitk/PlanarBezierCurve_48.png", isPlanarBezierCurve, this)); + + auto isPlanarSubdivisionPolygon = mitk::NodePredicateDataType::New("PlanarSubdivisionPolygon"); + AddDescriptor(new QmitkNodeDescriptor("PlanarSubdivisionPolygon", ":/Qmitk/PlanarSubdivisionPolygon_48.png", isPlanarSubdivisionPolygon, this)); } void QmitkNodeDescriptorManager::AddDescriptor(QmitkNodeDescriptor* descriptor) { descriptor->setParent(this); m_NodeDescriptors.push_back(descriptor); } void QmitkNodeDescriptorManager::RemoveDescriptor(QmitkNodeDescriptor* descriptor) { int index = m_NodeDescriptors.indexOf(descriptor); if (index != -1) { m_NodeDescriptors.removeAt(index); descriptor->setParent(nullptr); delete descriptor; } } QmitkNodeDescriptor* QmitkNodeDescriptorManager::GetDescriptor(const mitk::DataNode* node) const { QmitkNodeDescriptor* descriptor = m_UnknownDataNodeDescriptor; for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) descriptor = *it; } return descriptor; } QmitkNodeDescriptor* QmitkNodeDescriptorManager::GetDescriptor(const QString& className) const { QmitkNodeDescriptor* descriptor = nullptr; if (className == "Unknown") { return m_UnknownDataNodeDescriptor; } else { for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->GetNameOfClass() == className) descriptor = *it; } } return descriptor; } QList QmitkNodeDescriptorManager::GetActions(const mitk::DataNode* node) const { QList actions = m_UnknownDataNodeDescriptor->GetBatchActions(); actions.append(m_UnknownDataNodeDescriptor->GetActions()); QmitkNodeDescriptor* lastDescriptor = m_UnknownDataNodeDescriptor; for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) { actions.append(lastDescriptor->GetSeparator()); lastDescriptor = *it; actions.append(lastDescriptor->GetBatchActions()); actions.append(lastDescriptor->GetActions()); } } return actions; } QList QmitkNodeDescriptorManager::GetActions(const QList& nodes) const { QList actions = m_UnknownDataNodeDescriptor->GetBatchActions(); QmitkNodeDescriptor* lastDescriptor = m_UnknownDataNodeDescriptor; // find all descriptors for the nodes (unique) QSet nodeDescriptors; for (const auto& node : nodes) { for (QList::const_iterator it = m_NodeDescriptors.begin(); it != m_NodeDescriptors.end(); ++it) { if ((*it)->CheckNode(node)) { nodeDescriptors.insert(*it); } } } // add all actions for the found descriptors for (const auto& nodeDescriptor : nodeDescriptors) { actions.append(lastDescriptor->GetSeparator()); lastDescriptor = nodeDescriptor; actions.append(lastDescriptor->GetBatchActions()); } return actions; } QmitkNodeDescriptorManager::QmitkNodeDescriptorManager() : m_UnknownDataNodeDescriptor(new QmitkNodeDescriptor("Unknown", QString(":/Qmitk/DataTypeUnknown_48.png"), nullptr, this)) { Initialize(); } QmitkNodeDescriptorManager::~QmitkNodeDescriptorManager() { // delete m_UnknownDataNodeDescriptor; // qDeleteAll(m_NodeDescriptors); } QmitkNodeDescriptor *QmitkNodeDescriptorManager::GetUnknownDataNodeDescriptor() const { return m_UnknownDataNodeDescriptor; } diff --git a/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc b/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc index 2e92201b32..32f836a0e5 100644 --- a/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc +++ b/Modules/QtWidgetsExt/resource/QtWidgetsExt.qrc @@ -1,41 +1,30 @@ btnAddPointSet.png eraser.svg btnCube.xpm btnCylinder.xpm arrow-down.svg btnEllipsoid.xpm folder-open.svg btnMoveDown.png btnMoveUp.png btnPyramid.xpm btnRemovePoint.png save.svg btnSetPoints.png plus.svg plus-xyz.svg btnSetSeedPoint.xpm btnSwapSets.png arrow-up.svg cross.png icon_seedpoint.png defaultWatermarkSmall.png logo_mint-medical.png ModuleView.png QmitkStandardViewsDialogBar.xpm - PlanarAngle_48.png - PlanarBezierCurve_48.png - PlanarCircle_48.png - PlanarDoubleEllipse_48.png - PlanarEllipse_48.png - PlanarFourPointAngle_48.png - PlanarLine_48.png - PlanarPath_48.png - PlanarPolygon_48.png - PlanarRectangle_48.png - PlanarSubdivisionPolygon_48.png play.xpm stop.xpm diff --git a/Modules/REST/CMakeLists.txt b/Modules/REST/CMakeLists.txt index 09e301a39e..61b6633c19 100644 --- a/Modules/REST/CMakeLists.txt +++ b/Modules/REST/CMakeLists.txt @@ -1,14 +1,26 @@ set(boost_depends "Boost|date_time+regex+system") if(UNIX) set(boost_depends "${boost_depends}+atomic+chrono+filesystem+random+thread") endif() mitk_create_module( DEPENDS MitkCore PACKAGE_DEPENDS PUBLIC cpprestsdk OpenSSL|SSL ${boost_depends} ) if(TARGET ${MODULE_TARGET}) + if(MSVC) + #[[ Compiler warnings/errors because of C++ REST SDK's http_msg.h on Visual Studio 2022 version 17.8: + + 'stdext::checked_array_iterator': warning STL4043: stdext::checked_array_iterator, + stdext::unchecked_array_iterator, and related factory functions are non-Standard extensions + and will be removed in the future. std::span (since C++20) and gsl::span can be used instead. + You can define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING or _SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS + to suppress this warning. + ]] + target_compile_definitions(${MODULE_TARGET} PUBLIC _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) + endif() + add_subdirectory(test) endif() diff --git a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h index 19eca9289d..0ac6ef7e12 100644 --- a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h +++ b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h @@ -1,151 +1,149 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkVectorPropertySerializer_h #define mitkVectorPropertySerializer_h #include "mitkBasePropertySerializer.h" #include "mitkVectorProperty.h" #include #include namespace mitk { /** \brief Serializes a VectorProperty Serializes an instance of VectorProperty into a XML structure like \verbatim \endverbatim This class is implemented as a template and makes use of std::stringstream for necessary conversions of specific data types to and from string. For numeric types, the class adds a precision token to stringstream that should usually suffice. */ template class MITKSCENESERIALIZATIONBASE_EXPORT VectorPropertySerializer : public BasePropertySerializer { public: // Expand manually most of mitkClassMacro: // mitkClassMacro(VectorProperty, mitk::BaseProperty); // This manual expansion is done to override explicitely // the GetNameOfClass methods typedef VectorProperty PropertyType; typedef VectorPropertySerializer Self; typedef BasePropertySerializer SuperClass; typedef itk::SmartPointer Pointer; typedef itk::SmartPointer ConstPointer; std::vector GetClassHierarchy() const override { return mitk::GetClassHierarchy(); } // This function must return different // strings in function of the template parameter! // Serialization depends on this feature. static const char *GetStaticNameOfClass() { // concatenate a prefix dependent on the template type and our own classname static std::string nameOfClass = std::string(VectorPropertyDataType::prefix()) + "VectorPropertySerializer"; return nameOfClass.c_str(); } const char *GetNameOfClass() const override { return this->GetStaticNameOfClass(); } itkFactorylessNewMacro(Self); itkCloneMacro(Self); //! Build an XML version of this property tinyxml2::XMLElement* Serialize(tinyxml2::XMLDocument& doc) override { auto *listElement = doc.NewElement("Values"); if (const PropertyType *prop = dynamic_cast(m_Property.GetPointer())) { typename PropertyType::VectorType elements = prop->GetValue(); unsigned int index(0); for (auto listEntry : elements) { std::stringstream indexS; indexS << index++; auto *entryElement = doc.NewElement("Value"); entryElement->SetAttribute("idx", indexS.str().c_str()); entryElement->SetAttribute("value", boost::lexical_cast(listEntry).c_str()); listElement->InsertEndChild(entryElement); } return listElement; } else { return nullptr; } } //! Construct a property from an XML serialization BaseProperty::Pointer Deserialize(const tinyxml2::XMLElement *listElement) override { typename PropertyType::VectorType datalist; if (listElement) { - unsigned int index(0); std::string valueString; DATATYPE value; for (auto *valueElement = listElement->FirstChildElement("Value"); valueElement; valueElement = valueElement->NextSiblingElement("Value")) { valueString = valueElement->Attribute("value"); if (valueString.empty()) { MITK_ERROR << "Missing value attribute in list"; return nullptr; } try { value = boost::lexical_cast(valueString); } catch (boost::bad_lexical_cast &e) { MITK_ERROR << "Could not parse '" << valueString << "' as number: " << e.what(); return nullptr; } datalist.push_back(value); - ++index; } typename PropertyType::Pointer property = PropertyType::New(); property->SetValue(datalist); return property.GetPointer(); } else { MITK_ERROR << "Missing tag."; } return nullptr; } }; typedef VectorPropertySerializer DoubleVectorPropertySerializer; typedef VectorPropertySerializer IntVectorPropertySerializer; } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp index 86e8a0b011..0aec6159c9 100644 --- a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp @@ -1,486 +1,484 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCorrectorAlgorithm.h" #include "mitkContourUtils.h" #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageDataItem.h" #include #include "itkCastImageFilter.h" #include "itkImageDuplicator.h" #include "itkImageRegionIterator.h" mitk::CorrectorAlgorithm::CorrectorAlgorithm() : ImageToImageFilter(), m_FillColor(1), m_EraseColor(0) { } mitk::CorrectorAlgorithm::~CorrectorAlgorithm() { } template void ConvertBackToCorrectPixelType( itk::Image *, mitk::Image::Pointer target, itk::Image::Pointer segmentationPixelTypeImage) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::CastImageFilter CastImageFilterType; typename CastImageFilterType::Pointer castImageFilter = CastImageFilterType::New(); castImageFilter->SetInput(segmentationPixelTypeImage); castImageFilter->Update(); typename OutputImageType::Pointer tempItkImage = castImageFilter->GetOutput(); tempItkImage->DisconnectPipeline(); mitk::CastToMitkImage(tempItkImage, target); } void mitk::CorrectorAlgorithm::GenerateData() { Image::Pointer inputImage = ImageToImageFilter::GetInput(0); if (inputImage.IsNull() || inputImage->GetDimension() != 2) { itkExceptionMacro("CorrectorAlgorithm needs a 2D image as input."); } if (m_Contour.IsNull()) { itkExceptionMacro("CorrectorAlgorithm needs a Contour object as input."); } // copy the input (since m_WorkingImage will be changed later) m_WorkingImage = inputImage; TimeGeometry::Pointer originalGeometry = nullptr; if (inputImage->GetTimeGeometry()) { originalGeometry = inputImage->GetTimeGeometry()->Clone(); m_WorkingImage->SetTimeGeometry(originalGeometry); } else { itkExceptionMacro("Original image does not have a 'Time sliced geometry'! Cannot copy."); } Image::Pointer temporarySlice; // Convert to DefaultSegmentationDataType (because TobiasHeimannCorrectionAlgorithm relys on that data type) { itk::Image::Pointer correctPixelTypeImage; CastToItkImage(m_WorkingImage, correctPixelTypeImage); assert(correctPixelTypeImage.IsNotNull()); // possible bug in CastToItkImage ? // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479: // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 // && aSpacing[2]>0' failed // solution here: we overwrite it with an unity matrix itk::Image::DirectionType imageDirection; imageDirection.SetIdentity(); // correctPixelTypeImage->SetDirection(imageDirection); temporarySlice = this->GetOutput(); // temporarySlice = ImportItkImage( correctPixelTypeImage ); // m_FillColor = 1; m_EraseColor = 0; ImprovedHeimannCorrectionAlgorithm(correctPixelTypeImage); // this is suboptimal, needs to be kept synchronous to DefaultSegmentationDataType if (inputImage->GetChannelDescriptor().GetPixelType().GetComponentType() == itk::IOComponentEnum::USHORT) { // the cast at the beginning did not copy the data CastToMitkImage(correctPixelTypeImage, temporarySlice); } else { // it did copy the data and cast the pixel type AccessByItk_n(m_WorkingImage, ConvertBackToCorrectPixelType, (temporarySlice, correctPixelTypeImage)); } } temporarySlice->SetTimeGeometry(originalGeometry); } template itk::Index<2> mitk::CorrectorAlgorithm::ensureIndexInImage(ScalarType i0, ScalarType i1) { itk::Index<2> toReturn; itk::Size<5> size = m_WorkingImage->GetLargestPossibleRegion().GetSize(); toReturn[0] = std::min((ScalarType)(size[0] - 1), std::max((ScalarType)0.0, i0)); toReturn[1] = std::min((ScalarType)(size[1] - 1), std::max((ScalarType)0.0, i1)); return toReturn; } bool mitk::CorrectorAlgorithm::ImprovedHeimannCorrectionAlgorithm( itk::Image::Pointer pic) { /*! Some documentation (not by the original author) TobiasHeimannCorrectionAlgorithm will be called, when the user has finished drawing a freehand line. There should be different results, depending on the line's properties: 1. Without any prior segmentation, the start point and the end point of the drawn line will be connected to a contour and the area enclosed by the contour will be marked as segmentation. 2. When the whole line is inside a segmentation, start and end point will be connected to a contour and the area of this contour will be subtracted from the segmentation. 3. When the line starts inside a segmentation and ends outside with only a single transition from segmentation to no-segmentation, nothing will happen. 4. When there are multiple transitions between inside-segmentation and outside-segmentation, the line will be divided in so called segments. Each segment is either fully inside or fully outside a segmentation. When it is inside a segmentation, its enclosed area will be subtracted from the segmentation. When the segment is outside a segmentation, its enclosed area it will be added to the segmentation. The algorithm is described in full length in Tobias Heimann's diploma thesis (MBI Technical Report 145, p. 37 - 40). */ ContourModel::Pointer projectedContour = mitk::ContourModelUtils::ProjectContourTo2DSlice(m_WorkingImage, m_Contour); if (projectedContour.IsNull() || projectedContour->GetNumberOfVertices() < 2) return false; // Read the first point of the contour auto contourIter = projectedContour->Begin(); if (contourIter == projectedContour->End()) return false; itk::Index<2> previousIndex; previousIndex = ensureIndexInImage((*contourIter)->Coordinates[0], (*contourIter)->Coordinates[1]); ++contourIter; int currentColor = (pic->GetPixel(previousIndex) == m_FillColor); TSegData currentSegment; - int countOfSegments = 1; bool firstSegment = true; auto contourEnd = projectedContour->End(); for (; contourIter != contourEnd; ++contourIter) { // Get current point itk::Index<2> currentIndex; currentIndex = ensureIndexInImage((*contourIter)->Coordinates[0] + 0.5, (*contourIter)->Coordinates[1] + 0.5); // Calculate length and slope double slopeX = currentIndex[0] - previousIndex[0]; double slopeY = currentIndex[1] - previousIndex[1]; double length = std::sqrt(slopeX * slopeX + slopeY * slopeY); double deltaX = slopeX / length; double deltaY = slopeY / length; for (double i = 0; i <= length && length > 0; i += 1) { itk::Index<2> temporaryIndex; temporaryIndex = ensureIndexInImage(previousIndex[0] + deltaX * i, previousIndex[1] + deltaY * i); if (!pic->GetLargestPossibleRegion().IsInside(temporaryIndex)) continue; if ((pic->GetPixel(temporaryIndex) == m_FillColor) != currentColor) { currentSegment.points.push_back(temporaryIndex); if (!firstSegment) { ModifySegment(currentSegment, pic); } else { firstSegment = false; } currentSegment = TSegData(); - ++countOfSegments; currentColor = (pic->GetPixel(temporaryIndex) == m_FillColor); } currentSegment.points.push_back(temporaryIndex); } previousIndex = currentIndex; } return true; } void mitk::CorrectorAlgorithm::ColorSegment( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); int color = 0; if (colorMode) color = m_EraseColor; else color = m_FillColor; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); for (; indexIterator != indexEnd; ++indexIterator) { pic->SetPixel(*indexIterator, color); } } itk::Image::Pointer mitk::CorrectorAlgorithm::CloneImage( itk::Image::Pointer pic) { typedef itk::Image ItkImageType; typedef itk::ImageDuplicator DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(pic); duplicator->Update(); return duplicator->GetOutput(); } itk::Index<2> mitk::CorrectorAlgorithm::GetFirstPoint( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); itk::Index<2> index; for (; indexIterator != indexEnd; ++indexIterator) { for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); if ((pic->GetPixel(index) == m_FillColor) != colorMode) { return index; } } } } mitkThrow() << "No Starting point is found next to the curve."; } std::vector> mitk::CorrectorAlgorithm::FindSeedPoints( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; std::vector> seedPoints; try { itk::Index<2> firstPoint = GetFirstPoint(segment, pic); seedPoints.push_back(firstPoint); } catch (const mitk::Exception&) { return seedPoints; } if (segment.points.size() < 4) return seedPoints; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); ItkImagePointerType listOfPoints = CloneImage(pic); listOfPoints->FillBuffer(0); listOfPoints->SetPixel(seedPoints[0], 1); for (; indexIterator != indexEnd; ++indexIterator) { listOfPoints->SetPixel(*indexIterator, 2); } indexIterator = segment.points.begin(); indexIterator++; indexIterator++; indexEnd--; indexEnd--; for (; indexIterator != indexEnd; ++indexIterator) { bool pointFound = true; while (pointFound) { pointFound = false; itk::Index<2> index; itk::Index<2> index2; for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); index2 = index; if (listOfPoints->GetPixel(index2) > 0) continue; index[0] = index[0] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] - 1; index[1] = index[1] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[1] = index[1] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } } } } } return seedPoints; } int mitk::CorrectorAlgorithm::FillRegion( const std::vector> &seedPoints, itk::Image::Pointer pic) { int numberOfPixel = 0; int mode = (pic->GetPixel(seedPoints[0]) == m_FillColor); int drawColor = m_FillColor; if (mode) { drawColor = m_EraseColor; } std::vector> workPoints; workPoints = seedPoints; // workPoints.push_back(seedPoints[0]); while (workPoints.size() > 0) { itk::Index<2> currentIndex = workPoints.back(); workPoints.pop_back(); if ((pic->GetPixel(currentIndex) == m_FillColor) == mode) ++numberOfPixel; pic->SetPixel(currentIndex, drawColor); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] + 2, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1] - 1); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0], currentIndex[1] + 2); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); } return numberOfPixel; } void mitk::CorrectorAlgorithm::OverwriteImage( itk::Image::Pointer source, itk::Image::Pointer target) { typedef itk::Image ItkImageType; typedef itk::ImageRegionIterator ImageIteratorType; ImageIteratorType sourceIter(source, source->GetLargestPossibleRegion()); ImageIteratorType targetIter(target, target->GetLargestPossibleRegion()); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } bool mitk::CorrectorAlgorithm::ModifySegment(const TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; ItkImagePointerType firstSideImage = CloneImage(pic); ColorSegment(segment, firstSideImage); ItkImagePointerType secondSideImage = CloneImage(firstSideImage); std::vector> seedPoints = FindSeedPoints(segment, firstSideImage); if (seedPoints.size() < 1) return false; int firstSidePixel = FillRegion(seedPoints, firstSideImage); std::vector> secondSeedPoints = FindSeedPoints(segment, firstSideImage); if (secondSeedPoints.size() < 1) return false; int secondSidePixel = FillRegion(secondSeedPoints, secondSideImage); if (firstSidePixel < secondSidePixel) { OverwriteImage(firstSideImage, pic); } else { OverwriteImage(secondSideImage, pic); } return true; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 7016cc6971..96f5bf2fbd 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,622 +1,622 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(toolButtonClicked(int))); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; - QList::iterator it; + for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { layout->addWidget(gui); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); if (selectedToolButton) { selectedToolButton->setChecked(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::setEnabled(bool enable) { if (QWidget::isEnabled() == enable) return; QWidget::setEnabled(enable); if (enable) { m_ToolManager->RegisterClient(); auto id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } void QmitkToolSelectionBox::UpdateButtonsEnabledState() { auto buttons = m_ToolButtonGroup->buttons(); const auto refDataNode = m_ToolManager->GetReferenceData(0); const mitk::BaseData* refData = nullptr; if (nullptr != refDataNode) { refData = refDataNode->GetData(); } const auto workingDataNode = m_ToolManager->GetWorkingData(0); const mitk::BaseData* workingData = nullptr; if (nullptr != workingDataNode) { workingData = workingDataNode->GetData(); } for (const auto& button : qAsConst(buttons)) { const auto buttonID = m_ToolButtonGroup->id(button); const auto toolID = m_ToolIDForButtonID[buttonID]; const auto tool = m_ToolManager->GetToolById(toolID); button->setEnabled(tool->CanHandle(refData, workingData)); } } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); delete btn; } mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { button->setText(label); // a label button->setToolTip(tooltip); QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); this->UpdateButtonsEnabledState(); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegExp("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } diff --git a/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp b/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp index 16952fd4a7..f3d4924082 100644 --- a/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkCreateDistanceImageFromSurfaceFilter.cpp @@ -1,617 +1,614 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkImageCast.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkDoubleArray.h" #include "vtkPolyData.h" #include "vtkSmartPointer.h" #include "itkImageRegionIteratorWithIndex.h" #include "itkNeighborhoodIterator.h" #include void mitk::CreateDistanceImageFromSurfaceFilter::CreateEmptyDistanceImage() { // Determine the bounds of the input points in index- and world-coordinates DistanceImageType::PointType minPointInWorldCoordinates, maxPointInWorldCoordinates; DistanceImageType::IndexType minPointInIndexCoordinates, maxPointInIndexCoordinates; DetermineBounds( minPointInWorldCoordinates, maxPointInWorldCoordinates, minPointInIndexCoordinates, maxPointInIndexCoordinates); // Calculate the extent of the region that contains all given points in MM. // To do this, we take the difference between the maximal and minimal // index-coordinates (must not be less than 1) and multiply it with the // spacing of the reference-image. Vector3D extentMM; for (unsigned int dim = 0; dim < 3; ++dim) { extentMM[dim] = (std::abs(maxPointInIndexCoordinates[dim] - minPointInIndexCoordinates[dim])) * m_ReferenceImage->GetSpacing()[dim]; } /* * Now create an empty distance image. The created image will always have the same number of pixels, independent from * the original image (e.g. always consists of 500000 pixels) and will have an isotropic spacing. * The spacing is calculated like the following: * The image's volume = 500000 Pixels = extentX*spacing*extentY*spacing*extentZ*spacing * So the spacing is: spacing = ( extentX*extentY*extentZ / 500000 )^(1/3) */ double basis = (extentMM[0] * extentMM[1] * extentMM[2]) / m_DistanceImageVolume; double exponent = 1.0 / 3.0; m_DistanceImageSpacing = pow(basis, exponent); // calculate the number of pixels of the distance image for each direction unsigned int numberOfXPixel = extentMM[0] / m_DistanceImageSpacing; unsigned int numberOfYPixel = extentMM[1] / m_DistanceImageSpacing; unsigned int numberOfZPixel = extentMM[2] / m_DistanceImageSpacing; // We increase the sizeOfRegion by 4 as we decrease the origin by 2 later. // This expansion of the region is necessary to achieve a complete // interpolation. DistanceImageType::SizeType sizeOfRegion; sizeOfRegion[0] = numberOfXPixel + 8; sizeOfRegion[1] = numberOfYPixel + 8; sizeOfRegion[2] = numberOfZPixel + 8; // The region starts at index 0,0,0 DistanceImageType::IndexType initialOriginAsIndex; initialOriginAsIndex.Fill(0); DistanceImageType::PointType originAsWorld = minPointInWorldCoordinates; DistanceImageType::RegionType lpRegion; lpRegion.SetSize(sizeOfRegion); lpRegion.SetIndex(initialOriginAsIndex); // We initialize the itk::Image with // * origin and direction to have it correctly placed and rotated in the world // * the largest possible region to set the extent to be calculated // * the isotropic spacing that we have calculated above m_DistanceImageITK = DistanceImageType::New(); m_DistanceImageITK->SetOrigin(originAsWorld); m_DistanceImageITK->SetDirection(m_ReferenceImage->GetDirection()); m_DistanceImageITK->SetRegions(lpRegion); m_DistanceImageITK->SetSpacing(itk::Vector(m_DistanceImageSpacing)); m_DistanceImageITK->Allocate(); // First of all the image is initialized with the value 10*m_DistanceImageSpacing for each pixel m_DistanceImageDefaultBufferValue = 10 * m_DistanceImageSpacing; m_DistanceImageITK->FillBuffer(m_DistanceImageDefaultBufferValue); // Now we move the origin of the distanceImage 2 index-Coordinates // in all directions - DistanceImageType::IndexType originAsIndex; - m_DistanceImageITK->TransformPhysicalPointToIndex(originAsWorld, originAsIndex); + auto originAsIndex = m_DistanceImageITK->TransformPhysicalPointToIndex(originAsWorld); originAsIndex[0] -= 2; originAsIndex[1] -= 2; originAsIndex[2] -= 2; m_DistanceImageITK->TransformIndexToPhysicalPoint(originAsIndex, originAsWorld); m_DistanceImageITK->SetOrigin(originAsWorld); } mitk::CreateDistanceImageFromSurfaceFilter::CreateDistanceImageFromSurfaceFilter() : m_DistanceImageSpacing(0.0), m_DistanceImageDefaultBufferValue(0.0) { m_DistanceImageVolume = 50000; this->m_UseProgressBar = false; this->m_ProgressStepSize = 5; mitk::Image::Pointer output = mitk::Image::New(); this->SetNthOutput(0, output.GetPointer()); } mitk::CreateDistanceImageFromSurfaceFilter::~CreateDistanceImageFromSurfaceFilter() { } void mitk::CreateDistanceImageFromSurfaceFilter::GenerateData() { this->PreprocessContourPoints(); this->CreateEmptyDistanceImage(); // First of all we have to build the equation-system from the existing contour-edge-points this->CreateSolutionMatrixAndFunctionValues(); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(1); m_Weights = m_SolutionMatrix.partialPivLu().solve(m_FunctionValues); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(2); // The last step is to create the distance map with the interpolated distance function this->FillDistanceImage(); if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(2); m_Centers.clear(); m_Normals.clear(); } void mitk::CreateDistanceImageFromSurfaceFilter::PreprocessContourPoints() { unsigned int numberOfInputs = this->GetNumberOfIndexedInputs(); if (numberOfInputs == 0) { MITK_ERROR << "mitk::CreateDistanceImageFromSurfaceFilter: No input available. Please set an input!" << std::endl; itkExceptionMacro("mitk::CreateDistanceImageFromSurfaceFilter: No input available. Please set an input!"); return; } // First of all we have to extract the nomals and the surface points. // Duplicated points can be eliminated vtkSmartPointer polyData; vtkSmartPointer currentCellNormals; vtkSmartPointer existingPolys; vtkSmartPointer existingPoints; double p[3]; PointType currentPoint; PointType normal; for (unsigned int i = 0; i < numberOfInputs; i++) { auto currentSurface = this->GetInput(i); polyData = currentSurface->GetVtkPolyData(); if (polyData->GetNumberOfPolys() == 0) { MITK_INFO << "mitk::CreateDistanceImageFromSurfaceFilter: No input-polygons available. Please be sure the input " "surface consists of polygons!" << std::endl; } currentCellNormals = vtkDoubleArray::SafeDownCast(polyData->GetCellData()->GetNormals()); existingPolys = polyData->GetPolys(); existingPoints = polyData->GetPoints(); existingPolys->InitTraversal(); const vtkIdType *cell(nullptr); vtkIdType cellSize(0); for (existingPolys->InitTraversal(); existingPolys->GetNextCell(cellSize, cell);) { for (vtkIdType j = 0; j < cellSize; j++) { existingPoints->GetPoint(cell[j], p); currentPoint.copy_in(p); int count = std::count(m_Centers.begin(), m_Centers.end(), currentPoint); if (count == 0) { double currentNormal[3]; currentCellNormals->GetTuple(cell[j], currentNormal); normal.copy_in(currentNormal); m_Normals.push_back(normal); m_Centers.push_back(currentPoint); } } // end for all points } // end for all cells } // end for all outputs } void mitk::CreateDistanceImageFromSurfaceFilter::CreateSolutionMatrixAndFunctionValues() { // For we can now calculate the exact size of the centers we initialize the data structures unsigned int numberOfCenters = m_Centers.size(); m_Centers.reserve(numberOfCenters * 3); m_FunctionValues.resize(numberOfCenters * 3); m_FunctionValues.fill(0); PointType currentPoint; PointType normal; // Create inner points for (unsigned int i = 0; i < numberOfCenters; i++) { currentPoint = m_Centers.at(i); normal = m_Normals.at(i); currentPoint[0] = currentPoint[0] - normal[0] * m_DistanceImageSpacing; currentPoint[1] = currentPoint[1] - normal[1] * m_DistanceImageSpacing; currentPoint[2] = currentPoint[2] - normal[2] * m_DistanceImageSpacing; m_Centers.push_back(currentPoint); m_FunctionValues[numberOfCenters + i] = -m_DistanceImageSpacing; } // Create outer points for (unsigned int i = 0; i < numberOfCenters; i++) { currentPoint = m_Centers.at(i); normal = m_Normals.at(i); currentPoint[0] = currentPoint[0] + normal[0] * m_DistanceImageSpacing; currentPoint[1] = currentPoint[1] + normal[1] * m_DistanceImageSpacing; currentPoint[2] = currentPoint[2] + normal[2] * m_DistanceImageSpacing; m_Centers.push_back(currentPoint); m_FunctionValues[numberOfCenters * 2 + i] = m_DistanceImageSpacing; } // Now we have created all centers and all function values. Next step is to create the solution matrix numberOfCenters = m_Centers.size(); m_SolutionMatrix.resize(numberOfCenters, numberOfCenters); m_Weights.resize(numberOfCenters); PointType p1; PointType p2; double norm; for (unsigned int i = 0; i < numberOfCenters; i++) { for (unsigned int j = 0; j < numberOfCenters; j++) { // Calculate the RBF value. Currently using Phi(r) = r with r is the euclidian distance between two points p1 = m_Centers.at(i); p2 = m_Centers.at(j); p1 = p1 - p2; norm = p1.two_norm(); m_SolutionMatrix(i, j) = norm; } } } void mitk::CreateDistanceImageFromSurfaceFilter::FillDistanceImage() { /* * Now we must calculate the distance for each pixel. But instead of calculating the distance value * for all of the image's pixels we proceed similar to the region growing algorithm: * * 1. Take the first pixel from the narrowband_point_list and calculate the distance for each neighbor (6er) * 2. If the current index's distance value is below a certain threshold push it into the list * 3. Next iteration take the next index from the list and originAsIndex with 1. again * * This is done until the narrowband_point_list is empty. */ typedef itk::ImageRegionIteratorWithIndex ImageIterator; typedef itk::NeighborhoodIterator NeighborhoodImageIterator; std::queue narrowbandPoints; PointType currentPoint = m_Centers.at(0); double distance = this->CalculateDistanceValue(currentPoint); // create itk::Point from vnl_vector DistanceImageType::PointType currentPointAsPoint; currentPointAsPoint[0] = currentPoint[0]; currentPointAsPoint[1] = currentPoint[1]; currentPointAsPoint[2] = currentPoint[2]; // Transform the input point in world-coordinates to index-coordinates - DistanceImageType::IndexType currentIndex; - m_DistanceImageITK->TransformPhysicalPointToIndex(currentPointAsPoint, currentIndex); + auto currentIndex = m_DistanceImageITK->TransformPhysicalPointToIndex(currentPointAsPoint); assert( m_DistanceImageITK->GetLargestPossibleRegion().IsInside(currentIndex)); // we are quite certain this should hold narrowbandPoints.push(currentIndex); m_DistanceImageITK->SetPixel(currentIndex, distance); NeighborhoodImageIterator::RadiusType radius; radius.Fill(1); NeighborhoodImageIterator nIt(radius, m_DistanceImageITK, m_DistanceImageITK->GetLargestPossibleRegion()); unsigned int relativeNbIdx[] = {4, 10, 12, 14, 16, 22}; bool isInBounds = false; while (!narrowbandPoints.empty()) { nIt.SetLocation(narrowbandPoints.front()); narrowbandPoints.pop(); unsigned int *relativeNb = &relativeNbIdx[0]; for (int i = 0; i < 6; i++) { nIt.GetPixel(*relativeNb, isInBounds); if (isInBounds && nIt.GetPixel(*relativeNb) == m_DistanceImageDefaultBufferValue) { currentIndex = nIt.GetIndex(*relativeNb); // Transform the currently checked point from index-coordinates to // world-coordinates m_DistanceImageITK->TransformIndexToPhysicalPoint(currentIndex, currentPointAsPoint); // create a vnl_vector currentPoint[0] = currentPointAsPoint[0]; currentPoint[1] = currentPointAsPoint[1]; currentPoint[2] = currentPointAsPoint[2]; // and check the distance distance = this->CalculateDistanceValue(currentPoint); if (std::fabs(distance) <= m_DistanceImageSpacing * 2) { nIt.SetPixel(*relativeNb, distance); narrowbandPoints.push(currentIndex); } } relativeNb++; } } ImageIterator imgRegionIterator(m_DistanceImageITK, m_DistanceImageITK->GetLargestPossibleRegion()); imgRegionIterator.GoToBegin(); double prevPixelVal = 1; DistanceImageType::IndexType _size; _size.Fill(-1); _size += m_DistanceImageITK->GetLargestPossibleRegion().GetSize(); // Set every pixel inside the surface to -m_DistanceImageDefaultBufferValue except the edge point (so that the // received surface is closed) while (!imgRegionIterator.IsAtEnd()) { if (imgRegionIterator.Get() == m_DistanceImageDefaultBufferValue && prevPixelVal < 0) { while (imgRegionIterator.Get() == m_DistanceImageDefaultBufferValue) { if (imgRegionIterator.GetIndex()[0] == _size[0] || imgRegionIterator.GetIndex()[1] == _size[1] || imgRegionIterator.GetIndex()[2] == _size[2] || imgRegionIterator.GetIndex()[0] == 0U || imgRegionIterator.GetIndex()[1] == 0U || imgRegionIterator.GetIndex()[2] == 0U) { imgRegionIterator.Set(m_DistanceImageDefaultBufferValue); prevPixelVal = m_DistanceImageDefaultBufferValue; ++imgRegionIterator; break; } else { imgRegionIterator.Set((-1) * m_DistanceImageDefaultBufferValue); ++imgRegionIterator; prevPixelVal = (-1) * m_DistanceImageDefaultBufferValue; } } } else if (imgRegionIterator.GetIndex()[0] == _size[0] || imgRegionIterator.GetIndex()[1] == _size[1] || imgRegionIterator.GetIndex()[2] == _size[2] || imgRegionIterator.GetIndex()[0] == 0U || imgRegionIterator.GetIndex()[1] == 0U || imgRegionIterator.GetIndex()[2] == 0U) { imgRegionIterator.Set(m_DistanceImageDefaultBufferValue); prevPixelVal = m_DistanceImageDefaultBufferValue; ++imgRegionIterator; } else { prevPixelVal = imgRegionIterator.Get(); ++imgRegionIterator; } } Image::Pointer resultImage = this->GetOutput(); // Cast the created distance-Image from itk::Image to the mitk::Image // that is our output. CastToMitkImage(m_DistanceImageITK, resultImage); } double mitk::CreateDistanceImageFromSurfaceFilter::CalculateDistanceValue(PointType p) { double distanceValue(0); PointType p1; PointType p2; double norm; CenterList::iterator centerIter; unsigned int count(0); for (centerIter = m_Centers.begin(); centerIter != m_Centers.end(); centerIter++) { p1 = *centerIter; p2 = p - p1; norm = p2.two_norm(); distanceValue = distanceValue + (norm * m_Weights[count]); ++count; } return distanceValue; } void mitk::CreateDistanceImageFromSurfaceFilter::GenerateOutputInformation() { } void mitk::CreateDistanceImageFromSurfaceFilter::PrintEquationSystem() { std::stringstream out; out << "Nummber of rows: " << m_SolutionMatrix.rows() << " ****** Number of columns: " << m_SolutionMatrix.cols() << endl; out << "[ "; for (int i = 0; i < m_SolutionMatrix.rows(); i++) { for (int j = 0; j < m_SolutionMatrix.cols(); j++) { out << m_SolutionMatrix(i, j) << " "; } out << ";" << endl; } out << " ]\n\n\n"; for (unsigned int i = 0; i < m_Centers.size(); i++) { out << m_Centers.at(i) << ";" << endl; } std::cout << "Equation system: \n\n\n" << out.str(); } void mitk::CreateDistanceImageFromSurfaceFilter::SetInput(const mitk::Surface *surface) { this->SetInput(0, surface); } void mitk::CreateDistanceImageFromSurfaceFilter::SetInput(unsigned int idx, const mitk::Surface *surface) { if (this->GetInput(idx) != surface) { this->SetNthInput(idx, const_cast(surface)); this->Modified(); } } const mitk::Surface *mitk::CreateDistanceImageFromSurfaceFilter::GetInput() { if (this->GetNumberOfIndexedInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(0)); } const mitk::Surface *mitk::CreateDistanceImageFromSurfaceFilter::GetInput(unsigned int idx) { if (this->GetNumberOfIndexedInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(idx)); } void mitk::CreateDistanceImageFromSurfaceFilter::RemoveInputs(mitk::Surface *input) { DataObjectPointerArraySizeType nb = this->GetNumberOfIndexedInputs(); for (DataObjectPointerArraySizeType i = 0; i < nb; i++) { if (this->GetInput(i) == input) { this->RemoveInput(i); return; } } } void mitk::CreateDistanceImageFromSurfaceFilter::Reset() { for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { this->PopBackInput(); } this->SetNumberOfIndexedInputs(0); this->SetNumberOfIndexedOutputs(1); mitk::Image::Pointer output = mitk::Image::New(); this->SetNthOutput(0, output.GetPointer()); } void mitk::CreateDistanceImageFromSurfaceFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::CreateDistanceImageFromSurfaceFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; } void mitk::CreateDistanceImageFromSurfaceFilter::SetReferenceImage(itk::ImageBase<3>::Pointer referenceImage) { m_ReferenceImage = referenceImage; } void mitk::CreateDistanceImageFromSurfaceFilter::DetermineBounds( DistanceImageType::PointType &minPointInWorldCoordinates, DistanceImageType::PointType &maxPointInWorldCoordinates, DistanceImageType::IndexType &minPointInIndexCoordinates, DistanceImageType::IndexType &maxPointInIndexCoordinates) { PointType firstCenter = m_Centers.at(0); DistanceImageType::PointType tmpPoint; tmpPoint[0] = firstCenter[0]; tmpPoint[1] = firstCenter[1]; tmpPoint[2] = firstCenter[2]; // transform the first point from world-coordinates to index-coordinates - itk::ContinuousIndex tmpIndex; - m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint, tmpIndex); + auto tmpIndex = m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint); // initialize the variables with this first point DistanceImageType::IndexValueType xmin = tmpIndex[0]; DistanceImageType::IndexValueType ymin = tmpIndex[1]; DistanceImageType::IndexValueType zmin = tmpIndex[2]; DistanceImageType::IndexValueType xmax = tmpIndex[0]; DistanceImageType::IndexValueType ymax = tmpIndex[1]; DistanceImageType::IndexValueType zmax = tmpIndex[2]; // iterate over the rest of the points auto centerIter = m_Centers.begin(); for (++centerIter; centerIter != m_Centers.end(); centerIter++) { tmpPoint[0] = (*centerIter)[0]; tmpPoint[1] = (*centerIter)[1]; tmpPoint[2] = (*centerIter)[2]; // transform each point from world-coordinates to index-coordinates - m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint, tmpIndex); + tmpIndex = m_ReferenceImage->TransformPhysicalPointToContinuousIndex(tmpPoint); // and set the variables accordingly to find the minimum // and maximum in all directions in index-coordinates if (xmin > tmpIndex[0]) { xmin = tmpIndex[0]; } if (ymin > tmpIndex[1]) { ymin = tmpIndex[1]; } if (zmin > tmpIndex[2]) { zmin = tmpIndex[2]; } if (xmax < tmpIndex[0]) { xmax = tmpIndex[0]; } if (ymax < tmpIndex[1]) { ymax = tmpIndex[1]; } if (zmax < tmpIndex[2]) { zmax = tmpIndex[2]; } } // put the found coordinates into Index-Points minPointInIndexCoordinates[0] = xmin; minPointInIndexCoordinates[1] = ymin; minPointInIndexCoordinates[2] = zmin; maxPointInIndexCoordinates[0] = xmax; maxPointInIndexCoordinates[1] = ymax; maxPointInIndexCoordinates[2] = zmax; // and transform them into world-coordinates m_ReferenceImage->TransformIndexToPhysicalPoint(minPointInIndexCoordinates, minPointInWorldCoordinates); m_ReferenceImage->TransformIndexToPhysicalPoint(maxPointInIndexCoordinates, maxPointInWorldCoordinates); } diff --git a/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp b/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp index 282ab45610..cd843e9717 100644 --- a/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp +++ b/Modules/ToFProcessing/Testing/mitkToFDistanceImageToSurfaceFilterTest.cpp @@ -1,386 +1,380 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Test for the class "ToFDistanceImageToSurfaceFilter". */ typedef mitk::ToFProcessingCommon::ToFPoint2D ToFPoint2D; typedef mitk::ToFProcessingCommon::ToFPoint3D ToFPoint3D; typedef mitk::ToFProcessingCommon::ToFScalarType ToFScalarType; int mitkToFDistanceImageToSurfaceFilterTest(int /* argc */, char* /*argv*/[]) { MITK_TEST_BEGIN("ToFDistanceImageToSurfaceFilter"); mitk::ToFDistanceImageToSurfaceFilter::Pointer filter = mitk::ToFDistanceImageToSurfaceFilter::New(); // create test image unsigned int dimX =204; unsigned int dimY =204; mitk::Image::Pointer image = mitk::ImageGenerator::GenerateRandomImage(dimX,dimY); //initialize intrinsic parameters with some arbitrary values ToFScalarType focalLengthX = 295.78960; ToFScalarType focalLengthY = 296.348535; ToFPoint2D focalLengthXY; focalLengthXY[0]=focalLengthX; focalLengthXY[1]=focalLengthY; ToFScalarType k1=-0.36,k2=-0.14,p1=0.001,p2=-0.00; ToFPoint2D principalPoint; principalPoint[0] = 103.576546; principalPoint[1] = 100.1532; mitk::CameraIntrinsics::Pointer cameraIntrinsics = mitk::CameraIntrinsics::New(); cameraIntrinsics->SetFocalLength(focalLengthX,focalLengthY); cameraIntrinsics->SetPrincipalPoint(principalPoint[0],principalPoint[1]); cameraIntrinsics->SetDistorsionCoeffs(k1,k2,p1,p2); // test SetCameraIntrinsics() filter->SetCameraIntrinsics(cameraIntrinsics); MITK_TEST_CONDITION_REQUIRED((focalLengthX==filter->GetCameraIntrinsics()->GetFocalLengthX()),"Testing SetCameraIntrinsics with focalLength"); ToFPoint2D pp; pp[0] = filter->GetCameraIntrinsics()->GetPrincipalPointX(); pp[1] = filter->GetCameraIntrinsics()->GetPrincipalPointY(); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(principalPoint,pp),"Testing SetCameraIntrinsics with principalPoint()"); // test SetInterPixelDistance() ToFPoint2D interPixelDistance; interPixelDistance[0] = 0.04564; interPixelDistance[1] = 0.0451564; filter->SetInterPixelDistance(interPixelDistance); ToFPoint2D ipD = filter->GetInterPixelDistance(); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(ipD,interPixelDistance),"Testing Set/GetInterPixelDistance()"); // test SetReconstructionMode() filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance,"Testing Set/GetReconstructionMode()"); // test Set/GetInput() filter->SetInput(image); MITK_TEST_CONDITION_REQUIRED((image==filter->GetInput()),"Testing Set/GetInput()"); // test filter without subset (without interpixeldistance) MITK_INFO<<"Test filter with subset without interpixeldistance "; filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithOutInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithOutInterPixelDistance,"Testing Set/GetReconstructionMode()"); vtkSmartPointer expectedResult = vtkSmartPointer::New(); expectedResult->SetDataTypeToDouble(); - unsigned int counter = 0; double* point = new double[3]; // MITK_INFO<<"Test"; // MITK_INFO<<"focal: "< index = { { static_cast(i), static_cast< itk::IndexValueType >( j) } }; float distance = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distance = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } ToFPoint3D coordinate = mitk::ToFProcessingCommon::IndexToCartesianCoordinates(i,j,distance,focalLengthX,focalLengthY,principalPoint[0],principalPoint[1]); // if ((i==0)&&(j==0)) // { // MITK_INFO<<"Distance test: "<InsertPoint(pointID,point); } - counter++; } } filter->Update(); mitk::Surface::Pointer resultSurface = filter->GetOutput(); vtkSmartPointer result = vtkSmartPointer::New(); result->SetDataTypeToDouble(); result = resultSurface->GetVtkPolyData()->GetPoints(); MITK_TEST_CONDITION_REQUIRED((expectedResult->GetNumberOfPoints()==result->GetNumberOfPoints()),"Test if number of points in surface is equal"); bool pointSetsEqual = true; for (int i=0; iGetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; if (!mitk::Equal(expectedPoint,resultPoint)) { // MITK_INFO << i; pointSetsEqual = false; } } MITK_TEST_CONDITION_REQUIRED(pointSetsEqual,"Testing filter without subset"); // test filter without subset (with interpixeldistance) MITK_INFO<<"Test filter with subset with interpixeldistance "; filter->SetReconstructionMode(mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance); MITK_TEST_CONDITION_REQUIRED(filter->GetReconstructionMode() == mitk::ToFDistanceImageToSurfaceFilter::WithInterPixelDistance,"Testing Set/GetReconstructionMode()"); // calculate focal length considering inter pixel distance ToFScalarType focalLength = (focalLengthX*interPixelDistance[0]+focalLengthY*interPixelDistance[1])/2.0; expectedResult = vtkSmartPointer::New(); expectedResult->SetDataTypeToDouble(); - counter = 0; point = new double[3]; // MITK_INFO<<"Test"; // MITK_INFO<<"focal: "< index = {{ static_cast(i), static_cast< itk::IndexValueType >( j )}}; float distance = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distance = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } ToFPoint3D coordinate = mitk::ToFProcessingCommon::IndexToCartesianCoordinatesWithInterpixdist(i,j,distance,focalLength,interPixelDistance,principalPoint); // if ((i==0)&&(j==0)) // { // MITK_INFO<<"Distance test: "<InsertPoint(pointID,point); } - counter++; } } filter->Modified(); filter->Update(); resultSurface = filter->GetOutput(); result = vtkSmartPointer::New(); result->SetDataTypeToDouble(); result = resultSurface->GetVtkPolyData()->GetPoints(); MITK_TEST_CONDITION_REQUIRED((expectedResult->GetNumberOfPoints()==result->GetNumberOfPoints()),"Test if number of points in surface is equal"); pointSetsEqual = true; for (int i=0; iGetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; if (!mitk::Equal(expectedPoint,resultPoint)) { // MITK_INFO << i; MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D expectedPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(expectedPoint,focalLengthXY,principalPoint); ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(resultPoint,focalLengthXY,principalPoint); if (!mitk::Equal(expectedPointBackward,resultPointBackward)) { // MITK_INFO << i; // MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* expected = expectedResult->GetPoint(i); double* res = result->GetPoint(i); ToFPoint3D expectedPoint; expectedPoint[0] = expected[0]; expectedPoint[1] = expected[1]; expectedPoint[2] = expected[2]; ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D expectedPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(expectedPoint,focalLength,interPixelDistance,principalPoint); ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(resultPoint,focalLength,interPixelDistance,principalPoint); if (!mitk::Equal(expectedPointBackward,resultPointBackward)) { // MITK_INFO << i; // MITK_INFO<<"expected: "<GetNumberOfPoints(); i++) { double* res = result->GetPoint(i); ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinates(resultPoint,focalLengthXY,principalPoint); itk::Index<2> index = {{ (int) (resultPointBackward[0]+0.5), (int) (resultPointBackward[1]+0.5) }}; float distanceBackward = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distanceBackward = readAccess.GetPixelByIndex(index); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } if (!mitk::Equal(distanceBackward,(float) resultPointBackward[2])) { MITK_INFO<<"expected: " << resultPointBackward[2]; MITK_INFO<<"result: "<< distanceBackward; compareToInput = false; } } MITK_TEST_CONDITION_REQUIRED(compareToInput,"Testing backward transformation compared to original image without interpixeldistance"); //Backwardtransformation test compare to original input with interpixeldistance compareToInput = true; for ( int i=0; iGetNumberOfPoints(); i++) { double* res = result->GetPoint(i); ToFPoint3D resultPoint; resultPoint[0] = res[0]; resultPoint[1] = res[1]; resultPoint[2] = res[2]; ToFPoint3D resultPointBackward = mitk::ToFProcessingCommon::CartesianToIndexCoordinatesWithInterpixdist(resultPoint,focalLength,interPixelDistance,principalPoint); itk::Index<2> pixelIndex = {{ (int) (resultPointBackward[0]+0.5), (int) (resultPointBackward[1]+0.5) }}; float distanceBackward = 0.0; try { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData()); distanceBackward = readAccess.GetPixelByIndex(pixelIndex); } catch(mitk::Exception& e) { MITK_ERROR << "Image read exception!" << e.what(); } if (!mitk::Equal(distanceBackward, (float) resultPointBackward[2])) { compareToInput = false; } } MITK_TEST_CONDITION_REQUIRED(compareToInput,"Testing backward transformation compared to original image with interpixeldistance"); //clean up delete[] point; // expectedResult->Delete(); MITK_TEST_END(); } diff --git a/Modules/TubeGraph/src/IO/mitkTubeGraphObjectFactory.cpp b/Modules/TubeGraph/src/IO/mitkTubeGraphObjectFactory.cpp index 7165d1b8a3..71c131963a 100644 --- a/Modules/TubeGraph/src/IO/mitkTubeGraphObjectFactory.cpp +++ b/Modules/TubeGraph/src/IO/mitkTubeGraphObjectFactory.cpp @@ -1,97 +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. ============================================================================*/ #include "mitkTubeGraphObjectFactory.h" #include #include #include #include "mitkTubeGraph.h" #include "mitkTubeGraphVtkMapper3D.h" mitk::TubeGraphObjectFactory::TubeGraphObjectFactory() : CoreObjectFactoryBase() { static bool alreadyDone = false; if (!alreadyDone) { - MITK_INFO << "TubeGraphObjectFactory c'tor" << std::endl; CreateFileExtensionsMap(); - alreadyDone = true; } } mitk::Mapper::Pointer mitk::TubeGraphObjectFactory::CreateMapper(mitk::DataNode *node, MapperSlotId id) { mitk::Mapper::Pointer newMapper = nullptr; if (id == mitk::BaseRenderer::Standard3D) { if ((dynamic_cast(node->GetData()) != nullptr)) { newMapper = mitk::TubeGraphVtkMapper3D::New(); newMapper->SetDataNode(node); } } return newMapper; } void mitk::TubeGraphObjectFactory::SetDefaultProperties(mitk::DataNode *node) { if ((dynamic_cast(node->GetData()) != nullptr)) { node->SetProperty("Tube Graph.Clip Structures", mitk::BoolProperty::New(false)); mitk::TubeGraphVtkMapper3D::SetDefaultProperties(node); } } std::string mitk::TubeGraphObjectFactory::GetFileExtensions() { std::string fileExtension; this->CreateFileExtensions(m_FileExtensionsMap, fileExtension); return fileExtension.c_str(); }; mitk::CoreObjectFactoryBase::MultimapType mitk::TubeGraphObjectFactory::GetFileExtensionsMap() { return m_FileExtensionsMap; } std::string mitk::TubeGraphObjectFactory::GetSaveFileExtensions() { std::string fileExtension; this->CreateFileExtensions(m_SaveFileExtensionsMap, fileExtension); return fileExtension.c_str(); }; mitk::CoreObjectFactoryBase::MultimapType mitk::TubeGraphObjectFactory::GetSaveFileExtensionsMap() { return m_SaveFileExtensionsMap; } void mitk::TubeGraphObjectFactory::CreateFileExtensionsMap() { } struct RegisterTubeGraphObjectFactory { RegisterTubeGraphObjectFactory() : m_Factory(mitk::TubeGraphObjectFactory::New()) { mitk::CoreObjectFactory::GetInstance()->RegisterExtraFactory(m_Factory); } ~RegisterTubeGraphObjectFactory() { mitk::CoreObjectFactory::GetInstance()->UnRegisterExtraFactory(m_Factory); } mitk::TubeGraphObjectFactory::Pointer m_Factory; }; static RegisterTubeGraphObjectFactory registerTubeGraphObjectFactory; diff --git a/Modules/US/USModel/mitkUSDevicePersistence.cpp b/Modules/US/USModel/mitkUSDevicePersistence.cpp index 17b6a56082..34c9bb763c 100644 --- a/Modules/US/USModel/mitkUSDevicePersistence.cpp +++ b/Modules/US/USModel/mitkUSDevicePersistence.cpp @@ -1,339 +1,340 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkUSDevicePersistence.h" //Microservices #include #include #include #include #include mitk::USDevicePersistence::USDevicePersistence() : m_devices("MITK US", "Device Settings") { } void mitk::USDevicePersistence::StoreCurrentDevices() { us::ModuleContext* thisContext = us::GetModuleContext(); std::vector > services = thisContext->GetServiceReferences(); MITK_INFO << "Trying to save " << services.size() << " US devices."; int numberOfSavedDevices = 0; for (std::vector >::iterator it = services.begin(); it != services.end(); ++it) { mitk::USDevice::Pointer currentDevice = thisContext->GetService(*it); //check if it is a USVideoDevice if (currentDevice->GetDeviceClass() == "org.mitk.modules.us.USVideoDevice") { mitk::USVideoDevice::Pointer currentVideoDevice = dynamic_cast(currentDevice.GetPointer()); QString identifier = "device" + QString::number(numberOfSavedDevices); m_devices.setValue(identifier, USVideoDeviceToString(currentVideoDevice)); numberOfSavedDevices++; } else { MITK_WARN << "Saving of US devices of the type " << currentDevice->GetDeviceClass() << " is not supported at the moment. Skipping device."; } } m_devices.setValue("numberOfSavedDevices", numberOfSavedDevices); MITK_INFO << "Successfully saved " << numberOfSavedDevices << " US devices."; } std::vector mitk::USDevicePersistence::RestoreLastDevices() { std::vector devices; int numberOfSavedDevices = m_devices.value("numberOfSavedDevices").toInt(); for (int i = 0; i < numberOfSavedDevices; i++) { // Try each device. If an exception occurs: Ignore device and notify user try { QString currentString = m_devices.value("device" + QString::number(i)).toString(); mitk::USDevice::Pointer currentDevice = dynamic_cast(StringToUSVideoDevice(currentString).GetPointer()); //currentDevice->Initialize(); devices.push_back(currentDevice.GetPointer()); } catch (...) { MITK_ERROR << "Error occured while loading a USVideoDevice from persistence. Device assumed corrupt, will be deleted."; //QMessageBox::warning(nullptr, "Could not load device" ,"A stored ultrasound device is corrupted and could not be loaded. The device will be deleted."); } } - MITK_INFO << "Restoring " << numberOfSavedDevices << " US devices."; + if (numberOfSavedDevices > 0) + MITK_INFO << "Restoring " << numberOfSavedDevices << " US devices."; return devices; } QString mitk::USDevicePersistence::USVideoDeviceToString(mitk::USVideoDevice::Pointer d) { QString manufacturer = d->GetManufacturer().c_str(); QString model = d->GetName().c_str(); QString comment = d->GetComment().c_str(); int source = d->GetDeviceID(); std::string file = d->GetFilePath(); if (!d->GetIsSourceFile()) file = "none"; //if GetIsSourceFile is true, the device plays back a file mitk::USImageVideoSource::Pointer imageSource = dynamic_cast(d->GetUSImageSource().GetPointer()); if (!imageSource) { MITK_ERROR << "There is no USImageVideoSource at the current device."; mitkThrow() << "There is no USImageVideoSource at the current device."; } int greyscale = imageSource->GetIsGreyscale(); int resOverride = imageSource->GetResolutionOverride(); int resWidth = imageSource->GetResolutionOverrideWidth(); int resHight = imageSource->GetResolutionOverrideHeight(); QString probes = ""; //ACV$100%1%1%0$120%2%2%0$140%2%2%5!BDW$90%1%1%2$100%1%1%8!CSV$50%1%2%3$60%2%2%5 char probesSeperator = '!'; std::vector allProbesOfDevice = d->GetAllProbes(); if (allProbesOfDevice.size() > 0) { for (std::vector::iterator it = allProbesOfDevice.begin(); it != allProbesOfDevice.end(); it++) { if (it == allProbesOfDevice.begin()) { // if it is the first element there is no need for the probes seperator probes = probes + USProbeToString(*it); } else { probes = probes + probesSeperator + USProbeToString(*it); } } } char seperator = '|'; QString returnValue = manufacturer + seperator + model + seperator + comment + seperator + QString::number(source) + seperator + file.c_str() + seperator + QString::number(greyscale) + seperator + QString::number(resOverride) + seperator + QString::number(resWidth) + seperator + QString::number(resHight) + seperator + probes ; MITK_INFO << "Output String: " << returnValue.toStdString(); return returnValue; } QString mitk::USDevicePersistence::USProbeToString(mitk::USProbe::Pointer p) { QString probe = QString::fromStdString(p->GetName()); QString croppingSeparator = QString(","); probe = probe + croppingSeparator + QString::number(p->GetProbeCropping().top) + croppingSeparator + QString::number(p->GetProbeCropping().right) + croppingSeparator + QString::number(p->GetProbeCropping().bottom) + croppingSeparator + QString::number(p->GetProbeCropping().left) + croppingSeparator; char depthSeperator = '$'; char spacingSeperator = '%'; std::map depthsAndSpacing = p->GetDepthsAndSpacing(); if (depthsAndSpacing.size() > 0) { for (std::map::iterator it = depthsAndSpacing.begin(); it != depthsAndSpacing.end(); it++){ probe = probe + depthSeperator + QString::number(it->first) + spacingSeperator + QString::number(it->second[0]) + spacingSeperator + QString::number(it->second[1]) + spacingSeperator + QString::number(it->second[2]); } } return probe; } mitk::USVideoDevice::Pointer mitk::USDevicePersistence::StringToUSVideoDevice(QString s) { MITK_INFO << "Input String: " << s.toStdString(); std::vector data; std::string seperators = "|"; std::string text = s.toStdString(); split(text, seperators, data); if (data.size() != 10) { MITK_ERROR << "Cannot parse US device! (Size: " << data.size() << ")"; return mitk::USVideoDevice::New("INVALID", "INVALID", "INVALID"); } std::string manufacturer = data.at(0); std::string model = data.at(1); std::string comment = data.at(2); int source = (QString(data.at(3).c_str())).toInt(); std::string file = data.at(4); bool greyscale = (QString(data.at(5).c_str())).toInt(); bool resOverride = (QString(data.at(6).c_str())).toInt(); int resWidth = (QString(data.at(7).c_str())).toInt(); int resHight = (QString(data.at(8).c_str())).toInt(); // Create Device mitk::USVideoDevice::Pointer returnValue; if (file == "none") { returnValue = mitk::USVideoDevice::New(source, manufacturer, model); returnValue->SetComment(comment); } else { returnValue = mitk::USVideoDevice::New(file, manufacturer, model); returnValue->SetComment(comment); } mitk::USImageVideoSource::Pointer imageSource = dynamic_cast(returnValue->GetUSImageSource().GetPointer()); if (!imageSource) { MITK_ERROR << "There is no USImageVideoSource at the current device."; mitkThrow() << "There is no USImageVideoSource at the current device."; } // Set Video Options imageSource->SetColorOutput(!greyscale); // If Resolution override is activated, apply it if (resOverride) { imageSource->OverrideResolution(resWidth, resHight); imageSource->SetResolutionOverride(true); } std::string probes = data.at(9); std::string probesSeperator = "!"; std::vector probesVector; split(probes, probesSeperator, probesVector); for (std::vector::iterator it = probesVector.begin(); it != probesVector.end(); it++) { mitk::USProbe::Pointer probe = StringToUSProbe(*it); returnValue->AddNewProbe(probe); } return returnValue; } mitk::USProbe::Pointer mitk::USDevicePersistence::StringToUSProbe(std::string s) { mitk::USProbe::Pointer probe = mitk::USProbe::New(); std::string croppingSeparator = ","; std::string spacingSeperator = "%"; std::string depthSeperator = "$"; std::vector probeCropping; split(s, croppingSeparator, probeCropping); std::vector depthsWithSpacings; split(s, depthSeperator, depthsWithSpacings); //The first entry of the probeCropping vector is the name of the ultrasound probe: std::string probeName = probeCropping.at(0); probe->SetName(probeName); //The entries 1, 2, 3 and 4 of the probeCropping vector are the cropping top, // right, bottom and left: if( probeCropping.size() >= 6 ) { QString top = QString::fromStdString(probeCropping.at(1)); QString right = QString::fromStdString(probeCropping.at(2)); QString bottom = QString::fromStdString(probeCropping.at(3)); QString left = QString::fromStdString(probeCropping.at(4)); probe->SetProbeCropping(top.toUInt(), bottom.toUInt(), left.toUInt(), right.toUInt()); } for (std::vector::iterator it = depthsWithSpacings.begin(); it != depthsWithSpacings.end(); it++) { //The first element is the name of the probe and the cropping entries. //other elements are the scanning depths of the probe and the spacing if (it != depthsWithSpacings.begin()) { std::vector spacings; split(*it, spacingSeperator, spacings); mitk::Vector3D spacing; double x; double y; double z; int depth; try { x = spacingToDouble(spacings.at(1)); y = spacingToDouble(spacings.at(2)); z = spacingToDouble(spacings.at(3)); } catch (const mitk::Exception& e) { MITK_ERROR << e.GetDescription() << "Spacing of " << probe->GetName() << " at depth " << spacings.at(0) << " will be set to default value 1,1,0."; x = 1; y = 1; z = 1; } spacing[0] = x; spacing[1] = y; spacing[2] = z; try { depth = depthToInt(spacings.at(0)); } catch (const mitk::Exception& e) { MITK_ERROR << probe->GetName() << ": " << e.GetDescription(); continue; } probe->SetDepthAndSpacing(depth, spacing); } } return probe; } void mitk::USDevicePersistence::split(std::string& text, std::string& separators, std::vector& words) { int n = text.length(); int start, stop; start = text.find_first_not_of(separators); while ((start >= 0) && (start < n)) { stop = text.find_first_of(separators, start); if ((stop < 0) || (stop > n)) stop = n; words.push_back(text.substr(start, stop - start)); start = text.find_first_not_of(separators, stop + 1); } } double mitk::USDevicePersistence::spacingToDouble(std::string s) { std::istringstream i(s); double x; if (!(i >> x)) { //something went wrong because the string contains characters which can not be convertet into double mitkThrow() << "An error occured while trying to recover the spacing."; } return x; } int mitk::USDevicePersistence::depthToInt(std::string s) { std::istringstream i(s); int x; if (!(i >> x)) { //something went wrong because the string contains characters which can not be convertet into int mitkThrow() << "An error occured while trying to recover the scanning depth. " << s << " is not a valid scanning depth. "; } return x; } diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index 30109b8e31..231542bd9e 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,86 +1,84 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:ON org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF org.mitk.core.services:ON org.mitk.gui.common:ON - org.mitk.planarfigure:ON org.mitk.core.jobs:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.mitkworkbench.intro:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.mxnmultiwidgeteditor:OFF - org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.chartExample:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.datastorageviewertest:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicombrowser:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.deformableclippingplane:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.renderwindowmanager:OFF org.mitk.gui.qt.semanticrelations:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation:OFF org.mitk.gui.qt.overlaymanager:OFF org.mitk.gui.qt.igt.app.hummelprotocolmeasurements:OFF org.mitk.matchpoint.core.helper:OFF org.mitk.gui.qt.matchpoint.algorithm.browser:OFF org.mitk.gui.qt.matchpoint.algorithm.control:OFF org.mitk.gui.qt.matchpoint.mapper:OFF org.mitk.gui.qt.matchpoint.framereg:OFF org.mitk.gui.qt.matchpoint.visualizer:OFF org.mitk.gui.qt.matchpoint.evaluator:OFF org.mitk.gui.qt.matchpoint.manipulator:OFF org.mitk.gui.qt.preprocessing.resampling:OFF org.mitk.gui.qt.cest:OFF org.mitk.gui.qt.fit.demo:OFF org.mitk.gui.qt.fit.inspector:OFF org.mitk.gui.qt.fit.genericfitting:OFF org.mitk.gui.qt.pharmacokinetics.concentration.mri:OFF org.mitk.gui.qt.pharmacokinetics.curvedescriptor:OFF org.mitk.gui.qt.pharmacokinetics.mri:OFF org.mitk.gui.qt.pharmacokinetics.pet:OFF org.mitk.gui.qt.pharmacokinetics.simulation:OFF org.mitk.gui.qt.flowapplication:OFF org.mitk.gui.qt.flow.segmentation:OFF org.mitk.gui.qt.pixelvalue:ON ) diff --git a/Plugins/org.blueberry.ui.qt/src/internal/berryPartStack.cpp b/Plugins/org.blueberry.ui.qt/src/internal/berryPartStack.cpp index 33acb89a9d..e2c3036e06 100644 --- a/Plugins/org.blueberry.ui.qt/src/internal/berryPartStack.cpp +++ b/Plugins/org.blueberry.ui.qt/src/internal/berryPartStack.cpp @@ -1,1605 +1,1601 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "tweaklets/berryGuiWidgetsTweaklet.h" #include "berryPartStack.h" #include "berryPerspective.h" #include "berryPresentationFactoryUtil.h" #include "berryWorkbenchPlugin.h" #include "berryPresentationSerializer.h" #include "berryDragUtil.h" #include "berryEditorAreaHelper.h" #include "berryPerspectiveHelper.h" #include "berryWorkbenchConstants.h" #include "berryXMLMemento.h" #include "berryIWorkbenchPartConstants.h" #include "berryGeometry.h" #include namespace berry { const int PartStack::PROP_SELECTION = 0x42; PartStack::PartStackDropResult::Pointer PartStack::dropResult( new PartStack::PartStackDropResult()); void PartStack::PartStackDropResult::SetTarget(PartStack::Pointer stack, PartPane::Pointer pane, StackDropResult::Pointer result) { this->pane = pane; this->dropResult = result; this->stack = stack; } void PartStack::PartStackDropResult::Drop() { // If we're dragging a pane over itself do nothing //if (dropResult.getInsertionPoint() == pane.getPresentablePart()) { return; }; Object::Pointer cookie; if (dropResult != 0) { cookie = dropResult->GetCookie(); } PartPane::Pointer pane(this->pane); PartStack::Pointer stack(this->stack); // Handle cross window drops by opening a new editor if (pane->GetPartReference().Cast () != 0) { IEditorReference::Pointer editorRef = pane->GetPartReference().Cast< IEditorReference> (); if (pane->GetWorkbenchWindow() != stack->GetWorkbenchWindow()) { try { IEditorInput::Pointer input = editorRef->GetEditorInput(); // Close the old editor and capture the actual closed state incase of a 'cancel' bool editorClosed = pane->GetPage()->CloseEditor(editorRef, true); // Only open open the new editor if the old one closed if (editorClosed) stack->GetPage()->OpenEditor(input, editorRef->GetId()); return; } catch (const PartInitException& e) { //e.printStackTrace(); BERRY_ERROR << e.what(); } } } if (pane->GetContainer() != stack) { // Moving from another stack stack->DerefPart(pane); pane->Reparent(stack->GetParent()); stack->Add(pane, cookie); stack->SetSelection(pane); pane->SetFocus(); } else if (cookie != 0) { // Rearranging within this stack stack->GetPresentation()->MovePart(stack->GetPresentablePart(pane), cookie); } } CursorType PartStack::PartStackDropResult::GetCursor() { return CURSOR_CENTER; } QRect PartStack::PartStackDropResult::GetSnapRectangle() { if (dropResult == 0) { return DragUtil::GetDisplayBounds(stack.Lock()->GetControl()); } return dropResult->GetSnapRectangle(); } PartStack::MyStackPresentationSite::MyStackPresentationSite(PartStack* stack) : partStack(stack) { } void PartStack::MyStackPresentationSite::Close(IPresentablePart::Pointer part) { partStack->Close(part); } void PartStack::MyStackPresentationSite::Close(const QList< IPresentablePart::Pointer>& parts) { partStack->Close(parts); } void PartStack::MyStackPresentationSite::DragStart( IPresentablePart::Pointer beingDragged, QPoint& initialLocation, bool keyboard) { partStack->DragStart(beingDragged, initialLocation, keyboard); } void PartStack::MyStackPresentationSite::DragStart(QPoint& initialLocation, bool keyboard) { partStack->DragStart(IPresentablePart::Pointer(nullptr), initialLocation, keyboard); } bool PartStack::MyStackPresentationSite::IsPartMoveable( IPresentablePart::Pointer part) { return partStack->IsMoveable(part); } void PartStack::MyStackPresentationSite::SelectPart( IPresentablePart::Pointer toSelect) { partStack->PresentationSelectionChanged(toSelect); } bool PartStack::MyStackPresentationSite::SupportsState(int state) { return partStack->SupportsState(state); } void PartStack::MyStackPresentationSite::SetState(int newState) { partStack->SetState(newState); } IPresentablePart::Pointer PartStack::MyStackPresentationSite::GetSelectedPart() { return partStack->GetSelectedPart(); } // void AddSystemActions(IMenuManager menuManager) { // PartStack.this.addSystemActions(menuManager); // } bool PartStack::MyStackPresentationSite::IsStackMoveable() { return partStack->CanMoveFolder(); } void PartStack::MyStackPresentationSite::FlushLayout() { partStack->FlushLayout(); } PartStack::PresentableVector PartStack::MyStackPresentationSite::GetPartList() { return partStack->GetPresentableParts(); } QString PartStack::MyStackPresentationSite::GetProperty( const QString& id) { return partStack->GetProperty(id); } PartStack::PartStack(WorkbenchPage* p, bool allowsStateChanges, int appear, IPresentationFactory* fac) : LayoutPart("PartStack"), page(p), isActive(true), allowStateChanges( allowsStateChanges), appearance(appear), ignoreSelectionChanges(false), factory(fac) { QString str; QTextStream buf(&str); buf << "PartStack@" << this; this->SetID(str); presentationSite = new MyStackPresentationSite(this); } bool PartStack::IsMoveable(IPresentablePart::Pointer part) { PartPane::Pointer pane = this->GetPaneFor(part); Perspective::Pointer perspective = this->GetPage()->GetActivePerspective(); if (perspective == 0) { // Shouldn't happen -- can't have a ViewStack without a // perspective return true; } IWorkbenchPartReference::Pointer partRef = pane->GetPartReference(); if (partRef.Cast () != 0) return perspective->IsMoveable(partRef.Cast ()); return true; } bool PartStack::SupportsState(int /*newState*/) { if (page->IsFixedLayout()) { return false; } return allowStateChanges; } bool PartStack::CanMoveFolder() { if (appearance == PresentationFactoryUtil::ROLE_EDITOR) return true; Perspective::Pointer perspective = this->GetPage()->GetActivePerspective(); if (perspective == 0) { // Shouldn't happen -- can't have a ViewStack without a // perspective return false; } // We need to search if one of the presentations is not moveable // if that's the case the whole folder should not be moveable IStackPresentationSite::Pointer presenationSite; if ((presenationSite = this->GetPresentationSite()) != 0) { QList parts = presenationSite->GetPartList(); for (QList::iterator iter = parts.begin(); iter != parts.end(); ++iter) { if (!presenationSite->IsPartMoveable(*iter)) { return false; } } } return !perspective->IsFixedLayout(); } void PartStack::DerefPart(LayoutPart::Pointer toDeref) { if (appearance == PresentationFactoryUtil::ROLE_EDITOR) EditorAreaHelper::DerefPart(toDeref); else this->GetPage()->GetActivePerspective()->GetPresentation()->DerefPart( toDeref); } bool PartStack::AllowsDrop(PartPane::Pointer part) { PartStack::Pointer stack = part->GetContainer().Cast (); if (stack != 0) { if (stack->appearance == this->appearance) return true; } return false; } void PartStack::AddListener(IPropertyChangeListener *listener) { propEvents.AddListener(listener); } void PartStack::RemoveListener(IPropertyChangeListener *listener) { propEvents.RemoveListener(listener); } int PartStack::GetAppearance() const { return appearance; } QString PartStack::GetID() const { return LayoutPart::GetID(); } bool PartStack::IsStandalone() { return (appearance == PresentationFactoryUtil::ROLE_STANDALONE || appearance == PresentationFactoryUtil::ROLE_STANDALONE_NOTITLE); } IPresentablePart::Pointer PartStack::GetSelectedPart() { return presentationCurrent.Cast (); } IStackPresentationSite::Pointer PartStack::GetPresentationSite() { return presentationSite; } void PartStack::TestInvariants() { QWidget* focusControl = Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetFocusControl(); bool currentFound = false; ChildVector children = this->GetChildren(); for (ChildVector::iterator iter = children.begin(); iter != children.end(); ++iter) { LayoutPart::Pointer child = *iter; // No 0 children allowed poco_assert(child != 0) ; // "0 children are not allowed in PartStack" // Ensure that all the PartPanes have an associated presentable part IPresentablePart::Pointer part = this->GetPresentablePart(child); if (!child->IsPlaceHolder()) { poco_assert(part != 0); // "All PartPanes must have a non-0 IPresentablePart" } // Ensure that the child's backpointer points to this stack ILayoutContainer::Pointer childContainer = child->GetContainer(); // Disable tests for placeholders -- PartPlaceholder backpointers don't // obey the usual rules -- they sometimes point to a container placeholder // for this stack instead of the real stack. if (!child->IsPlaceHolder()) { // If the widgetry exists, the child's backpointer must point to us poco_assert(childContainer.GetPointer() == this); // "PartStack has a child that thinks it has a different parent" // If this child has focus, then ensure that it is selected and that we have // the active appearance. if (focusControl && Tweaklets::Get(GuiWidgetsTweaklet::KEY)->IsChild(child->GetControl(), focusControl)) { poco_assert(child == current); // "The part with focus is not the selected part" // focus check commented out since it fails when focus workaround in LayoutPart.setVisible is not present // Assert.isTrue(getActive() == StackPresentation.AS_ACTIVE_FOCUS); } } // Ensure that "current" points to a valid child if (child == current) { currentFound = true; } // Test the child's internal state child->TestInvariants(); } // If we have at least one child, ensure that the "current" pointer points to one of them if (this->GetPresentableParts().size()> 0) { poco_assert(currentFound); StackPresentation::Pointer presentation = this->GetPresentation(); // If the presentation controls have focus, ensure that we have the active appearance if (focusControl && Tweaklets::Get(GuiWidgetsTweaklet::KEY)->IsChild(presentation->GetControl(), focusControl)) { poco_assert(this->GetActive() == StackPresentation::AS_ACTIVE_FOCUS); // "The presentation has focus but does not have the active appearance" } } // Check to that we're displaying the zoomed icon iff we're actually maximized //poco_assert((this->GetState() == IStackPresentationSite::STATE_MAXIMIZED) // == (this->GetContainer() != 0 && this->GetContainer()->ChildIsZoomed(this))); } void PartStack::DescribeLayout(QString& buf) const { int activeState = this->GetActive(); if (activeState == StackPresentation::AS_ACTIVE_FOCUS) { buf.append("active "); //$NON-NLS-1$ } else if (activeState == StackPresentation::AS_ACTIVE_NOFOCUS) { buf.append("active_nofocus "); //$NON-NLS-1$ } buf.append("("); //$NON-NLS-1$ ChildVector children = this->GetChildren(); - int visibleChildren = 0; - for (ChildVector::iterator iter = children.begin(); iter != children.end(); ++iter) { LayoutPart::Pointer next = *iter; if (!next->IsPlaceHolder()) { if (iter != children.begin()) { buf.append(", "); //$NON-NLS-1$ } if (next == requestedCurrent) { buf.append("*"); //$NON-NLS-1$ } next->DescribeLayout(buf); - - visibleChildren++; } } buf.append(")"); //$NON-NLS-1$ } void PartStack::Add(LayoutPart::Pointer child) { this->Add(child, Object::Pointer(nullptr)); } void PartStack::Add(LayoutPart::Pointer newChild, Object::Pointer cookie) { children.push_back(newChild); // Fix for bug 78470: if(newChild->GetContainer().Cast() == 0) { newChild->SetContainer(ILayoutContainer::Pointer(this)); } this->ShowPart(newChild, cookie); } bool PartStack::AllowsAdd(LayoutPart::Pointer /*toAdd*/) { return !this->IsStandalone(); } bool PartStack::AllowsAutoFocus() { if (presentationSite->GetState() == IStackPresentationSite::STATE_MINIMIZED) { return false; } return LayoutPart::AllowsAutoFocus(); } void PartStack::Close(const QList& parts) { for (int idx = 0; idx < parts.size(); idx++) { IPresentablePart::Pointer part = parts[idx]; this->Close(part); } } void PartStack::Close(IPresentablePart::Pointer part) { if (!presentationSite->IsCloseable(part)) { return; } PartPane::Pointer pane = this->GetPaneFor(part); if (pane != 0) { pane->DoHide(); } } IPresentationFactory* PartStack::GetFactory() { if (factory != nullptr) { return factory; } return WorkbenchPlugin::GetDefault()->GetPresentationFactory(); } void PartStack::CreateControl(QWidget* parent) { if (this->GetPresentation() != 0) { return; } IPresentationFactory* factory = this->GetFactory(); PresentableVector partList = this->GetPresentableParts(); PresentationSerializer serializer(partList); StackPresentation::Pointer presentation = PresentationFactoryUtil ::CreatePresentation(factory, appearance, parent, presentationSite, &serializer, savedPresentationState); this->CreateControl(parent, presentation); Tweaklets::Get(GuiWidgetsTweaklet::KEY)->MoveBelow(this->GetControl(), nullptr); } IDropTarget::Pointer PartStack::GetDropTarget(Object::Pointer draggedObject, const QPoint& position) { if (draggedObject.Cast() == 0) { return IDropTarget::Pointer(nullptr); } PartPane::Pointer pane = draggedObject.Cast(); if (this->IsStandalone() || !this->AllowsDrop(pane)) { return IDropTarget::Pointer(nullptr); } // Don't allow views to be dragged between windows bool differentWindows = pane->GetWorkbenchWindow() != this->GetWorkbenchWindow(); bool editorDropOK = ((pane->GetPartReference().Cast() != 0) && pane->GetWorkbenchWindow()->GetWorkbench() == this->GetWorkbenchWindow()->GetWorkbench()); if (differentWindows && !editorDropOK) { return IDropTarget::Pointer(nullptr); } StackDropResult::Pointer dropResult = this->GetPresentation()->DragOver( this->GetControl(), position); if (dropResult == 0) { return IDropTarget::Pointer(nullptr); } return this->CreateDropTarget(pane, dropResult); } void PartStack::SetBounds(const QRect& r) { if (this->GetPresentation() != 0) { this->GetPresentation()->SetBounds(r); } } IDropTarget::Pointer PartStack::CreateDropTarget(PartPane::Pointer pane, StackDropResult::Pointer result) { dropResult->SetTarget(PartStack::Pointer(this), pane, result); return dropResult; } void PartStack::SetActive(bool isActive) { this->isActive = isActive; // Add all visible children to the presentation for(ChildVector::iterator iter = children.begin(); iter != children.end(); ++iter) { (*iter)->SetContainer(isActive ? ILayoutContainer::Pointer(this) : ILayoutContainer::Pointer(nullptr)); } for (PresentableVector::iterator iter = presentableParts.begin(); iter != presentableParts.end(); ++iter) { PresentablePart::Pointer next = iter->Cast(); next->EnableInputs(isActive); next->EnableOutputs(isActive); } } void PartStack::CreateControl(QWidget* /*parent*/, StackPresentation::Pointer presentation) { poco_assert(this->GetPresentation() == 0); if (presentationSite->GetPresentation() != 0) { return; } presentationSite->SetPresentation(presentation); // Add all visible children to the presentation // Use a copy of the current set of children to avoid a ConcurrentModificationException // if a part is added to the same stack while iterating over the children (bug 78470) ChildVector childParts(children); for (ChildVector::iterator iter = childParts.begin(); iter != childParts.end(); ++iter) { this->ShowPart(*iter, Object::Pointer(nullptr)); } if (savedPresentationState != 0) { PresentableVector partList = this->GetPresentableParts(); PresentationSerializer serializer(partList); presentation->RestoreState(&serializer, savedPresentationState); } //QWidget* ctrl = this->GetPresentation()->GetControl(); //TODO control setData ? //ctrl.setData(this); // We should not have a placeholder selected once we've created the widgetry if (requestedCurrent != 0 && requestedCurrent->IsPlaceHolder()) { requestedCurrent = nullptr; this->UpdateContainerVisibleTab(); } this->RefreshPresentationSelection(); } void PartStack::SavePresentationState() { if (this->GetPresentation() == 0) { return; } {// Save the presentation's state before disposing it XMLMemento::Pointer memento = XMLMemento ::CreateWriteRoot(WorkbenchConstants::TAG_PRESENTATION); memento->PutString(WorkbenchConstants::TAG_ID, this->GetFactory()->GetId()); QList parts(this->GetPresentableParts()); PresentationSerializer serializer(parts); this->GetPresentation()->SaveState(&serializer, memento); // Store the memento in savedPresentationState savedPresentationState = memento; } } PartStack::~PartStack() { //BERRY_INFO << "DELETING PARTSTACK"; } void PartStack::Dispose() { if (this->GetPresentation() == 0) { return; } this->SavePresentationState(); // for (PresentableVector::iterator iter = presentableParts.begin(); // iter != presentableParts.end(); ++iter) // { // iter->Cast()->Dispose(); // } presentableParts.clear(); presentationCurrent = nullptr; current = nullptr; this->FireInternalPropertyChange(PROP_SELECTION); } void PartStack::FindSashes(LayoutPart::Pointer /*toFind*/, PartPane::Sashes& sashes) { ILayoutContainer::Pointer container = this->GetContainer(); if (container != 0) { container->FindSashes(LayoutPart::Pointer(this), sashes); } } QRect PartStack::GetBounds() { if (this->GetPresentation() == 0) { return QRect(0, 0, 0, 0); } return Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl()); } QList PartStack::GetChildren() const { return children; } QWidget* PartStack::GetControl() { StackPresentation::Pointer presentation = this->GetPresentation(); if (presentation == 0) { return nullptr; } return presentation->GetControl(); } /** * Answer the number of children. */ PartStack::ChildVector::size_type PartStack::GetItemCount() { if (this->GetPresentation() == 0) { return children.size(); } return this->GetPresentableParts().size(); } PartPane::Pointer PartStack::GetPaneFor(IPresentablePart::Pointer part) { if (part == 0 || part.Cast() == 0) { return PartPane::Pointer(nullptr); } return part.Cast()->GetPane(); } QWidget* PartStack::GetParent() { return Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetParent(this->GetControl()); } PartStack::PresentableVector PartStack::GetPresentableParts() { return presentableParts; } PresentablePart::Pointer PartStack::GetPresentablePart(LayoutPart::Pointer pane) { for (PresentableVector::iterator iter = presentableParts.begin(); iter != presentableParts.end(); ++iter) { PresentablePart::Pointer part = iter->Cast(); if (part->GetPane() == pane) { return part; } } return PresentablePart::Pointer(nullptr); } StackPresentation::Pointer PartStack::GetPresentation() { return presentationSite->GetPresentation(); } PartPane::Pointer PartStack::GetSelection() { if (PartPane::Pointer partPane = current.Cast()) { return partPane; } return PartPane::Pointer(nullptr); } void PartStack::PresentationSelectionChanged(IPresentablePart::Pointer newSelection) { // Ignore selection changes that occur as a result of removing a part if (ignoreSelectionChanges) { return; } PartPane::Pointer newPart = this->GetPaneFor(newSelection); // This method should only be called on objects that are already in the layout poco_assert(newPart != 0); if (newPart == requestedCurrent) { return; } this->SetSelection(newPart); if (newPart != 0) { newPart->SetFocus(); } } void PartStack::Remove(LayoutPart::Pointer child) { IPresentablePart::Pointer presentablePart = this->GetPresentablePart(child); // Need to remove it from the list of children before notifying the presentation // since it may setVisible(false) on the part, leading to a partHidden notification, // during which findView must not find the view being removed. See bug 60039. children.removeAll(child); StackPresentation::Pointer presentation = this->GetPresentation(); if (presentablePart != 0 && presentation != 0) { ignoreSelectionChanges = true; presentableParts.removeAll(presentablePart); presentation->RemovePart(presentablePart); presentablePart = nullptr; ignoreSelectionChanges = false; } if (this->GetPresentation() != 0) { child->SetContainer(ILayoutContainer::Pointer(nullptr)); } if (child == requestedCurrent) { this->UpdateContainerVisibleTab(); } } void PartStack::Reparent(QWidget* newParent) { QWidget* control = this->GetControl(); GuiWidgetsTweaklet* tweaklet = Tweaklets::Get(GuiWidgetsTweaklet::KEY); if ((control == nullptr) || (tweaklet->GetParent(control) == newParent) || !tweaklet->IsReparentable(control)) { return; } LayoutPart::Reparent(newParent); for(ChildVector::iterator iter = children.begin(); iter != children.end(); ++iter) { (*iter)->Reparent(newParent); } } void PartStack::Replace(LayoutPart::Pointer oldChild, LayoutPart::Pointer newChild) { ChildVector::iterator loc = std::find(children.begin(), children.end(), oldChild); int idx = 0; int numPlaceholders = 0; //subtract the number of placeholders still existing in the list //before this one - they wont have parts. for (ChildVector::iterator iter = children.begin(); iter != loc; ++iter, ++idx) { if ((*iter)->IsPlaceHolder()) { numPlaceholders++; } } ObjectInt::Pointer cookie(new ObjectInt(idx - numPlaceholders)); children.insert(loc, newChild); this->ShowPart(newChild, cookie); if (oldChild == requestedCurrent && newChild.Cast() != 0) { this->SetSelection(newChild.Cast()); } this->Remove(oldChild); } int PartStack::ComputePreferredSize(bool width, int availableParallel, int availablePerpendicular, int preferredParallel) { return this->GetPresentation()->ComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel); } int PartStack::GetSizeFlags(bool horizontal) { StackPresentation::Pointer presentation = this->GetPresentation(); if (presentation != 0) { return presentation->GetSizeFlags(horizontal); } return 0; } bool PartStack::RestoreState(IMemento::Pointer memento) { // Read the active tab. QString activeTabID; memento->GetString(WorkbenchConstants::TAG_ACTIVE_PAGE_ID, activeTabID); // Read the page elements. QList children = memento->GetChildren(WorkbenchConstants::TAG_PAGE); // Loop through the page elements. for (int i = 0; i < children.size(); i++) { // Get the info details. IMemento::Pointer childMem = children[i]; QString partID; childMem->GetString(WorkbenchConstants::TAG_CONTENT, partID); // Create the part. LayoutPart::Pointer part(new PartPlaceholder(partID)); part->SetContainer(ILayoutContainer::Pointer(this)); this->Add(part); //1FUN70C: ITPUI:WIN - Shouldn't set Container when not active //part.setContainer(this); if (partID == activeTabID) { this->SetSelection(part); // Mark this as the active part. //current = part; } } //IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore(); //boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX); int expanded; if (memento->GetInteger(WorkbenchConstants::TAG_EXPANDED, expanded)) { //StartupThreading.runWithoutExceptions(new StartupRunnable() // { // void runWithException() throws Throwable // { SetState(expanded != IStackPresentationSite::STATE_MINIMIZED ? IStackPresentationSite::STATE_RESTORED : IStackPresentationSite::STATE_MINIMIZED); // } // }); } else { SetState(IStackPresentationSite::STATE_RESTORED); } int appearance; if (memento->GetInteger(WorkbenchConstants::TAG_APPEARANCE, appearance)) { this->appearance = appearance; } // Determine if the presentation has saved any info here savedPresentationState = nullptr; QList presentationMementos(memento ->GetChildren(WorkbenchConstants::TAG_PRESENTATION)); for (int idx = 0; idx < presentationMementos.size(); idx++) { IMemento::Pointer child = presentationMementos[idx]; QString id; child->GetString(WorkbenchConstants::TAG_ID, id); if (id == GetFactory()->GetId()) { savedPresentationState = child; break; } } IMemento::Pointer propertiesState = memento->GetChild(WorkbenchConstants::TAG_PROPERTIES); if (propertiesState) { QList props(propertiesState->GetChildren(WorkbenchConstants::TAG_PROPERTY)); for (int i = 0; i < props.size(); i++) { QString id = props[i]->GetID(); properties.insert(id, props[i]->GetTextData()); } } //return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", 0); //$NON-NLS-1$ return true; } void PartStack::SetVisible(bool makeVisible) { QWidget* ctrl = this->GetControl(); bool useShortcut = makeVisible || !isActive; if (ctrl != nullptr && useShortcut) { if (makeVisible == Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetVisible(ctrl)) { return; } } if (makeVisible) { for (PresentableVector::iterator iter = presentableParts.begin(); iter != presentableParts.end(); ++iter) { PresentablePart::Pointer next = iter->Cast(); next->EnableInputs(isActive); next->EnableOutputs(isActive); } } LayoutPart::SetVisible(makeVisible); StackPresentation::Pointer presentation = this->GetPresentation(); if (presentation != 0) { presentation->SetVisible(makeVisible); } if (!makeVisible) { for (PresentableVector::iterator iter = presentableParts.begin(); iter != presentableParts.end(); ++iter) { PresentablePart::Pointer next = iter->Cast(); next->EnableInputs(false); } } } bool PartStack::SaveState(IMemento::Pointer memento) { if (GetAppearance() != PresentationFactoryUtil::ROLE_EDITOR) { // Save the active tab. if (requestedCurrent) { memento->PutString(WorkbenchConstants::TAG_ACTIVE_PAGE_ID, requestedCurrent->GetID()); } // Write out the presentable parts (in order) QSet cachedIds; PartStack::PresentableVector pparts(GetPresentableParts()); for (PartStack::PresentableVector::iterator ppIter = pparts.begin(); ppIter != pparts.end(); ++ppIter) { PresentablePart::Pointer presPart = ppIter->Cast(); IMemento::Pointer childMem = memento->CreateChild(WorkbenchConstants::TAG_PAGE); PartPane::Pointer part = presPart->GetPane(); QString tabText = part->GetPartReference()->GetPartName(); childMem->PutString(WorkbenchConstants::TAG_LABEL, tabText); childMem->PutString(WorkbenchConstants::TAG_CONTENT, presPart->GetPane()->GetPlaceHolderId()); // Cache the id so we don't write it out later cachedIds.insert(presPart->GetPane()->GetPlaceHolderId()); } for (ChildVector::iterator iter = children.begin(); iter != children.end(); ++iter) { LayoutPart::Pointer next = *iter; PartPane::Pointer part; if ((part = next.Cast())) { // Have we already written it out? if (cachedIds.find(part->GetPlaceHolderId()) != cachedIds.end()) continue; } IMemento::Pointer childMem = memento ->CreateChild(WorkbenchConstants::TAG_PAGE); QString tabText = "LabelNotFound"; if (part) { tabText = part->GetPartReference()->GetPartName(); } childMem->PutString(WorkbenchConstants::TAG_LABEL, tabText); childMem->PutString(WorkbenchConstants::TAG_CONTENT, next->GetID()); } } // IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore(); // boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX); // if (useNewMinMax) // { memento->PutInteger(WorkbenchConstants::TAG_EXPANDED, presentationSite->GetState()); // } // else // { // memento // .putInteger( // IWorkbenchConstants.TAG_EXPANDED, // (presentationSite.getState() == IStackPresentationSite.STATE_MINIMIZED) ? IStackPresentationSite.STATE_MINIMIZED // : IStackPresentationSite.STATE_RESTORED); // } memento->PutInteger(WorkbenchConstants::TAG_APPEARANCE, appearance); this->SavePresentationState(); if (savedPresentationState) { IMemento::Pointer presentationState = memento ->CreateChild(WorkbenchConstants::TAG_PRESENTATION); presentationState->PutMemento(savedPresentationState); } if (!properties.empty()) { IMemento::Pointer propertiesState = memento->CreateChild(WorkbenchConstants::TAG_PROPERTIES); for (QHash::iterator iterator = properties.begin(); iterator != properties.end(); ++iterator) { if (iterator.value().isEmpty()) continue; IMemento::Pointer prop = propertiesState->CreateChild(WorkbenchConstants::TAG_PROPERTY, iterator.key()); prop->PutTextData(iterator.value()); } } //return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", 0); return true; } WorkbenchPage::Pointer PartStack::GetPage() { // WorkbenchWindow::Pointer window = this->GetWorkbenchWindow().Cast(); // // if (window == 0) // { // return 0; // } // // return window->GetActivePage().Cast(); return WorkbenchPage::Pointer(page); } void PartStack::SetActive(int activeState) { // if (activeState == StackPresentation::AS_ACTIVE_FOCUS && isMinimized) // { // setMinimized(false); // } presentationSite->SetActive(activeState); } int PartStack::GetActive() const { return presentationSite->GetActive(); } void PartStack::SetSelection(LayoutPart::Pointer part) { if (part == requestedCurrent) { return; } requestedCurrent = part; this->RefreshPresentationSelection(); } void PartStack::UpdateActions(PresentablePart::Pointer /*current*/) { } void PartStack::HandleDeferredEvents() { LayoutPart::HandleDeferredEvents(); this->RefreshPresentationSelection(); } void PartStack::RefreshPresentationSelection() { // If deferring UI updates, exit. if (this->IsDeferred()) { return; } // If the presentation is already displaying the desired part, then there's nothing // to do. if (current == requestedCurrent) { return; } StackPresentation::Pointer presentation = this->GetPresentation(); if (presentation != 0) { presentationCurrent = this->GetPresentablePart(requestedCurrent); // this->UupdateActions(presentationCurrent); if (presentationCurrent != 0 && presentation != 0) { requestedCurrent->CreateControl(this->GetParent()); GuiWidgetsTweaklet* tweaklet = Tweaklets::Get(GuiWidgetsTweaklet::KEY); if (tweaklet->GetParent(requestedCurrent->GetControl()) != tweaklet->GetParent(this->GetControl())) { requestedCurrent->Reparent(tweaklet->GetParent(this->GetControl())); } presentation->SelectPart(presentationCurrent); } // Update the return value of getVisiblePart current = requestedCurrent; this->FireInternalPropertyChange(PROP_SELECTION); } } int PartStack::GetState() { return presentationSite->GetState(); } void PartStack::SetState(const int newState) { int oldState = presentationSite->GetState(); if (!this->SupportsState(newState) || newState == oldState) { return; } // WorkbenchWindow::Pointer wbw = this->GetPage()->GetWorkbenchWindow().Cast(); // if (wbw == 0 || wbw->GetShell() == 0 || wbw->GetActivePage() == 0) // return; // // WorkbenchPage::Pointer page = wbw->GetActivePage(); // // bool useNewMinMax = Perspective::UseNewMinMax(page->GetActivePerspective()); // // // we have to fiddle with the zoom behavior to satisfy Intro req's // // by usning the old zoom behavior for its stack // if (newState == IStackPresentationSite::STATE_MAXIMIZED) // useNewMinMax = useNewMinMax; // && !this->IsIntroInStack(); // else if (newState == IStackPresentationSite::STATE_RESTORED) // { // PartStack::Pointer maxStack = page->GetActivePerspective()->GetPresentation()->GetMaximizedStack(); // useNewMinMax = useNewMinMax && maxStack == this; // } // // if (useNewMinMax) // { // //StartupThreading.runWithoutExceptions(new StartupRunnable() // // { // // void runWithException() throws Throwable // // { // wbw->GetPageComposite()->SetRedraw(false); // try // { // if (newState == IStackPresentationSite::STATE_MAXIMIZED) // { // smartZoom(); // } // else if (oldState == IStackPresentationSite::STATE_MAXIMIZED) // { // smartUnzoom(); // } // // if (newState == IStackPresentationSite::STATE_MINIMIZED) // { // setMinimized(true); // } // // wbw.getPageComposite().setRedraw(true); // // // Force a redraw (fixes Mac refresh) // wbw.getShell().redraw(); // // } // catch (...) // { // wbw.getPageComposite().setRedraw(true); // // // Force a redraw (fixes Mac refresh) // wbw.getShell().redraw(); // } // // this->SetPresentationState(newState); // // } // // }); // } // else // { //// bool minimized = (newState == IStackPresentationSite::STATE_MINIMIZED); //// this->SetMinimized(minimized); //// //// if (newState == IStackPresentationSite::STATE_MAXIMIZED) //// { //// requestZoomIn(); //// } //// else if (oldState == IStackPresentationSite::STATE_MAXIMIZED) //// { //// requestZoomOut(); //// //// if (newState == IStackPresentationSite::STATE_MINIMIZED) //// setMinimized(true); //// } // } } void PartStack::ShowPart(LayoutPart::Pointer part, Object::Pointer cookie) { if (this->GetPresentation() == 0) { return; } if (part->IsPlaceHolder()) { part->SetContainer(ILayoutContainer::Pointer(this)); return; } if (part.Cast() == 0) { WorkbenchPlugin::Log("Incorrect part " + part->GetID() + "contained in a part stack"); return; } PartPane::Pointer pane = part.Cast(); PresentablePart::Pointer presentablePart(new PresentablePart(pane, Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetParent(this->GetControl()))); presentableParts.push_back(presentablePart); if (isActive) { part->SetContainer(ILayoutContainer::Pointer(this)); // The active part should always be enabled if (part->GetControl() != nullptr) Tweaklets::Get(GuiWidgetsTweaklet::KEY)->SetEnabled(part->GetControl(), true); } presentationSite->GetPresentation()->AddPart(presentablePart, cookie); if (requestedCurrent == 0) { this->SetSelection(pane); } // if (childObscuredByZoom(part)) // { // presentablePart.enableInputs(false); // } } void PartStack::UpdateContainerVisibleTab() { ChildVector parts = this->GetChildren(); if (parts.size() < 1) { this->SetSelection(LayoutPart::Pointer(nullptr)); return; } PartPane::Pointer selPart; int topIndex = 0; WorkbenchPage::Pointer page = this->GetPage(); if (page != 0) { QList sortedParts = page->GetSortedParts(); for (ChildVector::iterator partIter = parts.begin(); partIter != parts.end(); ++partIter) { if (partIter->Cast() != 0) { IWorkbenchPartReference::Pointer part = partIter->Cast() ->GetPartReference(); int index = static_cast(std::find(sortedParts.begin(), sortedParts.end(), part) - sortedParts.begin()); if (index >= topIndex) { topIndex = index; selPart = partIter->Cast(); } } } } if (selPart == 0) { PresentableVector presentableParts = this->GetPresentableParts(); if (presentableParts.size() != 0) { IPresentablePart::Pointer part = presentableParts.front(); selPart = this->GetPaneFor(part); } } this->SetSelection(selPart); } void PartStack::ShowSystemMenu() { //this->GetPresentation()->ShowSystemMenu(); } void PartStack::ShowPaneMenu() { //this->GetPresentation()->ShowPaneMenu(); } void PartStack::ShowPartList() { this->GetPresentation()->ShowPartList(); } QList PartStack::GetTabList(LayoutPart::Pointer part) { if (part != 0) { IPresentablePart::Pointer presentablePart = this->GetPresentablePart(part); StackPresentation::Pointer presentation = this->GetPresentation(); if (presentablePart != 0 && presentation != 0) { return presentation->GetTabList(presentablePart); } } return QList(); } void PartStack::DragStart(IPresentablePart::Pointer beingDragged, QPoint& initialLocation, bool keyboard) { if (beingDragged == 0) { this->PaneDragStart(PartPane::Pointer(nullptr), initialLocation, keyboard); } else { if (presentationSite->IsPartMoveable(beingDragged)) { PartPane::Pointer pane = this->GetPaneFor(beingDragged); if (pane != 0) { this->PaneDragStart(pane, initialLocation, keyboard); } } } } void PartStack::PaneDragStart(PartPane::Pointer pane, QPoint& initialLocation, bool keyboard) { if (pane == 0) { if (this->CanMoveFolder()) { if (presentationSite->GetState() == IStackPresentationSite::STATE_MAXIMIZED) { // Calculate where the initial location was BEFORE the 'restore'...as a percentage QRect bounds = Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())); float xpct = (initialLocation.x() - bounds.x()) / (float)(bounds.width()); float ypct = (initialLocation.y() - bounds.y()) / (float)(bounds.height()); // Only restore if we're dragging views/view stacks if (this->GetAppearance() != PresentationFactoryUtil::ROLE_EDITOR) this->SetState(IStackPresentationSite::STATE_RESTORED); // Now, adjust the initial location to be within the bounds of the restored rect bounds = Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())); initialLocation.setX((int) (bounds.x() + (xpct * bounds.width()))); initialLocation.setY((int) (bounds.y() + (ypct * bounds.height()))); } DragUtil::PerformDrag(Object::Pointer(this), Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())), initialLocation, !keyboard); } } else { if (presentationSite->GetState() == IStackPresentationSite::STATE_MAXIMIZED) { // Calculate where the initial location was BEFORE the 'restore'...as a percentage QRect bounds = Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())); float xpct = (initialLocation.x() - bounds.x()) / (float)(bounds.width()); float ypct = (initialLocation.y() - bounds.y()) / (float)(bounds.height()); // Only restore if we're dragging views/view stacks if (this->GetAppearance() != PresentationFactoryUtil::ROLE_EDITOR) this->SetState(IStackPresentationSite::STATE_RESTORED); // Now, adjust the initial location to be within the bounds of the restored rect // See bug 100908 bounds = Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())); initialLocation.setX((int) (bounds.x() + (xpct * bounds.width()))); initialLocation.setY((int) (bounds.y() + (ypct * bounds.height()))); } DragUtil::PerformDrag(pane, Geometry::ToDisplay(this->GetParent(), Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetBounds(this->GetPresentation()->GetControl())), initialLocation, !keyboard); } } IMemento::Pointer PartStack::GetSavedPresentationState() { return savedPresentationState; } void PartStack::FireInternalPropertyChange(int id) { ObjectInt::Pointer val(new ObjectInt(id)); Object::Pointer source(this); PropertyChangeEvent::Pointer event(new PropertyChangeEvent(source, IWorkbenchPartConstants::INTEGER_PROPERTY, val, val)); propEvents.propertyChange(event); } QString PartStack::GetProperty(const QString& id) { return properties[id]; } void PartStack::SetProperty(const QString& id, const QString& value) { if (value == "") { properties.remove(id); } else { properties.insert(id, value); } } void PartStack::CopyAppearanceProperties(PartStack::Pointer copyTo) { copyTo->appearance = this->appearance; if (!properties.empty()) { for (QHash::iterator iter = properties.begin(); iter != properties.end(); ++iter) { copyTo->SetProperty(iter.key(), iter.value()); } } } void PartStack::ResizeChild(LayoutPart::Pointer /*childThatChanged*/) { } } diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/CMakeLists.txt b/Plugins/org.mitk.gui.qt.cmdlinemodules/CMakeLists.txt deleted file mode 100644 index 9f8ac489da..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -project(org_mitk_gui_qt_cmdlinemodules) - -mitk_create_plugin( - EXPORT_DIRECTIVE CLI_EXPORT - EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkQtWidgetsExt - PACKAGE_DEPENDS - PUBLIC CTK|CTKWidgets - PRIVATE CTK|CTKCommandLineModulesFrontendQtGui+CTKCommandLineModulesBackendLocalProcess Qt5|UiTools+XmlPatterns -) - -# Copy CTK Qt (designer) plugins to a Qt default path in the MITK bin -# this has been implemented as a fix for bug 19379 -# The Qt plugins provided by CTK were not found in the plugin paths -# when running in the build tree -if(MITK_USE_CTK) - if(EXISTS ${CTK_QTDESIGNERPLUGINS_DIR}) - - if(MACOSX_BUNDLE_NAMES) - set(_bindir ${MITK_BINARY_DIR}/bin/MitkWorkbench.app/Contents/MacOS/) - else() - set(_bindir ${MITK_BINARY_DIR}/bin) - endif() - - set(_releasedir) - set(_debugdir) - if(NOT CMAKE_CFG_INTDIR STREQUAL ".") - set(_releasedir "Release/") - set(_debugdir "Debug/") - endif() - - if(EXISTS ${CTK_QTDESIGNERPLUGINS_DIR}/designer/${_releasedir}) - file(COPY "${CTK_QTDESIGNERPLUGINS_DIR}/designer/${_releasedir}" DESTINATION ${_bindir}/${_releasedir}designer/) - endif() - if(EXISTS ${CTK_QTDESIGNERPLUGINS_DIR}/designer/${_debugdir}) - file(COPY "${CTK_QTDESIGNERPLUGINS_DIR}/designer/${_debugdir}" DESTINATION ${_bindir}/${_debugdir}designer/) - endif() - - endif() -endif() diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules.dox b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules.dox deleted file mode 100644 index 40a8735969..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules.dox +++ /dev/null @@ -1,168 +0,0 @@ -/** -\page org_mitk_views_cmdlinemodules The Command Line Modules View - -\imageMacro{cmdlinemodules_Icon.png,"Icon of the Command Line Modules View",2.00} - -\tableofcontents - -\section CLIPrefix Contribution - -This plugin was developed at the Centre For Medical Image Computing (CMIC), -part of University College London (UCL) and contributed back to the -MITK community with thanks. - -\section CLIIntroduction Introduction - -This view provides the facility to run third party command line programs, and load the data back -into the DataManager for immediate visualisation. All that is required is that the command line application can be called -with an argument of --xml and respond with a valid XML description of the necessary parameters, -and currently, that if the program requires images, they must be NifTI images. -This view can then generate a Graphical User Interface (GUI) dynamically from the XML to enable the -user to interact with the command line application. This provides an easy to use, and potentially -very flexible way to integrate almost any third party, medical imaging, command line application. - -As a high level introduction, this view performs the following steps: - -\li The view searches for available programs to run, and for each valid module, stores the XML document describing -the interface, and populates a searchable list of available programs. -\li When a program is selected, the GUI is generated. -\li The user can then set the necessary parameters and run the program. -\li Multiple programs can be launched in succession and run simultaneously, and where available on the host platform, -the user can pause, resume or cancel running jobs and see console output for each job. - -As a consequence of the very flexible nature of this plugin, these instructions can only describe how to launch -command line modules in a general sense. The examples shown have been constructed by the latest version of the NiftyReg package. -Further information on the NiftyReg package, including links to the source code, can be found on the wiki page here. -NiftyReg provides valid XML descriptors to enable the integration of the NiftyReg affine (RegAladin) and and non-rigid (RegF3D) image registration algorithms, as well -as utility programs to resample an image, and calculate a Jacobian image. These same XML descriptors work within -Slicer and MITK based applications. - -\section CLIPreferences Preferences - -The first time that the Command Line Modules View is launched, it is advisable to set the user preferences for the view. Please refer -to Figure 1. - -\imageMacro{cmdlinemodules_Preferences.png,"Figure 1. The Command Line Modules Preferences Page",16.00} - -Each of these preferences is now explained in some detail. - -\li show debug output: If checked will output more messages to the console for debugging purposes. -\li show advanced widgets: If selected, additional widgets appear in the front-end for advanced users. -\li XML time-out (secs): Sets the time-out for the retrieval of the XML. -\li XML validation mode: The user may select a different mode for XML validation. If this is changed, the application will -need to be restarted. There are 3 modes available. If the user selects "strict" mode, the XML schema produced by the -command line application must exactly conform to -this definition. For "none", there will be no validation. For "weak" validation, the application will report errors, -but try to carry on and load as many modules as possible. The XML validation errors are available as tool-tips on -the tab widget when the module is launched. Many third party modules included with Slicer currently have -incorrect XML (typically, mis-ordered XML tags), and so the "weak" or "none" mode may assist in loading them. -By default the "weak" mode is chosen so that only valid modules are loaded. -\li max concurrent processes: Sets the maximum number of concurrent jobs that can be run via this interface. The default is 4. -When the maximum number is reached, the green "Run" button is disabled until a job finishes. - -The next 7 preferences are to control where the view will search for valid command line programs. By default these are off -as the searching process can take a long time and slow down the startup time of the GUI. The options provided are: - -\li scan installation dir: This is the directory where the actual application is stored. -\li scan installation dir/cli-modules: Scans the sub-directory called cli-modules under the application installation directory. -\li scan home dir: Scan the users home directory. (See QDir::homePath().) -\li scan home dir/cli-modules: Scans the sub-directory called cli-modules under the users home directory. -\li scan current dir: Scan the current working directory. (See QDir::homePath().) -\li scan current dir/cli-modules: Scans the sub-directory called cli-modules under the current working directory. -\li scan CTK_MODULE_LOAD_PATH: Scans the directory or list of directories defined by the environment variable CTK_MODULE_LOAD_PATH. -A list is colon separated on Linux/Mac, and semi-colon separated on Windows. - -In most cases, it is suggested that the user will leave these options unchecked, as the user can also -specify custom directories, and even cherry-pick specific command line programs to load. Figure 2 shows -a selection box that enables the user to specify custom directories to scan, and Figure 3. shows a selection -box that enables the user to select specific modules. Picking specific directories, and specific executables -will most likely make the application quicker to launch. - -\imageMacro{cmdlinemodules_PreferencesAdditionalDirectories.png,"Figure 2. The User can specify specific directories to scan.",7.90} -\imageMacro{cmdlinemodules_PreferencesAdditionalModules.png,"Figure 3. The User can specify specific command line programs to load.",7.92} - -These directory and file selection boxes enable directories or files to be added, removed and updated in a similar fashion. - -The user must make sure that the list of files selected in the "additional modules" section are not already contained within -the directories specified in the "additional module directories" section. - -In addition, the preferences page provides: - -\li temporary directory: Images stored in the DataManager are first written to a temporary folder as -Nifti images before being passed to each command line program. -This temporary directory will default to a platform specific temporary folder, but the user may select their preferred choice -of temporary workspace. - -\section CLIUsage Usage - -When the view is launched, a simple interface is presented, as shown in Figure 4. - -\imageMacro{cmdlinemodules_Initial.png,"Figure 4. The initial interface\, with no command line programs available.",8.66} - -In this example, all the above check-box preferences were off, and the "additional module directories" -was empty, and the "additional modules" list was empty so no command line applications were found. -The "Search" box displays zero entries, and there is nothing to search. - -If the available search paths contain programs that are compatible (i.e. runnable) with this view, -the name of the programs are displayed in the "Search" box in a nested menu, shown in Figure 5. - -\imageMacro{cmdlinemodules_WithPrograms.png,"Figure 5. When valid paths are set\, and programs are discovered\, the menu is recalculated to show available programs.",10.54} - -When a program is selected, the relevant interface is displayed, by default as collapsed group boxes to save space. -Each section can be individually expanded if necessary to see the parameters. - -\imageMacro{cmdlinemodules_NiftyReg.png,"Figure 6. An example program\, showing parameters for NiftyReg's program RegAladin.",10.24} - -In this example, the parameters are displayed for NiftyReg -produced at UCL, and more specifically for the affine registration program called -RegAladin. The interface can contain a wide variety of controls. If a parameter for a command line program is an input image, -then the widget displayed is linked to the DataManager, so that as new images are loaded, the correct image can be easily -selected from the combo box. - -At this stage, multiple tabs can be opened, with one tab for each command line program. Figure 7 shows 2 tabs, -for the RegAladin and RegF3D programs. - -\imageMacro{cmdlinemodules_F3D.png,"Figure 7. Multiple tabs can be opened\, one for each command line program.",10.24} - -The main view provides some simple controls: - -\li Green arrow: Launch (run) the command line executable of the currently selected tab. -\li Yellow undo arrow: Resets the GUI controls of the currently selected tab to default values, if and only if the original XML specified a default value. - -At this stage, nothing has been launched. When the user hits the green arrow button, a job is launched. -Each running job is shown as a new progress reporting widget under the main tabbed widget, as shown in Figure 8. - -\imageMacro{cmdlinemodules_NiftyRegRunning2.png,"Figure 8. Multiple programs can be run\, each with individual controls and console output.",10.24} - -The controls for each running job are: - -\li Blue pause button: If supported on the host platform, this button will be enabled and can be toggled off (pause) or on (resume). -\li Red square: If supported on the host platform, this button will kill the command line program. -\li Black cross: Will remove the progress reporting widget from the GUI. - -When the user hits the green arrow in the main view: - -\li The currently selected tab is designated the "current" job, and contains the "current" set of parameters. -\li A new progress reporting widget is created. -\li The current parameters are copied to the progress reporting widget. In Figure 8. a parameters section -is visible, and by default is collapsed, as they are simply for referring back to. -\li All the output for the command line program is shown in the console widget, with a separate console for each job. -\li Each new progress reporting widget is simply stacked vertically (newest is top-most), and it is up to the -user to delete them when they are finished. - -It is easy to run multiple jobs. The green button simply launches the job corresponding to the current tab repeatedly. -It is up to the user to make sure that any output file names are changed between successive invocations of the same command -line module to avoid overwriting output data. - -In addition, each set of parameters contains an "About" section containing details of the contributors, the licence and acknowledgements and also -a "Help" section containing a description and a link to any online documentation. - -These documentation features are provided by the developers of the third party plugin, and not by the host program. -If information is missing, the user must contact the third party developers. - -\section CLITechnicalNotes Technical Notes - -From a technical perspective, the Command Line Modules View is a simple view, harnessing the power of the CTK -command line modules framework. For technical information see the CTK wiki page -and obviously the CTK code base. -*/ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_F3D.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_F3D.png deleted file mode 100644 index 76bc88a6c5..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_F3D.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Icon.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Icon.png deleted file mode 100644 index b3225e9630..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Icon.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Initial.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Initial.png deleted file mode 100644 index 5496829e82..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Initial.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyReg.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyReg.png deleted file mode 100644 index 5c7034ccc4..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyReg.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyRegRunning2.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyRegRunning2.png deleted file mode 100644 index 9b7a951f38..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_NiftyRegRunning2.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Preferences.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Preferences.png deleted file mode 100644 index 6b311789a1..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_Preferences.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalDirectories.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalDirectories.png deleted file mode 100644 index 124b3205c9..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalDirectories.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalModules.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalModules.png deleted file mode 100644 index d63e4b32f7..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_PreferencesAdditionalModules.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_WithPrograms.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_WithPrograms.png deleted file mode 100644 index 7baa15677b..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/UserManual/cmdlinemodules_WithPrograms.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/doxygen/modules.dox b/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/doxygen/modules.dox deleted file mode 100644 index 21b3313b85..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/documentation/doxygen/modules.dox +++ /dev/null @@ -1,18 +0,0 @@ -/** - \defgroup org_mitk_gui_qt_cmdlinemodules org.mitk.gui.qt.cmdlinemodules - \ingroup MITKPlugins - - \brief This plugin, provided by University College London (UCL), written by Matt - Clarkson (m.clarkson@ucl.ac.uk) uses the CTK (https://commontk.org/) Command - Line Modules library to run command line programs as an external process. - -*/ - -/** - \defgroup org_mitk_gui_qt_cmdlinemodules_internal Internal - \ingroup org_mitk_gui_qt_cmdlinemodules - - \brief This subcategory includes the internal classes of the org.mitk.gui.qt.cmdlinemodules plugin. Other - plugins must not rely on these classes. They contain implementation details and their interface - may change at any time. We mean it. -*/ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/files.cmake b/Plugins/org.mitk.gui.qt.cmdlinemodules/files.cmake deleted file mode 100644 index ab516fb30b..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/files.cmake +++ /dev/null @@ -1,63 +0,0 @@ -set(SRC_CPP_FILES - QmitkCmdLineModuleMenuComboBox.cpp -) - -set(INTERNAL_CPP_FILES - QmitkDirectoryListWidget.cpp - QmitkFileListWidget.cpp - QmitkCmdLineModuleGui.cpp - QmitkCmdLineModuleFactoryGui.cpp - QmitkUiLoader.cpp - org_mitk_gui_qt_cmdlinemodules_Activator.cpp - CommandLineModulesViewConstants.cpp - CommandLineModulesViewControls.cpp - CommandLineModulesPreferencesPage.cpp - CommandLineModulesView.cpp - QmitkCmdLineModuleRunner.cpp -) - -set(UI_FILES - src/internal/QmitkPathListWidget.ui - src/internal/CommandLineModulesViewControls.ui - src/internal/QmitkCmdLineModuleProgressWidget.ui -) - -set(MOC_H_FILES - src/QmitkCmdLineModuleMenuComboBox.h - src/internal/QmitkDirectoryListWidget.h - src/internal/QmitkFileListWidget.h - src/internal/QmitkCmdLineModuleGui.h - src/internal/QmitkUiLoader.h - src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.h - src/internal/CommandLineModulesViewControls.h - src/internal/CommandLineModulesPreferencesPage.h - src/internal/CommandLineModulesView.h - src/internal/QmitkCmdLineModuleRunner.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/icon.xpm - resources/run.png - resources/stop.png - plugin.xml -) - -# list of Qt .qrc files which contain additional resources -# specific to this plugin -set(QRC_FILES - resources/CommandLineModulesResources.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.cmdlinemodules/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.cmdlinemodules/manifest_headers.cmake deleted file mode 100644 index d792fa6273..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/manifest_headers.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(Plugin-Name "Command Line Modules") -set(Plugin-Version "0.1") -set(Plugin-Vendor "CMIC, Centre For Medical Image Computing, UCL.") -set(Plugin-ContactAddress "https://www.ucl.ac.uk/medical-image-computing") -set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/plugin.xml b/Plugins/org.mitk.gui.qt.cmdlinemodules/plugin.xml deleted file mode 100644 index 4c29515213..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/plugin.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Control the execution of Commandline-Tools - - - - - - - - - - - - - - - - - - - diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/CommandLineModulesResources.qrc b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/CommandLineModulesResources.qrc deleted file mode 100644 index 09dca11a7a..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/CommandLineModulesResources.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - run.png - stop.png - undo.png - pause.png - - diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.png deleted file mode 100644 index 5142150ad5..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.xpm b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.xpm deleted file mode 100644 index 8ef9be444a..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/icon.xpm +++ /dev/null @@ -1,2193 +0,0 @@ -/* XPM */ -static char * icon_xpm[] = { -"278 287 1903 2", -" c None", -". c #B3B3B3", -"+ c #B4B4B4", -"@ c #B3B4B3", -"# c #B2B4B2", -"$ c #BABCBA", -"% c #C7C9C8", -"& c #D2D3D2", -"* c #D8DAD8", -"= c #DDDEDD", -"- c #DDDFDD", -"; c #DCDEDC", -"> c #D9DAD9", -", c #D4D5D4", -"' c #C9CBCA", -") c #BABDBB", -"! c #B3B5B4", -"~ c #B2B3B2", -"{ c #B2B2B2", -"] c #B5B7B6", -"^ c #C1C3C2", -"/ c #CED0CF", -"( c #D8D9D8", -"_ c #E0E1E0", -": c #E7E8E7", -"< c #EEEEEE", -"[ c #F1F1F1", -"} c #F0F1F1", -"| c #EEEFEF", -"1 c #E3E5E4", -"2 c #D6D8D7", -"3 c #C4C6C5", -"4 c #B4B5B4", -"5 c #B5B5B5", -"6 c #BBBEBC", -"7 c #C8CAC9", -"8 c #D3D6D5", -"9 c #E4E5E4", -"0 c #EBECEB", -"a c #F0F1F0", -"b c #EFF0EF", -"c c #E8EAE8", -"d c #E2E4E2", -"e c #DBDDDB", -"f c #D5D7D5", -"g c #CFD2D0", -"h c #CACECB", -"i c #C8CCC9", -"j c #C8CBC9", -"k c #CCCFCD", -"l c #D1D3D1", -"m c #D8DAD9", -"n c #E2E3E2", -"o c #EDEEED", -"p c #E0E2E0", -"q c #CBCECB", -"r c #B5B7B5", -"s c #B3B4B4", -"t c #B6B9B8", -"u c #C4C5C4", -"v c #D0D1D0", -"w c #D9DBDA", -"x c #E1E3E2", -"y c #E8E9E8", -"z c #E5E6E5", -"A c #DEE0DE", -"B c #D1D4D1", -"C c #C6C9C6", -"D c #C5C9C6", -"E c #C5C8C6", -"F c #C5C8C5", -"G c #CFD2CF", -"H c #C7CAC7", -"I c #B1B3B2", -"J c #BEC0BF", -"K c #CACCCA", -"L c #D5D7D6", -"M c #E5E7E5", -"N c #EBECEC", -"O c #E1E3E1", -"P c #DBDCDB", -"Q c #D4D7D4", -"R c #CED0CE", -"S c #C8CBC8", -"T c #D4D6D4", -"U c #B7B9B7", -"V c #B8BAB9", -"W c #D1D2D1", -"X c #DADCDB", -"Y c #E8EAE9", -"Z c #F1F2F1", -"` c #E4E6E5", -" . c #DDDFDE", -".. c #D7D9D7", -"+. c #CACDCA", -"@. c #C5C7C5", -"#. c #E3E4E3", -"$. c #C0C1C0", -"%. c #B6B6B6", -"&. c #CBCDCC", -"*. c #D6D9D7", -"=. c #DEE1DF", -"-. c #E6E7E6", -";. c #ECEDEC", -">. c #E7E8E8", -",. c #E0E2E1", -"'. c #CDD0CE", -"). c #C7CAC8", -"!. c #C4C7C5", -"~. c #D4D6D5", -"{. c #EFEFEF", -"]. c #E4E6E4", -"^. c #C3C5C3", -"/. c #B1B1B1", -"(. c #B9BAB9", -"_. c #C7C7C7", -":. c #E9EBE9", -"<. c #EAEBEA", -"[. c #D0D2D0", -"}. c #C9CCCA", -"|. c #C6C9C7", -"1. c #C4C7C4", -"2. c #CDCFCD", -"3. c #C2C4C2", -"4. c #B2B3B3", -"5. c #B5B6B6", -"6. c #C2C2C2", -"7. c #CECFCE", -"8. c #E6E8E6", -"9. c #D9DCDA", -"0. c #D3D6D4", -"a. c #CCD0CD", -"b. c #C9CCC9", -"c. c #BCBDBC", -"d. c #B3B5B3", -"e. c #BBBDBC", -"f. c #C8CAC8", -"g. c #EFF0F0", -"h. c #E2E4E3", -"i. c #DCDFDD", -"j. c #D6D9D6", -"k. c #C6CAC7", -"l. c #C4C6C4", -"m. c #C9CBC9", -"n. c #B6B7B6", -"o. c #C2C5C3", -"p. c #CED1CF", -"q. c #D9DBD9", -"r. c #EEEFEE", -"s. c #DFE1E0", -"t. c #D8DBD9", -"u. c #D2D5D2", -"v. c #CBCFCC", -"w. c #C7C9C7", -"x. c #CCCECC", -"y. c #CBCDCB", -"z. c #CDCECD", -"A. c #EEEEED", -"B. c #CDCECC", -"C. c #BCBEBD", -"D. c #CACBCA", -"E. c #D4D7D5", -"F. c #DEDFDE", -"G. c #EBEDEC", -"H. c #E9EAE9", -"I. c #D5D8D5", -"J. c #CCCFCC", -"K. c #D3D5D3", -"L. c #CFD1CF", -"M. c #C4C6C3", -"N. c #BBBDBB", -"O. c #B8B9B8", -"P. c #D0D2D1", -"Q. c #DADCDA", -"R. c #E5E6E6", -"S. c #DFE0DF", -"T. c #D2D4D2", -"U. c #CCCDCC", -"V. c #DADBDA", -"W. c #D5D6D5", -"X. c #C6C8C6", -"Y. c #C3C6C3", -"Z. c #B0B1B0", -"`. c #B2B4B3", -" + c #BFC0BF", -".+ c #EEF0EF", -"++ c #DBDDDC", -"@+ c #CED1CE", -"#+ c #A9AAA9", -"$+ c #A5A6A5", -"%+ c #B1B2B1", -"&+ c #C1C2C1", -"*+ c #CCCECB", -"=+ c #C5C7C4", -"-+ c #BBBCBB", -";+ c #B9BCBA", -">+ c #C5C7C6", -",+ c #E3E5E3", -"'+ c #EFF1F0", -")+ c #D1D4D2", -"!+ c #BFC1BF", -"~+ c #B6B8B6", -"{+ c #999999", -"]+ c #828382", -"^+ c #717372", -"/+ c #727473", -"(+ c #7E807F", -"_+ c #BCBEBC", -":+ c #DBDCDA", -"<+ c #DFE1DF", -"[+ c #D6D8D6", -"}+ c #B0B0B0", -"|+ c #AFAFAF", -"1+ c #CDCFCE", -"2+ c #D7D9D8", -"3+ c #E6E8E7", -"4+ c #E7E9E7", -"5+ c #DADDDB", -"6+ c #CDD1CE", -"7+ c #C7CBC8", -"8+ c #AFB1B0", -"9+ c #A0A2A0", -"0+ c #8B8E8C", -"a+ c #767877", -"b+ c #797B7A", -"c+ c #A2A3A2", -"d+ c #B1B1B0", -"e+ c #C7CAC9", -"f+ c #D3D5D4", -"g+ c #DDE0DE", -"h+ c #D7DAD8", -"i+ c #D0D4D1", -"j+ c #C3C5C4", -"k+ c #BEC0BE", -"l+ c #B4B7B4", -"m+ c #A8A9A8", -"n+ c #949795", -"o+ c #7F8180", -"p+ c #818281", -"q+ c #AEB0AF", -"r+ c #C9CAC9", -"s+ c #C3C5C2", -"t+ c #DEDFDD", -"u+ c #BEBFBD", -"v+ c #CFD0CF", -"w+ c #E7E9E8", -"x+ c #C0C3C1", -"y+ c #B9BBBA", -"z+ c #AEAFAE", -"A+ c #9DA09E", -"B+ c #888B89", -"C+ c #757776", -"D+ c #696B6A", -"E+ c #616261", -"F+ c #5D5F5E", -"G+ c #656766", -"H+ c #707271", -"I+ c #787A79", -"J+ c #959796", -"K+ c #CCCCCC", -"L+ c #C3C4C2", -"M+ c #D0D3D0", -"N+ c #C9CDCA", -"O+ c #C2C6C4", -"P+ c #BDBFBE", -"Q+ c #B3B6B4", -"R+ c #A5A7A6", -"S+ c #929492", -"T+ c #7C7E7D", -"U+ c #6E706F", -"V+ c #616362", -"W+ c #535453", -"X+ c #444544", -"Y+ c #363736", -"Z+ c #272727", -"`+ c #191919", -" @ c #121212", -".@ c #181818", -"+@ c #363737", -"@@ c #737574", -"#@ c #888A89", -"$@ c #E0E0E0", -"%@ c #B7B8B7", -"&@ c #ECEEED", -"*@ c #D9DCD9", -"=@ c #D2D6D3", -"-@ c #B9BBB9", -";@ c #ACADAD", -">@ c #9B9D9C", -",@ c #868987", -"'@ c #676968", -")@ c #595B5A", -"!@ c #4A4C4B", -"~@ c #3D3D3D", -"{@ c #2D2E2E", -"]@ c #202020", -"^@ c #131313", -"/@ c #141414", -"(@ c #424342", -"_@ c #A8A9A9", -":@ c #D6D7D5", -"<@ c #C7C8C6", -"[@ c #B4B6B5", -"}@ c #BEBFBE", -"|@ c #CACCCB", -"1@ c #D6D7D6", -"2@ c #D0D3D1", -"3@ c #BDBFBD", -"4@ c #A3A4A3", -"5@ c #909291", -"6@ c #5F6060", -"7@ c #515252", -"8@ c #434444", -"9@ c #343534", -"0@ c #252525", -"a@ c #171717", -"b@ c #373838", -"c@ c #A1A2A2", -"d@ c #D1D3D2", -"e@ c #E5E7E6", -"f@ c #D3D6D3", -"g@ c #C0C2C0", -"h@ c #B8BAB8", -"i@ c #AAACAB", -"j@ c #999A9A", -"k@ c #848685", -"l@ c #575958", -"m@ c #4A4B4A", -"n@ c #393A3A", -"o@ c #2C2C2C", -"p@ c #1E1E1E", -"q@ c #4B4C4B", -"r@ c #989999", -"s@ c #BABBBA", -"t@ c #DCDDDC", -"u@ c #C2C4C1", -"v@ c #BFC0BE", -"w@ c #B5B6B5", -"x@ c #C0C0C0", -"y@ c #D5D8D6", -"z@ c #B0B2B0", -"A@ c #A1A4A2", -"B@ c #8E918F", -"C@ c #777978", -"D@ c #6C6E6D", -"E@ c #5E605F", -"F@ c #4F5050", -"G@ c #404141", -"H@ c #323332", -"I@ c #242524", -"J@ c #151615", -"K@ c #2F2F2F", -"L@ c #454545", -"M@ c #5B5B5B", -"N@ c #727272", -"O@ c #878787", -"P@ c #989898", -"Q@ c #A0A0A0", -"R@ c #9C9C9C", -"S@ c #636363", -"T@ c #303030", -"U@ c #6D6F6E", -"V@ c #8F908F", -"W@ c #DCDCDC", -"X@ c #C2C3C1", -"Y@ c #A5A5A5", -"Z@ c #DCDEDD", -"`@ c #D2D5D3", -" # c #A9ABA9", -".# c #979997", -"+# c #818482", -"@# c #636564", -"## c #565756", -"$# c #474847", -"%# c #393939", -"&# c #2A2A2A", -"*# c #1C1C1C", -"=# c #262626", -"-# c #3C3C3C", -";# c #535353", -"># c #6A6A6A", -",# c #808080", -"'# c #969696", -")# c #A4A4A4", -"!# c #A3A3A3", -"~# c #A2A2A2", -"{# c #A1A1A1", -"]# c #9F9F9F", -"^# c #9E9E9E", -"/# c #717171", -"(# c #232323", -"_# c #484949", -":# c #868786", -"<# c #D7D8D7", -"[# c #A7A7A7", -"}# c #AEAEAE", -"|# c #C0C2C1", -"1# c #CDCECE", -"2# c #DBDEDC", -"3# c #CED2CF", -"4# c #C2C4C3", -"5# c #BABDBA", -"6# c #AFB0AF", -"7# c #A0A2A1", -"8# c #6A6C6B", -"9# c #5C5E5D", -"0# c #4D4F4E", -"a# c #3F4040", -"b# c #303131", -"c# c #222222", -"d# c #151515", -"e# c #1B1B1B", -"f# c #333333", -"g# c #4A4A4A", -"h# c #616161", -"i# c #777777", -"j# c #8E8E8E", -"k# c #A8A8A8", -"l# c #A6A6A6", -"m# c #9D9D9D", -"n# c #8F8F8F", -"o# c #2E2E2E", -"p# c #2E2F2F", -"q# c #A6A8A7", -"r# c #C7C7C6", -"s# c #C1C3C1", -"t# c #AAAAAA", -"u# c #BBBEBB", -"v# c #A7A9A7", -"w# c #959696", -"x# c #6F7170", -"y# c #636464", -"z# c #545555", -"A# c #454646", -"B# c #373837", -"C# c #292929", -"D# c #1A1A1A", -"E# c #282828", -"F# c #404040", -"G# c #595959", -"H# c #6F6F6F", -"I# c #858585", -"J# c #A9A9A9", -"K# c #9B9B9B", -"L# c #9A9A9A", -"M# c #7B7D7C", -"N# c #9FA09F", -"O# c #BFC0C0", -"P# c #C8C9C8", -"Q# c #B6B8B7", -"R# c #E0E3E1", -"S# c #C1C4C2", -"T# c #BABCBB", -"U# c #8A8A8A", -"V# c #5A5B5A", -"W# c #4C4D4C", -"X# c #3D3E3E", -"Y# c #2F302F", -"Z# c #363636", -"`# c #4F4F4F", -" $ c #676767", -".$ c #7D7D7D", -"+$ c #959595", -"@$ c #ACACAC", -"#$ c #ABABAB", -"$$ c #979797", -"%$ c #797979", -"&$ c #646665", -"*$ c #969796", -"=$ c #DCDDDB", -"-$ c #CBCCCA", -";$ c #C1C2C0", -">$ c #929292", -",$ c #E9EBEA", -"'$ c #BDC1BF", -")$ c #B4B6B4", -"!$ c #929493", -"~$ c #606261", -"{$ c #525453", -"]$ c #353535", -"^$ c #262727", -"/$ c #161616", -"($ c #5D5D5D", -"_$ c #757575", -":$ c #8D8D8D", -"<$ c #ADADAD", -"[$ c #949494", -"}$ c #383838", -"|$ c #4F504F", -"1$ c #747675", -"2$ c #8C8D8D", -"3$ c #AEAFAF", -"4$ c #ABAEAC", -"5$ c #9B9D9B", -"6$ c #858887", -"7$ c #676867", -"8$ c #595A59", -"9$ c #494B4A", -"0$ c #3B3C3C", -"a$ c #2D2D2D", -"b$ c #1F1F1F", -"c$ c #3B3B3B", -"d$ c #6B6B6B", -"e$ c #848484", -"f$ c #939393", -"g$ c #6C6C6C", -"h$ c #D3D4D3", -"i$ c #DEE0DF", -"j$ c #C3C7C3", -"k$ c #A2A4A3", -"l$ c #909191", -"m$ c #505251", -"n$ c #333434", -"o$ c #313131", -"p$ c #626262", -"q$ c #7B7B7B", -"r$ c #919191", -"s$ c #909090", -"t$ c #A4A6A5", -"u$ c #CACBCB", -"v$ c #C6C7C5", -"w$ c #B7BAB8", -"x$ c #999B9A", -"y$ c #838483", -"z$ c #575858", -"A$ c #3A3B3A", -"B$ c #2B2C2B", -"C$ c #1D1D1D", -"D$ c #7A7C7B", -"E$ c #9C9D9D", -"F$ c #DADAD9", -"G$ c #C8C9C7", -"H$ c #7C7C7C", -"I$ c #BCBFBC", -"J$ c #C6C8C7", -"K$ c #A1A3A1", -"L$ c #767977", -"M$ c #6B6D6C", -"N$ c #5E5F5E", -"O$ c #323232", -"P$ c #242424", -"Q$ c #343434", -"R$ c #4E4E4E", -"S$ c #686868", -"T$ c #818181", -"U$ c #B8B8B8", -"V$ c #B7B7B7", -"W$ c #434343", -"X$ c #939494", -"Y$ c #DCDCDB", -"Z$ c #CCCDCB", -"`$ c #BEC1BE", -" % c #A8ABAA", -".% c #808180", -"+% c #545655", -"@% c #464747", -"#% c #383939", -"$% c #292A2A", -"%% c #5E5E5E", -"&% c #BBBBBB", -"*% c #BABABA", -"=% c #B9B9B9", -"-% c #8C8C8C", -";% c #8B8B8B", -">% c #494A4A", -",% c #8A8B8A", -"'% c #B1B2B2", -")% c #D0D1CF", -"!% c #C0C1BF", -"~% c #A7A8A7", -"{% c #C2C6C3", -"]% c #AFB1AF", -"^% c #9FA19F", -"/% c #8A8D8B", -"(% c #6A6B6B", -"_% c #5B5D5C", -":% c #4D4E4D", -"<% c #3F403F", -"[% c #303130", -"}% c #212121", -"|% c #545454", -"1% c #6D6D6D", -"2% c #868686", -"3% c #BDBDBD", -"4% c #BCBCBC", -"5% c #898989", -"6% c #828383", -"7% c #AAABAA", -"8% c #D4D4D3", -"9% c #A2A4A2", -"0% c #C4C8C5", -"a% c #BFC2C0", -"b% c #A7A9A8", -"c% c #949695", -"d% c #7D7E7E", -"e% c #626463", -"f% c #535454", -"g% c #444545", -"h% c #272827", -"i% c #191A19", -"j% c #494949", -"k% c #BFBFBF", -"l% c #BEBEBE", -"m% c #888888", -"n% c #7D7F7E", -"o% c #A2A3A3", -"p% c #C6C7C7", -"q% c #D7D7D7", -"r% c #C6C7C6", -"s% c #C1C5C3", -"t% c #AEB0AE", -"u% c #9C9F9D", -"v% c #878A88", -"w% c #686A69", -"x% c #3D3E3D", -"y% c #3E3E3E", -"z% c #737373", -"A% c #C1C1C1", -"B% c #9A9B9A", -"C% c #BCBCBB", -"D% c #D1D5D2", -"E% c #A5A7A5", -"F% c #929392", -"G% c #7B7C7B", -"H% c #5F6160", -"I% c #515352", -"J% c #434544", -"K% c #252626", -"L% c #4D4D4D", -"M% c #838383", -"N% c #C4C4C4", -"O% c #C3C3C3", -"P% c #828282", -"Q% c #464646", -"R% c #B6B7B7", -"S% c #C0C0BF", -"T% c #B8BBB9", -"U% c #9B9C9C", -"V% c #858886", -"W% c #666767", -"X% c #585959", -"Y% c #3A3B3B", -"Z% c #2C2D2C", -"`% c #424242", -" & c #787878", -".& c #C5C5C5", -"+& c #C6C6C6", -"@& c #5C5C5C", -"#& c #878888", -"$& c #AFB0B0", -"%& c #D8D8D7", -"&& c #C0C0BE", -"*& c #A3A4A4", -"=& c #CBCECC", -"-& c #A3A5A4", -";& c #787979", -">& c #505150", -",& c #414242", -"'& c #525252", -")& c #CACACA", -"!& c #C9C9C9", -"~& c #C8C8C8", -"{& c #7F7F7F", -"]& c #808281", -"^& c #A5A5A4", -"/& c #B8BBBA", -"(& c #AAABAB", -"_& c #828482", -":& c #565857", -"<& c #484948", -"[& c #393A39", -"}& c #2A2B2B", -"|& c #1C1D1D", -"1& c #CBCBCB", -"2& c #7E7E7E", -"3& c #7C7D7D", -"4& c #A0A1A0", -"5& c #C2C3C3", -"6& c #B3B4B2", -"7& c #5D5E5D", -"8& c #4E504F", -"9& c #3A3A3A", -"0& c #575757", -"a& c #747474", -"b& c #CFCFCF", -"c& c #CECECE", -"d& c #CDCDCD", -"e& c #979898", -"f& c #B9BABA", -"g& c #DBDBDA", -"h& c #C9C9C7", -"i& c #BFBFBE", -"j& c #BDBDBC", -"k& c #9D9F9D", -"l& c #4B4B4B", -"m& c #D1D1D1", -"n& c #D0D0D0", -"o& c #7A7A7A", -"p& c #484848", -"q& c #8D8F8E", -"r& c #B4B5B5", -"s& c #B0B1B1", -"t& c #939593", -"u& c #5A5C5B", -"v& c #4C4D4D", -"w& c #3E3F3E", -"x& c #202120", -"y& c #D3D3D3", -"z& c #D2D2D2", -"A& c #3E3F3F", -"B& c #858786", -"C& c #ADAEAE", -"D& c #D6D6D5", -"E& c #D1D1D0", -"F& c #505050", -"G& c #6E6E6E", -"H& c #D6D6D6", -"I& c #D5D5D5", -"J& c #D4D4D4", -"K& c #767676", -"L& c #D5D5D4", -"M& c #C4C4C3", -"N& c #BFBFBD", -"O& c #444444", -"P& c #D8D8D8", -"Q& c #9E9F9E", -"R& c #BEBFBF", -"S& c #C6C6C5", -"T& c #373737", -"U& c #555555", -"V& c #DBDBDB", -"W& c #DADADA", -"X& c #D9D9D9", -"Y& c #959695", -"Z& c #B8B9B9", -"`& c #C9C9C8", -" * c #BEBEBD", -".* c #8C8E8D", -"+* c #DDDDDD", -"@* c #707070", -"#* c #4D4E4E", -"$* c #8B8C8B", -"%* c #CECECC", -"&* c #C1C1BF", -"** c #B2B5B2", -"=* c #DEDEDE", -"-* c #5A5A5A", -";* c #838584", -">* c #ABACAC", -",* c #D2D2D0", -"'* c #696969", -")* c #A4A5A4", -"!* c #BEBEBC", -"~* c #ACACAB", -"{* c #B1B3B1", -"]* c #B9B9B7", -"^* c #A6A8A8", -"/* c #929393", -"(* c #BEBDBC", -"_* c #747574", -":* c #666666", -"<* c #474848", -"[* c #CECECD", -"}* c #888988", -"|* c #656565", -"1* c #646464", -"2* c #585858", -"3* c #333433", -"4* c #818382", -"5* c #D2D2D1", -"6* c #C2C2C0", -"7* c #9C9D9C", -"8* c #D6D5D5", -"9* c #C4C3C3", -"0* c #606060", -"a* c #5F5F5F", -"b* c #999A99", -"c* c #BDBDBB", -"d* c #BBBBBA", -"e* c #8F9190", -"f* c #CACAC9", -"g* c #BDBCBB", -"h* c #868887", -"i* c #A6A7A6", -"j* c #A6A7A7", -"k* c #858685", -"l* c #C0C1C1", -"m* c #D7D6D5", -"n* c #C4C3C2", -"o* c #BCBCBA", -"p* c #B2B2B1", -"q* c #505250", -"r* c #5F605F", -"s* c #565656", -"t* c #D9D9D8", -"u* c #C7C6C5", -"v* c #BBBBB9", -"w* c #505151", -"x* c #CBCACA", -"y* c #BCBBBA", -"z* c #C3C6C4", -"A* c #9A9C9A", -"B* c #3C3D3C", -"C* c #D4D5D5", -"D* c #CFCFCE", -"E* c #A5A4A4", -"F* c #515151", -"G* c #D3D3D2", -"H* c #A9AAA8", -"I* c #9C9E9D", -"J* c #BDBEBE", -"K* c #A6AAA7", -"L* c #4C4C4C", -"M* c #777878", -"N* c #C7C6C6", -"O* c #BCBAB9", -"P* c #BCBABA", -"Q* c #8C8D8C", -"R* c #EAEAEA", -"S* c #CCCBCA", -"T* c #BBBAB9", -"U* c #717271", -"V* c #FCFCFC", -"W* c #FFFFFF", -"X* c #FEFEFE", -"Y* c #F4F4F4", -"Z* c #E7E7E7", -"`* c #D0D0CF", -" = c #C0BFBE", -".= c #575957", -"+= c #F5F5F5", -"@= c #F0F0F0", -"#= c #E3E3E3", -"$= c #2B2B2B", -"%= c #D4D3D2", -"&= c #C2C0BF", -"*= c #ABADAC", -"== c #EDEDED", -"-= c #F6F6F6", -";= c #414141", -">= c #9A9C9B", -",= c #D8D7D6", -"'= c #B8B7B6", -")= c #FDFDFD", -"!= c #E2E2E2", -"~= c #919292", -"{= c #C8C7C6", -"]= c #BBB9B9", -"^= c #F7F7F7", -"/= c #E9E9E9", -"(= c #454645", -"_= c #878988", -":= c #D7D6D6", -"<= c #CCCCCB", -"[= c #BBB9B8", -"}= c #E1E1E1", -"|= c #D1D0CF", -"1= c #E8E8E8", -"2= c #D4D3D3", -"3= c #C2C0C0", -"4= c #BAB9B8", -"5= c #AEAFAD", -"6= c #8F918F", -"7= c #F9F9F9", -"8= c #979998", -"9= c #D9D7D7", -"0= c #C5C3C2", -"a= c #B9B8B7", -"b= c #7C7D7A", -"c= c #F8F8F8", -"d= c #8E908F", -"e= c #C9C7C6", -"f= c #BAB8B8", -"g= c #CDCCCB", -"h= c #BAB8B7", -"i= c #727372", -"j= c #ECECEC", -"k= c #FAFAFA", -"l= c #D1D0D0", -"m= c #BFBEBD", -"n= c #A7A7A6", -"o= c #8E8F8E", -"p= c #9E9F9F", -"q= c #D5D4D4", -"r= c #C1C0C0", -"s= c #BDBEBD", -"t= c #A4A4A3", -"u= c #D8D7D7", -"v= c #B1B3B0", -"w= c #E5E5E5", -"x= c #8B8D8C", -"y= c #C9C8C7", -"z= c #B9B7B7", -"A= c #A5A6A4", -"B= c #E6E6E6", -"C= c #474747", -"D= c #D3D2D2", -"E= c #CECDCC", -"F= c #BEBCBB", -"G= c #A4A3A3", -"H= c #F3F3F3", -"I= c #B9B7B6", -"J= c #AAA9A8", -"K= c #C1BFBF", -"L= c #B4B3B2", -"M= c #707270", -"N= c #B7B8B8", -"O= c #C5C3C3", -"P= c #F2F2F2", -"Q= c #B0B2B1", -"R= c #CAC9C8", -"S= c #B9B6B6", -"T= c #848584", -"U= c #FBFBFB", -"V= c #343535", -"W= c #A9AAAA", -"X= c #B8B6B6", -"Y= c #A5A5A3", -"Z= c #3F3F3F", -"`= c #C5C6C5", -" - c #D3D1D1", -".- c #C0BEBD", -"+- c #B8B6B5", -"@- c #ADACAB", -"#- c #B6B7B4", -"$- c #ADAEAD", -"%- c #D7D5D5", -"&- c #B6B5B4", -"*- c #AAACAA", -"=- c #BABBB9", -"-- c #E4E4E4", -";- c #575857", -">- c #909190", -",- c #C6C4C4", -"'- c #9D9E9D", -")- c #C1C1C0", -"!- c #424343", -"~- c #B8B5B5", -"{- c #A7A8A8", -"]- c #CFCDCD", -"^- c #BDBBBA", -"/- c #9FA1A0", -"(- c #D3D2D1", -"_- c #BFBDBD", -":- c #B8B5B4", -"<- c #AFAEAE", -"[- c #656765", -"}- c #DFDFDF", -"|- c #969897", -"1- c #C3C0C0", -"2- c #B7B5B4", -"3- c #8D8E8E", -"4- c #C7C5C5", -"5- c #BAB7B7", -"6- c #B7B4B4", -"7- c #949493", -"8- c #CCCAC9", -"9- c #BCB9B8", -"0- c #A7A6A6", -"a- c #BABAB9", -"b- c #D0CECE", -"c- c #AFB0AE", -"d- c #9D9E9E", -"e- c #D4D2D2", -"f- c #B7B4B3", -"g- c #B1AFAF", -"h- c #949594", -"i- c #969695", -"j- c #D7D7D6", -"k- c #C8C5C5", -"l- c #B7B3B3", -"m- c #878785", -"n- c #828483", -"o- c #D2D1D1", -"p- c #CCCACA", -"q- c #BBB8B7", -"r- c #B6B3B3", -"s- c #A6A5A5", -"t- c #D1CFCE", -"u- c #BDBABA", -"v- c #A9A8A8", -"w- c #9B9C9B", -"x- c #BBBCBC", -"y- c #D5D3D3", -"z- c #C0BDBD", -"A- c #B4B2B2", -"B- c #919392", -"C- c #C3C1C1", -"D- c #B6B3B2", -"E- c #BCBDBB", -"F- c #A1A1A0", -"G- c #C8C6C6", -"H- c #B6B2B2", -"I- c #B3B3B1", -"J- c #313232", -"K- c #CDCBCB", -"L- c #BBB8B8", -"M- c #A9A9A8", -"N- c #1D1E1D", -"O- c #D1CFCF", -"P- c #B5B2B2", -"Q- c #ACAAAA", -"R- c #D6D4D4", -"S- c #90908E", -"T- c #C5C1C1", -"U- c #B8B4B4", -"V- c #B5B2B1", -"W- c #7D7D7B", -"X- c #C9C7C7", -"Y- c #B5B1B1", -"Z- c #AAA9A9", -"`- c #BDBAB9", -" ; c #AEACAC", -".; c #B6B6B4", -"+; c #ABAAAA", -"@; c #C5C2C2", -"#; c #B4B1B1", -"$; c #B3B2B2", -"%; c #ADADAC", -"&; c #B7B7B6", -"*; c #CAC7C7", -"=; c #B9B5B5", -"-; c #B4B0B0", -";; c #A3A2A1", -">; c #C9C8C8", -",; c #CFCCCC", -"'; c #BBB7B7", -"); c #B0AEAE", -"!; c #868685", -"~; c #C1BEBE", -"{; c #494A49", -"]; c #898B8A", -"^; c #C6C3C3", -"/; c #B4AFB0", -"(; c #B5B0B1", -"_; c #CBC8C8", -":; c #B4AFAF", -"<; c #919190", -"[; c #B3AFAF", -"}; c #A9A8A7", -"|; c #D4D1D2", -"1; c #BEBABB", -"2; c #B2AFAF", -"3; c #B1B0AF", -"4; c #C2BEBE", -"5; c #A8A7A5", -"6; c #C7C4C4", -"7; c #B3AEAF", -"8; c #9D9C9A", -"9; c #CBC9C9", -"0; c #B3AEAE", -"a; c #A5A3A3", -"b; c #8F8F8D", -"c; c #D0CDCD", -"d; c #AAA8A8", -"e; c #7F7F7D", -"f; c #D5D2D2", -"g; c #525352", -"h; c #D6D4D5", -"i; c #C3BFBF", -"j; c #B5B0B0", -"k; c #B2AEAE", -"l; c #898988", -"m; c #C8C4C4", -"n; c #B7B2B2", -"o; c #B2ADAE", -"p; c #9E9D9D", -"q; c #CDC9C9", -"r; c #B9B4B4", -"s; c #A7A5A6", -"t; c #B4B2B1", -"u; c #D1CECE", -"v; c #B2ADAD", -"w; c #ADAAAA", -"x; c #ACAAA9", -"y; c #B8B7B7", -"z; c #D5D2D3", -"A; c #A1A0A0", -"B; c #BDBBBB", -"C; c #C4C0C0", -"D; c #B0AFAF", -"E; c #C9C5C5", -"F; c #B2ACAD", -"G; c #B1ACAD", -"H; c #C7C8C7", -"I; c #CDCACA", -"J; c #6E6F6F", -"K; c #D2CFCF", -"L; c #B1ACAC", -"M; c #AFACAC", -"N; c #7F7E7E", -"O; c #D6D3D4", -"P; c #BFBBBB", -"Q; c #969595", -"R; c #464847", -"S; c #C4C1C1", -"T; c #B1ABAC", -"U; c #B6B4B3", -"V; c #A8A7A7", -"W; c #C9C6C6", -"X; c #A5A3A4", -"Y; c #AEADAC", -"Z; c #B5B4B3", -"`; c #A1A3A2", -" > c #C3C4C3", -".> c #CECBCB", -"+> c #AAA8A7", -"@> c #D3D0D0", -"#> c #BCB7B7", -"$> c #B0ABAC", -"%> c #B1ADAE", -"&> c #9C9B9B", -"*> c #C0BBBC", -"=> c #B3ADAE", -"-> c #B0ABAB", -";> c #918F8D", -">> c #C6C1C2", -",> c #B5AFB0", -"'> c #B0AAAB", -")> c #CDCCCC", -"!> c #B6B1B2", -"~> c #767675", -"{> c #ABA8A8", -"]> c #8F8E8E", -"^> c #D4D1D1", -"/> c #BCB7B8", -"(> c #B1ADAD", -"_> c #A2A1A1", -":> c #D5D3D4", -"<> c #C1BCBD", -"[> c #B0AAAA", -"}> c #B1ABAB", -"|> c #B1AFAE", -"1> c #3B3C3B", -"2> c #D2D0D1", -"3> c #C6C2C3", -"4> c #B0A9AA", -"5> c #AFA9AA", -"6> c #A8A7A8", -"7> c #AAA7A7", -"8> c #CBC7C8", -"9> c #B7B1B2", -"0> c #A1A09E", -"a> c #ADA9AA", -"b> c #989494", -"c> c #BCB8B8", -"d> c #898787", -"e> c #C2BDBE", -"f> c #AFA8A9", -"g> c #353635", -"h> c #D0CFCF", -"i> c #C7C3C4", -"j> c #B4AEAF", -"k> c #878686", -"l> c #CCC8C9", -"m> c #A7A4A5", -"n> c #9B9A9A", -"o> c #B9B3B4", -"p> c #AEA8A9", -"q> c #AEABAB", -"r> c #ABAAA9", -"s> c #BEB8B9", -"t> c #B1AAAB", -"u> c #ADA9A9", -"v> c #B6B4B4", -"w> c #D3D1D2", -"x> c #C3BEBF", -"y> c #AEA7A9", -"z> c #A5A2A2", -"A> c #2F3030", -"B> c #AEA7A8", -"C> c #9E9B9B", -"D> c #C2C3C2", -"E> c #B6B1B1", -"F> c #A8A5A6", -"G> c #939090", -"H> c #666867", -"I> c #B9B3B5", -"J> c #AFA8AA", -"K> c #616060", -"L> c #D4D2D3", -"M> c #BFB9BA", -"N> c #AFAAAA", -"O> c #7D7C7C", -"P> c #C4BFC0", -"Q> c #B2ABAC", -"R> c #ADA7A8", -"S> c #ADA6A8", -"T> c #AAA9AA", -"U> c #949393", -"V> c #C9C4C5", -"W> c #ADA6A7", -"X> c #B4B1B0", -"Y> c #CDC9CA", -"Z> c #B7B0B1", -"`> c #B2B0B0", -" , c #D2D0D0", -"., c #BAB4B6", -"+, c #A9A5A5", -"@, c #BFBABB", -"#, c #A39E9E", -"$, c #C5C0C1", -"%, c #ADA5A7", -"&, c #A9A7A7", -"*, c #989595", -"=, c #CAC5C6", -"-, c #B4ADAE", -";, c #ACA5A7", -">, c #A5A2A3", -",, c #CFCBCB", -"', c #ACA5A6", -"), c #ABA8A9", -"!, c #5B5C5C", -"~, c #BBB5B6", -"{, c #AEA6A8", -"], c #8C8B8B", -"^, c #A09F9F", -"/, c #ACA4A6", -"(, c #B0ACAC", -"_, c #CAC6C6", -":, c #A6A3A4", -"<, c #B7B5B5", -"[, c #D0CCCC", -"}, c #B7B0B2", -"|, c #ACA4A5", -"1, c #555756", -"2, c #BCB5B7", -"3, c #9E9A9A", -"4, c #B7B2B3", -"5, c #414241", -"6, c #C2BCBD", -"7, c #B0A8AA", -"8, c #ABA4A5", -"9, c #ABA3A5", -"0, c #CCCBCB", -"a, c #C7C2C2", -"b, c #B2AAAB", -"c, c #A6A4A4", -"d, c #696868", -"e, c #CCC7C8", -"f, c #848383", -"g, c #B8B1B2", -"h, c #ADA8A9", -"i, c #999898", -"j, c #BDB6B8", -"k, c #ABA3A4", -"l, c #ADA5A6", -"m, c #C3BDBE", -"n, c #ABA2A4", -"o, c #A8A8A7", -"p, c #ADA8A8", -"q, c #B5B1B2", -"r, c #C8C2C4", -"s, c #B2AAAC", -"t, c #AAA2A4", -"u, c #A4A2A3", -"v, c #A8A4A4", -"w, c #CDC8C8", -"x, c #B5ADAE", -"y, c #A9A5A6", -"z, c #A29E9E", -"A, c #D2CECF", -"B, c #B9B1B3", -"C, c #ACA3A5", -"D, c #9A9496", -"E, c #AAA2A3", -"F, c #AAA1A3", -"G, c #7B7A7A", -"H, c #BDB7B9", -"I, c #C4BEC0", -"J, c #9B999A", -"K, c #939191", -"L, c #BAB4B5", -"M, c #C9C3C4", -"N, c #979496", -"O, c #B5AEAF", -"P, c #A9A1A3", -"Q, c #D0CBCC", -"R, c #908F8F", -"S, c #AEAAAA", -"T, c #B1A8AA", -"U, c #D3CFD0", -"V, c #AAA6A6", -"W, c #B8B4B5", -"X, c #ADA4A6", -"Y, c #A9A1A2", -"Z, c #D7D3D4", -"`, c #787B7A", -" ' c #A4A0A0", -".' c #B6B2B3", -"+' c #B0ABAD", -"@' c #A9A0A2", -"#' c #BBB4B6", -"$' c #CBC6C7", -"%' c #656B69", -"&' c #8B8D8D", -"*' c #9F9999", -"=' c #767776", -"-' c #5C6260", -";' c #737776", -">' c #545353", -",' c #E8E6E6", -"'' c #ABA6A6", -")' c #656A68", -"!' c #A6A2A3", -"~' c #B7AFB1", -"{' c #E0DDDE", -"]' c #5F6664", -"^' c #8A8989", -"/' c #1F201F", -"(' c #5B5C5B", -"_' c #A9A4A5", -":' c #DAD6D7", -"<' c #C4BDBF", -"[' c #6A6E6D", -"}' c #9F9C9D", -"|' c #636463", -"1' c #A39E9F", -"2' c #F0EFEF", -"3' c #ABA5A6", -"4' c #2A2929", -"5' c #4F4D4D", -"6' c #757372", -"7' c #999496", -"8' c #CEC9CA", -"9' c #9EA0A0", -"0' c #403E3E", -"a' c #615E5F", -"b' c #7E7A7B", -"c' c #969192", -"d' c #A69EA0", -"e' c #B8B0B2", -"f' c #F2F0F1", -"g' c #A7A2A3", -"h' c #2F2E2E", -"i' c #525050", -"j' c #726F6F", -"k' c #8D8989", -"l' c #A09A9B", -"m' c #A9A2A3", -"n' c #ECEAEB", -"o' c #636967", -"p' c #A29D9D", -"q' c #1F1E1E", -"r' c #656263", -"s' c #827F80", -"t' c #989394", -"u' c #A6A0A1", -"v' c #AAA3A4", -"w' c #E1DEDF", -"x' c #A5A1A1", -"y' c #6E7271", -"z' c #343233", -"A' c #565454", -"B' c #767273", -"C' c #8F8A8C", -"D' c #A19B9C", -"E' c #C7C2C3", -"F' c #F1EFF0", -"G' c #B1A9AB", -"H' c #7B7F7D", -"I' c #686767", -"J' c #474546", -"K' c #696767", -"L' c #868283", -"M' c #9B9597", -"N' c #A8A1A3", -"O' c #A9A2A4", -"P' c #C2BBBD", -"Q' c #F6F5F5", -"R' c #777A78", -"S' c #757876", -"T' c #828080", -"U' c #373636", -"V' c #5B5858", -"W' c #7A7677", -"X' c #928D8E", -"Y' c #A49D9F", -"Z' c #C5BFC0", -"`' c #BAB3B5", -" ) c #5D6361", -".) c #4C4E4E", -"+) c #979595", -"@) c #272626", -"#) c #4D4B4C", -"$) c #6D6A6A", -"%) c #888384", -"&) c #9E9799", -"*) c #D1CCCD", -"=) c #F3F2F2", -"-) c #A7A5A5", -";) c #191818", -">) c #3C3C3B", -",) c #5E5D5D", -"') c #7D7979", -")) c #959091", -"!) c #A59EA0", -"~) c #E8E5E6", -"{) c #E9E6E7", -"]) c #B3ACAE", -"^) c #7E807E", -"/) c #595C5B", -"() c #ACA6A7", -"_) c #B3AFB0", -":) c #2D2C2C", -"<) c #504E4E", -"[) c #706D6D", -"}) c #8A8786", -"|) c #F7F6F6", -"1) c #CFCACB", -"2) c #AAA4A6", -"3) c #6C706E", -"4) c #606564", -"5) c #0A0B0B", -"6) c #A9A2A2", -"7) c #413F40", -"8) c #626060", -"9) c #807C7D", -"0) c #A7A1A1", -"a) c #B3ABAD", -"b) c #D5D0D1", -"c) c #DCD8D9", -"d) c #B4ADAF", -"e) c #929192", -"f) c #4A4E4D", -"g) c #A59F9F", -"h) c #313031", -"i) c #545152", -"j) c #737070", -"k) c #8E8B8B", -"l) c #A29C9D", -"m) c #AAA3A5", -"n) c #C3BCBE", -"o) c #EDEBEC", -"p) c #9B9999", -"q) c #626866", -"r) c #070707", -"s) c #434342", -"t) c #666364", -"u) c #848081", -"v) c #9A9596", -"w) c #B9B2B3", -"x) c #C6C0C1", -"y) c #F8F7F7", -"z) c #EFEDEE", -"A) c #DFDCDD", -"B) c #CDC8C9", -"C) c #A6A1A3", -"D) c #898A88", -"E) c #292C2B", -"F) c #343333", -"G) c #585556", -"H) c #787475", -"I) c #918C8D", -"J) c #A29C9E", -"K) c #ABA4A6", -"L) c #C0B9BB", -"M) c #CDC7C9", -"N) c #E7E5E5", -"O) c #F4F3F3", -"P) c #F5F3F4", -"Q) c #E8E6E7", -"R) c #9E9C9C", -"S) c #8F8D8E", -"T) c #464B49", -"U) c #000000", -"V) c #797878", -"W) c #252424", -"X) c #484647", -"Y) c #6B6868", -"Z) c #878283", -"`) c #9D9798", -" ! c #C7C1C2", -".! c #E2DFDF", -"+! c #EEEDED", -"@! c #F7F6F7", -"#! c #EEECED", -"$! c #D4D0D1", -"%! c #C7C1C3", -"&! c #A39FA0", -"*! c #979495", -"=! c #858584", -"-! c #515654", -";! c #050505", -">! c #918F8F", -",! c #393838", -"'! c #5D5A5A", -")! c #7C7879", -"!! c #948F90", -"~! c #A59FA0", -"{! c #C1BBBC", -"]! c #F5F4F4", -"^! c #B5AFB1", -"/! c #A7A3A3", -"(! c #9D9A9B", -"_! c #787A7A", -":! c #616462", -"~ c #858282", -",~ c #9C9798", -"'~ c #B9B2B4", -")~ c #E1DDDE", -"!~ c #EDECEC", -"~~ c #E2DFE0", -"{~ c #D5D1D2", -"]~ c #C8C2C3", -"^~ c #BDB8B8", -"/~ c #B0A9AB", -"(~ c #999696", -"_~ c #878886", -":~ c #5B615F", -"<~ c #4F5452", -"[~ c #373B3A", -"}~ c #161818", -"|~ c #353534", -"1~ c #5A5858", -"2~ c #7A7777", -"3~ c #938E8F", -"4~ c #A49E9F", -"5~ c #B3ACAD", -"6~ c #C0BABB", -"7~ c #DBD7D8", -"8~ c #DBD8D8", -"9~ c #B6B0B2", -"0~ c #A7A3A4", -"a~ c #9E9B9C", -"b~ c #7A7C7A", -"c~ c #5F6563", -"d~ c #535856", -"e~ c #484C4B", -"f~ c #3C403F", -"g~ c #2F3130", -"h~ c #1E201F", -"i~ c #252625", -"j~ c #4B4949", -"k~ c #888485", -"l~ c #9E9999", -"m~ c #AEA5A7", -"n~ c #BBB3B5", -"o~ c #EFEDED", -"p~ c #EEECEC", -"q~ c #BCB6B7", -"r~ c #979695", -"s~ c #6A6E6C", -"t~ c #585E5C", -"u~ c #4D5250", -"v~ c #424544", -"w~ c #343836", -"x~ c #262827", -"y~ c #060606", -"z~ c #C2BBBC", -"A~ c #888686", -"B~ c #3A3939", -"C~ c #5E5C5C", -"D~ c #7D797A", -"E~ c #969191", -"F~ c #A7A0A1", -"G~ c #B5ADAF", -"H~ c #DCD9D9", -"I~ c #E9E7E7", -"J~ c #F4F2F3", -"K~ c #E7E4E5", -"L~ c #B5AFAF", -"M~ c #A7A2A4", -"N~ c #9D9A9A", -"O~ c #8D8C8C", -"P~ c #5E6462", -"Q~ c #525856", -"R~ c #474B4A", -"S~ c #3A3E3C", -"T~ c #2C302F", -"U~ c #1D201E", -"V~ c #0C0E0E", -"W~ c #535252", -"X~ c #4E4C4D", -"Y~ c #6F6D6E", -"Z~ c #8B8788", -"`~ c #A09A9C", -" { c #AFA7A9", -".{ c #BCB5B6", -"+{ c #CAC4C5", -"@{ c #D7D2D3", -"#{ c #ECEBEB", -"${ c #DFDCDC", -"%{ c #BAB5B5", -"&{ c #A29E9F", -"*{ c #808282", -"={ c #676C6A", -"-{ c #575C5A", -";{ c #4C504F", -">{ c #3F4342", -",{ c #323634", -"'{ c #252826", -"){ c #030303", -"!{ c #CECACA", -"~{ c #5C5B5B", -"{{ c #403E3F", -"]{ c #625F60", -"^{ c #989395", -"/{ c #A8A2A3", -"({ c #B6AFB0", -"_{ c #F2F1F1", -":{ c #E5E2E3", -"<{ c #CBC5C6", -"[{ c #C0BABC", -"}{ c #A6A1A2", -"|{ c #747676", -"1{ c #5A605E", -"2{ c #444A48", -"3{ c #383C3B", -"4{ c #2B2E2D", -"5{ c #1C1E1E", -"6{ c #0B0B0B", -"7{ c #999697", -"8{ c #302F30", -"9{ c #545252", -"0{ c #737071", -"a{ c #8F8B8C", -"b{ c #BEB7B9", -"c{ c #B8B2B4", -"d{ c #ABA6A7", -"e{ c #949391", -"f{ c #636966", -"g{ c #565C5A", -"h{ c #4A4F4E", -"i{ c #3E4341", -"j{ c #313534", -"k{ c #232524", -"l{ c #131413", -"m{ c #010303", -"n{ c #D8D6D6", -"o{ c #B8B2B2", -"p{ c #A8A3A4", -"q{ c #928E8F", -"r{ c #7F7C7C", -"s{ c #838081", -"t{ c #A19D9D", -"u{ c #D2CECE", -"v{ c #B2ABAD", -"w{ c #A5A0A1", -"x{ c #999797", -"y{ c #707473", -"z{ c #4F5553", -"A{ c #444847", -"B{ c #373B39", -"C{ c #1A1B1B", -"D{ c #080A08", -"E{ c #DFDDDD", -"F{ c #BFB9BB", -"G{ c #B8B1B3", -"H{ c #918F90", -"I{ c #545957", -"J{ c #494E4C", -"K{ c #3C4140", -"L{ c #2F3332", -"M{ c #212322", -"N{ c #101212", -"O{ c #010101", -"P{ c #D5D1D1", -"Q{ c #AFA9AB", -"R{ c #A3A0A1", -"S{ c #989797", -"T{ c #595F5D", -"U{ c #4E5352", -"V{ c #434745", -"W{ c #353937", -"X{ c #272929", -"Y{ c #171A19", -"Z{ c #060707", -"`{ c #BBB5B7", -" ] c #DBD8D9", -".] c #E7E5E6", -"+] c #C2BDBD", -"@] c #B6B0B0", -"#] c #9F9B9B", -"$] c #777A79", -"%] c #3A3F3E", -"&] c #2D3130", -"*] c #202121", -"=] c #0F100F", -"-] c #AFA7A8", -";] c #C9C3C5", -">] c #E3E0E0", -",] c #F0EEEE", -"'] c #BBB6B7", -")] c #A3A09F", -"!] c #969494", -"~] c #818483", -"{] c #585D5B", -"]] c #414543", -"^] c #333635", -"/] c #040404", -"(] c #B1AEAE", -"_] c #EAE7E8", -":] c #F6F5F6", -"<] c #B3AEB0", -"[] c #9C9A9A", -"}] c #757877", -"|] c #525755", -"1] c #3A3D3C", -"2] c #2C2F2D", -"3] c #1D201F", -"4] c #0C0C0C", -"5] c #BEB9BA", -"6] c #BDB7B8", -"7] c #E4E1E2", -"8] c #ECEAEA", -"9] c #939292", -"0] c #818282", -"a] c #666B69", -"b] c #4B504F", -"c] c #3F4341", -"d] c #323635", -"e] c #242625", -"f] c #141514", -"g] c #C5C0C0", -"h] c #DEDBDC", -"i] c #D8D4D4", -"j] c #CBC5C7", -"k] c #9B9899", -"l] c #727674", -"m] c #444948", -"n] c #383B3A", -"o] c #2A2E2D", -"p] c #1B1C1C", -"q] c #0A0A0A", -"r] c #A19C9C", -"s] c #929191", -"t] c #646967", -"u] c #565B59", -"v] c #3D4241", -"w] c #303433", -"x] c #222424", -"y] c #111313", -"z] c #736E6E", -"A] c #F1F0F0", -"B] c #C3BEBE", -"C] c #D6D3D3", -"D] c #B1ABAD", -"E] c #A5A1A2", -"F] c #888788", -"G] c #4E5453", -"H] c #424746", -"I] c #191A1A", -"J] c #070808", -"K] c #444443", -"L] c #B2AFB0", -"M] c #DAD7D8", -"N] c #F4F3F4", -"O] c #9E9D9C", -"P] c #919090", -"Q] c #7B7E7D", -"R] c #484D4B", -"S] c #2F3230", -"T] c #202221", -"U] c #101010", -"V] c #4B4F4E", -"W] c #767777", -"X] c #E6E4E5", -"Y] c #DCD9DA", -"Z] c #C4BEBF", -"`] c #C8C3C4", -" ^ c #A4A1A2", -".^ c #979596", -"+^ c #595E5C", -"@^ c #4E5351", -"#^ c #424644", -"$^ c #272928", -"%^ c #181918", -"&^ c #9A9797", -"*^ c #E0DDDD", -"=^ c #EBE9EA", -"-^ c #8E8D8E", -";^ c #535857", -">^ c #3B3E3D", -",^ c #2D3030", -"'^ c #1E2020", -")^ c #0E0F0F", -"!^ c #272A28", -"~^ c #818080", -"{^ c #9A9697", -"]^ c #B7B3B4", -"^^ c #949292", -"/^ c #8A8788", -"(^ c #7F7E7F", -"_^ c #626765", -":^ c #575D5B", -"<^ c #4C514F", -"[^ c #404443", -"}^ c #252727", -"|^ c #151616", -"1^ c #363A38", -"2^ c #454948", -"3^ c #2B2F2D", -"4^ c #1C1F1D", -"5^ c #393D3C", -"6^ c #4A504E", -"7^ c #3E4342", -"8^ c #323533", -"9^ c #242525", -"0^ c #131414", -"a^ c #333735", -"b^ c #505553", -"c^ c #2A2D2D", -"d^ c #1A1C1B", -"e^ c #080A0A", -"f^ c #232624", -"g^ c #555A59", -"h^ c #3E4140", -"i^ c #111111", -"j^ c #464A49", -"k^ c #434746", -"l^ c #363938", -"m^ c #282B29", -"n^ c #1F2120", -"o^ c #4F5352", -"p^ c #2E3231", -"q^ c #0F1010", -"r^ c #1F2020", -"s^ c #4A4F4D", -"t^ c #555A58", -"u^ c #515655", -"v^ c #474C4B", -"w^ c #2D302F", -"x^ c #090A09", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" . . . . . . . . . ", -" + + @ # $ % & * = - ; > , ' ) ! ~ . . ", -" + . { ~ ] ^ / ( _ : < [ [ [ [ [ } } } } } | 1 2 3 4 5 . ", -" . . { @ 6 7 8 - 9 0 a [ [ [ b c d e f g h i j j k l m n o a b p q r 5 . ", -" . . { s t u v w x y | [ [ [ a 0 z A * B q C D D D E E E E E E E F F F F G _ b a p H I . . ", -" + . . 4 J K L A M N } [ [ [ | y O P Q R H C C C C D D D E E E E E E F F F F F F F F F F S ; b o T U . { ", -" + . @ # V 3 W X d Y b Z [ [ a 0 ` ...l +.C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.' #.a A $.+ . ", -" + . %.. ! $.&.*.=.-.;.Z Z Z Z < >.,.X T '.).C C C C C C C C C D D D E E E E E E E F F F F F F F F F F F F @.@.@.@.!.!.!.!.!.~.{.].^./.. ", -" { . @ ~ (._.& ; 1 :.a Z Z Z a <.1 - 2 [.}.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.1.1.1.1.2.0 -.3.{ . ", -" { . . 4.5.6.7.( p : o Z Z Z Z o 8.p 9.0.a.).|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.!.1.1.1.1.1.1.1.1.b.<.n c.. . ", -" . . . d.e.f., = 9 0 a Z Z Z g.:.h.i.j.g i k.k.k.|.|.|.|.|.|.C C C C C C C C C D D D D E E E E E E F F F F F F F F F F F F @.@.@.@.!.!.E |.|.|.F 1.1.1.1.1.1.1.1.l.l.l.l.m.0 > + . ", -" . . { + n.o.p.q.O y r.Z Z Z Z ;.-.s.t.u.v.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.E w.b.q x.y.m.C F 1.1.l.l.l.l.l.l.l.l.z.A.B.. { ", -" . . . ! C.D.E.F.M G.[ Z Z Z b H.d ; I.G i k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E E F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.!.1.1.F w.b.J.[.K.K.L.D.@.l.l.l.l.l.l.l.M.M.M.f : N.. ", -" { . { . O.l.P.Q.h.Y b Z Z Z [ G.R.S.* T.x.H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C D D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.1.1.1.1.1.1.1.F C S U.& ..V.W.x.X.l.l.M.M.M.Y.Y.Y.^.^.n T Z.{ ", -" + . { `.4 +y.2 s.-.;.Z Z Z Z .+c n ++f @+S H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.1.1.1.1.1.1.1.1.1.3.c.~ #+$+%+&+v > - ..*+=+M.Y.Y.Y.^.^.^.^.^.m.y -+. ", -" . . . @ ;+>+B ; ,+:.'+Z Z Z } 0 ` A * )+y.).).H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E E F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.!.1.1.1.1.1.l.!+~+#+{+]+^+^+^+^+^+^+^+/+(+_+:+<+[+b.^.^.^.^.^.^.^.^.^.^.e W { }+ ", -" . . |+{ 5.&+1+2+_ 3+o Z Z Z Z r.4+O 5+E.6+i 7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.1.1.1.3.$ 8+9+0+a+^+^+^+^+^+^+^+^+^+^+^+^+^+^+/+b+c+F.S.[.@.^.^.^.^.^.^.^.^.^.w._ d+. ", -" . { @ 4 e.e+f+i.z <.a Z Z Z a <.].g+h+i+h 7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.@.!.!.!.!.!.!.j+k+l+m+n+o+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+/+p+q+n ( r+^.^.^.^.^.^.^.^.s+s+t+u+. ", -" . + { { n.^.v+w O w+r.Z Z Z Z o 3+,.Q.0.'.7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E E F F F F F F F F F F F F @.@.@.@.!.!.!.x+y+z+A+B+C+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+D+E+F+G+H+^+^+^+^+^+I+J+K+F.2.l.^.^.^.s+s+s+L+L+L+L.R . { ", -" + . 4.4 C.' f A M G.} Z Z Z a :.,+- j.M+N+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F F F F F F F @.@.X.O+P+Q+R+S+T+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U+V+W+X+Y+Z+`+ @ @ @ @.@+@V+^+^+^+@@#@Z.$@& =+s+s+s+L+L+L+3.3.3.^.f /.. ", -" + . s { %@l.g X n Y .+Z Z Z Z &@8.,.*@=@a.7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E E F F F F F F F F F F F F @.&+-@;@>@,@@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+'@)@!@~@{@]@^@ @ @ @ @ @ @ @ @ @ @ @ @/@(@^+^+^+p+_@f+:@<@L+L+3.3.3.3.3.3.3.3.T . . ", -" . { @ [@}@|@1@=.-.;.Z Z Z Z b H.,+i.*.2@+.7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C D D D D E E E E E E F F F F F F F F F X.Y.3@# 4@5@b+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U+6@7@8@9@0@a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @b@^+^+T+c@u * f.3.3.3.3.3.3.3.3.3.3.y.~+. ", -" . + . s V >+d@++,+:.b Z Z Z Z ;.e@<+q.f@a.i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F F F F F F F 1.g@h@i@j@k@@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+G+l@m@n@o@p@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @q@^+I+r@s@t@K 3.3.3.3.3.3.3.u@u@u@s+v@. {+ ", -" . + { ~ w@x@x...p 3+&@Z Z Z Z .+Y d ; y@g N+i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).).H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E E E E E E F F F F o._+z@A@B@C@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+D@E@F@G@H@I@J@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+K@L@M@N@O@P@Q@R@O@S@T@ @ @ @ @ @ @.@U@C+V@w@W@R ^.3.3.3.u@u@u@X@X@X@X@X@+ Y@ ", -" + . . . y+w.T.Z@1 <.a Z Z Z } 0 ` =.t.`@v.i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E E F 1.g@~+ #.#+#^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+H+@###$#%#&#*# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@=#-#;#>#,#'#Y@Y@)#!#!#~#{#{#Q@]#^#^#/#(# @ @ @ @ @_#/+:#z+<#T.=+u@u@X@X@X@X@X@X@X@X@X@[#}# ", -" . { . ~ n.|#1#q.,.4+o Z Z Z Z r.w+x 2#I.3#N+i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D D E E E E E 4#5#6#7#0+C+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+8#9#0#a#b#c#d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @e#f#g#h#i#j#)#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@n#o# @ @ @ @p#^+o+q#7.:@r#X@X@X@X@X@X@X@X@s#s#s#t#. ", -" . + @ d.u#j 0.- ` 0 } Z Z Z a <.].A h+)+v.i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).).H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C C D D D E E !.J ] v#w#(+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+x#y#z#A#B#C#D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#E#F#G#H#I#m#t#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#{+n#(# @ @ @.@^+M#N#O#> P#X@X@X@X@X@s#s#s#s#s#s#Q#. ", -" %.4.h@f.* O y r.Z Z Z Z o : R#5+Q 6+i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C C C C C C D D S#T#z+A+U#@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+D+V#W#X#Y#]@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @p@Z#`# $.$+$t#@$#$t#t#J#J#J#k#[#[#l#Y@)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@$$$$%$^@ @ @ @&$C@*$(.=$-$L+s#s#s#s#s#s#s#s#s#;$ +. >$ ", -" ~ g@..,+r.Z Z '+,$1 .*.i+h i i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.|.C C C C C C C C 1.'$)$$+!$M#^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U+~${$X+]$^$.@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/$o@L@($_$:$)#|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$}$ @ @ @|$1$2$s V.v+^.s#s#s#s#s#s#;$;$;$;$;$3$)# ", -" 4 y.=.r.Z H.p *.6+i i i i i i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C C C F s#(.4$5$6$@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+7$8$9$0$a$b$^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @c#c$;#d$e$R@}+/.}+}+|+}#}#<$@$#$#$t#J#J#J#k#k#[#l#Y@Y@)#!#!#~#{#{#Q@]#^#^#m#R@R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$g$ @ @ @0$/+k@;@W.h$l.s#s#;$;$;$;$;$;$;$;$;$c+|+ ", -" { l.i$} ,$5+a.i i i i i i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.C C C j$k+@ k$l$b+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U@E@m$(@n$0@/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @.@o$g#p$q$f$#$+ . . { /.}+}+|+}#}#<$@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$U#d# @ @Z+^+(+t$u$[+v$;$;$;$;$;$;$;$;$;$;$;$<$. ", -" n.0.&@w+`@i i i i i i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.|.|.|.|.|.|.E s#w$i@x$y$^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+H+G+z$_#A$B$C$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@=#F#G#N@U#!#%.%.5 5 + . . { /./.}+|+}#}#<$@$@$#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$+$[$f$>$>$r$s$s$n#n#n#&# @ @d#U+D$E$c.F$G$;$;$;$;$;$;$;$;$;$;$;$h@. H$ ", -" I$h+;.0.i i i i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.k.k.k.k.k.k.|.|.J$j+C.%+K$n#L$^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+M$N$|$F#O$P$d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#Q$R$S$T$L#. U$V$V$V$%.%.5 + + . { /./.}+|+|+}#<$<$@$#$t#t#J#J#J#k#[#[#l#Y@)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@$$$$'#+$+$[$f$f$>$r$s$s$n#n#n#j#:$:$W$ @ @ @E@a+X$O.Y$Z$X@;$;$;$;$;$;$g@g@g@$.$.. [$ ", -" `$5+` v.i i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H k.k.k.k.k.k.k.k.k.E |#~+ %.#.%^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+x#&$+%@%#%$%D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@C#W$%%i#s$t#&%*%*%=%U$U$V$V$%.%.5 + + . { { /.}+|+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%M@ @ @ @>%@@,%'%> )%L+;$;$g@g@g@g@$.$.$.!%!%~%Y@ ", -" `$I.A i i i i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).).H H H H H H k.k.k.k.k.k.D {%6 ]%^%/%C+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+(%_%:%<%[%}%d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @p@}$|%1%2%~#=%3%3%4%&%*%*%=%U$U$V$V$V$%.5 + + . { { /.}+}+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#{#Q@]#^#^#m#R@R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$r$r$s$n#n#n#j#j#:$-%;%;%U#5%5%_$ @ @ @Z#/+6%7%& 8%l.g@g@$.$.$.!%!%!%!%!%!%9%}+ ", -" O.3#i.i i i i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H k.k.k.k.0%a%r b%c%d%^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U+e%f%g%+@h%i% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/$o@j%S@.${+. x@x@k%l%3%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /.}+}+|+}#}#<$@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@I#.@ @ @}%^+n%o%p%q%r%$.!%!%!%!%!%!%!%!%!%!%6#. ", -" { i t.i i i i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H H H H C s%T#t%u%v%@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+w%V#q@x%p#b$/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @}%y%G#z%j#J#k%6.6.A%x@x@k%l%l%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /./.}+|+}#}#<$@$@$#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%m%O@2%I#I#e$T@ @ @^@M$b+B%&%V.P#!%!%!%!%!%!%!%!%!%!%!%C%. ,# ", -" k.D%h i i i i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H H H H 1.`$)$E%F%G%^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+U+H%I%J%]$K%.@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a@O$L%S$M%]#=%N%N%N%O%O%6.A%x@x@k%l%l%3%4%4%&%*%=%=%U$V$V$V$%.%.5 + . . { /./.}+|+|+}#<$@$@$#$t#t#J#J#J#k#[#[#l#Y@)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@$$$$'#+$+$[$f$f$>$r$s$s$n#n#n#j#:$:$-%;%U#U#5%m%m%O@2%2%I#e$M%M%P%Q% @ @ @8$C+5@R%P Z$;$!%!%!%!%!%!%!%!%!%S%S%{ P@ ", -" $ N+'.i i i i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).).H w.3.T%4$U%V%@@^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+W%X%>%Y%Z%p@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @=#`%($ &+$}+.&_.+&.&.&N%N%O%O%6.A%A%x@k%k%l%3%4%4%&%*%*%=%U$U$V$V$%.%.5 + + . { { /.}+|+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$@& @ @ @g%@@#&$&%&v L+!%!%!%!%!%S%S%S%S%&&&&*&[# ", -" 7+=&i i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+).).!.k+@ -&V@;&^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+D@E@>&,&f#0@/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#Z#'&1%;%l#A%)&!&~&_._.+&.&.&N%N%N%O%6.A%A%x@k%k%l%3%3%4%&%*%*%=%U$U$V$V$V$%.5 + + . { { /.}+}+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#j#:$-%;%;%U#5%5%m%O@O@2%I#e$e$M%P%P%P%T$T$,#{&N@ @ @ @[%^+]&m+v+, u !%!%S%S%S%&&&&&&v@v@v@^&{ ", -" y+i i 7+7+7+7+7+7+7+7+7+7+7+7+7+7+7+E /&(&L#_&^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+H+&$:&<&[&}&|& @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@&#Q%p$T$R@V$1&K+1&)&)&!&~&~&_.+&.&.&N%N%N%O%6.6.A%x@k%k%l%3%3%4%&%&%*%=%U$U$V$V$V$%.5 5 + . { { /.}+}+|+}#}#<$@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@2%I#I#e$M%P%P%P%T$T$,#{&{&2&.$H$C$ @ @*#^+3&4&5&%&+&S%&&&&&&v@v@v@v@v@v@v@6&. ", -" |.}.7+7+7+7+7+7+7+7+7+7+7+7+7+).C./%^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+8#7&8&<%o$(#d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @C$9&0&a&r$@$~&b&c&d&d&K+1&1&)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%l%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /./.}+|+}#}#<$@$@$#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$q$f# @ @ @7$I+e&f&g&h&S%v@v@v@v@v@v@v@v@v@i&j&. I# ", -" ~ 7+i 7+7+7+7+7+7+7+7+7+).).).).k&^+^+^+^+^+^+^+^+^+^+^+^+^+x#@#z#A#b@$%D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@o#l&S$2%~#k%m&m&n&n&b&c&d&d&K+1&1&)&!&!&~&_.+&+&.&N%N%N%O%O%6.A%x@x@k%l%l%3%4%4%&%*%=%=%U$V$V$V$%.%.5 + . . { /./.}+|+|+}#<$@$@$#$t#t#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#L#{+P@$$$$'#+$+$[$f$f$>$r$s$s$n#n#n#j#:$:$-%;%U#U#5%m%m%O@2%2%I#e$M%M%P%P%P%T$,#,#{&2&.$.$H$q$q$o&%$%$p& @ @ @W+1$q&r&V.U.;$v@v@v@v@v@v@i&i&i&i&S%s&R@ ", -" C.7+7+7+7+7+7+7+7+).).).H H H t&^+^+^+^+^+^+^+^+D+u&v&w&Y#x&/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @}%y%@&q$$$5 n&y&z&m&m&m&n&n&b&c&c&d&K+1&1&)&!&!&~&_._.+&.&N%N%N%O%O%6.A%A%x@k%l%l%3%4%4%&%*%*%=%U$U$V$V$%.%.5 + + . { { /.}+|+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$q$q$o&%$%$ &i#i#@& @ @ @A&/+B&C&D&E&X@v@v@i&i&i&i&i&i&i&i&i&c+#$ ", -" E 7+7+7+7+).).).H H H H H H i@^+^+^+^+e%m@Z#Z+`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a@O$F&G&-%J#_.H&I&J&J&y&z&z&m&m&m&n&b&c&c&d&K+K+1&)&)&!&~&_._.+&.&.&N%N%N%O%6.A%A%x@k%k%l%3%3%4%&%*%*%=%U$U$V$V$V$%.5 + + . { { /.}+}+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$T$,#{&2&2&.$H$H$q$o&o&%$ &i#i#K&_$_$H#^@ @ @}&^+o+R+U.L&M&i&i&i&i&i&i&i&i&N&N&N&m+. ", -" 7+).).).).H H H H H H k.k.!.@@^+^+9#}& @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @0@O&p$T$^#4%H&P&q%q%H&I&I&J&y&z&z&m&m&m&n&b&b&c&d&K+K+1&)&)&!&~&~&_.+&.&.&N%N%N%O%6.6.A%x@k%k%l%3%3%4%&%&%*%=%U$U$V$V$V$%.5 5 + . { { /.}+}+|+}#}#<$@$#$#$t#J#J#J#k#k#[#l#Y@Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@2%I#I#e$M%P%P%P%T$T$,#{&{&2&.$H$H$q$o&o&%$ & &i#K&_$_$_$a&a&z%c# @ @/$H+D$Q&R&%&S&i&i&i&i&N&N&N&N&N&N&N&w@. ", -" k#).H H H H H H k.k.k.k.k.k.t%^+^+@%/@ @ @ @ @ @ @ @ @ @ @`+T&U&_$>$/.b&V&W&W&X&P&q%q%H&I&I&J&y&y&z&m&m&m&n&b&b&c&d&d&K+1&)&)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%l%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /./.}+|+}#}#<$@$@$#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#]$ @ @ @V+C@Y&Z&g&`&S%N&N&N&N&N&N&u+u+u+ *j&. ;% ", -" 6#H H H H k.k.k.k.k.k.k.k.k..*^+<& @ @ @ @ @ @ @ @F#K&Y@N%W@+*+*W@V&W&W&X&P&P&q%H&H&I&J&y&y&z&m&m&m&n&n&b&c&d&d&K+1&1&)&!&!&~&_.+&+&.&N%N%N%O%O%6.A%x@x@k%l%l%3%4%4%&%*%=%=%U$V$V$V$%.%.5 + . . { /./.}+|+|+}#<$@$@$#$t#t#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#{+{+P@$$$$'#+$+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@2%2%I#e$M%M%P%P%P%T$,#,#{&2&.$.$H$q$q$o&%$%$ &i#K&K&_$_$_$a&z%z%N@/#@*@*H#j% @ @ @#*1$$*4.X&%*&*N&N&u+u+u+ * * * * * *$&]# ", -" **k.k.k.k.k.k.k.k.k.k.|.|.|.L$~$/@ @ @ @ @ @ @Q%. =*=*+*+*W@V&V&W&X&P&P&q%H&H&I&J&J&y&z&m&m&m&n&n&b&c&c&d&K+1&1&)&!&!&~&_._.+&.&N%N%N%O%O%6.A%A%x@k%l%l%3%4%4%&%*%*%=%U$V$V$V$%.%.5 + + . { /./.}+|+|+}#<$<$@$#$t#t#J#J#J#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$q$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%-* @ @ @%#/+;*>*8%,*X@u+u+ * * * * * * * * *o%}# ", -" ~ k.k.k.k.k.k.|.|.|.|.|.|.C ^+p# @ @ @ @ @^@ &W@+*+*W@V&V&W&X&X&P&q%H&H&I&J&J&y&z&z&m&m&n&n&b&c&c&d&K+K+1&)&!&!&~&_._.+&.&.&N%N%N%O%6.A%A%x@k%k%l%3%3%4%&%*%*%=%U$U$V$V$V$%.5 + + . { { /.}+}+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&a&z%N@/#/#@*H#H#G&1%1%g$d$'*d# @ @0@^+n%)*P#L&M& * * * * * * * *!*!*!*~*. ", -" {*k.k.|.|.|.|.|.|.|.C C C C /+/@ @ @ @ @ @2&+*W@W@V&W&X&X&P&q%q%H&I&I&J&y&z&z&m&m&m&n&b&b&c&d&K+K+1&)&)&!&~&~&_.+&.&.&N%N%N%O%6.6.A%x@k%k%l%3%3%4%&%&%*%=%U$U$V$V$V$%.5 5 + . { { /.}+}+|+}#}#<$@$#$#$t#J#J#J#k#k#[#l#Y@Y@)#!#!#~#{#{#Q@]#^#^#m#R@R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@2%I#I#e$M%P%P%P%T$T$,#{&{&2&.$H$H$q$o&o&%$ & &i#K&_$_$_$a&a&z%N@N@/#@*H#H#G&1%1%g$d$d$>#'*S$=# @ @/@U+b+>@4%P&S& * * * *!*!*!*!*!*!*!*]*. _$ ", -" ^*|.|.|.|.|.C C C C C C C C H# @ @ @ @ @l&W@V&W&W&X&P&q%q%H&I&I&J&y&y&z&m&m&m&n&b&b&c&d&d&K+1&)&)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%3%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /./.}+|+}#}#<$@$@$#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$ $T& @ @ @_%a+/*%@W&!&N&!*!*!*!*!*!*!*!*!*(*(*. r$ ", -" [$|.C C C C C C C C C C D D _* @ @ @ @d#3%W&X&P&P&q%H&I&I&J&y&y&z&m&m&m&n&n&b&c&d&d&K+1&1&)&!&!&~&_.+&+&.&N%N%N%O%O%6.A%x@x@k%l%l%3%4%4%&%*%=%=%U$V$V$V$%.%.5 + . . { /./.}+|+|+}#<$@$@$#$t#t#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#{+{+P@$$$$'#+$+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@2%I#I#e$M%M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$%$ &i#K&K&_$_$_$a&z%z%N@/#@*@*H#G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*p& @ @ @<*@@#@Z.%&[*&*!*!*!*!*!*(*(*(*j&j&j&k#!# ", -" C C C C C C C D D D E E E }* @ @ @ @g#P&P&q%H&H&I&J&J&y&z&m&m&m&n&n&b&c&c&d&K+1&1&)&!&!&~&_._.+&.&N%N%N%O%O%6.A%A%x@k%l%l%3%4%4%&%*%*%=%U$V$V$V$%.%.5 + + . { /./.}+|+|+}#<$<$@$#$t#t#J#J#J#k#[#[#l#Y@)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$q$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*2* @ @ @3*^+4*#+E&5*6*!*!*(*(*(*j&j&j&j&j&j&)*/. ", -" X.C C C D D D E E E E E E 7* @ @ @ @m%H&H&I&J&J&y&z&z&m&m&n&n&b&c&c&d&K+K+1&)&!&!&~&_._.+&.&.&N%N%O%O%6.A%A%x@k%k%l%3%4%4%&%*%*%=%U$U$V$V$V$%.5 + + . { { /.}+}+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$>#>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#`+ @ @b$^+T+c@u 8*9*(*j&j&j&j&j&j&j&j&j&j&6#. ", -" Y.D D E E E E E E F F F F z@ @ @ @ @k#I&J&y&z&z&m&m&m&n&b&b&c&d&K+K+1&)&)&!&~&~&_.+&.&.&N%N%N%O%6.6.A%x@k%k%l%3%3%4%&%&%*%=%U$U$V$V$V$%.5 5 + . { { /.}+}+|+}#}#<$@$#$#$t#J#J#J#k#k#[#l#Y@Y@)#!#!#~#{#{#Q@]#^#^#m#R@R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$r$r$s$n#n#n#j#j#:$-%;%;%U#5%5%m%O@O@2%I#e$e$M%P%P%P%T$T$,#{&{&2&.$H$H$q$o&o&%$ & &i#K&_$_$_$a&a&z%N@N@/#@*H#H#G&1%1%g$d$d$>#'*S$S$S$ $ $:*|*|*1*S@p$p$h#0*0*a*E# @ @ @D+I+b*s@X&S&j&j&j&j&j&j&j&c*c*c*c*d*. q$ ", -" 3@E E E E E F F F F F F F !+ @ @ @ @U$y&z&m&m&m&n&b&b&c&d&d&K+1&)&)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%3%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + . . { /.}+}+|+}#}#<$@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($T& @ @ @##C+e*w@F$f*i&j&j&c*c*c*c*c*c*c*g*(*/.+$ ", -" [@E F F F F F F F F F F F F ^@ @ @ @<$m&m&n&b&b&c&d&d&K+1&1&)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%l%3%4%4%&%*%=%=%U$V$V$V$%.%.5 + . . { /./.}+|+|+}#<$@$@$#$t#t#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#{+{+P@$$$$'#+$+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@2%I#I#e$M%M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*|*1*S@S@p$h#h#0*a*a*%%($@&@&M@Q% @ @ @(@/+h*3$1@c&&&c*c*c*c*c*g*g*g*g*g*g*)*Y@ ", -" i*F F F F F F F F F F @.@.X.b$ @ @ @R@n&b&c&c&d&K+1&1&)&!&!&~&_._.+&.&N%N%N%O%O%6.A%A%x@x@k%k%l%3%3%4%&%&%=%U$V$V$V$%.%.5 + + . { /./.}+|+|+}#<$<$@$#$t#t#J#J#J#k#[#[#l#Y@)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@$$$$'#+$+$[$f$f$>$r$s$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$q$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$h#h#0*a*a*%%($($@&M@-*-*-*|% @ @ @{@^+o+q#c&5*6*c*g*g*g*g*g*g*g*g*g*g*j*{ ", -" k*F F F F F F F @.@.@.!.!.!.w& @ @ @M%c&d&K+K+1&)&!&!&~&_._.+&.&.&N%N%O%O%6.A%A%x@x@x@k%l%3%3%3%4%4%*%=%=%U$U$V$V$5 + + . { { /.}+|+|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&e# @ @D#^+M#N#l*m*n*g*g*g*g*g*g*g*C%C%C%o*p*. ", -" q*F F F @.@.@.!.!.!.!.!.!.1.r* @ @ @d$K+1&)&)&!&~&~&_.+&.&.&N%N%N%O%6.6.A%x@k%k%k%l%l%l%3%3%3%4%&%&%*%*%*%U$V$%.5 + . . /.}+}+|+}#}#<$@$#$#$t#J#J#J#k#k#[#l#Y@Y@)#!#!#~#{#{#Q@]#^#^#m#R@R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$r$r$s$n#n#n#j#j#:$-%;%;%U#5%5%m%O@O@2%I#e$e$M%P%P%P%T$T$,#{&2&2&.$H$H$q$o&o&%$ &i#i#K&_$_$_$a&a&z%N@N@/#@*H#H#G&1%1%g$d$d$>#'*S$S$S$ $ $:*|*|*1*S@p$p$h#0*0*a*%%%%($@&M@M@-*-*-*G#2*2*0&s*U&&# @ @ @&$C@*$(.t*u*(*g*g*g*C%C%C%o*o*o*o*v*. e$ ", -" @.@.!.!.!.!.!.!.!.1.1.1.1..% @ @ @;#)&!&~&~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%3%l%3%4%3%4%4%3%4%4%4%4%&%*%*%U$V$%.5 . { { }+|+|+}#@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%;#T& @ @ @w*1$2$s X&x* *C%C%o*o*o*o*o*o*o*y*y*/.{+ ", -" z*!.!.!.!.1.1.1.1.1.1.1.1.A* @ @ @-#~&_.+&+&.&N%N%N%O%6.6.A%x@x@k%l%l%3%4%&%4%&%&%4%4%4%3%l%3%3%3%4%&%*%=%V$V$5 . . /.}+}#}#<$#$#$t#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@]#]#^#m#m#R@R@R@K#L#{+{+P@$$$$'#+$+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@2%I#I#e$M%M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*|*1*S@S@p$h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&W$ @ @ @B*/+k@;@C*D*&&o*o*o*o*o*y*y*y*y*y*y*E*t# ", -" a%1.1.1.1.1.1.1.1.1.1.l.l.6# @ @ @0@+&.&N%N%N%O%O%6.A%A%x@k%l%l%3%4%4%&%*%&%*%*%*%&%3%3%k%k%k%x@x@l%3%3%&%=%U$%.+ . { }+}#}#@$@$#$t#k#k#[#l#)#)#!#~#~#{#Q@Q@]#^#m#m#R@R@R@K#L#L#{+P@$$$$'#+$+$[$f$f$>$r$s$s$n#n#n#j#:$:$-%;%U#U#5%m%m%O@2%2%I#e$M%M%P%P%P%T$,#,#{&2&.$.$H$q$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$h#h#0*a*a*%%($($@&M@-*-*-*G#G#2*0&0&s*U&|%|%;#'&'&F*F&F&L%/@ @ @E#^+(+$+D.G*6*o*y*y*y*y*y*y*y*y*y*y*H*. ", -" U 1.1.1.1.1.1.l.l.l.l.l.l._+ @ @ @ @l%N%O%O%6.A%A%x@k%k%l%3%4%4%&%*%*%=%U$=%=%*%*%4%3%l%x@A%A%O%6.6.A%x@l%4%&%=%V$5 . /.}+|+<$@$#$J#k#[#l#Y@!#!#~#{#Q@]#^#^#m#R@R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%p@ @ @d#x#D$I*J*m*n*y*y*y*y*y*y*y*y*y*y*y*4 . ", -" K*1.1.1.l.l.l.l.l.l.l.l.l.M. @ @ @ @l#6.A%A%x@k%k%l%3%3%4%&%*%*%=%U$U$V$V$U$U$U$*%&%3%k%A%O%N%+&_.+&+&N%O%A%k%4%*%=%V$5 . /.|+<$@$t#k#k#l#)#)#~#{#Q@]#^#m#R@R@K#K#L#{+P@P@$$'#'#+$[$[$f$>$r$r$s$n#n#n#j#j#:$-%;%;%U#5%5%m%O@O@2%I#e$e$M%P%P%P%T$T$,#{&2&2&.$H$H$q$o&o&%$ &i#i#K&_$_$_$a&a&z%N@/#/#@*H#H#G&1%1%g$d$>#>#'*S$S$S$ $ $:*|*|*1*S@p$p$h#0*0*a*%%%%($@&M@M@-*-*-*G#2*2*0&s*U&U&|%;#;#'&F*F*F&`#R$R$L%L%L%L*E# @ @ @E@M*X$O.t*N*g*y*y*y*y*y*y*y*y*y*O*P*. m% ", -" Q*l.l.l.l.l.l.l.l.l.M.M.M.Y.e# @ @ @n#x@x@k%l%3%3%4%&%&%*%=%=%U$V$V$V$%.%.%.V$V$*%&%l%x@~&R*V&c&1&1&1&)&~&+&N%6.x@l%&%=%V$5 { }+|+@$t#J#[#Y@)#!#{#]#]#^#m#m#K#L#L#P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@2%I#I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%;#;#'&F*F*F&`#`#R$L%L%L%L*l&l&p&C$ @ @ @!@@@,%~ P&S*(*y*y*y*y*y*O*O*O*T*T*y*@$m# ", -" U*l.l.l.l.l.M.M.M.Y.Y.Y.^.^.T& @ @ @i#l%l%3%4%&%&%*%=%=%U$V$V$V$%.5 5 + + + 5 %.=%&%l%A%.&V*W*X*Y*Z*V&n&b&d&1&~&+&N%A%l%4%=%%.5 { }+<$@$J#[#l#)#!#~#Q@^#m#R@L#{+{+$$'#+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@2%I#I#e$M%M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L%L%L*L*l&W$Q$0@/$ @ @ @ @Y+/+6%i@& `* =y*y*O*O*O*T*T*T*T*T*T**&@$ ", -" .=l.M.M.M.Y.Y.Y.Y.^.^.^.^.^.-* @ @ @h#4%4%&%*%*%=%U$V$V$V$%.%.5 + + . { { . . 5 V$*%l%O%_.+=W*W*W*W*W*V*@=#=H&b&K+)&_.N%6.k%4%*%V$+ { }+<$@$t#k#Y@)#~#Q@^#m#K#{+P@$$+$+$f$>$s$s$n#n#n#j#:$:$-%;%U#U#5%m%m%O@2%2%I#e$M%M%P%P%P%T$,#,#{&2&.$.$H$q$q$o&%$%$ &i#K&K&_$_$_$a&z%z%N@/#@*@*H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$h#h#0*a*a*%%($($@&M@-*-*-*G#G#2*0&0&s*U&|%|%;#'&'&F*F&F&`#R$L%L%L%L*L*j%c$$=e# @ @ @ @ @ @ @ @c#^+n%4@_.%=&=O*T*T*T*T*T*T*T*T*T*T**=. ", -" M.Y.Y.Y.^.^.^.^.^.^.^.^.^.q$ @ @ @g#*%*%=%U$U$V$V$%.%.5 + + . { { /.}+}+/./.. %.=%3%O%~&==W*W*W*W*W*W*W*W*W*-=R*W@b&1&!&+&O%A%l%&%U$%.. /.|+@$J#[#Y@~#Q@]#R@L#{+$$+$[$>$s$s$n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%L%L*L*;=o$c#/@ @ @ @ @ @ @ @ @ @ @ @^@U@b+>=c.,=n*T*T*T*T*T*T*T*T*T*T*T*'=. ", -" 3.^.^.^.^.^.^.^.^.^.^.^.^.*$ @ @ @f#U$U$V$V$V$%.5 + + . { { /.}+}+|+}#}#|+}+/.5 U$3%6.~&Z*W*W*W*W*W*W*W*W*W*W*W*W*)=@=!=z&1&~&+&O%x@3%*%V$+ { }#@$J#l#Y@~#]#R@L#P@'#[$f$r$s$j#-%;%;%U#5%5%m%O@O@2%I#e$e$M%P%P%P%T$T$,#{&2&2&.$H$H$q$o&o&%$ &i#i#K&_$_$_$a&a&z%N@/#/#@*H#H#G&1%1%g$d$>#>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#0*0*a*%%($($@&M@M@-*-*-*G#2*2*0&s*U&U&|%;#;#'&F*F*F&`#R$R$L%L%L%l&c$&#`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8$C+~=R%t*{=y*T*T*T*T*T*T*T*T*T*]=P*. :$ ", -" _+^.^.^.^.^.^.^.^.^.^.^.^. # @ @ @p@V$V$%.5 5 + . . { /.}+}+|+}#}#<$@$<$<$}#|+. V$4%6._.=*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*^=/=W&1&~&N%A%l%4%U$5 . }+<$t#[#!#Q@m#K#P@'#f$r$n#:$;%5%5%m%O@O@2%I#I#e$M%P%P%P%T$T$,#{&{&2&.$H$H$q$o&o&%$ & &i#K&_$_$_$a&a&z%N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%;#;#'&F*F*F&`#`#R$L%L%L%Q%Q$0@d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @(=@@_=$&:=<=(*T*T*T*T*T*]=]=]=[=[=[=#+{# ", -" {*^.^.^.^.^.^.^.^.s+s+s+L+O. @ @ @ @@$5 + . . { /./.}+|+}#}#<$@$@$#$t##$#$@$}#{ 5 *%k%.&I&W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*)=@=}=n&+&O%x@3%&%V$+ /.<$t#[#!#]#K#P@[$s$n#-%U#5%O@2%I#I#e$M%M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L%L%F#o#C$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @b#^+]&_@v+|= =T*]=]=]=[=[=[=[=[=[=[=)*}# ", -" Q&^.^.^.^.s+s+s+L+L+L+L+3.g@ @ @ @ @'#. { /./.}+|+|+}#<$<$@$#$t#t#J#J#t#t#t#<$|+{ V$4%6.1&W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*^=1=P&~&O%k%4%=%5 /.<$k#!#^#K#'#>$n#;%5%O@I#e$M%M%P%P%P%T$,#,#{&2&.$.$H$q$q$o&%$%$ &i#K&K&_$_$_$a&z%z%N@/#@*@*H#G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*|*1*S@S@p$h#h#0*a*a*%%($($@&M@-*-*-*G#G#2*0&0&s*U&|%|%;#'&'&F*F&F&`#R$L%L%L*~@$=`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @|&^+3&7#5&2=3=]=[=[=[=[=[=[=4=4=4=4=5=. ", -" 6=^.s+s+s+L+L+L+3.3.3.3.3.^.a@ @ @ @T$/.}+|+|+}#<$<$@$#$#$t#J#J#J#k#[#[#[#k#t#@$|+. =%l%O%7=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*)=[ }=c&6.l%=%+ |+J#!#^#P@f$n#;%m%2%e$P%P%P%T$,#,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%l&9&E#a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @w%I+8=s@9=0=O*[=[=[=4=4=4=4=4=4=4=a=. i# ", -" b=L+L+L+3.3.3.3.3.3.3.3.3.3.a$ @ @ @d$|+}#<$<$@$#$#$t#J#J#J#k#[#[#l#Y@Y@Y@l#[#J#@$|++ =%l%@=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*c=1=J&k%%.|+J#~#K#+$s$;%O@I#M%T$T$,#{&2&2&.$H$H$q$o&o&%$ &i#i#K&_$_$_$a&a&z%N@/#/#@*H#H#G&1%1%g$d$>#>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#0*0*a*%%($($@&M@M@-*-*-*G#2*0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%j%}$0@d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @f%C+d=r&t*e=y*4=4=4=4=4=4=4=4=f=f=]=+ >$ ", -" 1*3.3.3.3.3.3.3.3.3.3.3.3.u@'& @ @ @s*<$@$@$#$t#J#J#J#k#k#[#l#l#Y@)#!#!#!#!#Y@l#k#@$|+. U$#=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*X*< q%4%l#]#$$r$-%m%I#P%,#{&2&.$H$H$q$o&o&%$ & &i#K&_$_$_$a&a&z%N@N@/#@*H#H#G&1%1%g$d$d$>#'*S$S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%;#;#'&F*F*F&`#`#R$L%g#T&(#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @<%/+B&C&L&g=(*4=4=4=4=4=f=f=f=h=h=h=i*)# ", -" 3.3.3.3.3.3.3.3.3.u@u@u@X@i= @ @ @;=#$t#t#J#J#k#k#[#l#l#Y@)#)#!#~#{#{#{#{#~#!#)#[#t#|+{ U$~&V&j=k=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*-=#$K#f$:$m%e$T$2&.$H$q$q$o&%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%l&}$0@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @}&^+o+j*K+l=m=4=f=f=f=h=h=h=h=h=h=h=n=/. ", -" 3.3.3.3.3.u@u@u@X@X@X@X@X@o= @ @ @o#J#J#J#k#[#l#l#Y@)#)#!#~#~#{#Q@Q@]#^#^#]#]#{#)#l#J#@$|+. %.*%4%)&V&R*7=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*4%m#+$:$O@M%{&H$H$o&%$%$ &i#K&K&_$_$_$a&z%z%N@/#@*@*H#G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*|*1*S@S@p$h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&F*F&F&`#R$L%L*9&=#d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @.@H+M#p=R&q=r=f=h=h=h=h=h=h=h=h=h=h=d+. ", -" s=u@u@u@u@X@X@X@X@X@X@X@X@t= @ @ @e#k#[#[#l#Y@Y@)#!#~#~#{#Q@Q@]#^#^#m#R@m#^#^#^#Q@{#!#l#k##$<$}+. 5 V$=%&%+&q%Z*-=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*1&^#+$:$2%P%2&q$o& &i#i#K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%-#Z+d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @e%C@J+Z&u=0=[=h=h=h=h=h=h=h=h=h=a=a=. {& ", -" v=X@X@X@X@X@X@X@X@X@s#s#s#@ @ @ @ @K#Y@Y@)#!#!#~#{#Q@Q@]#^#^#m#R@R@R@K#L#K#K#K#R@m#^#Q@{#!#Y@[#J##$<$|+}+. %.V$=%6.J&w=+=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*X&]#+$-%2%,#H$%$ &K&_$_$_$a&a&z%N@/#/#@*H#H#G&1%1%g$d$>#>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#0*0*a*%%($($@&M@M@-*-*-*G#2*0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%`%o@.@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @0#1$x=4.u=y=O*h=h=h=h=h=a=a=a=z=z=f=}+P@ ", -" A=X@X@X@X@X@X@s#s#s#s#s#s#}@ @ @ @ @O@!#!#~#{#{#Q@]#]#^#m#R@R@R@K#K#L#{+{+P@P@P@{+{+{+K#K#m#^#Q@{#~#)#l#[#J##$<$}+{ 5 U$4%N%H&Z*Y*X*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*B=]#+$-%I#{&o&i#K&_$a&a&z%N@N@/#@*H#H#G&1%1%g$d$d$>#'*S$S$S$ $ $:*|*|*1*S@p$p$h#0*0*a*%%%%($@&M@M@-*-*-*G#2*2*0&s*s*U&|%;#;#'&F*F*F&`#`#R$L%C=f#*# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @A$/+;*>*D=E=F=h=a=a=a=a=z=z=z=z=z=z=G=[# ", -" *$X@X@s#s#s#s#s#s#s#s#s#s#X@/@ @ @ @z%{#{#Q@]#]#^#m#m#R@R@K#K#L#{+{+P@$$$$'#+$+$'#'#+$'#$$$$P@{+L#K#m#^#^#{#~#)#[#J#@$}+5 =%l%N%)&z&}=< c=X*W*W*W*W*W*W*W*W*W*W*W*W*H=^#f$U#M%.$%$i#_$z%N@N@/#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L*}$(#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @0@^+n%)*!&E&m=a=z=z=z=z=z=z=I=I=I=I=J={ ", -" k*s#s#s#s#s#s#s#s#;$;$;$;$X@=# @ @ @a*]#]#^#m#m#R@R@R@K#L#{+{+P@$$$$'#+$+$[$f$f$f$>$>$f$f$f$[$[$[$+$'#'#$${+K#m#Q@~#l##$|++ *%x@_.c&I&V&}=B={.V*W*W*W*W*W*W*W*W*W*W*X*^#r$m%P%H$i#_$z%/#@*@*H#G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*|*1*S@S@p$h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&F*F&`#`#R$L%L%F#E#/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#U+D$>@c.q=K=z=z=z=z=I=I=I=I=I=I=I=L=. ", -" M=s#s#s#s#;$;$;$;$;$;$;$;$;$C= @ @ @L%^#m#R@R@R@K#L#L#{+P@P@$$'#+$+$[$f$f$>$r$r$s$n#s$s$s$n#s$s$s$s$r$r$>$[$'#P@K#^#~#[#@${ U$k%+&d&J&W&$@R*k=W*W*W*W*W*W*W*W*W*W*W*W*t#n#2%{&o&_$z%/#H#G&G&1%g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$h#h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%j%f#*# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @9#a+!$N=P&O=h=I=I=I=I=I=I=I=I=I=I=f=. e$ ", -" s*s#;$;$;$;$;$;$;$;$;$;$;$;$'* @ @ @9&R@R@K#L#L#{+P@P@$$'#'#+$[$f$f$>$r$r$s$n#n#n#j#:$:$:$-%-%-%-%:$:$j#n#r$f$+${+R@Q@l#@${ U$x@+&d&J&W@P=W*W*W*W*W*W*W*W*W*W*W*W*W*W*%.;%P%H$i#z%/#H#1%1%g$d$>#>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#0*0*a*%%($($@&M@M@-*-*-*G#2*0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%L%F#Z+/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @<&@@#@Q=:=R=O*I=I=I=I=I=I=I=I=S=S=S=C&K# ", -" ;$;$;$;$;$;$;$;$;$;$;$;$;$T= @ @ @E#K#L#{+{+P@$$'#'#+$[$[$f$>$>$r$s$n#n#n#j#j#:$-%-%;%U#5%U#5%5%;%;%-%j#s$>$+$L#^#!#J#|+5 4%6.!&n&w=V*W*W*W*W*W*W*W*W*W*W*W*W*W*W*U=<$2%2&%$a&@*H#1%d$d$>#'*S$S$S$ $ $:*|*|*1*S@p$p$h#0*0*a*%%%%($@&M@M@-*-*-*G#2*2*0&s*U&U&|%;#;#'&F*F*F&`#R$R$L%L%j%f#e# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @V=^+4*W=n&E=F=I=I=I=I=S=S=S=S=X=X=X=Y=#$ ", -" v@;$;$;$;$;$;$;$;$g@g@g@$.K# @ @ @a@{+P@$$$$'#+$[$[$f$>$>$r$s$s$n#n#j#j#:$-%-%;%U#U#5%m%m%m%m%m%U#;%-%n#r$+$P@m#~#[#}#+ *%A%_.H&+=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*w=Q@5%T$o&K&/#G&g$>#'*'*S$S$ $ $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L%L%Z==#/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @b$^+n%o%`= -.-I=S=S=S=X=X=X=+-+-+-+-@-. ", -" #-;$;$;$;$;$g@g@g@$.$.$.!%$- @ @ @ @;%'#+$+$[$f$>$>$r$s$s$n#n#n#j#:$-%-%;%U#U#5%m%m%O@O@2%O@m%m%;%-%n#>$'#{+^#!#k#<$+ *%x@)&/=X*W*W*W*W*W*W*W*W*W*W*W*W*W*W*k=N%f$;%M%H$i#N@G&d$>#S$S$S$ $:*:*|*1*S@S@p$h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&F*F&`#`#R$L%L%L%g#Z#C$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8#b+x$&%%-3=X=X=X=+-+-+-+-+-+-+-+-&-. ", -" *-;$g@g@g@$.$.$.!%!%!%!%!%=- @ @ @ @ &[$f$f$>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%m%m%O@2%2%2%2%O@O@m%;%:$s$>$$$K#]#Y@t#}+5 4%6.V&k=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*--l#f$U#e$H$K&z%G&d$'*'* $:*:*|*1*1*S@p$h#h#0*a*a*%%($($@&M@-*-*-*G#G#2*0&0&s*U&|%|%;#'&'&F*F&F&`#R$R$L%L%L*Q%o#a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @;-C+>-] u=,-h=+-+-+-+-+-+-+-+-+-+-I=. U# ", -" '-g@$.$.$.!%!%!%!%!%!%!%!%)- @ @ @ @:*>$r$r$s$n#n#n#j#:$:$-%;%;%U#5%5%m%O@2%2%I#e$I#I#I#O@5%;%:$s$[$P@m#~#[#<$. =%l%b&P=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*7=O%{+r$5%P%H$i#/#H#g$'*S$:*|*1*1*S@p$p$h#0*0*a*%%($($@&M@M@-*-*-*G#2*0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%L%L%L*F#=#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @!-@@h*3$L&R=[=+-+-+-+-+-+-+-+-~-~-~-W=]# ", -" n#!%!%!%!%!%!%!%!%!%!%!%!%;$b$ @ @ @|%s$n#n#n#j#j#:$-%-%;%U#5%5%m%O@O@2%I#I#e$M%M%e$I#2%m%U#:$r$+$L#^#Y@t#}+%.4%.&w=X*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*!=l#+$j#O@,#o&K&/#G&d$S$:*:*1*S@p$p$h#0*0*a*%%%%($@&M@M@-*-*-*G#2*2*0&s*U&U&|%;#;#'&F*F*F&`#R$R$L%L%L%L*g#T&p@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @K@^+o+{-c&]-^-+-+-+-+-~-~-~-~-~-~-~-)#}# ", -" q$!%!%!%!%!%!%!%!%!%!%!%!%S%9& @ @ @W$n#j#j#:$-%-%;%U#U#5%m%O@O@2%I#I#e$M%M%P%P%P%e$I#O@U#:$s$+$K#Q@l#<$. =%k%P&7=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*^=x@{+r$U#e$2&%$a&/#1%'*S$:*1*S@p$h#0*0*a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L%L%L*l&p&T@.@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#^+3&/-A%(-_-+-~-~-~-~-~-~-~-~-~-:-<-. ", -" [-!%!%!%!%!%!%!%!%!%S%S%S%&&M@ @ @ @f#:$-%-%;%U#U#5%m%m%O@2%I#I#e$M%M%P%P%P%T$T$P%M%I#O@;%s$+$K#{#k#|+5 4%1&@=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*}-!#[$:$2%T$H$K&N@H#d$S$|*|*S@h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&F*F&`#`#R$L%L%L%L*L*l&L@o@d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @&$I+|-f&%-1-S=~-~-~-~-~-:-:-:-:-2-2-. a& ", -" !%!%!%!%!%S%S%S%&&&&&&v@v@o& @ @ @(#;%;%U#5%m%m%O@2%2%I#e$e$M%P%P%P%T$,#,#,#,#T$M%I#m%j#f${+Q@k#}+U$6.#=X*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*-=4%$$s$5%M%2& &a&H#g$'* $1*p$p$0*a*a*%%($($@&M@-*-*-*G#G#2*0&0&s*U&U&U&|%;#;#'&F*F&`#R$L%L%L%L*L*l&g#`%C#/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @m$1$3-4 q%4-5-~-~-:-:-:-2-2-2-6-6-~-{ n# ", -" !%S%S%S%S%&&&&&&v@v@v@v@v@7- @ @ @d#O@5%m%O@2%2%I#e$e$M%P%P%P%T$,#,#{&2&2&2&2&,#P%I#U#s$$$^#l#|+V$y&c=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*V&Q@f$:$I#{&o&_$@*1%>#:*|*S@h#0*a*%%($($@&M@M@-*-*-*G#2*2*2*0&s*s*U&|%U&|%;#'&'&F*`#`#R$R$L%L*l&g#g#F#=#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @~@/+k@$-y&8-9-:-2-2-2-6-6-6-6-6-6-6-0-~# ", -" a-&&&&&&v@v@v@v@v@v@v@v@v@~% @ @ @ @%$O@2%I#e$e$M%P%P%P%T$T$,#{&2&2&.$H$H$H$.${&P%2%-%>$L#~#@$k%j=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*Y*U$'#n#m%P%q$i#N@1%>#S$|*p$p$0*a*%%($@&M@M@-*-*M@-*G#G#2*0&0&0&0&s*s*U&U&U&|%|%;#'&F*F&F&R$L*L*l&j%-#(# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @C#^+(+$+D.b-^-2-6-6-6-6-6-6-6-6-6-6-n=}+ ", -" c-v@v@v@v@v@v@v@v@v@i&i&i&5 @ @ @ @'*I#e$M%M%P%P%T$T$,#{&{&2&.$.$H$q$o&o&q$q$2&T$2%:$[$^#t#P&)=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*X*q%^#>$;%e$2& &z%H#d$S$|*S@h#a*a*%%($($@&M@M@M@-*G#-*-*G#G#2*2*0&2*0&0&2*2*0&0&s*U&U&U&|%F*F*R$L%l&c$]@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @J@x#D$d-J*e-_-6-6-6-6-6-6-6-6-6-6-f-g-. ", -" 4@v@v@v@v@v@i&i&i&i&i&i&i& * @ @ @ @-*M%P%P%P%T$,#{&{&2&.$.$H$q$q$o&%$ & &%$o&.$,#2%j#'#]#3%W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*P=+ +$j#O@,#o&_$@*g$'*:*S@p$h#a*($($@&M@@&@&@&@&M@-*-*M@-*-*M@-*G#-*M@-*-*M@-*M@M@@&M@-*2*s*|%'&`#F#P$/$/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @6@M*h-Z&:=1-~-6-6-6-6-6-f-f-f-f-f-2-. o& ", -" i-v@v@i&i&i&i&i&i&i&i&i&N&&*.@ @ @ @l&P%T$,#,#{&2&2&.$H$q$q$o&%$%$ &i#i#i#i#%$.$T$2%j#'#Q@/.W*W*W*W*W*W*W*W*W*W*W*W*W*W*X*J&R@s$5%P%H$K&/#1%># $|*p$0*0*a*%%($($($($%%($@&($($@&@&($@&($%%($%%%%a*a*a*h#h#p$p$p$h#0*%%M@2*g#o#C$D#.@/$/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @q@@@,%~ j-k-5-6-6-f-f-f-f-f-f-l-l-l-z+[$ ", -" m-i&i&i&i&i&i&i&i&N&N&N&N&&&K@ @ @ @c$,#{&2&2&.$H$H$q$o&%$%$ &i#i#K&_$_$K&K&i#q${&e$:$'#Q@t#k=W*W*W*W*W*W*W*W*W*W*W*W*{.}+f$-%I#2&%$a&G&>#S$|*S@p$0*0*0*a*a*a*0*a*%%0*a*a*0*0*0*0*h#h#h#S@S@1*|*|* $'*'*>#>#d$d$'*S$|*G#y%o@Z+(#p@D#.@d#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @B#/+n-i@o-p-q-f-f-f-f-l-l-l-r-r-r-r-s-l# ", -" N@i&i&i&i&N&N&N&N&N&N&u+u+u+F* @ @ @o@2&.$H$H$q$o&o&%$ &i#i#K&_$_$_$a&a&a&a&_$%$.$M%;%[$^#k#==W*W*W*W*W*W*W*W*W*W*)=b&P@j#O@T$o&_$@*g$S$:*1*p$h#h#0*0*h#h#h#p$p$p$p$S@S@1*|*|*|* $S$'*'*>#d$1%H#@*/#z%a&_$_$K&_$a&d$;#;=-#]$K@E#(#C$`+/$/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @(#^+n%*&_.t-u-f-l-l-l-r-r-r-r-r-r-r-v-{ ", -" N&N&N&N&N&N&N&u+u+u+ * * */# @ @ @b$H$q$o&o&%$ & &i#K&K&_$_$a&a&z%N@N@/#N@a&K&q$,#m%s$L#)#$@W*W*W*W*W*W*W*W*W*==@$>$U#P%H$K&/#1%'*:*1*S@h#0*h#p$S@S@1*|*|* $ $ $S$'*>#d$g$1%G&H#/#/#z%_$i#i#o&q$H${&,#T$T$T$q$S$-*|%R$Q%Z=Z#o#=#]@e#.@d#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@U@b+w-x-y-z-r-r-r-r-r-r-r-r-r-r-r-A-. ", -" N&N&N&u+u+u+ * * * * * * *;% @ @ @^@ &%$ & &i#K&K&_$_$_$a&z%N@N@/#@*@*H#@*N@a& &.$e$-%+$^#n&W*W*W*W*W*W*W*)=K+'#-%2%{& &N@G&>#:*1*h#0*0*p$h#p$1*:* $'*>#d$1%H#H#/#N@z%a&K&i# &o&H$2&,#T$P%e$2%O@5%;%-%:$;%q$G&g$S$p$-*s*S$Q%f#$=P$C$`+/$^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @)@a+B-R%:=C-6-r-r-r-r-r-r-r-r-D-D-2-. ,# ", -" E-u+ * * * * * * * * * *!*F- @ @ @ @>#i#K&K&_$_$_$a&z%z%N@/#/#@*H#G&G&1%1%H#/#_$o&,#O@n#$$4%W*W*W*W*W*W*/=[#n#m%T$q$_$H#d$ $S@0*0*a*a*h#p$1*:*'*d$1%@*z%_$i#%$o&H$.${&,#T$M%I#O@m%U#-%j#n#s$>$[$+$$$$$;%,#j#]#/.O%H&j=X*W*@*%#K@=#b$`+/$/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @A#@@#@$&8*G-S=r-r-r-r-r-D-D-D-H-H-H->*P@ ", -" I- * * * * * * *!*!*!*!*!*d+ @ @ @ @@&_$_$_$a&z%z%N@/#/#@*H#H#G&1%g$g$d$d$1%G&/#K&H$P%m%n#l#W*W*W*W*U=.&r$U#M%.$K&/#g$ $1*h#a*%%%%($%%h#S@:*>#G&/#_$%$H${&T$M%2%m%5%;%:$n#n#r$f$+$'#P@{+L#R@~#{ A%K+P&1=k=W*W*W*W*W*W*W*W*r$-#O$Z+]@D#a@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @J-^+p+_@b&K-L-r-D-D-D-H-H-H-H-H-H-H-^&J# ", -" M- * * * *!*!*!*!*!*!*!*!*d* @ @ @ @L%a&a&z%N@/#/#@*H#H#G&1%1%g$d$>#>#'*'*>#1%H#z%i#q$T$O@j#)=W*W*#=^#5%M%2& &N@1%'*1*p$a*($M@M@@&@&%%p$|*>#H#a&%${&M%2%U#:$n#r$[$'#$${+L#K#m#]#@$&%)&W&Z*+=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*<$y%f#E#}%D#a@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @N-^+T+7#O%O-u-H-H-H-H-H-H-H-H-H-H-P-Q-. ", -" m#!*!*!*!*!*!*!*!*!*!*(*(*i&/@ @ @ @F#N@N@/#@*@*H#G&1%1%g$d$d$>#'*'*S$S$S$'*>#d$H#/#_$o&2&T$< 7=V$M%T$.$ &a&G&>#:*S@0*($M@-*2*G#-*M@%%S@S$G&a&q$T$O@-%r$+$P@K#m#]#[#%..&y&!={.)=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*!&y%f#E#}%D#a@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @w%I+8=s@R-z-l-H-H-H-H-H-P-P-P-P-P-P-. ", -" S-!*!*!*!*!*!*(*(*(*j&j&j& *Z+ @ @ @f#@*@*H#G&G&1%g$d$d$>#'*'*S$S$S$ $:*|*:*:*S$>#g$H#z%K& &5 U#o& &K&N@G&d$ $S@0*($-*G#0&s*s*0&2*-*%%1*>#/# &,#m%n#P@t#&%K+V&/=^=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*---#o$Z+]@D#a@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @z#C+d=w@:=T-U-H-H-P-P-P-P-P-P-V-V-r-. O@ ", -" W-!*!*(*(*(*j&j&j&j&j&j&j&j&O& @ @ @Z+G&G&1%g$g$d$>#'*'*S$S$S$ $:*:*|*1*1*1*1*1*:* $>#g$G&H#/#@*@*G&g$># $1*h#%%@&G#0&U&U&|%;#U&s*-*%%1*d$z%T$_.V&{.)=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*V*c$o#=#p@`+/$/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a#/+:#z+2=X-X=P-P-P-P-V-V-V-V-Y-Y-Y-Z-m# ", -" (*(*j&j&j&j&j&j&j&j&j&c*c*|* @ @ @e#g$g$d$>#>#'*S$S$S$ $:*:*|*1*1*S@p$p$p$h#p$S@S@|* $ $S$'*S$S$ $|*p$0*%%M@2*0&U&|%'&'&'&F*;#U&2*%%|*1%_$,#k=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*k=$@6.~#~@&#(#*#`+/$^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @o@^+o+q#K+K-L-P-V-V-V-Y-Y-Y-Y-Y-Y-Y-^&#$ ", -" j&j&j&j&j&j&j&j&c*c*c*c*c*P% @ @ @ @S$>#'*S$S$S$ $ $:*|*1*1*S@p$p$h#0*0*a*a*%%0*a*0*h#h#p$S@p$h#0*%%@&-*G#0&U&|%;#F*F&F&`#`#F*;#0&%%|*g$_${&/=W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*@=X&A%[#U#1%`#Q%y%]$a$=#b$D#.@d#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @.@^+M#p= +|=`-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y- ;. ", -" j&j&j&j&c*c*c*c*c*c*g*g*g*{+ @ @ @ @M@S$S$ $ $:*|*|*1*S@S@p$h#0*0*a*%%%%($@&($@&@&($%%%%($($@&M@M@G#0&s*U&;#'&F*F&`#`#R$L%R$F&'&s*M@p$>#z%H$J&W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*W*7=#=d&%.m#I#d$0*($G#|%R$j%`%c$Q$a$Z+}%*#`+/$/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @y#C@J+(.R-z-H-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-H-. ", -" .;c*c*c*c*c*c*g*g*g*g*g*g*+; @ @ @ @`# $:*|*|*1*S@S@p$h#h#0*a*%%%%($@&@&M@-*-*M@-*G#-*-*G#G#2*0&0&U&;#'&'&F&`#`#R$L%L%L%L*L*R$F&;#G#a*:*H#i#x@W*W*W*W*W*W*W*W*W*W*W*W*W*W*X*==q%x@[#s$i#S@0*($-*2*U&'&`#L*p&O&F#c$Z#o$$==#}%C$`+a@d#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8&1$2$@ %-@;l-Y-Y-Y-Y-Y-Y-Y-Y-#;#;#;$;-% ", -" %;c*c*g*g*g*g*g*g*g*g*g*g*&; @ @ @ @W$|*1*S@S@p$h#h#0*a*a*%%($@&@&M@-*-*-*G#G#2*0&2*0&s*U&U&|%;#;#'&F*F&`#R$L%L%L%L*L*l&g#l&L*R$F&U&-*h#'*/#[#W*W*W*W*W*W*W*W*c=B=c&. L#P%'*h#%%M@G#s*;#F*R$L*j%Q%O&;=Z=-#%#Z#f#K@$=Z+P$]@*#`+a@d#/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @n@/+;**=z&*;=;Y-Y-Y-Y-#;#;#;-;-;-;-;0-Q@ ", -" ;;g*g*g*g*g*g*g*g*C%C%C%o* * @ @ @ @}$S@p$p$h#0*a*a*%%($($@&M@M@-*-*G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%L%L*L*l&g#g#j%p&j%j%g#L%F*s*M@p$'*U#W*)=j=X&_.+ Q@;%a&S@@&-*2*U&'&`#L%g#p&L@`%F#y%c$%#T&Q$f#o$K@o@&#Z+=#(#]@C$D#`+a@d#/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @=#^+(+)*>;,;';Y-#;#;#;-;-;-;-;-;-;-;0-}# ", -" i-g*g*g*g*C%C%C%o*o*o*o*o*!*]@ @ @ @a$h#0*0*a*%%($($@&M@M@-*-*-*G#2*0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%L%L%L*l&g#g#j%p&p&C=Q%C=Q%p&g#L%F&U&-*a*'*P%/#/#z%a&z%H#M@F&L%l&j%Q%W$;=Z=~@9&}$Z#f#O$T@o#o@&#C#Z+=#0@(#}%b$p@*#D#`+a@/$d#/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#U+D$E$c. -u--;-;-;-;-;-;-;-;-;-;-;);. ", -" !;g*C%C%C%o*o*o*o*o*o*y*y*y*}$ @ @ @c#a*%%%%($@&@&M@-*-*-*G#2*2*0&s*s*U&|%;#;#'&F*F*F&`#`#R$L%L%L%L*l&l&g#j%j%p&C=Q%Q%L@O&O&O&L@Q%p&l&`#;#0&M@a*p$S@|*|*2*Q%;=F#y%c$%#Z#Q$f#o$K@a$$=C#E#=#0@P$(#}%]@b$C$*#e#D#`+`+.@a@/$d#/@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @F+a+!$N=8*~;Y--;-;-;-;-;-;-;-;-;-;P-. K& ", -" o*o*o*o*o*o*o*y*y*y*y*y*y*-* @ @ @.@($@&@&M@-*-*-*G#2*2*0&s*s*U&|%|%;#'&F*F*F&`#`#R$L%L%L%L*l&l&g#j%j%p&C=C=Q%L@O&O&W$`%`%`%;=W$L@C=j%L*R$F*|%U&0&|%F#]$Q$f#O$T@o#o@&#E#Z+=#0@(#c#}%b$p@C$*#e#D#`+`+.@.@a@/$/$d#d#/@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @{;@@];Q=R-^;l--;-;-;-;-;-;-;-;/;/;(;}#>$ ", -" o*o*o*y*y*y*y*y*y*y*y*y*y* & @ @ @ @2*-*-*-*G#G#2*0&s*s*U&|%|%;#'&'&F*F&`#`#R$L%L%L%L*L*l&g#j%j%p&C=C=Q%L@L@O&W$W$`%;=F#F#F#F#;=;=`%L@Q%C=p&g#l&F#o@C#C#E#Z+=#0@P$c#}%]@b$C$*#e#D#D#`+`+.@a@a@/$/$d#d#/@/@/@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @]$^+6%W=n&_;=;-;-;-;-;/;/;/;/;/;/;:;E*)# ", -" y*y*y*y*y*y*y*y*y*y*y*y*y*<; @ @ @ @L%G#G#2*0&0&s*U&U&|%;#'&'&F*F&F&`#R$R$L%L%L*L*l&g#g#j%p&p&C=Q%L@L@O&W$W$`%;=;=F#F#Z=Z=y%y%Z=y%y%F#F#;=;=y%$=]@}%}%]@]@b$p@C$*#e#D#`+`+.@.@a@a@/$d#d#/@/@/@^@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @x&^+n%o%`=]-';/;/;/;/;/;/;/;:;:;:;[;};/. ", -" I=y*y*y*y*y*y*y*y*y*y*O*O*E* @ @ @ @`%0&0&s*U&U&|%;#;#'&F*F&F&`#R$R$L%L%L%L*l&g#g#j%p&p&C=Q%Q%L@O&W$W$`%;=;=F#F#F#Z=y%~@~@-#c$-#c$c$c$c$c$T@*#`+D#D#D#`+`+`+`+.@a@a@/$/$d#d#/@/@/@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8#b+B%&%|;1;/;/;/;:;:;:;[;[;[;[;[;2;. ", -" 3;y*y*y*y*y*y*y*O*O*O*T*T*$; @ @ @ @%#U&U&|%;#;#'&F*F*F&`#`#R$L%L%L%L*l&l&g#j%j%p&C=Q%Q%L@O&O&W$`%`%;=F#F#F#Z=y%y%~@-#-#c$9&%#%#}$}$}$]$]@d#/$/$/$/$/$/$/$d#d#/@/@/@^@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @l@C+>-] 8*4;Y-:;:;[;[;[;[;[;[;[;[;#;. H$ ", -" 5;y*y*y*O*O*O*T*T*T*T*T*T*d* @ @ @ @K@|%;#'&F*F*F&`#`#R$L%L%L%L*l&l&g#j%j%p&C=C=Q%L@O&O&W$`%`%;=F#F#F#Z=y%y%~@-#-#c$9&9&%#}$T&T&Z#]$&#/$^@^@^@^@^@^@^@^@^@^@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8@@@#&6#y-6;H-[;[;[;[;[;[;[;7;7;7;7;>*'# ", -" 8;O*O*O*T*T*T*T*T*T*T*T*T*j&.@ @ @ @=#'&F*F&`#`#R$L%L%L%L*L*l&g#j%j%p&C=C=Q%L@L@O&W$`%`%;=F#F#F#Z=Z=y%~@-#-#c$9&9&%#}$}$T&Z#Z#]$f#b$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @Y#^+]&m+[*9;~-[;[;[;[;7;7;7;7;7;7;0;a;[# ", -" b;T*T*T*T*T*T*T*T*T*T*T*T*d*K@ @ @ @C$F&`#R$R$L%L%L*L*l&g#g#j%p&p&C=Q%L@L@O&W$W$`%;=;=F#F#Z=Z=y%~@~@-#c$c$9&%#}$}$T&Z#Z#]$Q$Q$a$a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#^+3&/-A%c;';7;7;7;7;7;7;0;0;0;0;0;d;{ ", -" e;T*T*T*T*T*T*T*T*T*T*T*]=]=R$ @ @ @/$R$L%L%L%L*l&g#g#j%p&p&C=Q%Q%L@O&W$W$`%;=;=F#F#F#Z=y%~@~@-#c$c$9&%#%#}$T&Z#Z#]$Q$Q$f#f#P$^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @G+I+e&f&f;1;/;7;7;0;0;0;0;0;0;0;0;-;. ", -" T*T*T*T*T*T*T*T*]=]=]=[=[=G& @ @ @ @j%L*l&l&g#j%p&p&C=Q%Q%L@O&O&W$`%;=;=F#F#F#Z=y%y%~@-#-#c$9&%#%#}$T&T&Z#]$]$Q$f#f#f#K@D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @g;1$q&4 h;i;j;0;0;0;0;0;0;0;0;k;k;-;+ e$ ", -" T*T*T*T*]=]=]=[=[=[=[=[=[=l; @ @ @ @F#g#j%j%p&C=C=Q%L@O&O&W$`%`%;=F#F#F#Z=y%y%~@-#-#c$9&9&%#}$T&T&Z#]$]$Q$f#f#f#O$o$C#d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @x%/+k@$-D=m;n;0;0;0;0;k;k;k;o;o;o;o;Z-K# ", -" z=]=]=]=[=[=[=[=[=[=4=4=4=p; @ @ @ @T&p&C=C=Q%L@L@O&W$`%`%;=F#F#F#Z=Z=y%~@-#-#c$9&9&%#}$}$T&Z#]$]$Q$f#f#f#O$O$o$T@}% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @$%^+o+R+)&q;r;k;k;k;k;o;o;o;o;o;o;o;s;t# ", -" t;[=[=[=[=[=4=4=4=4=4=4=4=%; @ @ @ @K@Q%L@L@O&W$W$`%;=;=F#F#Z=Z=y%~@~@-#c$c$9&%#}$}$T&Z#Z#]$Q$Q$f#f#O$O$o$T@T@o@`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/$H+D$d-3%u;';o;o;o;o;o;o;o;o;o;v;v;w;. ", -" x;[=[=4=4=4=4=4=4=4=4=4=f=y; @ @ @ @Z+O&W$W$`%;=;=F#F#F#Z=y%~@~@-#c$c$9&%#%#}$T&Z#Z#]$Q$Q$f#f#f#O$o$T@T@K@o#Z+d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @H%M*c%O.z;1;7;o;o;o;o;o;v;v;v;v;v;-;. ", -" A;4=4=4=4=4=4=4=f=f=f=f=h=B;d# @ @ @}%`%;=;=F#F#F#Z=y%y%~@-#c$c$9&%#%#}$T&T&Z#]$Q$Q$f#f#f#O$o$o$T@K@K@o#a$}%^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @v&@@$*4.q=C;(;o;v;v;v;v;v;v;v;v;v;0;D;U# ", -" i-4=4=4=4=f=f=f=h=h=h=h=h=O*E# @ @ @`+F#F#F#Z=y%y%~@-#-#c$9&9&%#}$T&T&Z#]$]$Q$f#f#f#O$o$o$T@K@K@o#a$a$$=D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @b@/+;*>*m&E;n;v;v;v;v;v;v;v;F;F;F;G;Y@m# ", -" m%f=f=f=h=h=h=h=h=h=h=h=h=h=`% @ @ @/@Z=Z=y%~@-#-#c$9&9&%#}$}$T&Z#]$]$Q$f#f#f#O$O$o$T@K@K@o#a$a$o@$=Z+a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @P$^+n%*&H;I;r;v;v;v;F;F;F;F;G;G;G;G;s;<$ ", -" h=h=h=h=h=h=h=h=h=h=h=h=a=1* @ @ @ @%#~@-#c$9&9&%#}$}$T&Z#Z#]$Q$Q$f#f#O$O$o$T@T@K@o#o#a$o@$=$=&#(#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@J;b+w-4%K;';F;F;F;G;G;G;G;G;G;L;L;M;. ", -" h=h=h=h=h=h=h=h=h=a=a=a=z=N; @ @ @ @O$c$9&%#%#}$T&Z#Z#]$Q$Q$f#f#f#O$o$T@T@K@o#o#a$o@o@$=&#C#C#p@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @V#a+B-%@O;P;o;G;G;G;G;G;L;L;L;L;L;0;. /# ", -" h=h=h=h=h=a=a=a=z=z=z=z=z=Q; @ @ @ @o@%#}$T&T&Z#]$Q$Q$f#f#f#O$o$o$T@K@o#o#a$o@o@$=&#&#C#E#=#`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @R;@@#@Z.2=S;/;G;L;L;L;L;L;L;L;L;L;T;}#j# ", -" U;a=a=a=a=z=z=z=z=z=z=I=I=V; @ @ @ @=#T&Z#]$]$Q$f#f#f#O$o$o$T@K@K@o#a$a$o@$=&#&#C#E#E#Z+0@a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @O$^+4*#+b&W;H-L;L;L;L;L;L;L;T;T;T;T;X;~# ", -" Y;z=z=z=z=z=z=I=I=I=I=I=I=Z; @ @ @ @]@]$Q$f#f#f#O$O$o$T@K@K@o#a$a$o@$=$=&#C#E#E#Z+=#=#c#/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @p@^+T+`; >.>r;L;L;L;T;T;T;T;T;T;T;T;+>|+ ", -" s-z=z=z=I=I=I=I=I=I=I=I=I=O* @ @ @ @e#f#f#O$O$o$T@T@K@o#a$a$o@$=$=&#C#C#E#Z+Z+=#=#0@b$^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D+I+b*s@@>#>T;T;T;T;T;T;T;T;T;$>$>%>. ", -" &>I=I=I=I=I=I=I=I=I=I=I=I=P*}% @ @ @a@O$o$T@T@K@o#o#a$o@o@$=&#C#C#E#Z+Z+=#=#=#0@P$e# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @z#C+e*w@R-*>=>T;T;T;T;$>$>$>$>->->k;. %$ ", -" ;>I=I=I=I=I=I=I=I=S=S=S=X=z=T& @ @ @^@T@K@o#o#a$o@o@$=&#&#C#E#Z+Z+=#=#=#0@P$P$(#`+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @G@/+h*3$D=>>,>T;$>$>$>->->->->->->'>Z->$ ", -" I=I=I=I=S=S=S=S=X=X=X=+-+-0& @ @ @ @$=a$a$o@$=&#&#C#E#E#Z+=#=#=#0@P$P$(#c#}%a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @o@^+o+q#)>*;!>->->->->->->->'>'>'>'>E*Y@ ", -" I=S=S=S=X=X=X=+-+-+-+-+-+-~> @ @ @ @=#$=$=&#C#E#E#Z+=#=#=#0@0@P$(#c#c#}%b$d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+^+M#N#O#,;U-->->->'>'>'>'>'>'>'>'>{>/. ", -" X=X=X=+-+-+-+-+-+-+-+-+-+-]> @ @ @ @}%C#C#E#Z+=#=#=#0@0@P$(#(#c#}%]@]@C$/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@#C@*$f&^>/>T;'>'>'>'>'>'>'>'>'>'>(>. ", -" 2-+-+-+-+-+-+-+-+-+-+-+-~-_> @ @ @ @C$Z+Z+=#=#=#0@P$(#(#c#}%}%]@b$b$e#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @8&1$2$s :><>o;'>'>'>'>'>'>'>[>[>[>}>{ {& ", -" |>+-+-+-+-+-+-+-+-~-~-~-~-g- @ @ @ @`+=#=#0@P$P$(#c#}%}%]@b$b$p@C$D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @1>/+k@;@2>3>/;'>'>'>'>[>[>[>4>4>4>5>6>P@ ", -" 7>+-+-+-+-~-~-~-~-~-~-~-~-z= @ @ @ @a@P$P$(#c#c#}%]@]@b$p@C$C$*#.@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @=#^+(+t$!&8>9>[>[>[>4>4>4>5>5>5>5>5>E*J# ", -" 0>+-~-~-~-~-~-~-~-~-~-:-:-9-D# @ @ @/@c#c#}%]@]@b$p@p@C$*#e#e#a@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#U+D$E$c.,;r;4>4>4>5>5>5>5>5>5>5>5>a>. ", -" b>~-~-~-~-~-~-:-:-:-:-2-2-'=T@ @ @ @ @]@]@b$p@p@C$*#*#e#D#`+/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @N$M*X$O.f;c>'>5>5>5>5>5>5>5>5>5>5>G;. ", -" d>~-~-~-:-:-:-2-2-2-6-6-6-6-L* @ @ @ @C$p@C$*#*#e#D#D#`+`+d# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @m@@@,%'%2=e>F;5>5>5>5>5>5>5>5>5>5>f>8+e$ ", -" :-:-2-2-2-6-6-6-6-6-6-6-6-g$ @ @ @ @D#*#e#D#D#`+`+`+.@/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @g>/+6%7%h>i>j>5>5>5>5>5>5>5>f>f>f>f>0-K# ", -" 2-2-6-6-6-6-6-6-6-6-6-6-6-k> @ @ @ @.@D#`+`+`+.@a@a@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @x&^+n%4@+&l>!>5>5>5>f>f>f>f>f>f>f>f>m>#$ ", -" 6-6-6-6-6-6-6-6-6-6-6-f-f-n> @ @ @ @/$`+.@.@a@/$d#^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@M$b+B%&%u;o>f>f>f>f>f>f>f>f>f>p>p>q>. ", -" 2;6-6-6-6-6-6-f-f-f-f-f-f-r> @ @ @ @/@a@/$/$d#/@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @X%C+5@R%f;s>t>f>f>f>f>p>p>p>p>p>p>$>. ", -" u>6-6-6-f-f-f-f-f-f-l-l-l-v> @ @ @ @^@d#/@/@^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @X+@@#&6#w>x>F;f>p>p>p>p>p>p>p>p>p>y><$;% ", -" z>f-f-f-f-f-l-l-l-r-r-r-r-L-d# @ @ @ @^@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @A>^+]&m+)>m;j>p>p>p>p>p>p>p>y>y>y>B>E*^# ", -" C>f-f-l-l-l-r-r-r-r-r-r-r-S=C# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @e#^+3&4&D>l>E>p>p>p>y>y>y>B>B>B>B>B>F>}# ", -" G>l-r-r-r-r-r-r-r-r-r-r-r-r-;= @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @H>I+e&f&O-I>J>y>B>B>B>B>B>B>B>B>B>$>. ", -" r-r-r-r-r-r-r-r-r-r-D-D-D-K> @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @g;1$q&r&L>M>4>B>B>B>B>B>B>B>B>B>B>N>. z% ", -" r-r-r-r-r-r-r-D-D-D-H-H-H-O> @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @A&/+B&C&l=P>Q>B>B>B>B>B>B>B>R>R>R>S>T>s$ ", -" r-r-r-D-D-D-H-H-H-H-H-H-H-U> @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @$%^+o+j*)&V>j>B>B>B>R>R>R>S>S>S>W>W>G=~# ", -" X>D-H-H-H-H-H-H-H-H-H-H-P-E* @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/$H+D$Q&R&Y>Z>R>R>R>S>S>S>W>W>W>W>W>7>/. ", -" q>H-H-H-H-H-H-H-P-P-P-P-P-`> @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @E+C@Y&Z& ,.,y>S>W>W>W>W>W>W>W>W>W>'>. ", -" +,H-H-H-H-P-P-P-P-P-P-V-V-z= @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @v&1$$*4.e-@,4>W>W>W>W>W>W>W>W>W>W>B>{ o& ", -" #,P-P-P-P-P-P-V-V-V-V-Y-Y-=;c# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @%#/+;*>*h>$,Q>W>W>W>W>W>W>W>W>W>W>%,&,f$ ", -" *,P-P-P-V-V-V-Y-Y-Y-Y-Y-Y-H-}$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @0@^+n%)*_.=,-,W>W>W>W>W>W>%,%,%,;,;,>,l# ", -" V-V-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-U& @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@J;b+>@c.,,Z>W>W>W>%,%,%,;,;,;,',',),{ ", -" Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-z% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @!,a+/*%@ -~,{,%,;,;,;,',',',',',',5>. ", -" Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-Y-#;#;], @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @R;@@#@Z.w>*>5>;,',',',',',',',',',%,s&P% ", -" -;Y-Y-Y-Y-Y-Y-#;#;#;-;-;-;^, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @3*^+4*#+[*T-Q>',',',',',',/,/,/,/,/,s;P@ ", -" (,Y-Y-Y-#;#;#;-;-;-;-;-;-; ; @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @b$^+T+`;O%_,-,',',',/,/,/,/,/,/,/,/,:,t# ", -" d;#;#;-;-;-;-;-;-;-;-;-;-;<, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D+b+b*s@[,},/,/,/,/,/,/,/,/,/,|,|,p>. ", -" z>-;-;-;-;-;-;-;-;-;-;-;-;S=D# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @1,C+e*w@ -2,{,/,/,/,/,|,|,|,|,|,|,R>. ", -" 3,-;-;-;-;-;-;-;-;-;-;-;/;4,o$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @5,/+h*3$2>6,7,|,|,|,|,|,|,|,8,8,8,9,@$O@ ", -" -;-;-;-;-;-;-;/;/;/;/;/;/;j% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a$^+o+{-0,a,b,|,|,|,8,8,8,9,9,9,9,9,c,m# ", -" -;-;-;/;/;/;/;/;/;/;:;:;:;d, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+^+M#/-$.e,-,8,8,8,9,9,9,9,9,9,9,9,m><$ ", -" /;/;/;/;/;/;:;:;:;[;[;[;[;f, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @&$I+|-f&u;g,/,9,9,9,9,9,9,9,9,9,9,h,. ", -" /;/;:;:;:;:;[;[;[;[;[;[;[;i, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @F@1$.*4 -j,W>9,9,9,9,9,9,9,k,k,k,l,+ H# ", -" v;:;[;[;[;[;[;[;[;[;[;7;7;&, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @0$/+k@$-l=m,7,9,9,9,k,k,k,k,k,k,k,n,o,-% ", -" p,[;[;[;[;[;[;[;7;7;7;7;7;q, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @h%^+(+$+>;r,s,k,k,k,k,k,k,n,n,n,t,t,u,Q@ ", -" v,[;[;[;7;7;7;7;7;7;0;0;0;S=/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#x#D$I*J*w,x,k,k,n,n,n,n,t,t,t,t,t,y,|+ ", -" z,7;7;7;7;7;7;0;0;0;0;0;0;H-&# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @E@C@c%Z&A,B,C,n,t,t,t,t,t,t,t,t,t,4>. ", -" D,7;7;0;0;0;0;0;0;0;0;0;0;2;F# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @9$1$.*4 ,2,/,t,t,t,t,t,t,t,E,E,E,Z>{ K& ", -" 0;0;0;0;0;0;0;0;k;k;k;o;o;%% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @Z#@@_=Q=0,s>%,t,t,t,E,E,E,E,E,E,F,B,Y@r$ ", -" 0;0;0;0;k;k;k;k;o;o;o;o;o;G, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @c#/+#&$&A%H,%,E,E,E,E,E,E,F,F,F,F,I,J,)# ", -" 0;k;k;k;o;o;o;o;o;o;o;o;o;K, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@^+x=~ (.L,|,E,E,F,F,F,F,F,F,F,F,M,N,}+ ", -" G;o;o;o;o;o;o;o;o;o;v;v;v;z> @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @H+c%r&z+O,n,F,F,F,F,F,F,F,F,P,P,Q,R,. ", -" S,o;o;o;o;o;v;v;v;v;v;v;v;2; @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@a+4&s J#T,F,F,F,F,F,P,P,P,P,P,P,U,U#/..$ ", -" V,o;o;v;v;v;v;v;v;v;v;v;F;W, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @p@x=7%7%v-X,F,F,P,P,P,P,P,P,Y,Y,k,Z,`,`;$$ ", -" 'v;v;v;v;v;v;v;F;F;F;F;G;.'c# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+L@I*_@*$+'P,P,P,P,P,Y,Y,Y,Y,@'@'#'$'%'&')# ", -" *'v;v;v;v;F;F;F;G;G;G;G;G;[;}$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@e#a$='/-|-o=%,P,P,Y,Y,Y,@'@'@'@'@'@'A,j,-';'#$ ", -" F;F;F;G;G;G;G;G;G;G;L;L;L;>' @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@e#o@g#/*V@4*R>Y,Y,@'@'@'@'@'@'@'@'@'@','''-')'{ ", -" G;G;G;G;G;G;L;L;L;L;L;L;L;/# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@`+}%o$z%;*D$!'@'@'@'@'@'@'@'@'@'@'@'@'~'{'U>-']'. ", -" G;G;L;L;L;L;L;L;L;L;L;L;T;^' @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@.@/'('C@o&_'@'@'@'@'@'@'@'@'@'@'@'@'@':'<'['-'-'/. ", -" L;L;L;L;L;L;L;L;T;T;T;T;T;}' @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @o@|'e$1'9,@'@'@'@'@'@'@'@'@'@'@'@'@'7,2'3'-'-'-'^* ", -" S,L;L;L;L;T;T;T;T;T;T;T;T;w; @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @4'5'6'7';,/,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@':'8':#-'-'-'9' ", -" ''T;T;T;T;T;T;T;T;T;$>$>$>r- @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#0'a'b'c'd'F,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'e'f'R>-'-'-'-'s$ ", -" g'T;T;T;T;T;T;$>$>$>->->->l-e# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@h'i'j'k'l'm'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'F,n'I,]&-'-'-'o'K& ", -" p'T;T;$>$>$>->->->->->->->/;o$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @q'`%r's't'u'v'Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'Z,w'x'-'-'-'-'y'l& ", -" $>$>->->->->->->'>'>'>'>T;j% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @d#z'A'B'C'D'v'P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'E'F'G'%'-'-'-'-'H' ", -" ->->->->'>'>'>'>'>'>'>'>'>I' @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @c#J'K'L'M'N'O'P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'P'Q'Z>R'-'-'-'-'-'S' ", -" ->'>'>'>'>'>'>'>'>'>'>'>'>T' @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a@U'V'W'X'Y'9,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'Z'Q'`':#-'-'-'-'-' ).) ", -" '>'>'>'>'>'>'>'>'>'>[>[>[>+) @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@)#)$)%)&)t,n,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'*)=)B,5%-'-'-'-'-'-'[' ", -" 4>'>'>'>'>'>[>[>[>4>4>4>5>-) @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @;)>),)')))!)n,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'b,~){)])^)-'-'-'-'-'-'-'/) ", -" ()'>'>[>[>[>4>4>4>5>5>5>5>_) @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @:)<)[)})l'm'E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'/,A,|)1)2)3)-'-'-'-'-'-'-'4)5) ", -" 6)[>4>4>4>5>5>5>5>5>5>5>5>4,/$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @*#7)8)9)t'0)8,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'a)b)Q'c)d)e)-'-'-'-'-'-'-'-'-'f) ", -" g)4>5>5>5>5>5>5>5>5>5>5>5>/;&# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @^@h)i)j)k)l)m)t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'G'n)b)o)f'U,-,p)q)-'-'-'-'-'-'-'-'-' )r) ", -" *'5>5>5>5>5>5>5>5>5>5>5>5>$>F# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @}%s)t)u)v)N'9,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'C,w)x)A,{'o)y)z)A)B)`'C)D) )-'-'-'-'-'-'-'-'-'-'-'E) ", -" 5>5>5>5>5>5>5>5>5>f>f>f>f>@& @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/$F)G)H)I)J)K)t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'a)L)M):'N)O)P)Q)c)8'e>9>_'R)S)H+-'-'-'-'-'-'-'-'-'-'-'-'-'-'T)U) ", -" 5>5>5>5>5>f>f>f>f>f>f>f>f>V) @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @W)X)Y)Z)`)t,9,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'%,`' !b).!+!@!#!w'$!%!/>5>&!*!=!3)-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-!;! ", -" 5>5>f>f>f>f>f>f>f>f>f>p>p>>! @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+,!'!)!!!~!/,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'F,-,{!1)c){)]!O)N):'e,6,^!/!(!:$_!:!-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'f>f>f>f>f>f>f>p>p>p>p>p>}! @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @E#|!1!2!3!v'k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'4!5!M,6!7!8!@!9!0!a!x)2,b!}!c!]+d!-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'e!f!U) ", -" ()f>f>f>p>p>p>p>p>p>p>p>p>M; @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+g!h!i!j!k!|,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'n,l!m,m!n!o!|)p!q!r!$'s!=>C)J,$*t! )-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'u!5)U) ", -" O'p>p>p>p>p>p>p>p>p>y>y>y>4, @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @o#v!w!x!y!8,/,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'7,j,=,z!A!f'|)B!C!*)D!o>()E!e)o+%'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'F!G!H!U) ", -" I!p>p>p>p>p>y>y>y>B>B>B>B>j;(# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @p@J!K!L!M!N!/,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'9,e'Z'a!O!n'@!F'P!Z,=,Q!G;x'R!5%S!-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'T!U!U)U) ", -" 1'p>p>y>y>y>B>B>B>B>B>B>B>T;%# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @/@V!W!X!Y!J)K)9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'b,Q!e,Z!`!=)Q' ~.~+~m,@~#~$~<;%~&~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'*~=~U)U)U) ", -" y>B>B>B>B>B>B>B>B>B>B>B>B>p$ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @}%-~;~>~,~O'8,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'@'/,'~x)U,)~!~y)z)~~{~]~^~/~ '(~_~y'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-':~<~[~}~U)U)U) ", -" B>B>B>B>B>B>B>B>B>B>R>R>R>c! @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @a@|~1~2~3~4~()8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'@'@'@'5~6~B)7~~)P)P),'8~B)6,9~0~a~n#b~c~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'d~e~f~g~h~r)U)U)U)U) ", -" t>B>B>B>B>B>B>R>R>R>S>S>S>o;}% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @i~j~$)k~l~8,l,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'@'@'@'@'m~n~E'{~~~o~y)p~w'U, !q~h,}!r~T=s~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'t~u~v~w~x~}~y~U)U)U)U)U)U)U)U) ", -" z~B>B>R>R>R>S>S>S>W>W>W>W>W>A~ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @`+B~C~D~E~F~l,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@'@'@'F,G~P'+~H~I~Q'J~K~Z!e,{!L~M~N~O~L$P~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-':~Q~R~S~T~U~V~U)U)U)U)U)U)U)U)U)U)U)U) ", -" G-R>S>S>S>S>W>W>W>W>W>W>W>W>4>W~ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @4'X~Y~Z~`~K)/,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'@'@'@' {.{+{@{7!2'@!#{${a!x)%{()&{U>*{={-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-{;{>{,{'{J@){U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" !{^!W>W>W>W>W>W>W>W>W>W>W>W>W>'>~{ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @D#{{]{9)^{/{%,/,/,/,|,|,|,|,|,|,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,Y,Y,Y,@'@'n,({<'*)C!B!|)_{:{z!<{[{])}{p),%|{P~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{-!2{3{4{5{6{U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U,W>W>W>W>W>W>W>W>W>W>W>W>%,%,{,7{o$ @ @ @ @ @ @ @/@8{9{0{a{l);,/,/,/,/,/,/,|,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,P,P,P,P,Y,Y,4>b{$'r!:{p!|)B!n!m!P>c{d{$~e{^)f{-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'g{h{i{j{k{l{m{U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" n{o{W>W>W>W>W>W>W>W>%,%,%,;,;,;,',W>p{q{r{s{a{t{()W>/,/,/,/,/,/,/,/,/,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,F,P,P,P,P,P,K)o{$,u{A)#{@!2'7!6!V>s>v{w{x{5%y{-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{z{A{B{E)C{D{U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" E{W>W>W>W>%,%,%,;,;,;,',',',',',',',',',',/,/,/,/,/,/,/,/,/,|,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,P,P,P,v{F{l>:'K~O)Q'{)c)1)m,G{_'}'H{D$&~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'I{J{K{L{M{N{O{U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" m!e,%,%,;,;,;,;,',',',',',',',',',/,/,/,/,/,/,/,/,/,/,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,F,F,F,%,`'%!$!w'#!y)o~.!P{]~j,Q{R{S{:#3)-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'T{U{V{W{X{Y{Z{U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" `!`{;,',',',',',',',',',/,/,/,/,/,/,/,/,/,/,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,F,F,F,F,F,F,n,G~{!1) ],']!]!.]7~B)+]@]/{#]S)$]c~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-':~],]y)!~{'U,E'']u>)]!]~]['-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'{]u~]]^]x~}~/]U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" (]+!5~',/,/,/,/,/,/,/,/,/,/,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,t,E,E,E,E,E,E,n,l!m,Q,.~_]:]=)`!Z!e,<><]M~[]],}]P~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'F!|]T)1]2]3]4]U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" 5]+!({/,/,/,/,/,/,|,|,|,|,|,|,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,n,t,t,t,t,t,t,t,t,t,E,E,E,E,4>6]<{Z,7]f'|)8]O!a!>>~,d{E!9]0]a]-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'g{b]c]d]e]f]){U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" S=O)M>/,|,|,|,|,|,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,t,E,|,g,g]a!h]8]|)_{A!i]j]6~=>g'k]D)l]P~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{-!m]n]o]p]q]U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" r]=)6!l,|,|,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,t,t,t,t,t,t,s,F{e,Z!`!=)-=o!n!*)Z'o>V,t{s](+t]-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'u]f)v]w]x]y]m{U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" z]h]A]B]8,9,9,9,9,9,9,9,9,9,9,9,9,k,k,k,k,k,k,k,n,n,n,t,t,t,t,%,`'>>U,{'!~y)2'7!C]V>5]D]E]x{F]H+-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{G]H]B{E)I]J]U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" K]L]!~n'E'/,9,9,9,9,9,9,9,k,k,k,k,k,k,n,n,n,t,t,d){!Y>M]~)N]]!I~H~1)x>@~y,O]P]Q]q)-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'I{R]f~S]T]U]O{U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)V]W]=;X]^=Y]Z]G'k,k,k,k,k,k,n,n,4!~,r,{~~~z)y)+!~~{~`]^~'> ^.^I#3)-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'+^@^#^W{$^%^y~U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" N{t~-'H+&^=,A!+=O)o!*^n!n!7]=^]!N].]M]Y>6,!>v,a~-^`,:!-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-':~;^R~>^,^'^)^U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)!^:~-'-'P~^+~^{^h,5]x>x>]^v,^^/^(^|{_^-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-':^<^[^^]}^|^/]U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)1^-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'F!|]2^G!3^4^6{U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)5^-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-{6^7^8^9^0^){U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)a^:~-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{b^A{n]c^d^e^U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)f^-{-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'g^f)h^w]M{i^O{U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)V~j^-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'1{<~k^l^m^I]r)U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)n^o^-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'-';^e~f~p^T]q^U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)U)r^k^T{-'-'-'-'-'-'-'-'-'-'-'-'-'-'-'t~u~v~w~x~}~y~U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)U)U)/]K%1]s^t^:~-'-'-'T{u^v^S~w^U~V~U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)U)U)U)U)U)O{x^x^Z{U)U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" U)U)U)U)U)U)U)U)U)U)U)U)U)U)U)U) ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" "}; diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/pause.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/pause.png deleted file mode 100644 index 7a6cc970e8..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/pause.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/run.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/run.png deleted file mode 100644 index 496f9a22ca..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/run.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/stop.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/stop.png deleted file mode 100644 index 16899a3df4..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/stop.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/undo.png b/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/undo.png deleted file mode 100644 index 351e692719..0000000000 Binary files a/Plugins/org.mitk.gui.qt.cmdlinemodules/resources/undo.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.cpp deleted file mode 100644 index 30d4cc33a1..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkCmdLineModuleMenuComboBox.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// ------------------------------------------------------------------------- -QmitkCmdLineModuleMenuComboBox::QmitkCmdLineModuleMenuComboBox(QWidget* parent) -: ctkMenuComboBox(parent) -, m_ModuleManager(nullptr) -{ - qRegisterMetaType(); -} - - -// ------------------------------------------------------------------------- -QmitkCmdLineModuleMenuComboBox::~QmitkCmdLineModuleMenuComboBox() -{ - -} - - -// ------------------------------------------------------------------------- -void QmitkCmdLineModuleMenuComboBox::SetManager(ctkCmdLineModuleManager* manager) -{ - if (m_ModuleManager != nullptr) - { - QObject::disconnect(manager, nullptr, this, nullptr); - } - - m_ModuleManager = manager; - - connect(m_ModuleManager, SIGNAL(moduleRegistered(const ctkCmdLineModuleReference&)), this, SLOT(OnModuleRegistered(const ctkCmdLineModuleReference&))); - connect(m_ModuleManager, SIGNAL(moduleUnregistered(const ctkCmdLineModuleReference&)), this, SLOT(OnModuleUnRegistered(const ctkCmdLineModuleReference&))); -} - - -// ------------------------------------------------------------------------- -ctkCmdLineModuleManager* QmitkCmdLineModuleMenuComboBox::GetManager() const -{ - return m_ModuleManager; -} - - -// ------------------------------------------------------------------------- -void QmitkCmdLineModuleMenuComboBox::OnModuleRegistered(const ctkCmdLineModuleReference&) -{ - this->RebuildMenu(); -} - - -// ------------------------------------------------------------------------- -void QmitkCmdLineModuleMenuComboBox::OnModuleUnRegistered(const ctkCmdLineModuleReference&) -{ - this->RebuildMenu(); -} - - -// ------------------------------------------------------------------------- -void QmitkCmdLineModuleMenuComboBox::AddName( - QList< QHash* >& listOfHashMaps, - const int& depth, - const QString& name, - QMenu* menu - ) -{ - if (depth >= listOfHashMaps.size()) - { - int need = depth - listOfHashMaps.size(); - for (int i = 0; i <= need; i++) - { - auto newHashMap = new QHash(); - listOfHashMaps.push_back(newHashMap); - } - } - listOfHashMaps[depth]->insert(name, menu); -} - - -// ------------------------------------------------------------------------- -void QmitkCmdLineModuleMenuComboBox::RebuildMenu() -{ - if (m_ModuleManager == nullptr) - { - qDebug() << "QmitkCmdLineModuleMenuComboBox::RebuildMenu(): Module Manager is nullptr? Surely a bug?"; - return; - } - - // Can't see another way to get a nested menu, without rebuilding the whole thing each time. - // :-( - - auto menu = new QMenu(); - QStringList listOfModules; - - QList references = m_ModuleManager->moduleReferences(); - - // Get full names - for (int i = 0; i < references.size(); i++) - { - ctkCmdLineModuleReference reference = references[i]; - ctkCmdLineModuleDescription description = reference.description(); - QString title = description.title(); - QString category = description.category(); - QString fullName = category + "." + title; - listOfModules << fullName; - } - - // Sort list, so menu comes out in some sort of nice order. - listOfModules.sort(); - - // Temporary data structure to enable connecting menus together. - QList< QHash* > list; - - // Iterate through all modules. - foreach (QString fullName, listOfModules) - { - // Pointer to keep track of where we are in the menu tree. - QMenu *currentMenu = menu; - - // Get individual parts, as they correspond to menu levels. - QStringList nameParts = fullName.split(".", QString::SkipEmptyParts); - - // Iterate over each part, building up either a QMenu or QAction. - for (int i = 0; i < nameParts.size(); i++) - { - QString part = nameParts[i]; - - if (i != nameParts.size() - 1) - { - // Need to add a menu/submenu, not an action. - if (list.size() <= i || list[i] == nullptr || !list[i]->contains(part)) - { - auto newMenu = new QMenu(part); - currentMenu->addMenu(newMenu); - currentMenu = newMenu; - - // Add this newMenu pointer to temporary datastructure, - // so we know we have already created it. - this->AddName(list, i, part, newMenu); - } - else - { - currentMenu = list[i]->value(part); - } - } - else - { - // Leaf node, just add the action. - QAction *action = currentMenu->addAction(part); - - // We set the object name, so we can retrieve it later when we want to - // rebuild a new GUI depending on the name of the action. - // see QmitkCmdLineModuleProgressWidget. - action->setObjectName(fullName); - } - } - } - - // Clearup temporary data structure - for (int i = 0; i < list.size(); i++) - { - delete list[i]; - } - - // Set the constructed menu on the base class combo box. - this->setMenu(menu); -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.h deleted file mode 100644 index 2036ac073b..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/QmitkCmdLineModuleMenuComboBox.h +++ /dev/null @@ -1,64 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkCmdLineModuleMenuComboBox_h -#define QmitkCmdLineModuleMenuComboBox_h - -#include -#include -#include -#include -#include - -/** - * \class QmitkCmdLineModuleMenuComboBox - * \brief Subclass of ctkMenuComboBox to listen to ctkCmdLineModuleManager - * moduleRegistered and moduleUnregistered signals, and update the menu accordingly. - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \sa ctkMenuComboBox - */ -class QmitkCmdLineModuleMenuComboBox : public ctkMenuComboBox { - - Q_OBJECT - -public: - - QmitkCmdLineModuleMenuComboBox(QWidget* parent = nullptr); - ~QmitkCmdLineModuleMenuComboBox() override; - - /** - * \brief Inject the module manager, so that this widget can - * still easily be used via widget promotion in Qt Designer, - * as it will maintain the default constructor. - */ - void SetManager(ctkCmdLineModuleManager* manager); - - /** - * \brief Returns the ctkCmdLineModuleManager. - */ - ctkCmdLineModuleManager* GetManager() const; - -private slots: - - void OnModuleRegistered(const ctkCmdLineModuleReference&); - void OnModuleUnRegistered(const ctkCmdLineModuleReference&); - -private: - - void RebuildMenu(); - void AddName(QList< QHash* >& listOfHashMaps, const int& depth, const QString& name, QMenu* menu); - - ctkCmdLineModuleManager* m_ModuleManager; -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.cpp deleted file mode 100644 index 2a5eb434d6..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "CommandLineModulesPreferencesPage.h" -#include "CommandLineModulesViewConstants.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "QmitkDirectoryListWidget.h" -#include "QmitkFileListWidget.h" - -namespace -{ - mitk::IPreferences* GetPreferences() - { - auto* preferencesService = mitk::CoreServices::GetPreferencesService(); - return preferencesService->GetSystemPreferences()->Node(CommandLineModulesViewConstants::VIEW_ID); - } -} - -//----------------------------------------------------------------------------- -CommandLineModulesPreferencesPage::CommandLineModulesPreferencesPage() -: m_MainControl(nullptr) -, m_DebugOutput(nullptr) -, m_ShowAdvancedWidgets(nullptr) -, m_OutputDirectory(nullptr) -, m_TemporaryDirectory(nullptr) -, m_ModulesDirectories(nullptr) -, m_ModulesFiles(nullptr) -, m_GridLayoutForLoadCheckboxes(nullptr) -, m_LoadFromHomeDir(nullptr) -, m_LoadFromHomeDirCliModules(nullptr) -, m_LoadFromCurrentDir(nullptr) -, m_LoadFromCurrentDirCliModules(nullptr) -, m_LoadFromApplicationDir(nullptr) -, m_LoadFromApplicationDirCliModules(nullptr) -, m_LoadFromAutoLoadPathDir(nullptr) -, m_ValidationMode(nullptr) -, m_MaximumNumberProcesses(nullptr) -{ - -} - - -//----------------------------------------------------------------------------- -CommandLineModulesPreferencesPage::~CommandLineModulesPreferencesPage() -{ - -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesPreferencesPage::Init(berry::IWorkbench::Pointer ) -{ - -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesPreferencesPage::CreateQtControl(QWidget* parent) -{ - mitk::CoreServicePointer prefService(mitk::CoreServices::GetPreferencesService()); - - m_MainControl = new QWidget(parent); - - m_TemporaryDirectory = new ctkDirectoryButton(m_MainControl); - m_TemporaryDirectory->setCaption("Select a directory for temporary files ... "); - m_OutputDirectory = new ctkDirectoryButton(m_MainControl); - m_OutputDirectory->setCaption("Select a default directory for output files ... "); - - m_ModulesDirectories = new QmitkDirectoryListWidget(m_MainControl); - m_ModulesDirectories->m_Label->setText("Select directories to scan:"); - m_ModulesFiles = new QmitkFileListWidget(m_MainControl); - m_ModulesFiles->m_Label->setText("Select additional executables:"); - - m_DebugOutput = new QCheckBox(m_MainControl); - m_DebugOutput->setToolTip("Output debugging information to the console."); - - m_ShowAdvancedWidgets = new QCheckBox(m_MainControl); - m_ShowAdvancedWidgets->setToolTip("If selected, additional widgets appear\nin front-end for advanced users."); - - m_LoadFromAutoLoadPathDir = new QCheckBox(m_MainControl); - m_LoadFromAutoLoadPathDir->setText("CTK_MODULE_LOAD_PATH"); - m_LoadFromAutoLoadPathDir->setToolTip("Scan the directory specified by\nthe environment variable CTK_MODULE_LOAD_PATH."); - m_LoadFromAutoLoadPathDir->setLayoutDirection(Qt::RightToLeft); - m_LoadFromApplicationDir = new QCheckBox(m_MainControl); - m_LoadFromApplicationDir->setText("install dir"); - m_LoadFromApplicationDir->setToolTip("Scan the directory where\nthe application is installed."); - m_LoadFromApplicationDir->setLayoutDirection(Qt::RightToLeft); - m_LoadFromApplicationDirCliModules = new QCheckBox(m_MainControl); - m_LoadFromApplicationDirCliModules->setText("install dir/cli-modules"); - m_LoadFromApplicationDirCliModules->setToolTip("Scan the 'cli-modules' sub-directory\nwithin the installation directory."); - m_LoadFromApplicationDirCliModules->setLayoutDirection(Qt::RightToLeft); - m_LoadFromHomeDir = new QCheckBox(m_MainControl); - m_LoadFromHomeDir->setText("home dir"); - m_LoadFromHomeDir->setToolTip("Scan the users home directory."); - m_LoadFromHomeDir->setLayoutDirection(Qt::RightToLeft); - m_LoadFromHomeDirCliModules = new QCheckBox(m_MainControl); - m_LoadFromHomeDirCliModules->setText("home dir/cli-modules"); - m_LoadFromHomeDirCliModules->setToolTip("Scan the 'cli-modules' sub-directory\nwithin the users home directory."); - m_LoadFromHomeDirCliModules->setLayoutDirection(Qt::RightToLeft); - m_LoadFromCurrentDir = new QCheckBox(m_MainControl); - m_LoadFromCurrentDir->setText("current dir"); - m_LoadFromCurrentDir->setToolTip("Scan the current working directory\nfrom where the application was launched."); - m_LoadFromCurrentDir->setLayoutDirection(Qt::RightToLeft); - m_LoadFromCurrentDirCliModules = new QCheckBox(m_MainControl); - m_LoadFromCurrentDirCliModules->setText("current dir/cli-modules"); - m_LoadFromCurrentDirCliModules->setToolTip("Scan the 'cli-modules' sub-directory\nwithin the current working directory \n from where the application was launched."); - m_LoadFromCurrentDirCliModules->setLayoutDirection(Qt::RightToLeft); - - m_GridLayoutForLoadCheckboxes = new QGridLayout; - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromApplicationDir, 0, 0); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromApplicationDirCliModules, 0, 1); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromHomeDir, 1, 0); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromHomeDirCliModules, 1, 1); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromCurrentDir, 2, 0); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromCurrentDirCliModules, 2, 1); - m_GridLayoutForLoadCheckboxes->addWidget(m_LoadFromAutoLoadPathDir, 3, 1); - - m_ValidationMode = new QComboBox(m_MainControl); - - m_ValidationMode->addItem("strict", ctkCmdLineModuleManager::STRICT_VALIDATION); - m_ValidationMode->addItem("none", ctkCmdLineModuleManager::SKIP_VALIDATION); - m_ValidationMode->addItem("weak", ctkCmdLineModuleManager::WEAK_VALIDATION); - m_ValidationMode->setCurrentIndex(0); - m_MaximumNumberProcesses = new QSpinBox(m_MainControl); - m_MaximumNumberProcesses->setMinimum(1); - m_MaximumNumberProcesses->setMaximum(1000000); - m_XmlTimeoutInSeconds = new QSpinBox(m_MainControl); - m_XmlTimeoutInSeconds->setMinimum(1); - m_XmlTimeoutInSeconds->setMaximum(3600); - - auto formLayout = new QFormLayout; - formLayout->addRow("show debug output:", m_DebugOutput); - formLayout->addRow("show advanced widgets:", m_ShowAdvancedWidgets); - formLayout->addRow("XML time-out (secs):", m_XmlTimeoutInSeconds); - formLayout->addRow("XML validation mode:", m_ValidationMode); - formLayout->addRow("max. concurrent processes:", m_MaximumNumberProcesses); - formLayout->addRow("scan:", m_GridLayoutForLoadCheckboxes); - formLayout->addRow("additional module directories:", m_ModulesDirectories); - formLayout->addRow("additional modules:", m_ModulesFiles); - formLayout->addRow("temporary directory:", m_TemporaryDirectory); - formLayout->addRow("default output directory:", m_OutputDirectory); - - m_MainControl->setLayout(formLayout); - - this->Update(); -} - - -//----------------------------------------------------------------------------- -QWidget* CommandLineModulesPreferencesPage::GetQtControl() const -{ - return m_MainControl; -} - - -//----------------------------------------------------------------------------- -std::string CommandLineModulesPreferencesPage::ConvertToStdString(const QStringList& list) -{ - std::string output; - for (int i = 0; i < list.count(); i++) - { - QString path = list[i] + ";"; - output += path.toStdString(); - } - return output; -} - -//----------------------------------------------------------------------------- -bool CommandLineModulesPreferencesPage::PerformOk() -{ - auto* prefs = GetPreferences(); - - prefs->Put(CommandLineModulesViewConstants::TEMPORARY_DIRECTORY_NODE_NAME, m_TemporaryDirectory->directory().toStdString()); - prefs->Put(CommandLineModulesViewConstants::OUTPUT_DIRECTORY_NODE_NAME, m_OutputDirectory->directory().toStdString()); - prefs->PutBool(CommandLineModulesViewConstants::DEBUG_OUTPUT_NODE_NAME, m_DebugOutput->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::SHOW_ADVANCED_WIDGETS_NAME, m_ShowAdvancedWidgets->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR, m_LoadFromApplicationDir->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR_CLI_MODULES, m_LoadFromApplicationDirCliModules->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR, m_LoadFromHomeDir->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR_CLI_MODULES, m_LoadFromHomeDirCliModules->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR, m_LoadFromCurrentDir->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR_CLI_MODULES, m_LoadFromCurrentDirCliModules->isChecked()); - prefs->PutBool(CommandLineModulesViewConstants::LOAD_FROM_AUTO_LOAD_DIR, m_LoadFromAutoLoadPathDir->isChecked()); - - const auto paths = m_ModulesDirectories->directories().join(";").toStdString(); - prefs->Put(CommandLineModulesViewConstants::MODULE_DIRECTORIES_NODE_NAME, paths); - - const auto modules = m_ModulesFiles->files().join(";").toStdString(); - prefs->Put(CommandLineModulesViewConstants::MODULE_FILES_NODE_NAME, modules); - - int currentValidationMode = prefs->GetInt(CommandLineModulesViewConstants::XML_VALIDATION_MODE, 2); - if (currentValidationMode != m_ValidationMode->currentIndex()) - { - QMessageBox msgBox; - msgBox.setText("Changing the XML validation mode will require a restart of the application."); - msgBox.exec(); - } - - prefs->PutInt(CommandLineModulesViewConstants::XML_VALIDATION_MODE, m_ValidationMode->currentIndex()); - prefs->PutInt(CommandLineModulesViewConstants::XML_TIMEOUT_SECS, m_XmlTimeoutInSeconds->value()); - prefs->PutInt(CommandLineModulesViewConstants::MAX_CONCURRENT, m_MaximumNumberProcesses->value()); - return true; -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesPreferencesPage::PerformCancel() -{ -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesPreferencesPage::Update() -{ - auto* prefs = GetPreferences(); - - const auto fallbackTmpDir = QDir::tempPath().toStdString(); - m_TemporaryDirectory->setDirectory(QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::TEMPORARY_DIRECTORY_NODE_NAME, fallbackTmpDir))); - - const auto fallbackOutputDir = QDir::homePath().toStdString(); - m_OutputDirectory->setDirectory(QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::OUTPUT_DIRECTORY_NODE_NAME, fallbackOutputDir))); - - m_ShowAdvancedWidgets->setChecked(prefs->GetBool(CommandLineModulesViewConstants::SHOW_ADVANCED_WIDGETS_NAME, false)); - m_DebugOutput->setChecked(prefs->GetBool(CommandLineModulesViewConstants::DEBUG_OUTPUT_NODE_NAME, false)); - m_LoadFromApplicationDir->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR, false)); - m_LoadFromApplicationDirCliModules->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR_CLI_MODULES, true)); - m_LoadFromHomeDir->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR, false)); - m_LoadFromHomeDirCliModules->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR_CLI_MODULES, false)); - m_LoadFromCurrentDir->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR, false)); - m_LoadFromCurrentDirCliModules->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR_CLI_MODULES, false)); - m_LoadFromAutoLoadPathDir->setChecked(prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_AUTO_LOAD_DIR, false)); - - const auto paths = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::MODULE_DIRECTORIES_NODE_NAME, "")); - QStringList directoryList = paths.split(";", QString::SkipEmptyParts); - m_ModulesDirectories->setDirectories(directoryList); - - const auto files = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::MODULE_FILES_NODE_NAME, "")); - QStringList fileList = files.split(";", QString::SkipEmptyParts); - m_ModulesFiles->setFiles(fileList); - - m_ValidationMode->setCurrentIndex(prefs->GetInt(CommandLineModulesViewConstants::XML_VALIDATION_MODE, 2)); - m_XmlTimeoutInSeconds->setValue(prefs->GetInt(CommandLineModulesViewConstants::XML_TIMEOUT_SECS, 30)); // 30 secs = QProcess default timeout - m_MaximumNumberProcesses->setValue(prefs->GetInt(CommandLineModulesViewConstants::MAX_CONCURRENT, 4)); -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.h deleted file mode 100644 index 41f7aefb28..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesPreferencesPage.h +++ /dev/null @@ -1,105 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef COMMANDLINEMODULESPREFERENCESPAGE_H -#define COMMANDLINEMODULESPREFERENCESPAGE_H - -#include "berryIQtPreferencePage.h" -#include - -class QWidget; -class QGridLayout; -class QCheckBox; -class QComboBox; -class QSpinBox; -class QmitkDirectoryListWidget; -class QmitkFileListWidget; -class ctkDirectoryButton; - -/** - * \class CommandLineModulesPreferencesPage - * \brief Preference page for CommandLineModulesView - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -class CommandLineModulesPreferencesPage : public QObject, public berry::IQtPreferencePage -{ - Q_OBJECT - Q_INTERFACES(berry::IPreferencePage) - -public: - CommandLineModulesPreferencesPage(); - ~CommandLineModulesPreferencesPage() override; - - /** - * \brief Called by framework to initialise this preference page, but currently does nothing. - * \param workbench The workbench. - */ - void Init(berry::IWorkbench::Pointer workbench) override; - - /** - * \brief Called by framework to create the GUI, and connect signals and slots. - * \param widget The Qt widget that acts as parent to all GUI components, as this class itself is not derived from QWidget. - */ - void CreateQtControl(QWidget* widget) override; - - /** - * \brief Required by framework to get hold of the GUI. - * \return QWidget* the top most QWidget for the GUI. - */ - QWidget* GetQtControl() const override; - - /** - * \see IPreferencePage::PerformOk - */ - bool PerformOk() override; - - /** - * \see IPreferencePage::PerformCancel - */ - void PerformCancel() override; - - /** - * \see IPreferencePage::Update - */ - void Update() override; - -public slots: - -protected: - - QWidget *m_MainControl; - QCheckBox *m_DebugOutput; - QCheckBox *m_ShowAdvancedWidgets; - ctkDirectoryButton *m_OutputDirectory; - ctkDirectoryButton *m_TemporaryDirectory; - QmitkDirectoryListWidget *m_ModulesDirectories; - QmitkFileListWidget *m_ModulesFiles; - QGridLayout *m_GridLayoutForLoadCheckboxes; - QCheckBox *m_LoadFromHomeDir; - QCheckBox *m_LoadFromHomeDirCliModules; - QCheckBox *m_LoadFromCurrentDir; - QCheckBox *m_LoadFromCurrentDirCliModules; - QCheckBox *m_LoadFromApplicationDir; - QCheckBox *m_LoadFromApplicationDirCliModules; - QCheckBox *m_LoadFromAutoLoadPathDir; - - QComboBox *m_ValidationMode; - QSpinBox *m_XmlTimeoutInSeconds; - QSpinBox *m_MaximumNumberProcesses; - -private: - - std::string ConvertToStdString(const QStringList& list); -}; - -#endif // COMMANDLINEMODULESPREFERENCESPAGE_H diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.cpp deleted file mode 100644 index 87eb17ff3a..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.cpp +++ /dev/null @@ -1,532 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include -#include -#include - -// Qmitk -#include "CommandLineModulesView.h" -#include "CommandLineModulesViewConstants.h" -#include "CommandLineModulesViewControls.h" -#include "CommandLineModulesPreferencesPage.h" -#include "QmitkCmdLineModuleFactoryGui.h" -#include "QmitkCmdLineModuleGui.h" -#include "QmitkCmdLineModuleRunner.h" - -// Qt -#include -#include -#include -#include -#include -#include -#include -#include - -// CTK -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//----------------------------------------------------------------------------- -CommandLineModulesView::CommandLineModulesView() -: m_Controls(nullptr) -, m_Parent(nullptr) -, m_Layout(nullptr) -, m_ModuleManager(nullptr) -, m_ModuleBackend(nullptr) -, m_DirectoryWatcher(nullptr) -, m_TemporaryDirectoryName("") -, m_MaximumConcurrentProcesses(4) -, m_CurrentlyRunningProcesses(0) -, m_DebugOutput(false) -, m_XmlTimeoutSeconds(30) // 30 seconds = QProcess default timeout. -{ -} - - -//----------------------------------------------------------------------------- -CommandLineModulesView::~CommandLineModulesView() -{ - if (m_ModuleManager != nullptr) - { - delete m_ModuleManager; - } - - if (m_ModuleBackend != nullptr) - { - delete m_ModuleBackend; - } - - if (m_DirectoryWatcher != nullptr) - { - delete m_DirectoryWatcher; - } - - if (m_Layout != nullptr) - { - delete m_Layout; - } - - for (int i = 0; i < m_ListOfModules.size(); i++) - { - delete m_ListOfModules[i]; - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::SetFocus() -{ - this->m_Controls->m_ComboBox->setFocus(); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::CreateQtPartControl( QWidget *parent ) -{ - m_Parent = parent; - - if (!m_Controls) - { - // We create CommandLineModulesViewControls, which derives from the Qt generated class. - m_Controls = new CommandLineModulesViewControls(parent); - - // Create a layout to contain a display of QmitkCmdLineModuleRunner. - m_Layout = new QVBoxLayout(m_Controls->m_RunningWidgets); - m_Layout->setContentsMargins(0,0,0,0); - m_Layout->setSpacing(0); - - // This must be done independent of other preferences, as we need it before - // we create the ctkCmdLineModuleManager to initialise the Cache. - this->RetrieveAndStoreTemporaryDirectoryPreferenceValues(); - this->RetrieveAndStoreValidationMode(); - - // Start to create the command line module infrastructure. - m_ModuleBackend = new ctkCmdLineModuleBackendLocalProcess(); - m_ModuleManager = new ctkCmdLineModuleManager(m_ValidationMode, m_TemporaryDirectoryName); - - // Set the main object, the ctkCmdLineModuleManager onto all the objects that need it. - m_Controls->m_ComboBox->SetManager(m_ModuleManager); - m_DirectoryWatcher = new ctkCmdLineModuleDirectoryWatcher(m_ModuleManager); - connect(this->m_DirectoryWatcher, SIGNAL(errorDetected(QString)), this, SLOT(OnDirectoryWatcherErrorsDetected(QString))); - m_ModuleManager->registerBackend(m_ModuleBackend); - - // Setup the remaining preferences. - this->RetrieveAndStorePreferenceValues(); - - // Connect signals to slots after we have set up GUI. - connect(this->m_Controls->m_RunButton, SIGNAL(pressed()), this, SLOT(OnRunButtonPressed())); - connect(this->m_Controls->m_RestoreDefaults, SIGNAL(pressed()), this, SLOT(OnRestoreButtonPressed())); - connect(this->m_Controls->m_ComboBox, SIGNAL(actionChanged(QAction*)), this, SLOT(OnActionChanged(QAction*))); - connect(this->m_Controls->m_TabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(OnTabCloseRequested(int))); - connect(this->m_Controls->m_ClearXMLCache, SIGNAL(pressed()), this, SLOT(OnClearCache())); - connect(this->m_Controls->m_ReloadModules, SIGNAL(pressed()), this, SLOT(OnReloadModules())); - - this->UpdateRunButtonEnabledStatus(); - } -} - - -//----------------------------------------------------------------------------- -mitk::IPreferences* CommandLineModulesView::RetrievePreferences() const -{ - return mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::RetrieveAndStoreTemporaryDirectoryPreferenceValues() -{ - auto* prefs = this->RetrievePreferences(); - - const auto fallbackTmpDir = QDir::tempPath().toStdString(); - m_TemporaryDirectoryName = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::TEMPORARY_DIRECTORY_NODE_NAME, fallbackTmpDir)); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::RetrieveAndStoreValidationMode() -{ - auto* prefs = this->RetrievePreferences(); - - int value = prefs->GetInt(CommandLineModulesViewConstants::XML_VALIDATION_MODE, 2); - if (value == 0) - { - m_ValidationMode = ctkCmdLineModuleManager::STRICT_VALIDATION; - } - else if (value == 1) - { - m_ValidationMode = ctkCmdLineModuleManager::SKIP_VALIDATION; - } - else - { - m_ValidationMode = ctkCmdLineModuleManager::WEAK_VALIDATION; - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::RetrieveAndStorePreferenceValues() -{ - auto* prefs = this->RetrievePreferences(); - - const auto fallbackHomeDir = QDir::homePath().toStdString(); - m_OutputDirectoryName = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::OUTPUT_DIRECTORY_NODE_NAME, fallbackHomeDir)); - - m_MaximumConcurrentProcesses = prefs->GetInt(CommandLineModulesViewConstants::MAX_CONCURRENT, 4); - - m_XmlTimeoutSeconds = prefs->GetInt(CommandLineModulesViewConstants::XML_TIMEOUT_SECS, 30); - m_ModuleManager->setTimeOutForXMLRetrieval(m_XmlTimeoutSeconds * 1000); // preference is in seconds, underlying CTK library in milliseconds. - - // Get the flag for debug output, useful when parsing all the XML. - m_DebugOutput = prefs->GetBool(CommandLineModulesViewConstants::DEBUG_OUTPUT_NODE_NAME, false); - m_DirectoryWatcher->setDebug(m_DebugOutput); - - // Show/Hide the advanced widgets - this->m_Controls->SetAdvancedWidgetsVisible(prefs->GetBool(CommandLineModulesViewConstants::SHOW_ADVANCED_WIDGETS_NAME, false)); - - bool loadApplicationDir = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR, false); - bool loadApplicationDirCliModules = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR_CLI_MODULES, true); - bool loadHomeDir = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR, false); - bool loadHomeDirCliModules = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR_CLI_MODULES, false); - bool loadCurrentDir = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR, false); - bool loadCurrentDirCliModules = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR_CLI_MODULES, false); - bool loadAutoLoadDir = prefs->GetBool(CommandLineModulesViewConstants::LOAD_FROM_AUTO_LOAD_DIR, false); - - // Get some default application paths. - - // Here we can use the preferences to set up the builder, - ctkCmdLineModuleDefaultPathBuilder builder; - if (loadApplicationDir) builder.addApplicationDir(); - if (loadApplicationDirCliModules) builder.addApplicationDir("cli-modules"); - if (loadHomeDir) builder.addHomeDir(); - if (loadHomeDirCliModules) builder.addHomeDir("cli-modules"); - if (loadCurrentDir) builder.addCurrentDir(); - if (loadCurrentDirCliModules) builder.addCurrentDir("cli-modules"); - if (loadAutoLoadDir) builder.addCtkModuleLoadPath(); - - // and then we ask the builder to set up the paths. - QStringList defaultPaths = builder.getDirectoryList(); - - // We get additional directory paths from preferences. - const auto pathString = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::MODULE_DIRECTORIES_NODE_NAME, "")); - QStringList additionalPaths = pathString.split(";", QString::SkipEmptyParts); - - // Combine the sets of directory paths. - QStringList totalPaths; - totalPaths << defaultPaths; - totalPaths << additionalPaths; - - const auto additionalModulesString = QString::fromStdString(prefs->Get(CommandLineModulesViewConstants::MODULE_FILES_NODE_NAME, "")); - QStringList additionalModules = additionalModulesString.split(";", QString::SkipEmptyParts); - - // OnPreferencesChanged can be called for each preference in a dialog box, so - // when you hit "OK", it is called repeatedly, whereas we want to only call these only once. - if (m_DirectoryPaths != totalPaths) - { - m_DirectoryPaths = totalPaths; - m_DirectoryWatcher->setDirectories(totalPaths); - } - if (m_ModulePaths != additionalModules) - { - m_ModulePaths = additionalModules; - m_DirectoryWatcher->setAdditionalModules(additionalModules); - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnPreferencesChanged(const mitk::IPreferences* /*prefs*/) -{ - this->RetrieveAndStoreTemporaryDirectoryPreferenceValues(); - this->RetrieveAndStoreValidationMode(); - this->RetrieveAndStorePreferenceValues(); -} - - -//----------------------------------------------------------------------------- -ctkCmdLineModuleReference CommandLineModulesView::GetReferenceByFullName(QString fullName) -{ - ctkCmdLineModuleReference result; - - QList references = this->m_ModuleManager->moduleReferences(); - - ctkCmdLineModuleReference ref; - foreach(ref, references) - { - QString name = ref.description().categoryDotTitle(); - if (name == fullName) - { - result = ref; - } - } - - return result; -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnActionChanged(QAction* action) -{ - QString fullName = action->objectName(); - ctkCmdLineModuleReference ref = this->GetReferenceByFullName(fullName); - - // ref should never be invalid, as the menu was generated from each ctkCmdLineModuleReference. - // But just to be sure ... if invalid, don't do anything. - if (ref) - { - // Check if we already have the reference. - int tabIndex = -1; - for (int i = 0; i < m_ListOfModules.size(); i++) - { - ctkCmdLineModuleReference tabsReference = m_ListOfModules[i]->moduleReference(); - if (ref.location() == tabsReference.location()) - { - tabIndex = i; - break; - } - } - - // i.e. we found a matching tab, so just switch to it. - if (tabIndex != -1) - { - m_Controls->m_TabWidget->setCurrentIndex(tabIndex); - } - else // i.e. we did not find a matching tab - { - // In this case, we need to create a new tab, and give - // it a GUI for the user to setup the parameters with. - QmitkCmdLineModuleFactoryGui factory(this->GetDataStorage()); - ctkCmdLineModuleFrontend *frontEnd = factory.create(ref); - QmitkCmdLineModuleGui *theGui = dynamic_cast(frontEnd); - - // Add to list and tab wigdget - m_ListOfModules.push_back(frontEnd); - int tabIndex = m_Controls->m_TabWidget->addTab(theGui->getGui(), ref.description().title()); - m_Controls->m_TabWidget->setTabToolTip(tabIndex, ref.description().title() + ":" + ref.xmlValidationErrorString()); - - // Here lies a small caveat. - // - // The XML may specify a default output file name. - // However, this will probably have no file path, so we should probably add one. - // Otherwise you will likely be trying to write in the application installation folder - // eg. C:/Program Files (Windows) or /Applications/ (Mac) - // - // Also, we may find that 3rd party apps crash when they can't write. - // So lets plan for the worst and hope for the best :-) - - QString parameterName; - QList parameters; - parameters = frontEnd->parameters("image", ctkCmdLineModuleFrontend::Output); - parameters << frontEnd->parameters("file", ctkCmdLineModuleFrontend::Output); - parameters << frontEnd->parameters("geometry", ctkCmdLineModuleFrontend::Output); - foreach (ctkCmdLineModuleParameter parameter, parameters) - { - parameterName = parameter.name(); - QString outputFileName = frontEnd->value(parameterName, ctkCmdLineModuleFrontend::DisplayRole).toString(); - QFileInfo outputFileInfo(outputFileName); - - if (outputFileInfo.absoluteFilePath() != outputFileName) - { - QDir defaultOutputDir(m_OutputDirectoryName); - QFileInfo replacementFileInfo(defaultOutputDir, outputFileName); - frontEnd->setValue(parameterName, replacementFileInfo.absoluteFilePath(), ctkCmdLineModuleFrontend::DisplayRole); - } - } - } - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnTabCloseRequested(int tabNumber) -{ - - ctkCmdLineModuleFrontend *frontEnd = m_ListOfModules[tabNumber]; - - m_Controls->m_TabWidget->removeTab(tabNumber); - m_ListOfModules.removeAt(tabNumber); - - delete frontEnd; -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::AskUserToSelectAModule() const -{ - QMessageBox msgBox; - msgBox.setText("Please select a module!"); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnRestoreButtonPressed() -{ - int tabNumber = m_Controls->m_TabWidget->currentIndex(); - if (tabNumber >= 0) - { - ctkCmdLineModuleFrontend *frontEnd = m_ListOfModules[tabNumber]; - frontEnd->resetValues(); - } - else - { - this->AskUserToSelectAModule(); - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnRunButtonPressed() -{ - int tabNumber = m_Controls->m_TabWidget->currentIndex(); - if (tabNumber >= 0) - { - // 1. Create a new QmitkCmdLineModuleRunner to represent the running widget. - auto widget = new QmitkCmdLineModuleRunner(m_Controls->m_RunningWidgets); - widget->SetDataStorage(this->GetDataStorage()); - widget->SetManager(m_ModuleManager); - widget->SetOutputDirectory(m_OutputDirectoryName); - - // 2. Create a new front end. - QmitkCmdLineModuleFactoryGui factory(this->GetDataStorage()); - - ctkCmdLineModuleFrontend *frontEndOnCurrentTab = m_ListOfModules[tabNumber]; - QmitkCmdLineModuleGui *frontEndGuiOnCurrentTab = dynamic_cast(frontEndOnCurrentTab); - ctkCmdLineModuleReference currentTabFrontendReferences = frontEndGuiOnCurrentTab->moduleReference(); - - ctkCmdLineModuleFrontend *newFrontEnd = factory.create(currentTabFrontendReferences); - QmitkCmdLineModuleGui *newFrontEndGui = dynamic_cast(newFrontEnd); - widget->SetFrontend(newFrontEndGui); - m_Layout->insertWidget(0, widget); - - // 3. Copy parameters. This MUST come after widget->SetFrontEnd - newFrontEndGui->copyParameters(*frontEndGuiOnCurrentTab); - newFrontEndGui->setParameterContainerEnabled(false); - - // 4. Connect widget signals to here, to count how many jobs running. - connect(widget, SIGNAL(started()), this, SLOT(OnJobStarted())); - connect(widget, SIGNAL(finished()), this, SLOT(OnJobFinished())); - - // 5. GO. - widget->Run(); - } - else - { - this->AskUserToSelectAModule(); - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::UpdateRunButtonEnabledStatus() -{ - if (m_CurrentlyRunningProcesses >= m_MaximumConcurrentProcesses) - { - m_Controls->m_RunButton->setEnabled(false); - } - else - { - m_Controls->m_RunButton->setEnabled(true); - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnJobStarted() -{ - m_CurrentlyRunningProcesses++; - this->UpdateRunButtonEnabledStatus(); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnJobFinished() -{ - m_CurrentlyRunningProcesses--; - this->UpdateRunButtonEnabledStatus(); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnDirectoryWatcherErrorsDetected(const QString& errorMsg) -{ - ctkCmdLineModuleUtils::messageBoxForModuleRegistration(errorMsg); -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnClearCache() -{ - if (this->m_DebugOutput) - { - qDebug() << "CommandLineModulesView::OnClearCache(): starting"; - } - - m_ModuleManager->clearCache(); - - if (this->m_DebugOutput) - { - qDebug() << "CommandLineModulesView::OnClearCache(): finishing"; - } -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesView::OnReloadModules() -{ - QList urls; - - QList moduleRefs = m_ModuleManager->moduleReferences(); - foreach (ctkCmdLineModuleReference ref, moduleRefs) - { - urls.push_back(ref.location()); - } - - if (this->m_DebugOutput) - { - qDebug() << "CommandLineModulesView::OnReloadModules(): unloading:" << urls; - } - - foreach (ctkCmdLineModuleReference ref, moduleRefs) - { - m_ModuleManager->unregisterModule(ref); - } - - if (this->m_DebugOutput) - { - qDebug() << "CommandLineModulesView::OnReloadModules(): reloading."; - } - - QList refResults = QtConcurrent::blockingMapped(urls, - ctkCmdLineModuleConcurrentRegister(m_ModuleManager, m_DebugOutput)); - - if (this->m_DebugOutput) - { - qDebug() << "CommandLineModulesView::OnReloadModules(): finished."; - } - - QString errorMessages = ctkCmdLineModuleUtils::errorMessagesFromModuleRegistration(refResults, - m_ModuleManager->validationMode()); - ctkCmdLineModuleUtils::messageBoxForModuleRegistration(errorMessages); -} - diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.h deleted file mode 100644 index 79f5caa40c..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesView.h +++ /dev/null @@ -1,244 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef CommandLineModulesView_h -#define CommandLineModulesView_h - -#include -#include -#include -#include - -class ctkCmdLineModuleBackendLocalProcess; -class ctkCmdLineModuleDirectoryWatcher; -class CommandLineModulesViewControls; -class QmitkCmdLineModuleRunner; -class QAction; -class QVBoxLayout; - -/*! - * \class CommandLineModulesView - * \brief Provides basic GUI interface to the CTK command line modules. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \sa QmitkAbstractView - */ -class CommandLineModulesView : public QmitkAbstractView -{ - // this is needed for all Qt objects that should have a Qt meta-object - // (everything that derives from QObject and wants to have signal/slots) - Q_OBJECT - -public: - - CommandLineModulesView(); - ~CommandLineModulesView() override; - - /** - * \brief Main method, called by framework to create the GUI at the right time. - * \param parent The parent QWidget, as this class itself is not a QWidget subclass. - */ - void CreateQtPartControl(QWidget *parent) override; - - /** - * \brief Called by the framework to indicate that the preferences have changed. - * \param prefs not used, as we call RetrievePreferenceValues(). - */ - void OnPreferencesChanged(const mitk::IPreferences* prefs) override; - -protected Q_SLOTS: - - /** - * \brief Called when the ctkMenuComboBox has the menu selection changed, - * and causes the corresponding GUI to be displayed. - */ - void OnActionChanged(QAction*); - - /** - * \brief Slot that is called when the restore defaults button is pressed, - * to reset the current GUI form to the default values, if the XML specifies defaults. - */ - void OnRestoreButtonPressed(); - - /** - * \brief Slot that is called when the Run button is pressed to run the current module. - */ - void OnRunButtonPressed(); - - /** - * \brief Alerts the user of any errors coming out of the directory watcher. - */ - void OnDirectoryWatcherErrorsDetected(const QString&); - -protected: - - /** - * \brief Called by framework to set the focus on the right widget - * when this view has focus, so currently, that's the ctkMenuCombo box. - */ - void SetFocus() override; - -private slots: - - /** - * \brief Called when the user clicks to close a tab, and removes the front end from m_ListOfModules - */ - void OnTabCloseRequested(int tabNumber); - - /** - * \brief Called from QmitkCmdLineModuleProgressWidget to indicate a job has started. - */ - void OnJobStarted(); - - /** - * \brief Called from QmitkCmdLineModuleProgressWidget to indicate a job has finished. - */ - void OnJobFinished(); - - /** - * \brief Called when the user hits the 'clear XML cache' button. - */ - void OnClearCache(); - - /** - * \brief Called when the user hits the 'reload modules' button. - */ - void OnReloadModules(); - -private: - - /** - * \brief Called on startup and by OnPreferencesChanged to load all - * preferences except the temporary folder into member variables. - */ - void RetrieveAndStorePreferenceValues(); - - /** - * \brief Called on startup and by OnPreferencesChanged to load the temporary folder - * preference into member variable m_TemporaryDirectoryName. - */ - void RetrieveAndStoreTemporaryDirectoryPreferenceValues(); - - /** - * \brief Called on startup and by OnPreferencesChanged to set the validation mode, but will require a restart. - */ - void RetrieveAndStoreValidationMode(); - - /** - * \brief Called to get hold of the actual preferences node. - */ - mitk::IPreferences* RetrievePreferences() const; - - /** - * \brief Search all modules for the one matching the given identifier. - * \param fullName The "fullName" is the . from the XML. - * \return ctkCmdLineModuleReference the reference corresponding to the fullName, or an invalid reference if non found. - */ - ctkCmdLineModuleReference GetReferenceByFullName(QString fullName); - - /** - * \brief Raises a message box asking the user to select a module first. - */ - void AskUserToSelectAModule() const; - - /** - * \brief Enables or Disables the Run Button. - */ - void UpdateRunButtonEnabledStatus(); - - /** - * \brief The GUI controls contain a reset and run button, and a QWidget container, and the GUI component - * for each command line module is added to the QWidget dynamically at run time. - */ - CommandLineModulesViewControls *m_Controls; - - /** - * \brief We store the parent, passed in via CommandLineModulesView::CreateQtPartControl, - * as this class itself is not a QWidget. - */ - QWidget *m_Parent; - - /** - * \brief We keep a local layout, and arrange a display of QmitkCmdLineModuleProgressWidget, - * where each QmitkCmdLineModuleProgressWidget represents a single running job. - */ - QVBoxLayout *m_Layout; - - /** - * \brief The manager is responsible for loading and instantiating command line modules. - */ - ctkCmdLineModuleManager *m_ModuleManager; - - /** - * \brief We are using a back-end that runs locally installed command line programs. - */ - ctkCmdLineModuleBackendLocalProcess *m_ModuleBackend; - - /** - * \brief The ctkCmdLineModuleDirectoryWatcher maintains the list of directories - * we are using to load modules, to provide automatic updates. - */ - ctkCmdLineModuleDirectoryWatcher *m_DirectoryWatcher; - - /** - * \brief We store a temporary folder name, accessible via user preferences. - */ - QString m_TemporaryDirectoryName; - - /** - * \brief We store an output folder name, accessible via user preferences for when - * the file specified in a default output path is not within a writable directory. - */ - QString m_OutputDirectoryName; - - /** - * \brief Cache the list of directory paths locally to avoid repeatedly trying to update Directory Watcher. - */ - QStringList m_DirectoryPaths; - - /** - * \brief Cache the list of module/executable paths locally to avoid repeatedly trying to update Directory Watcher. - */ - QStringList m_ModulePaths; - - /** - * \brief We store the validation mode, accessible via user preferences. - */ - ctkCmdLineModuleManager::ValidationMode m_ValidationMode; - - /** - * \brief We store the maximum number of concurrent processes, and disable the run button accordingly. - */ - int m_MaximumConcurrentProcesses; - - /** - * \brief Counts the number of currently running processes. - */ - int m_CurrentlyRunningProcesses; - - /** - * \brief Member variable, taken from preference page. - */ - bool m_DebugOutput; - - /** - * \brief Member variable, taken from preferences page. - */ - int m_XmlTimeoutSeconds; - - /** - * \brief We keep a list of front ends to match the m_TabWidget. - */ - QList<ctkCmdLineModuleFrontend*> m_ListOfModules; -}; - -#endif // CommandLineModulesView_h diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.cpp deleted file mode 100644 index e36d3eb42e..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "CommandLineModulesViewConstants.h" - -const std::string CommandLineModulesViewConstants::VIEW_ID = "org.mitk.views.cmdlinemodules"; -const std::string CommandLineModulesViewConstants::TEMPORARY_DIRECTORY_NODE_NAME = "temporary directory"; -const std::string CommandLineModulesViewConstants::OUTPUT_DIRECTORY_NODE_NAME = "output directory"; -const std::string CommandLineModulesViewConstants::MODULE_DIRECTORIES_NODE_NAME = "module directories"; -const std::string CommandLineModulesViewConstants::MODULE_FILES_NODE_NAME = "module files"; -const std::string CommandLineModulesViewConstants::DEBUG_OUTPUT_NODE_NAME = "debug output"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR = "load from application dir"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR = "load from home dir"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR = "load from current dir"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_AUTO_LOAD_DIR = "load from auto-load dir"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_APPLICATION_DIR_CLI_MODULES = "load from application dir/cli-modules"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_HOME_DIR_CLI_MODULES = "load from home dir/cli-modules"; -const std::string CommandLineModulesViewConstants::LOAD_FROM_CURRENT_DIR_CLI_MODULES = "load from current dir/cli-modules"; -const std::string CommandLineModulesViewConstants::XML_VALIDATION_MODE = "xml validation mode"; -const std::string CommandLineModulesViewConstants::XML_TIMEOUT_SECS = "xml time-out"; -const std::string CommandLineModulesViewConstants::MAX_CONCURRENT = "max concurrent processes"; -const std::string CommandLineModulesViewConstants::SHOW_ADVANCED_WIDGETS_NAME = "show advanced widgets"; diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.h deleted file mode 100644 index 48a230b399..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewConstants.h +++ /dev/null @@ -1,121 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef CommandLineModulesViewConstants_h -#define CommandLineModulesViewConstants_h - -#include <QString> - -/** - * \class CommandLineModulesViewConstants - * \brief Structure to define a namespace for constants used privately within this view. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -struct CommandLineModulesViewConstants -{ - /** - * \brief The name of the preferences node containing the temporary directory. - */ - static const std::string TEMPORARY_DIRECTORY_NODE_NAME; - - /** - * \brief The name of the preferences node containing the output directory. - */ - static const std::string OUTPUT_DIRECTORY_NODE_NAME; - - /** - * \brief The name of the preferences node containing the list of directories to scan. - */ - static const std::string MODULE_DIRECTORIES_NODE_NAME; - - /** - * \brief The name of the preferences node containing the additional files to add to the module list. - */ - static const std::string MODULE_FILES_NODE_NAME; - - /** - * \brief The name of the preferences node containing whether we are producing debug output. - */ - static const std::string DEBUG_OUTPUT_NODE_NAME; - - /** - * \brief The name of the preferences node containing whether we are displaying advanced widgets. - */ - static const std::string SHOW_ADVANCED_WIDGETS_NAME; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the application directory. - */ - static const std::string LOAD_FROM_APPLICATION_DIR; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the "application directory/cli-modules". - */ - static const std::string LOAD_FROM_APPLICATION_DIR_CLI_MODULES; - - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the users home directory. - */ - static const std::string LOAD_FROM_HOME_DIR; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the users "home directory/cli-modules". - */ - static const std::string LOAD_FROM_HOME_DIR_CLI_MODULES; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the applications current working directory. - */ - static const std::string LOAD_FROM_CURRENT_DIR; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the applications "current working directory/cli-modules". - */ - static const std::string LOAD_FROM_CURRENT_DIR_CLI_MODULES; - - /** - * \brief The name of the preferences node containing a boolean describing whether - * we are loading modules from the directory specified in CTK_MODULE_LOAD_PATH. - */ - static const std::string LOAD_FROM_AUTO_LOAD_DIR; - - /** - * \brief The name of the preferences node containing the validation mode. - */ - static const std::string XML_VALIDATION_MODE; - - /** - * \brief The name of the preferences node containing the timeout in seconds for XML retrieval. - */ - static const std::string XML_TIMEOUT_SECS; - - /** - * \brief The name of the preferences node containing the maximum number of concurrent processes. - */ - static const std::string MAX_CONCURRENT; - - /** - * \brief The View ID = org.mitk.gui.qt.cmdlinemodules, and should match that in plugin.xml. - */ - static const std::string VIEW_ID; - -}; - -#endif // CommandLineModulesViewConstants_h diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.cpp deleted file mode 100644 index 0cfa06f475..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "CommandLineModulesViewControls.h" -#include <QIcon> -#include <QSize> -#include <QHBoxLayout> - -//----------------------------------------------------------------------------- -CommandLineModulesViewControls::CommandLineModulesViewControls(QWidget *parent) -{ - this->setupUi(parent); - this->m_ChooseLabel->setVisible(false); - this->m_ComboBox->setToolTip("Choose a command line module"); - this->m_RunButton->setIcon(QIcon(":/CommandLineModulesResources/run.png")); - this->m_RestoreDefaults->setIcon(QIcon(":/CommandLineModulesResources/undo.png")); -} - - -//----------------------------------------------------------------------------- -CommandLineModulesViewControls::~CommandLineModulesViewControls() -{ - -} - - -//----------------------------------------------------------------------------- -void CommandLineModulesViewControls::SetAdvancedWidgetsVisible(const bool& isVisible) -{ - this->m_ClearXMLCache->setVisible(isVisible); - this->m_ReloadModules->setVisible(isVisible); -} - diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.h deleted file mode 100644 index f4bec90d4e..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.h +++ /dev/null @@ -1,44 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef CommandLineModulesViewControls_h -#define CommandLineModulesViewControls_h - -#include "ui_CommandLineModulesViewControls.h" - -class QHBoxLayout; - -/** - * \class CommandLineModulesViewControls - * \brief Class derived from Ui_CommandLineModulesViewControls to provide access to GUI widgets. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -class CommandLineModulesViewControls : public QWidget, public Ui_CommandLineModulesViewControls -{ - // this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) - Q_OBJECT - -public: - - CommandLineModulesViewControls(QWidget *parent = nullptr); - ~CommandLineModulesViewControls() override; - - void SetAdvancedWidgetsVisible(const bool& isVisible); - -protected: - -private: - -}; - -#endif // CommandLineModulesViewControls_h diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.ui b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.ui deleted file mode 100644 index aaf96fcc0f..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/CommandLineModulesViewControls.ui +++ /dev/null @@ -1,172 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>CommandLineModulesViewControls</class> - <widget class="QWidget" name="CommandLineModulesViewControls"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>678</width> - <height>582</height> - </rect> - </property> - <property name="minimumSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="windowTitle"> - <string>QmitkTemplate</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="m_ChooseLabel"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string/> - </property> - <property name="text"> - <string>choose:</string> - </property> - </widget> - </item> - <item> - <widget class="QmitkCmdLineModuleMenuComboBox" name="m_ComboBox" native="true"/> - </item> - <item> - <widget class="QPushButton" name="m_RestoreDefaults"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>restore default parameters</string> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="m_RunButton"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>run the command line module</string> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <spacer name="m_HorizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QPushButton" name="m_ClearXMLCache"> - <property name="text"> - <string>clear XML cache</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="m_ReloadModules"> - <property name="text"> - <string>reload modules</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <widget class="QWidget" name="m_RunningWidgets" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item> - <widget class="QTabWidget" name="m_TabWidget"> - <property name="currentIndex"> - <number>-1</number> - </property> - <property name="tabsClosable"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>250</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </widget> - <layoutdefault spacing="6" margin="11"/> - <customwidgets> - <customwidget> - <class>QmitkCmdLineModuleMenuComboBox</class> - <extends>QWidget</extends> - <header>QmitkCmdLineModuleMenuComboBox.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.cpp deleted file mode 100644 index 07ec529af6..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkCmdLineModuleFactoryGui.h" -#include "QmitkCmdLineModuleGui.h" - -//----------------------------------------------------------------------------- -struct QmitkCmdLineModuleFactoryGuiPrivate -{ - QmitkCmdLineModuleFactoryGuiPrivate(const mitk::DataStorage* dataStorage) - : m_DataStorage(dataStorage) - {} - - const mitk::DataStorage* m_DataStorage; -}; - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleFactoryGui::QmitkCmdLineModuleFactoryGui(const mitk::DataStorage* dataStorage) - : d(new QmitkCmdLineModuleFactoryGuiPrivate(dataStorage)) -{ -} - - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleFactoryGui::~QmitkCmdLineModuleFactoryGui() -{ -} - - -//----------------------------------------------------------------------------- -ctkCmdLineModuleFrontendQtGui* QmitkCmdLineModuleFactoryGui::create(const ctkCmdLineModuleReference& moduleRef) -{ - ctkCmdLineModuleFrontendQtGui* gui = new QmitkCmdLineModuleGui(d->m_DataStorage, moduleRef); - return gui; -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.h deleted file mode 100644 index 2c73a708e1..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleFactoryGui.h +++ /dev/null @@ -1,51 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkCmdLineModuleFactoryGui_h -#define QmitkCmdLineModuleFactoryGui_h - -#include <ctkCmdLineModuleFrontendFactoryQtGui.h> -#include <ctkCmdLineModuleReference.h> - -namespace mitk { - class DataStorage; -} -struct QmitkCmdLineModuleFactoryGuiPrivate; - -/** - * \class QmitkCmdLineModuleFactoryGui - * \brief Derived from ctkCmdLineModuleFactory to instantiate QmitkCmdLineModuleGui front ends. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \sa QmitkCmdLineModuleGui - * \sa ctkCmdLineModuleFrontendFactoryQtGui - */ -class QmitkCmdLineModuleFactoryGui - : public ctkCmdLineModuleFrontendFactoryQtGui -{ -public: - - QmitkCmdLineModuleFactoryGui(const mitk::DataStorage* dataStorage); - ~QmitkCmdLineModuleFactoryGui() override; - - /** - * \brief Simply creates QmitkCmdLineModuleGui which is an MITK specific Qt front end. - */ - ctkCmdLineModuleFrontendQtGui* create(const ctkCmdLineModuleReference& moduleRef) override; - -private: - - QScopedPointer<QmitkCmdLineModuleFactoryGuiPrivate> d; - -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.cpp deleted file mode 100644 index 0aae6cd570..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkCmdLineModuleGui.h" -#include "QmitkUiLoader.h" -#include <QVariant> -#include <QIODevice> -#include <QFile> -#include <QScopedPointer> -#include <QWidget> -#include <QTextBrowser> -#include <QVBoxLayout> -#include <QmitkCustomVariants.h> -#include <ctkCmdLineModuleXslTransform.h> -#include <ctkCmdLineModuleParameter.h> -#include <ctkCmdLineModuleDescription.h> -#include <ctkCollapsibleGroupBox.h> -#include "mitkDataStorage.h" -#include <mitkImage.h> - -//----------------------------------------------------------------------------- -struct QmitkCmdLineModuleGuiPrivate -{ - QmitkCmdLineModuleGuiPrivate(const mitk::DataStorage* dataStorage) - : m_DataStorage(dataStorage), m_Loader(nullptr), m_Transform(nullptr), m_TopLevelWidget(nullptr) - { - } - const mitk::DataStorage* m_DataStorage; - mutable QScopedPointer<QUiLoader> m_Loader; - mutable QScopedPointer<ctkCmdLineModuleXslTransform> m_Transform; - mutable QScopedPointer<QWidget> m_TopLevelWidget; -}; - - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleGui::QmitkCmdLineModuleGui(const mitk::DataStorage* dataStorage, const ctkCmdLineModuleReference& moduleRef) - : ctkCmdLineModuleFrontendQtGui(moduleRef) -, d(new QmitkCmdLineModuleGuiPrivate(dataStorage)) -{ - qRegisterMetaType<mitk::DataNode::Pointer>(); - qRegisterMetaType<mitkDataNodePtr>(); -} - - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleGui::~QmitkCmdLineModuleGui() -{ -} - - -//----------------------------------------------------------------------------- -QUiLoader* QmitkCmdLineModuleGui::uiLoader() const -{ - // Here we are creating a QUiLoader locally, so when this method - // is called, it overrides the one in the base class, so the base - // class one is never constructed, and this one is constructed as - // a replacement. - if (d->m_Loader == nullptr) - { - d->m_Loader.reset(new QmitkUiLoader(d->m_DataStorage)); - } - return d->m_Loader.data(); -} - - -//----------------------------------------------------------------------------- -ctkCmdLineModuleXslTransform* QmitkCmdLineModuleGui::xslTransform() const -{ - // This is a virtual getter, overriding the one in the base class. - // However, we want to use the transform in the base class, and just append to it. - // So we call the base class one, modify it by adding some stuff, and then return - // the pointer to the one in the base class. - ctkCmdLineModuleXslTransform *transform = ctkCmdLineModuleFrontendQtGui::xslTransform(); - if (transform != nullptr) - { - transform->bindVariable("imageInputWidget", QVariant(QString("QmitkDataStorageComboBoxWithSelectNone"))); - transform->bindVariable("imageInputValueProperty", "currentValue"); - transform->bindVariable("imageInputSetProperty", ""); // Don't need this, as we are connected to DataStorage. - } - return transform; -} - - -//----------------------------------------------------------------------------- -QVariant QmitkCmdLineModuleGui::value(const QString ¶meter, int role) const -{ - if (role == UserRole) - { - ctkCmdLineModuleParameter param = this->moduleReference().description().parameter(parameter); - if (param.channel() == "input" && param.tag() == "image") - { - return this->customValue(parameter, "SelectedNode"); - } - return QVariant(); - } - return ctkCmdLineModuleFrontendQtGui::value(parameter, role); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleGui::setValue(const QString& parameter, const QVariant& value, int role) -{ - if (role == UserRole) - { - ctkCmdLineModuleParameter param = this->moduleReference().description().parameter(parameter); - if (param.channel() == "input" && param.tag() == "image") - { - this->setCustomValue(parameter, value, "SelectedNode"); - } - else - { - ctkCmdLineModuleFrontendQtGui::setValue(parameter, value, role); - } - } - else - { - ctkCmdLineModuleFrontendQtGui::setValue(parameter, value, role); - } -} - -//----------------------------------------------------------------------------- -QWidget* QmitkCmdLineModuleGui::getGui() -{ - if (!d->m_TopLevelWidget) - { - // Construct additional widgets to contain full GUI. - auto aboutBoxContainerWidget = new QWidget(); - - auto aboutBox = new ctkCollapsibleGroupBox(aboutBoxContainerWidget); - aboutBox->setTitle("About"); - - auto aboutBrowser = new QTextBrowser(aboutBox); - aboutBrowser->setReadOnly(true); - aboutBrowser->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - aboutBrowser->setOpenExternalLinks(true); - aboutBrowser->setOpenLinks(true); - - auto aboutLayout = new QVBoxLayout(aboutBox); - aboutLayout->addWidget(aboutBrowser); - - auto aboutBoxContainerWidgetLayout = new QVBoxLayout(aboutBoxContainerWidget); - aboutBoxContainerWidgetLayout->addWidget(aboutBox); - - auto helpBoxContainerWidget = new QWidget(); - - auto helpBox = new ctkCollapsibleGroupBox(); - helpBox->setTitle("Help"); - - auto helpBrowser = new QTextBrowser(helpBox); - helpBrowser->setReadOnly(true); - helpBrowser->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - helpBrowser->setOpenExternalLinks(true); - helpBrowser->setOpenLinks(true); - - auto helpLayout = new QVBoxLayout(helpBox); - helpLayout->addWidget(helpBrowser); - - auto helpBoxContainerWidgetLayout = new QVBoxLayout(helpBoxContainerWidget); - helpBoxContainerWidgetLayout->addWidget(helpBox); - - QObject* guiHandle = this->guiHandle(); - QWidget* generatedGuiWidgets = qobject_cast<QWidget*>(guiHandle); - - auto topLevelWidget = new QWidget(); - - auto topLevelLayout = new QGridLayout(topLevelWidget); - topLevelLayout->setContentsMargins(0,0,0,0); - topLevelLayout->setSpacing(0); - topLevelLayout->addWidget(aboutBoxContainerWidget, 0, 0); - topLevelLayout->addWidget(helpBoxContainerWidget, 1, 0); - topLevelLayout->addWidget(generatedGuiWidgets, 2, 0); - - ctkCmdLineModuleDescription description = this->moduleReference().description(); - - QString helpString = ""; - - if (!description.title().isEmpty()) - { - QString titleHtml = "<h1>" + description.title() + "</h1>"; - helpString += titleHtml; - } - - if (!description.description().isEmpty()) - { - QString descriptionHtml = "<p>" + description.description() + "</p>"; - helpString += descriptionHtml; - } - - if (!description.documentationURL().isEmpty()) - { - QString docUrlHtml = "<p>For more information please see <a href=\"" + description.documentationURL() - + "\">" + description.documentationURL() + "</a></p>"; - helpString += docUrlHtml; - } - - QString aboutString = ""; - - if (!description.title().isEmpty()) - { - QString titleHtml = "<h1>" + description.title() + "</h1>"; - aboutString += titleHtml; - } - - if (!description.contributor().isEmpty()) - { - QString contributorHtml = "<h2>Contributed By</h2><p>" + description.contributor() + "</p>"; - aboutString += contributorHtml; - } - - if (!description.license().isEmpty()) - { - QString licenseHtml = "<h2>License</h2><p>" + description.license() + "</p>"; - aboutString += licenseHtml; - } - - if (!description.acknowledgements().isEmpty()) - { - QString acknowledgementsHtml = "<h2>Acknowledgements</h2><p>" + description.acknowledgements() + "</p>"; - aboutString += acknowledgementsHtml; - } - - helpBrowser->clear(); - helpBrowser->setHtml(helpString); - helpBox->setCollapsed(true); - - aboutBrowser->clear(); - aboutBrowser->setHtml(aboutString); - aboutBox->setCollapsed(true); - - d->m_TopLevelWidget.reset(topLevelWidget); - } - - return d->m_TopLevelWidget.data(); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleGui::copyParameters(QmitkCmdLineModuleGui& another) -{ - - // This copies "display" parameter types. - // See ctkCmdLineModuleFrontend::DisplayRole - this->setValues(another.values()); - - // For custom types, we must manually copy the values. - // In our case, we copy image types manually, to pass across the mitk::DataNode pointer. - QList<ctkCmdLineModuleParameter> parameters = another.parameters("image", ctkCmdLineModuleFrontend::Input); - foreach (ctkCmdLineModuleParameter parameter, parameters) - { - QString parameterName = parameter.name(); - - QVariant tmp = another.value(parameterName, ctkCmdLineModuleFrontend::UserRole); - mitk::DataNode::Pointer node = tmp.value<mitk::DataNode::Pointer>(); - - if (node.IsNotNull()) - { - mitk::Image* image = dynamic_cast<mitk::Image*>(node->GetData()); - if (image != nullptr) - { - this->setValue(parameterName, tmp, ctkCmdLineModuleFrontend::UserRole); - } - } - } -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.h deleted file mode 100644 index 9709bf60ce..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleGui.h +++ /dev/null @@ -1,114 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkCmdLineModuleGui_h -#define QmitkCmdLineModuleGui_h - -#include <QBuffer> -#include <QUiLoader> - -#include <ctkCmdLineModuleReference.h> -#include <ctkCmdLineModuleFrontendQtGui.h> - -namespace mitk -{ - class DataStorage; -} -struct QmitkCmdLineModuleGuiPrivate; -class QWidget; - -/** - * \class QmitkCmdLineModuleGui - * \brief Derived from ctkCmdLineModuleQtGui to implement an MITK specific command line module, - * that has access to the mitk::DataStorage, and also instantiates QmitkDataStorageComboBox - * for any "imageInputWidget" type, and also provides QmitkDataStorageComboBox.xsl to override - * the standard CTK xslt transformation. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \sa QmitkCmdLineModuleFactoryGui - * \sa ctkCmdLineModuleFrontendQtGui - */ -class QmitkCmdLineModuleGui : public ctkCmdLineModuleFrontendQtGui -{ - Q_OBJECT - -public: - QmitkCmdLineModuleGui(const mitk::DataStorage* dataStorage, const ctkCmdLineModuleReference& moduleRef); - ~QmitkCmdLineModuleGui() override; - - /** - * \brief Returns the top level widget containing the whole GUI, and - * should be used in preference to ctkCmdLineModuleFrontend::guiHandle. - */ - QWidget* getGui(); - - /** - * \brief Copies the visible parameters from another QmitkCmdLineModuleGui; - * \param another a QmitkCmdLineModuleGui frontend. - */ - void copyParameters(QmitkCmdLineModuleGui& another); - - /** - * \brief A custom method to enable access to a mitk::DataNode::Pointer for input images. - * \param parameter The name of the parameter as specified in XML. - * \param role The role, \see ctkCmdLineModuleFrontend. - * \return QVariant - * - * If role==UserRole and the parameter specified by parameter name is an - * input image, will return a mitk::DataNode::Pointer wrapped in a QVariant. - * - * If role==UserRole and the parameter specified is not an input image, - * returns an Empty QVariant. - * - * For any other scenario, calls base class. - * - * \sa ctkCmdLineModuleFrontend::value - */ - QVariant value(const QString ¶meter, int role) const override; - - /** - * \brief A custom method to enable the setting of mitk::DataNode::Pointer for input images. - * \param parameter The name of the parameter as specified in XML. - * \param value QVariant containing a mitk::DataNode::Pointer - * \param role The role, \see ctkCmdLineModuleFrontend. - * - * If role==UserRole and the parameter specified by parameter name is an - * input image, will set the value for that parameter. - * - * - * For any other scenario, calls base class. - * - * \sa ctkCmdLineModuleFrontend::setValue - */ - void setValue(const QString& parameter, const QVariant& value, int role = DisplayRole) override; - -protected: - - /** - * \brief Virtual getter. - * \see ctkCmdLineModuleFrontendQtGui::uiLoader() - */ - QUiLoader* uiLoader() const override; - - /** - * \brief Virtual getter. - * \see ctkCmdLineModuleFrontendQtGui::xslTransform() - */ - ctkCmdLineModuleXslTransform* xslTransform() const override; - -private: - - QScopedPointer<QmitkCmdLineModuleGuiPrivate> d; - -}; // end class - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleProgressWidget.ui b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleProgressWidget.ui deleted file mode 100644 index 2cbf66be90..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleProgressWidget.ui +++ /dev/null @@ -1,131 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>QmitkCmdLineModuleProgressWidget</class> - <widget class="QWidget" name="QmitkCmdLineModuleProgressWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>593</width> - <height>390</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QFrame" name="m_Frame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="m_ProgressTitle"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>TextLabel</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QProgressBar" name="m_ProgressBar"> - <property name="value"> - <number>0</number> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="m_PauseButton"> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset> - <normaloff>:/CommandLineModulesResources/pause.png</normaloff>:/CommandLineModulesResources/pause.png</iconset> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="m_CancelButton"> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset> - <normaloff>:/CommandLineModulesResources/stop.png</normaloff>:/CommandLineModulesResources/stop.png</iconset> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="m_RemoveButton"> - <property name="text"> - <string>Remove</string> - </property> - <property name="autoRaise"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="ctkCollapsibleGroupBox" name="m_ParametersGroupBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Parameters</string> - </property> - </widget> - </item> - <item> - <widget class="ctkCollapsibleGroupBox" name="m_ConsoleGroupBox"> - <property name="title"> - <string>Console</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QPlainTextEdit" name="m_Console"/> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ctkCollapsibleGroupBox</class> - <extends>QGroupBox</extends> - <header>ctkCollapsibleGroupBox.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.cpp deleted file mode 100644 index 5d179b37f1..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.cpp +++ /dev/null @@ -1,665 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkCmdLineModuleRunner.h" -#include "ui_QmitkCmdLineModuleProgressWidget.h" - -// Qt -#include <QFile> -#include <QFileInfo> -#include <QDir> -#include <QMessageBox> -#include <QVBoxLayout> -#include <QLayoutItem> -#include <QTextBrowser> -#include <QByteArray> -#include <QApplication> -#include <QRegExp> -#include <QStyle> - -// CTK -#include <ctkCmdLineModuleFuture.h> -#include <ctkCmdLineModuleFutureWatcher.h> -#include <ctkCmdLineModuleManager.h> -#include <ctkCmdLineModuleFrontend.h> -#include <ctkCmdLineModuleDescription.h> -#include <ctkCollapsibleGroupBox.h> - -// MITK -#include <mitkIOUtil.h> -#include <mitkDataStorage.h> -#include <mitkExceptionMacro.h> -#include <mitkFileReaderSelector.h> -#include <QmitkCustomVariants.h> -#include "QmitkCmdLineModuleGui.h" - - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleRunner::QmitkCmdLineModuleRunner(QWidget *parent) - : QWidget(parent) -, m_ModuleManager(nullptr) -, m_DataStorage(nullptr) -, m_UI(new Ui::QmitkCmdLineModuleProgressWidget) -, m_Layout(nullptr) -, m_ModuleFrontEnd(nullptr) -, m_FutureWatcher(nullptr) -{ - m_UI->setupUi(this); - m_UI->m_RemoveButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_TitleBarCloseButton)); - - m_Layout = new QVBoxLayout(); - m_Layout->setContentsMargins(0,0,0,0); - m_Layout->setSpacing(0); - m_UI->m_ParametersGroupBox->setLayout(m_Layout); - - qRegisterMetaType<ctkCmdLineModuleReference>(); - - connect(m_UI->m_RemoveButton, SIGNAL(clicked()), this, SLOT(OnRemoveButtonClicked())); - - // Due to Qt bug 12152, we cannot listen to the "paused" signal because it is - // not emitted directly when the QFuture is paused. Instead, it is emitted after - // resuming the future, after the "resume" signal has been emitted... we use - // a polling approach instead. - connect(&m_PollPauseTimer, SIGNAL(timeout()), SLOT(OnCheckModulePaused())); - m_PollPauseTimer.setInterval(300); - m_PollPauseTimer.start(); -} - - -//----------------------------------------------------------------------------- -QmitkCmdLineModuleRunner::~QmitkCmdLineModuleRunner() -{ - if (m_ModuleFrontEnd != nullptr) - { - delete m_ModuleFrontEnd; - } - - this->ClearUpTemporaryFiles(); - - delete m_UI; -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::SetManager(ctkCmdLineModuleManager* manager) -{ - this->m_ModuleManager = manager; -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::SetDataStorage(mitk::DataStorage* dataStorage) -{ - this->m_DataStorage = dataStorage; -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::SetOutputDirectory(const QString& directoryName) -{ - this->m_OutputDirectoryName = directoryName; -} - - -//----------------------------------------------------------------------------- -QString QmitkCmdLineModuleRunner::GetTitle() -{ - assert(m_ModuleFrontEnd); - - ctkCmdLineModuleReference reference = m_ModuleFrontEnd->moduleReference(); - ctkCmdLineModuleDescription description = reference.description(); - - return description.title(); -} - - -//----------------------------------------------------------------------------- -QString QmitkCmdLineModuleRunner::GetFullName() const -{ - assert(m_ModuleFrontEnd); - - ctkCmdLineModuleReference reference = m_ModuleFrontEnd->moduleReference(); - ctkCmdLineModuleDescription description = reference.description(); - - return description.categoryDotTitle(); -} - - -//----------------------------------------------------------------------------- -QString QmitkCmdLineModuleRunner::GetValidNodeName(const QString& nodeName) const -{ - QString outputName = nodeName; - - // We will allow A-Z, a-z, 0-9, period, hyphen and underscore in the output file name. - // This method is parsing a node name, and other bits of code add on a file extension .nii. - // So, in the output string from this function, we should not allow period, so that - // the second recommendation on this page: - // https://www.boost.org/doc/libs/1_43_0/libs/filesystem/doc/portability_guide.htm - // is still true. - - QRegExp rx("[A-Z|a-z|0-9|-|_]{1,1}"); - - QString singleLetter; - - for (int i = 0; i < outputName.size(); i++) - { - if (i == 0 && outputName[i] == '-') - { - outputName[i] = '_'; - } - - singleLetter = outputName[i]; - - if (!rx.exactMatch(singleLetter)) - { - outputName[i] = '-'; - } - } - return outputName; -} - - -//----------------------------------------------------------------------------- -bool QmitkCmdLineModuleRunner::IsStarted() const -{ - bool isStarted = false; - if (m_FutureWatcher != nullptr && m_FutureWatcher->isStarted()) - { - isStarted = true; - } - return isStarted; -} - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnCheckModulePaused() -{ - if (!this->IsStarted()) - { - return; - } - - if (this->m_FutureWatcher->future().isPaused()) - { - if (!m_UI->m_PauseButton->isChecked()) - { - m_UI->m_PauseButton->setChecked(true); - } - } - else - { - if (m_UI->m_PauseButton->isChecked()) - { - m_UI->m_PauseButton->setChecked(false); - } - } -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnPauseButtonToggled(bool toggled) -{ - this->m_FutureWatcher->setPaused(toggled); - - if (toggled) - { - this->m_UI->m_ProgressTitle->setText(this->GetTitle() + ": paused"); - } - else - { - this->m_UI->m_ProgressTitle->setText(this->GetTitle() + ": resumed"); - } -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnRemoveButtonClicked() -{ - this->deleteLater(); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleStarted() -{ - this->m_UI->m_ProgressBar->setMaximum(0); - - QString message = "started."; - this->PublishMessage(message); - - emit started(); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleCanceled() -{ - QString message = "cancelling."; - this->PublishMessage(message); - - this->m_UI->m_PauseButton->setEnabled(false); - this->m_UI->m_PauseButton->setChecked(false); - this->m_UI->m_CancelButton->setEnabled(false); - this->m_UI->m_RemoveButton->setEnabled(true); - - this->m_UI->m_ParametersGroupBox->setCollapsed(true); - this->m_UI->m_ConsoleGroupBox->setCollapsed(true); - this->m_UI->m_ProgressTitle->setText(this->GetTitle() + ": cancelled"); - - message = "cancelled."; - this->PublishMessage(message); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleFinished() -{ - this->m_UI->m_PauseButton->setEnabled(false); - this->m_UI->m_PauseButton->setChecked(false); - this->m_UI->m_CancelButton->setEnabled(false); - this->m_UI->m_RemoveButton->setEnabled(true); - - if (!this->m_FutureWatcher->isCanceled()) - { - QString message = "finishing."; - this->PublishMessage(message); - - // If no incremental results from stdout, try getting hold of the whole buffer and printing it. - if (m_OutputCount == 0) - { - message = "Output channel is:"; - this->PublishMessage(message); - this->PublishByteArray(this->m_FutureWatcher->readAllOutputData()); - } - - // If no incremental results from stderr, try getting hold of the whole buffer and printing it. - if (m_ErrorCount == 0) - { - message = "Error channel is:"; - this->PublishMessage(message); - this->PublishByteArray(this->m_FutureWatcher->readAllErrorData()); - } - - this->m_UI->m_ProgressTitle->setText(this->GetTitle() + ": finished"); - - this->LoadOutputData(); - this->ClearUpTemporaryFiles(); - - message = "finished."; - this->PublishMessage(message); - - } - - emit finished(); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleResumed() -{ - this->m_UI->m_PauseButton->setChecked(false); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleProgressRangeChanged(int progressMin, int progressMax) -{ - this->m_UI->m_ProgressBar->setMinimum(progressMin); - this->m_UI->m_ProgressBar->setMaximum(progressMax); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleProgressTextChanged(const QString& progressText) -{ - this->m_UI->m_Console->appendPlainText(progressText); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnModuleProgressValueChanged(int progressValue) -{ - this->m_UI->m_ProgressBar->setValue(progressValue); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnOutputDataReady() -{ - m_OutputCount++; - this->PublishByteArray(this->m_FutureWatcher->readPendingOutputData()); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::OnErrorDataReady() -{ - m_ErrorCount++; - this->PublishByteArray(this->m_FutureWatcher->readPendingErrorData()); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::PublishMessage(const QString& message) -{ - QString prefix = ""; // Can put additional prefix here if needed. - QString outputMessage = prefix + message; - - qDebug() << outputMessage; - this->m_UI->m_Console->appendPlainText(outputMessage); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::PublishByteArray(const QByteArray& array) -{ - QString message = array.data(); - this->PublishMessage(message); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::ClearUpTemporaryFiles() -{ - QString message; - QString fileName; - - foreach (QTemporaryFile* file, m_TemporaryFiles) - { - assert(file != nullptr); - - fileName = file->fileName(); - message = QObject::tr("removing %1").arg(fileName); - this->PublishMessage(message); - - delete file; - } - - m_TemporaryFiles.clear(); -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::LoadOutputData() -{ - assert(m_DataStorage); - - QString fileName; - foreach (fileName, m_OutputDataToLoad) - { - QString message; - - try - { - - mitk::IOUtil::LoadInfo info(fileName.toStdString()); - std::vector<mitk::FileReaderSelector::Item> readers = info.m_ReaderSelector.Get(); - if (readers.size() > 0) - { - mitk::IOUtil::Load(fileName.toStdString(), *(m_DataStorage)); - message = QObject::tr("Loaded %1").arg(fileName); - } - else - { - message = QObject::tr("Not loading %1, as no IFileReader is available.").arg(fileName); - } - } - catch (const mitk::Exception& e) - { - message = QObject::tr("Failed to load %1, due to %2\n").arg(fileName).arg(e.what()); - MITK_ERROR << message.toStdString(); - } - - this->PublishMessage(message); - } -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::SetFrontend(QmitkCmdLineModuleGui* frontEnd) -{ - assert(frontEnd); - assert(m_ModuleManager); - assert(m_DataStorage); - - // We are assuming that this method is ONLY EVER CALLED ONCE. - assert(!m_ModuleFrontEnd); - - // Assign the frontEnd to the member variable. - m_ModuleFrontEnd = frontEnd; - - // We put the new GUI into the layout. - m_Layout->insertWidget(0, m_ModuleFrontEnd->getGui()); - - // And configure a few other niceties. - m_UI->m_ProgressTitle->setText(this->GetTitle()); - m_UI->m_ConsoleGroupBox->setCollapsed(true); // We basically call SetFrontend then Run - m_UI->m_ParametersGroupBox->setCollapsed(true); // so in practice the user will only want the progress bar. -} - - -//----------------------------------------------------------------------------- -void QmitkCmdLineModuleRunner::Run() -{ - assert(m_ModuleManager); - assert(m_DataStorage); - assert(m_ModuleFrontEnd); - - m_OutputDataToLoad.clear(); - - QString parameterName; - QString message; - QList<ctkCmdLineModuleParameter> parameters; - - ctkCmdLineModuleReference reference = m_ModuleFrontEnd->moduleReference(); - ctkCmdLineModuleDescription description = reference.description(); - - // Check we have valid output. If at all possible, they should be somewhere writable. - - parameters = m_ModuleFrontEnd->parameters("image", ctkCmdLineModuleFrontend::Output); - parameters << m_ModuleFrontEnd->parameters("file", ctkCmdLineModuleFrontend::Output); - parameters << m_ModuleFrontEnd->parameters("geometry", ctkCmdLineModuleFrontend::Output); - - foreach (ctkCmdLineModuleParameter parameter, parameters) - { - parameterName = parameter.name(); - QString outputFileName = m_ModuleFrontEnd->value(parameterName, ctkCmdLineModuleFrontend::DisplayRole).toString(); - - // Try to make sure we are not running in the application installation folder, - // as more likely than not, it should not have write access, and you certainly - // don't want users output files dumped there. - // - // eg. C:/Program Files (Windows), /Applications (Mac), /usr/local (Linux) etc. - - QFileInfo outputFileInfo(outputFileName); - - QString applicationDir = QApplication::applicationDirPath(); - QString outputDir = outputFileInfo.dir().absolutePath(); - - if (applicationDir == outputDir) - { - qDebug() << "QmitkCmdLineModuleRunner::Run(), output folder = application folder, so will swap to defaultOutputDir, specified in CLI module preferences"; - - QFileInfo newOutputFileInfo(m_OutputDirectoryName, outputFileInfo.fileName()); - QString newOutputFileAbsolutePath = newOutputFileInfo.absoluteFilePath(); - - qDebug() << "QmitkCmdLineModuleRunner::Run(), swapping " << outputFileName << " to " << newOutputFileAbsolutePath; - - QMessageBox msgBox; - msgBox.setText("The output directory is the same as the application installation directory"); - msgBox.setInformativeText(tr("Output file:\n%1\n\nwill be swapped to\n%2").arg(outputFileName).arg(newOutputFileAbsolutePath)); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); - - m_ModuleFrontEnd->setValue(parameterName, newOutputFileAbsolutePath, ctkCmdLineModuleFrontend::DisplayRole); - } - } - - // For each output image or file, store the filename, so we can auto-load it once the process finishes. - foreach (ctkCmdLineModuleParameter parameter, parameters) - { - parameterName = parameter.name(); - QString outputFileName = m_ModuleFrontEnd->value(parameterName, ctkCmdLineModuleFrontend::DisplayRole).toString(); - - if (!outputFileName.isEmpty()) - { - m_OutputDataToLoad.push_back(outputFileName); - - message = "Registered " + outputFileName + " to auto load upon completion."; - this->PublishMessage(message); - } - } - - // For each input image, write a temporary file as a Nifti image (TODO - iterate through list of file formats). - // and then save the full path name back on the parameter. - - message = "Saving image data to temporary storage..."; - this->PublishMessage(message); - - parameters = m_ModuleFrontEnd->parameters("image", ctkCmdLineModuleFrontend::Input); - foreach (ctkCmdLineModuleParameter parameter, parameters) - { - parameterName = parameter.name(); - - QVariant tmp = m_ModuleFrontEnd->value(parameterName, ctkCmdLineModuleFrontend::UserRole); - mitk::DataNode::Pointer node = tmp.value<mitkDataNodePtr>(); - - if (node.IsNotNull()) - { - mitk::Image* image = dynamic_cast<mitk::Image*>(node->GetData()); - if (image != nullptr) - { - QString errorMessage; - QTemporaryFile* tempFile = this->SaveTemporaryImage(parameter, node.GetPointer(), errorMessage); - - if(tempFile == nullptr) - { - QMessageBox::warning(this, "Saving temporary file failed", errorMessage); - return; - } - - m_TemporaryFiles.push_back(tempFile); - m_ModuleFrontEnd->setValue(parameterName, tempFile->fileName()); - - message = "Saved " + tempFile->fileName(); - this->PublishMessage(message); - - } // end if image - } // end if node - } // end foreach input image - - m_OutputCount = 0; - m_ErrorCount = 0; - - // Now we run stuff. - message = "starting."; - this->PublishMessage(message); - - if (m_FutureWatcher == nullptr) - { - m_FutureWatcher = new ctkCmdLineModuleFutureWatcher(); - - connect(m_FutureWatcher, SIGNAL(started()), SLOT(OnModuleStarted())); - connect(m_FutureWatcher, SIGNAL(canceled()), SLOT(OnModuleCanceled())); - connect(m_FutureWatcher, SIGNAL(finished()), SLOT(OnModuleFinished())); - connect(m_FutureWatcher, SIGNAL(resumed()), SLOT(OnModuleResumed())); - connect(m_FutureWatcher, SIGNAL(progressRangeChanged(int,int)), SLOT(OnModuleProgressRangeChanged(int,int))); - connect(m_FutureWatcher, SIGNAL(progressTextChanged(QString)), SLOT(OnModuleProgressTextChanged(QString))); - connect(m_FutureWatcher, SIGNAL(progressValueChanged(int)), SLOT(OnModuleProgressValueChanged(int))); - connect(m_FutureWatcher, SIGNAL(outputDataReady()), SLOT(OnOutputDataReady())); - connect(m_FutureWatcher, SIGNAL(errorDataReady()), SLOT(OnErrorDataReady())); - - connect(m_UI->m_CancelButton, SIGNAL(clicked()), m_FutureWatcher, SLOT(cancel())); - connect(m_UI->m_PauseButton, SIGNAL(toggled(bool)), this, SLOT(OnPauseButtonToggled(bool))); - - } - ctkCmdLineModuleFuture future = m_ModuleManager->run(m_ModuleFrontEnd); - m_FutureWatcher->setFuture(future); - - m_UI->m_PauseButton->setEnabled(future.canPause()); - m_UI->m_CancelButton->setEnabled(future.canCancel()); - m_UI->m_RemoveButton->setEnabled(!future.isRunning()); - - // Give some immediate indication that we are running. - m_UI->m_ProgressTitle->setText(description.title() + ": running"); -} - - -//----------------------------------------------------------------------------- -QTemporaryFile* QmitkCmdLineModuleRunner::SaveTemporaryImage(const ctkCmdLineModuleParameter ¶meter, mitk::DataNode::ConstPointer node, QString& errorMessage) const -{ - // Don't call this if node is null or node is not an image. - assert(node.GetPointer()); - mitk::Image* image = dynamic_cast<mitk::Image*>(node->GetData()); - assert(image); - - QString intermediateError; - QString intermediateErrors; - - QTemporaryFile *returnedFile = nullptr; - QString name = this->GetValidNodeName(QString::fromStdString(node->GetName())); - QString fileNameTemplate = name + "_XXXXXX"; - - // If no file extensions are specified, we default to .nii - QStringList fileExts = parameter.fileExtensions(); - if (fileExts.isEmpty()) - { - fileExts.push_back(".nii"); - } - - // Try each extension until we get a good one. - foreach (QString extension, fileExts) - { - // File extensions may or may not include the leading dot, so add one if necessary. - if (!extension.startsWith(".")) - { - extension.prepend("."); - } - fileNameTemplate = fileNameTemplate + extension; - - try - { - QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QDir::separator() + fileNameTemplate); - if (tempFile->open()) - { - tempFile->close(); - try - { - mitk::IOUtil::Save( image, tempFile->fileName().toStdString() ); - returnedFile = tempFile; - break; - } - catch(const mitk::Exception &) - { - intermediateError = QObject::tr("Tried %1, failed to save image:\n%2\n").arg(extension).arg(tempFile->fileName()); - } - } - else - { - intermediateError = QObject::tr("Tried %1, failed to open file:\n%2\n").arg(extension).arg(tempFile->fileName()); - } - } - catch(const mitk::Exception &e) - { - intermediateError = QObject::tr("Tried %1, caught MITK Exception:\nDescription: %2\nFilename: %3\nLine: %4\n") - .arg(extension).arg(e.GetDescription()).arg(e.GetFile()).arg(e.GetLine()); - } - catch(const std::exception& e) - { - intermediateError = QObject::tr("Tried %1, caught exception:\nDescription: %2\n") - .arg(extension).arg(e.what()); - } - intermediateErrors += intermediateError; - } - errorMessage = intermediateErrors; - return returnedFile; -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.h deleted file mode 100644 index e750b9a539..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkCmdLineModuleRunner.h +++ /dev/null @@ -1,232 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkCmdLineModuleRunner_h -#define QmitkCmdLineModuleRunner_h - -#include <QWidget> -#include <QTimer> -#include <QList> - -#include <ctkCmdLineModuleParameter.h> -#include <mitkDataNode.h> - -class QVBoxLayout; -class QTemporaryFile; -class QmitkCmdLineModuleGui; -class ctkCmdLineModuleManager; -class ctkCmdLineModuleFutureWatcher; - -namespace Ui { -class QmitkCmdLineModuleProgressWidget; -} - -namespace mitk { -class DataStorage; -} - -/** - * \class QmitkCmdLineModuleRunner - * \brief Based on ctkCmdLineModuleExplorerProgressWidget, implements a progress widget - * with console output, and space for storing the GUI widgets. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \sa ctkCmdLineModuleExplorerProgressWidget - */ -class QmitkCmdLineModuleRunner : public QWidget -{ - Q_OBJECT - -public: - - QmitkCmdLineModuleRunner(QWidget *parent = nullptr); - ~QmitkCmdLineModuleRunner() override; - - /** - * \brief Sets the manager on this object, and must be called immediately - * after construction, before using the widget. - */ - void SetManager(ctkCmdLineModuleManager* manager); - - /** - * \brief Sets the DataStorage on this object, and must be called immediately - * after construction, before using the widget. - */ - void SetDataStorage(mitk::DataStorage* dataStorage); - - /** - * \brief Sets the Output Directory on this widget, and must be called - * immediately after construction, before using the widget. - */ - void SetOutputDirectory(const QString& directoryName); - - /** - * \brief Tells this widget, which module frontend it is running - * \param frontEnd our QmitkCmdLineModuleGui class derived from ctkCmdLineModuleFrontend - */ - void SetFrontend(QmitkCmdLineModuleGui* frontEnd); - - /** - * \brief Runs the module that this widget is currently referring to. - */ - void Run(); - -Q_SIGNALS: - - // These signals so that container classes such as CommandLineModuleView - // can keep track of how many modules are running simultaneously. - - void started(); // emitted when the module is started. - void finished(); // emitted when the module is completely finished. - -private Q_SLOTS: - - void OnCheckModulePaused(); - - void OnPauseButtonToggled(bool toggled); - void OnRemoveButtonClicked(); - - void OnModuleStarted(); - void OnModuleCanceled(); - void OnModuleFinished(); - void OnModuleResumed(); - void OnModuleProgressRangeChanged(int progressMin, int progressMax); - void OnModuleProgressTextChanged(const QString& progressText); - void OnModuleProgressValueChanged(int progressValue); - void OnOutputDataReady(); - void OnErrorDataReady(); - -private: - - /** - * \brief Simply returns true if this widget is considered as having been started. - */ - bool IsStarted() const; - - /** - * \brief Used to write output to the console widget, and also to qDebug(). - */ - void PublishMessage(const QString& message); - - /** - * \brief Used to write output to the console widget, and also to qDebug(). - */ - void PublishByteArray(const QByteArray& array); - - /** - * \brief Destroys any images listed in m_TemporaryFileNames. - */ - void ClearUpTemporaryFiles(); - - /** - * \brief Loads any data listed in m_OutputDataToLoad into the m_DataStorage. - */ - void LoadOutputData(); - - /** - * \brief Saves temporary image to file. - * \param[in] node non-nullptr pointer to node containing a non-nullptr mitk::Image. - * \param[out] errorMessage which if not empty means an error occurred. - * \return QTemporaryFile temporary file that the caller is responsible for deleting. - * - * If the returned QTemporaryFile is nullptr, check errorMessage. - * If the returned QTemporaryFile is not-nullptr, there could still be data in the errorMessage. - * It could be that this method tried n file extensions, before finding a successful one. - * In this case, the returned QTemporaryFile is the successful one, and the errorMessage contains error messages of all the failed attempts. - */ - QTemporaryFile* SaveTemporaryImage(const ctkCmdLineModuleParameter& parameter, mitk::DataNode::ConstPointer node, QString& errorMessage) const; - - /** - * \brief Utility method to look up the title from the description. - */ - QString GetTitle(); - - /** - * \brief Returns <category>.<title>, derived from the ctkCmdLineModuleReference and - * hence from the ctkCmdLineModuleDescription. - */ - QString GetFullName() const; - - /** - * \brief Takes nodeName, and makes sure that it only contains A-Z, a-z, 0-9, hyphen and underscore, - * and does not use hyphen as the first character. - * - * Inspired by <a href="https://www.boost.org/doc/libs/1_43_0/libs/filesystem/doc/portability_guide.htm">boost recommendations</a>. - */ - QString GetValidNodeName(const QString& nodeName) const; - - /** - * \brief This must be injected before the Widget is used. - */ - ctkCmdLineModuleManager *m_ModuleManager; - - /** - * \brief This must be injected before the Widget is used. - */ - mitk::DataStorage *m_DataStorage; - - /** - * \brief This must be injected before the Widget is used. - */ - QString m_OutputDirectoryName; - - /** - * \brief We instantiate the main widgets from this .ui file. - */ - Ui::QmitkCmdLineModuleProgressWidget *m_UI; - - /** - * \brief The m_ParametersGroupBox needs a layout. - */ - QVBoxLayout *m_Layout; - - /** - * \brief The QmitkCmdLineModuleGui is created by the QmitkCmdLineModuleFactoryGui outside - * of this class and injected into this class before being run. - */ - QmitkCmdLineModuleGui *m_ModuleFrontEnd; - - /** - * \brief Main object to keep track of a running command line module. - */ - ctkCmdLineModuleFutureWatcher *m_FutureWatcher; - - /** - * \brief Due to Qt bug 12152, we use a timer to correctly check for a paused module. - */ - QTimer m_PollPauseTimer; - - /** - * \brief We store a list of temporary file names that are saved to disk before - * launching a command line app, and then must be cleared up when the command line - * app successfully finishes. - */ - QList<QTemporaryFile*> m_TemporaryFiles; - - /** - * \brief We store a list of output images, so that on successful completion of - * the command line module, we automatically load the output data into the mitk::DataStorage. - */ - QStringList m_OutputDataToLoad; - - /** - * \brief We track how many times the OnOutputDataReady is called. - */ - int m_OutputCount; - - /** - * \brief We track how many times the OnErrorDataReady is called. - */ - int m_ErrorCount; -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.cpp deleted file mode 100644 index 1b47794cf0..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkDirectoryListWidget.h" - -#include <ctkPathListWidget.h> -#include <ctkPathListButtonsWidget.h> - -//----------------------------------------------------------------------------- -QmitkDirectoryListWidget::QmitkDirectoryListWidget(QWidget*) -{ - this->setupUi(this); - this->m_PathListWidget->setMode(ctkPathListWidget::DirectoriesOnly); - this->m_PathListWidget->setDirectoryOptions(ctkPathListWidget::Exists | ctkPathListWidget::Readable | ctkPathListWidget::Executable); - this->m_PathListButtonsWidget->init(this->m_PathListWidget); - this->m_PathListButtonsWidget->setOrientation(Qt::Vertical); - connect(this->m_PathListWidget, SIGNAL(pathsChanged(QStringList,QStringList)), this, SLOT(OnPathsChanged(QStringList, QStringList))); -} - - -//----------------------------------------------------------------------------- -void QmitkDirectoryListWidget::OnPathsChanged(const QStringList& before, const QStringList& after) -{ - emit pathsChanged(before, after); -} - - -//----------------------------------------------------------------------------- -QStringList QmitkDirectoryListWidget::directories(bool absolutePath) const -{ - return this->m_PathListWidget->directories(absolutePath); -} - - -//----------------------------------------------------------------------------- -void QmitkDirectoryListWidget::setDirectories(const QStringList& paths) -{ - this->m_PathListWidget->setPaths(paths); -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.h deleted file mode 100644 index ef413beceb..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkDirectoryListWidget.h +++ /dev/null @@ -1,55 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkDirectoryListWidget_h -#define QmitkDirectoryListWidget_h - -#include "ui_QmitkPathListWidget.h" -#include <QWidget> - -/** - * \class QmitkDirectoryListWidget - * \brief Widget to contain a ctkPathListWidget and a ctkPathListButtonsWidget - * and provide simple directory access for readable, executable directories. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -class QmitkDirectoryListWidget : public QWidget, public Ui::QmitkPathListWidget -{ - Q_OBJECT - -public: - QmitkDirectoryListWidget(QWidget* parent=nullptr); - - /** - * \brief Get all directory entries. - * \param absolutePath If <code>true</code>, resolve all entries to absolute paths. - * \return A list of all directory entries. - */ - QStringList directories(bool absolutePath = false) const; - - /** - * \brief Sets the list of directory entries. - * \param paths The new path list. - */ - void setDirectories(const QStringList& paths); - -Q_SIGNALS: - - void pathsChanged(const QStringList&, const QStringList&); - -private Q_SLOTS: - - void OnPathsChanged(const QStringList&, const QStringList&); -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.cpp deleted file mode 100644 index 90fb7991cf..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkFileListWidget.h" - -#include <ctkPathListWidget.h> -#include <ctkPathListButtonsWidget.h> - -//----------------------------------------------------------------------------- -QmitkFileListWidget::QmitkFileListWidget(QWidget*) -{ - this->setupUi(this); - this->m_PathListWidget->setMode(ctkPathListWidget::FilesOnly); - this->m_PathListWidget->setFileOptions(ctkPathListWidget::Exists | ctkPathListWidget::Readable | ctkPathListWidget::Executable); - this->m_PathListButtonsWidget->init(this->m_PathListWidget); - this->m_PathListButtonsWidget->setOrientation(Qt::Vertical); - connect(this->m_PathListWidget, SIGNAL(pathsChanged(QStringList,QStringList)), this, SLOT(OnPathsChanged(QStringList, QStringList))); -} - - -//----------------------------------------------------------------------------- -void QmitkFileListWidget::OnPathsChanged(const QStringList& before, const QStringList& after) -{ - emit pathsChanged(before, after); -} - - -//----------------------------------------------------------------------------- -QStringList QmitkFileListWidget::files(bool absolutePath) const -{ - return this->m_PathListWidget->files(absolutePath); -} - - -//----------------------------------------------------------------------------- -void QmitkFileListWidget::setFiles(const QStringList& paths) -{ - this->m_PathListWidget->setPaths(paths); -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.h deleted file mode 100644 index 0ce523b282..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkFileListWidget.h +++ /dev/null @@ -1,55 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkFileListWidget_h -#define QmitkFileListWidget_h - -#include "ui_QmitkPathListWidget.h" -#include <QWidget> - -/** - * \class QmitkFileListWidget - * \brief Widget to contain a ctkPathListWidget and a ctkPathListButtonsWidget - * and provide simple file access for readable, executable files. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -class QmitkFileListWidget : public QWidget, public Ui::QmitkPathListWidget -{ - Q_OBJECT - -public: - QmitkFileListWidget(QWidget* parent=nullptr); - - /** - * \brief Get all file entries. - * \param absolutePath If <code>true</code>, resolve all entries to absolute paths. - * \return A list of all file entries. - */ - QStringList files(bool absolutePath = false) const; - - /** - * \brief Sets the list of file entries. - * \param paths The new path list. - */ - void setFiles(const QStringList& paths); - -Q_SIGNALS: - - void pathsChanged(const QStringList&, const QStringList&); - -private Q_SLOTS: - - void OnPathsChanged(const QStringList&, const QStringList&); -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkPathListWidget.ui b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkPathListWidget.ui deleted file mode 100644 index 1c21a8441d..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkPathListWidget.ui +++ /dev/null @@ -1,74 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>QmitkPathListWidget</class> - <widget class="QWidget" name="QmitkPathListWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>449</width> - <height>312</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="m_Label"> - <property name="text"> - <string>Enter Label Text</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="ctkPathListWidget" name="m_PathListWidget"> - <property name="textElideMode"> - <enum>Qt::ElideMiddle</enum> - </property> - </widget> - </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="m_VerticalLayout"> - <item> - <widget class="ctkPathListButtonsWidget" name="m_PathListButtonsWidget" native="true"> - <property name="buttonsAutoRaise" stdset="0"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <spacer name="m_VerticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ctkPathListButtonsWidget</class> - <extends>QWidget</extends> - <header>ctkPathListButtonsWidget.h</header> - </customwidget> - <customwidget> - <class>ctkPathListWidget</class> - <extends>QListView</extends> - <header>ctkPathListWidget.h</header> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.cpp deleted file mode 100644 index 715a42da4e..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkUiLoader.h" -#include "QmitkDataStorageComboBoxWithSelectNone.h" -#include "mitkNodePredicateDataType.h" -#include "mitkNodePredicateOr.h" -#include "mitkImage.h" - -//----------------------------------------------------------------------------- -QmitkUiLoader::QmitkUiLoader(const mitk::DataStorage* dataStorage, QObject *parent) - : ctkCmdLineModuleQtUiLoader(parent) -, m_DataStorage(dataStorage) -{ - -} - - -//----------------------------------------------------------------------------- -QmitkUiLoader::~QmitkUiLoader() -{ - -} - - -//----------------------------------------------------------------------------- -QStringList QmitkUiLoader::availableWidgets () const -{ - QStringList availableWidgets = ctkCmdLineModuleQtUiLoader::availableWidgets(); - availableWidgets << "QmitkDataStorageComboBoxWithSelectNone"; - return availableWidgets; -} - - -//----------------------------------------------------------------------------- -QWidget* QmitkUiLoader::createWidget(const QString& className, QWidget* parent, const QString& name) -{ - QWidget* widget = nullptr; - if (className == "QmitkDataStorageComboBoxWithSelectNone") - { - auto comboBox = new QmitkDataStorageComboBoxWithSelectNone(parent); - comboBox->setObjectName(name); - comboBox->SetAutoSelectNewItems(false); - comboBox->SetPredicate(mitk::TNodePredicateDataType< mitk::Image >::New()); - comboBox->SetDataStorage(const_cast<mitk::DataStorage*>(m_DataStorage)); - comboBox->setCurrentIndex(0); - widget = comboBox; - } - else - { - widget = ctkCmdLineModuleQtUiLoader::createWidget(className, parent, name); - } - return widget; -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.h deleted file mode 100644 index 3e280b20e0..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/QmitkUiLoader.h +++ /dev/null @@ -1,56 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkUiLoader_h -#define QmitkUiLoader_h - -#include <ctkCmdLineModuleQtUiLoader.h> -#include <QStringList> -#include "mitkDataStorage.h" - -/** - * \class QmitkUiLoader - * \brief Derived from ctkCmdLineModuleQtGuiLoader to enable us to instantiate widgets from Qmitk at runtime, - * and currently we instantiate QmitkDataStorageComboBoxWithSelectNone, used for image input widgets. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - * \sa ctkCmdLineModuleQtUiLoader - */ -class QmitkUiLoader : public ctkCmdLineModuleQtUiLoader -{ - - Q_OBJECT - -public: - QmitkUiLoader(const mitk::DataStorage* dataStorage, QObject *parent=nullptr); - ~QmitkUiLoader() override; - - /** - * \brief Returns the list of available widgets in ctkCmdLineModuleQtGuiLoader and also QmitkDataStorageComboBoxWithSelectNone. - * \see ctkCmdLineModuleQtGuiLoader::availableWidgets() - */ - QStringList availableWidgets () const; - - /** - * \brief If className is QmitkDataStorageComboBox, instantiates QmitkDataStorageComboBoxWithSelectNone and - * otherwise delegates to base class. - * \see ctkCmdLineModuleQtGuiLoader::createWidget() - */ - QWidget* createWidget(const QString & className, QWidget * parent = nullptr, const QString & name = QString() ) override; - -private: - - const mitk::DataStorage* m_DataStorage; - -}; // end class - -#endif diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.cpp b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.cpp deleted file mode 100644 index a99aea6a9f..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#include "org_mitk_gui_qt_cmdlinemodules_Activator.h" -#include "CommandLineModulesView.h" -#include "CommandLineModulesPreferencesPage.h" - -#include <usModuleInitialization.h> - -US_INITIALIZE_MODULE - -namespace mitk { - -void org_mitk_gui_qt_cmdlinemodules_Activator::start(ctkPluginContext* context) -{ - BERRY_REGISTER_EXTENSION_CLASS(CommandLineModulesView, context) - BERRY_REGISTER_EXTENSION_CLASS(CommandLineModulesPreferencesPage, context) -} - -void org_mitk_gui_qt_cmdlinemodules_Activator::stop(ctkPluginContext* context) -{ - Q_UNUSED(context) -} - -} diff --git a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.h b/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.h deleted file mode 100644 index 5e58f00e7a..0000000000 --- a/Plugins/org.mitk.gui.qt.cmdlinemodules/src/internal/org_mitk_gui_qt_cmdlinemodules_Activator.h +++ /dev/null @@ -1,43 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef org_mitk_gui_qt_cmdlinemodules_Activator_h -#define org_mitk_gui_qt_cmdlinemodules_Activator_h - -#include <ctkPluginActivator.h> - -namespace mitk { - -/** - * \class org_mitk_gui_qt_cmdlinemodules_Activator - * \brief Blueberry plugin activator for CommandLineModulesView. - * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal - */ -class org_mitk_gui_qt_cmdlinemodules_Activator : - public QObject, public ctkPluginActivator -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_cmdlinemodules") - Q_INTERFACES(ctkPluginActivator) - -public: - - void start(ctkPluginContext* context) override; - void stop(ctkPluginContext* context) override; - -}; // org_mitk_gui_qt_cmdlinemodules_Activator - -} - -#endif // org_mitk_gui_qt_cmdlinemodules_Activator_h diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp index 91201dbe33..b6f5c20ae0 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp @@ -1,1439 +1,1444 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkExtWorkbenchWindowAdvisor.h" #include "QmitkExtActionBarAdvisor.h" #include <QMenu> #include <QMenuBar> #include <QMainWindow> #include <QStatusBar> #include <QString> #include <QFile> #include <QRegExp> #include <QTextStream> #include <QSettings> #include <ctkPluginException.h> #include <service/event/ctkEventAdmin.h> #include <berryPlatform.h> #include <berryPlatformUI.h> #include <berryIActionBarConfigurer.h> #include <berryIWorkbenchWindow.h> #include <berryIWorkbenchPage.h> #include <berryIPerspectiveRegistry.h> #include <berryIPerspectiveDescriptor.h> #include <berryIProduct.h> #include <berryIWorkbenchPartConstants.h> #include <berryQtPreferences.h> #include <berryQtStyleManager.h> #include <berryWorkbenchPlugin.h> #include <internal/berryQtShowViewAction.h> #include <internal/berryQtOpenPerspectiveAction.h> #include <QmitkFileOpenAction.h> #include <QmitkFileSaveAction.h> #include <QmitkExtFileSaveProjectAction.h> #include <QmitkFileExitAction.h> #include <QmitkCloseProjectAction.h> #include <QmitkUndoAction.h> #include <QmitkRedoAction.h> #include <QmitkDefaultDropTargetListener.h> #include <QmitkStatusBar.h> #include <QmitkProgressBar.h> #include <QmitkMemoryUsageIndicatorView.h> #include <QmitkPreferencesDialog.h> #include <QmitkOpenDicomEditorAction.h> #include <QmitkOpenMxNMultiWidgetEditorAction.h> #include <QmitkOpenStdMultiWidgetEditorAction.h> #include <itkConfigure.h> #include <mitkVersion.h> #include <mitkIDataStorageService.h> #include <mitkIDataStorageReference.h> #include <mitkDataStorageEditorInput.h> #include <mitkWorkbenchUtil.h> #include <mitkCoreServices.h> #include <mitkIPreferencesService.h> #include <mitkIPreferences.h> #include <vtkVersionMacros.h> // UGLYYY #include "internal/QmitkExtWorkbenchWindowAdvisorHack.h" #include "internal/QmitkCommonExtPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include <QToolBar> #include <QToolButton> #include <QMessageBox> #include <QMouseEvent> #include <QLabel> #include <QmitkAboutDialog.h> QmitkExtWorkbenchWindowAdvisorHack* QmitkExtWorkbenchWindowAdvisorHack::undohack = new QmitkExtWorkbenchWindowAdvisorHack(); QString QmitkExtWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; static bool USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS = false; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast<berry::IEditorReference> ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast<berry::IEditorReference> ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForViewNavigator: public berry::IPartListener { public: PartListenerForViewNavigator(QAction* act) : viewNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } private: QAction* viewNavigatorAction; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED; } else { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator<QAction*> i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } //GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->closeProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->viewNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(true); } } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList<berry::IPerspectiveDescriptor::Pointer> perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator<QAction*> i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->closeProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->viewNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(false); } } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; QmitkExtWorkbenchWindowAdvisor::QmitkExtWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showPerspectiveToolbar(false) , showVersionInfo(true) , showMitkVersionInfo(true) , showViewMenuItem(true) , showNewWindowMenuItem(false) , showClosePerspectiveMenuItem(true) , viewNavigatorFound(false) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkExtWorkbenchWindowAdvisor::~QmitkExtWorkbenchWindowAdvisor() { } berry::ActionBarAdvisor::Pointer QmitkExtWorkbenchWindowAdvisor::CreateActionBarAdvisor(berry::IActionBarConfigurer::Pointer configurer) { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { berry::ActionBarAdvisor::Pointer actionBarAdvisor(new QmitkExtActionBarAdvisor(configurer)); return actionBarAdvisor; } else { return berry::WorkbenchWindowAdvisor::CreateActionBarAdvisor(configurer); } } QWidget* QmitkExtWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast<QWidget*>(parent); auto label = new QLabel(parentWidget); label->setText("<b>No perspectives are open. Open a perspective in the <i>Window->Open Perspective</i> menu.</b>"); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkExtWorkbenchWindowAdvisor::ShowClosePerspectiveMenuItem(bool show) { showClosePerspectiveMenuItem = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowClosePerspectiveMenuItem() { return showClosePerspectiveMenuItem; } void QmitkExtWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkExtWorkbenchWindowAdvisor::ShowNewWindowMenuItem(bool show) { showNewWindowMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewMenuItem(bool show) { showViewMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowPerspectiveToolbar(bool show) { showPerspectiveToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkExtWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkExtWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast<QMainWindow*> (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); auto fileOpenAction = new QmitkFileOpenAction(berry::QtStyleManager::ThemeIcon(basePath + "document-open.svg"), window); fileOpenAction->setShortcut(QKeySequence::Open); auto fileSaveAction = new QmitkFileSaveAction(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg"), window); fileSaveAction->setShortcut(QKeySequence::Save); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); closeProjectAction = new QmitkCloseProjectAction(window); closeProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "edit-delete.svg")); auto perspGroup = new QActionGroup(menuBar); std::map<QString, berry::IViewDescriptor::Pointer> VDMap; // sort elements (converting vector to map...) QList<berry::IViewDescriptor::Pointer>::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList<berry::IViewDescriptor::Pointer> viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; i<viewExcludeList.size(); i++) { if (viewExcludeList.at(i) == (*iter)->GetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair<QString, berry::IViewDescriptor::Pointer> p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map<QString, berry::IViewDescriptor::Pointer>::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } if (!USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileOpenAction); fileMenu->addAction(fileSaveAction); fileMenu->addAction(fileSaveProjectAction); fileMenu->addAction(closeProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); if (showNewWindowMenuItem) { windowMenu->addAction("&New Window", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onNewWindow())); windowMenu->addSeparator(); } QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); QMenu* viewMenu = nullptr; if (showViewMenuItem) { viewMenu = windowMenu->addMenu("Show &View"); viewMenu->setObjectName("Show View"); } windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); if(showClosePerspectiveMenuItem) closePerspAction = windowMenu->addAction("&Close Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onClosePerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList<berry::IPerspectiveDescriptor::Pointer> perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList<berry::IPerspectiveDescriptor::Pointer>::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; i<perspectiveExcludeList.size(); i++) { if (perspectiveExcludeList.at(i) == (*perspIt)->GetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); if (showViewMenuItem) { for (auto viewAction : qAsConst(viewActions)) { viewMenu->addAction(viewAction); } } // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== } else { undoAction = new QmitkUndoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), nullptr); undoAction->setShortcut(QKeySequence::Undo); redoAction = new QmitkRedoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), nullptr); redoAction->setShortcut(QKeySequence::Redo); } // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { openDicomEditorAction = new QmitkOpenDicomEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "dicom.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { openStdMultiWidgetEditorAction = new QmitkOpenStdMultiWidgetEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "Editor.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { openMxNMultiWidgetEditorAction = new QmitkOpenMxNMultiWidgetEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "Editor.svg"), window); } if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } viewNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.ext/view-manager.svg")),"&View Navigator", nullptr); viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigator"); if (viewNavigatorFound) { QObject::connect(viewNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onViewNavigator())); viewNavigatorAction->setCheckable(true); // add part listener for view navigator viewNavigatorPartListener.reset(new PartListenerForViewNavigator(viewNavigatorAction)); window->GetPartService()->AddPartListener(viewNavigatorPartListener.data()); berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigator"); viewNavigatorAction->setChecked(false); if (viewnavigatorview) { bool isViewNavigatorVisible = window->GetActivePage()->IsPartVisible(viewnavigatorview); if (isViewNavigatorVisible) viewNavigatorAction->setChecked(true); } viewNavigatorAction->setToolTip("Toggle View Navigator"); } mainActionsToolBar->addAction(fileOpenAction); mainActionsToolBar->addAction(fileSaveProjectAction); mainActionsToolBar->addAction(closeProjectAction); mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if(this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { mainActionsToolBar->addAction(openDicomEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { mainActionsToolBar->addAction(openStdMultiWidgetEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { mainActionsToolBar->addAction(openMxNMultiWidgetEditorAction); } if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } if (viewNavigatorFound) { mainActionsToolBar->addAction(viewNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== Perspective Toolbar ================================== auto qPerspectiveToolbar = new QToolBar; qPerspectiveToolbar->setObjectName("perspectiveToolBar"); if (showPerspectiveToolbar) { qPerspectiveToolbar->addActions(perspGroup->actions()); mainWindow->addToolBar(qPerspectiveToolbar); } else delete qPerspectiveToolbar; if (showViewToolbar) { auto* prefService = mitk::CoreServices::GetPreferencesService(); auto* stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap<QString, berry::IViewDescriptor::Pointer> categoryViewDescriptorMap; for (const auto &labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (const auto &category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); if (!viewDescriptorsInCurrentCategory.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren<QWidget*>()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (const auto &viewDescriptor : qAsConst(viewDescriptorsInCurrentCategory)) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); // progBar->AddStepsToDo(2); // progBar->Progress(1); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkExtWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); // show the shortcut bar and progress indicator, which are hidden by // default //configurer->SetShowPerspectiveBar(true); //configurer->SetShowFastViewBars(true); //configurer->SetShowProgressIndicator(true); // // add the drag and drop support for the editor area // configurer.addEditorAreaTransfer(EditorInputTransfer.getInstance()); // configurer.addEditorAreaTransfer(ResourceTransfer.getInstance()); // configurer.addEditorAreaTransfer(FileTransfer.getInstance()); // configurer.addEditorAreaTransfer(MarkerTransfer.getInstance()); // configurer.configureEditorAreaDropListener(new EditorAreaDropAdapter( // configurer.getWindow())); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkExtWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); ctkServiceReference serviceRef = context->getServiceReference<mitk::IDataStorageService>(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService<mitk::IDataStorageService>(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } auto introPart = configurer->GetWindow()->GetWorkbench()->GetIntroManager()->GetIntro(); if (introPart.IsNotNull()) { configurer->GetWindow()->GetWorkbench()->GetIntroManager()->ShowIntro(GetWindowConfigurer()->GetWindow(), false); } } void QmitkExtWorkbenchWindowAdvisor::onIntro() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkExtWorkbenchWindowAdvisor::onHelp() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkExtWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkExtWorkbenchWindowAdvisor::onAbout() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onAbout(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkExtWorkbenchWindowAdvisorHack::QmitkExtWorkbenchWindowAdvisorHack() : QObject() { } QmitkExtWorkbenchWindowAdvisorHack::~QmitkExtWorkbenchWindowAdvisorHack() { } void QmitkExtWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast<mitk::VerboseLimitedLinearUndo*>( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkExtWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast<mitk::VerboseLimitedLinearUndo*>( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if( wbench == nullptr ) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if( wbench_window.IsNull() ) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if( wbench_page.IsNull() ) return; auto wbench_view = wbench_page->FindView( view_query_name ); if( wbench_view.IsNotNull() ) { bool isViewVisible = wbench_page->IsPartVisible( wbench_view ); if( isViewVisible ) { wbench_page->HideView( wbench_view ); return; } } wbench_page->ShowView( view_query_name ); } void QmitkExtWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onViewNavigator() { // show/hide viewnavigatorView SafeHandleNavigatorView("org.mitk.views.viewnavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkExtWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkExtWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkExtWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkExtWorkbenchWindowAdvisorHack::onNewWindow() { berry::PlatformUI::GetWorkbench()->OpenWorkbenchWindow(nullptr); } void QmitkExtWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)<title>(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkExtWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(),nullptr); aboutDialog->open(); } void QmitkExtWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkExtWorkbenchWindowAdvisor>(this, &QmitkExtWorkbenchWindowAdvisor::PropertyChange)); // configurer.getWindow().addPageListener(new IPageListener() { // public void pageActivated(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageClosed(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageOpened(IWorkbenchPage page) { // // do nothing // } // }); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkExtWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkExtWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkExtWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { lockedLastActiveEditor->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkExtWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { QString newTitle = lockedLastActiveEditor->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkExtWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkExtWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkExtWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); - QSettings settings(GetQSettingsFile(), QSettings::IniFormat); - settings.setValue("ToolbarPosition", mainWindow->saveState()); + auto fileName = this->GetQSettingsFile(); + + if (!fileName.isEmpty()) + { + QSettings settings(fileName, QSettings::IniFormat); + settings.setValue("ToolbarPosition", mainWindow->saveState()); + } } QString QmitkExtWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkCommonExtPlugin::getContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } diff --git a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp index fb76fb6949..35379f49fa 100644 --- a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp @@ -1,1153 +1,1158 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkFlowApplicationWorkbenchWindowAdvisor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QmitkExtFileSaveProjectAction.h" #include #include #include #include #include #include #include #include #include #include // UGLYYY #include "QmitkFlowApplicationWorkbenchWindowAdvisorHack.h" #include "QmitkFlowApplicationPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include QmitkFlowApplicationWorkbenchWindowAdvisorHack* QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack = new QmitkFlowApplicationWorkbenchWindowAdvisorHack(); QString QmitkFlowApplicationWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; QmitkFlowApplicationWorkbenchWindowAdvisor::QmitkFlowApplicationWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showVersionInfo(true) , showMitkVersionInfo(true) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkFlowApplicationWorkbenchWindowAdvisor::~QmitkFlowApplicationWorkbenchWindowAdvisor() { } QWidget* QmitkFlowApplicationWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkFlowApplicationWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileSaveProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== View Toolbar ================================== if (showViewToolbar) { auto* prefService = mitk::CoreServices::GetPreferencesService(); auto* stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (auto labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (auto category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); QList > relevantViewDescriptors; for (auto viewDescriptor : viewDescriptorsInCurrentCategory) { if (viewDescriptor->GetId() != "org.mitk.views.flow.control" && viewDescriptor->GetId() != "org.mitk.views.segmentationtasklist") { relevantViewDescriptors.push_back(viewDescriptor); } } if (!relevantViewDescriptors.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (auto viewDescriptor : relevantViewDescriptors) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::onIntro() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelp() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onAbout() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onAbout(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkFlowApplicationWorkbenchWindowAdvisor>(this, &QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange)); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkFlowApplicationWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkFlowApplicationWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { lockedLastActiveEditor->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { QString newTitle = lockedLastActiveEditor->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); - QSettings settings(GetQSettingsFile(), QSettings::IniFormat); - settings.setValue("ToolbarPosition", mainWindow->saveState()); + auto fileName = this->GetQSettingsFile(); + + if (!fileName.isEmpty()) + { + QSettings settings(fileName, QSettings::IniFormat); + settings.setValue("ToolbarPosition", mainWindow->saveState()); + } } QString QmitkFlowApplicationWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkFlowApplicationWorkbenchWindowAdvisorHack::QmitkFlowApplicationWorkbenchWindowAdvisorHack() : QObject() { } QmitkFlowApplicationWorkbenchWindowAdvisorHack::~QmitkFlowApplicationWorkbenchWindowAdvisorHack() { } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if (wbench == nullptr) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if (wbench_window.IsNull()) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if (wbench_page.IsNull()) return; auto wbench_view = wbench_page->FindView(view_query_name); if (wbench_view.IsNotNull()) { bool isViewVisible = wbench_page->IsPartVisible(wbench_view); if (isViewVisible) { wbench_page->HideView(wbench_view); return; } } wbench_page->ShowView(view_query_name); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(), nullptr); aboutDialog->open(); } diff --git a/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkDirectoryListWidget.h b/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkDirectoryListWidget.h index ef413beceb..8a1517839e 100644 --- a/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkDirectoryListWidget.h +++ b/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkDirectoryListWidget.h @@ -1,55 +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 QmitkDirectoryListWidget_h #define QmitkDirectoryListWidget_h #include "ui_QmitkPathListWidget.h" #include /** * \class QmitkDirectoryListWidget * \brief Widget to contain a ctkPathListWidget and a ctkPathListButtonsWidget * and provide simple directory access for readable, executable directories. * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal */ class QmitkDirectoryListWidget : public QWidget, public Ui::QmitkPathListWidget { Q_OBJECT public: QmitkDirectoryListWidget(QWidget* parent=nullptr); /** * \brief Get all directory entries. * \param absolutePath If true, resolve all entries to absolute paths. * \return A list of all directory entries. */ QStringList directories(bool absolutePath = false) const; /** * \brief Sets the list of directory entries. * \param paths The new path list. */ void setDirectories(const QStringList& paths); Q_SIGNALS: void pathsChanged(const QStringList&, const QStringList&); private Q_SLOTS: void OnPathsChanged(const QStringList&, const QStringList&); }; #endif diff --git a/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkFileListWidget.h b/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkFileListWidget.h index 0ce523b282..ae8fc13635 100644 --- a/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkFileListWidget.h +++ b/Plugins/org.mitk.matchpoint.core.helper/src/internal/QmitkFileListWidget.h @@ -1,55 +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 QmitkFileListWidget_h #define QmitkFileListWidget_h #include "ui_QmitkPathListWidget.h" #include /** * \class QmitkFileListWidget * \brief Widget to contain a ctkPathListWidget and a ctkPathListButtonsWidget * and provide simple file access for readable, executable files. * \author Matt Clarkson (m.clarkson@ucl.ac.uk) - * \ingroup org_mitk_gui_qt_cmdlinemodules_internal */ class QmitkFileListWidget : public QWidget, public Ui::QmitkPathListWidget { Q_OBJECT public: QmitkFileListWidget(QWidget* parent=nullptr); /** * \brief Get all file entries. * \param absolutePath If true, resolve all entries to absolute paths. * \return A list of all file entries. */ QStringList files(bool absolutePath = false) const; /** * \brief Sets the list of file entries. * \param paths The new path list. */ void setFiles(const QStringList& paths); Q_SIGNALS: void pathsChanged(const QStringList&, const QStringList&); private Q_SLOTS: void OnPathsChanged(const QStringList&, const QStringList&); }; #endif diff --git a/Plugins/org.mitk.planarfigure/CMakeLists.txt b/Plugins/org.mitk.planarfigure/CMakeLists.txt deleted file mode 100644 index ca29acd698..0000000000 --- a/Plugins/org.mitk.planarfigure/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -project(org_mitk_planarfigure) - -mitk_create_plugin( - EXPORT_DIRECTIVE ORG_MITK_PLANARFIGURE_EXPORT - EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkPlanarFigure MitkQtWidgets - ) diff --git a/Plugins/org.mitk.planarfigure/documentation/doxygen/modules.dox b/Plugins/org.mitk.planarfigure/documentation/doxygen/modules.dox deleted file mode 100644 index 36087c2397..0000000000 --- a/Plugins/org.mitk.planarfigure/documentation/doxygen/modules.dox +++ /dev/null @@ -1,19 +0,0 @@ -/** - \defgroup org_mitk_planarfigure org.mitk.planarfigure - \ingroup MITKPlugins - - \brief This small plug-in is responsible for initializing the PlanarFigure module. - - It is started with an "eager" Bundle-ActivationPolicy, such that the plug-ins - activator is executed before any other (non-eager) plug-ins. - -*/ - -/** - \defgroup org_mitk_planarfigure_internal Internal - \ingroup org_mitk_planarfigure - - \brief This subcategory includes the internal classes of the org.mitk.planarfigure plugin. Other - plugins must not rely on these classes. They contain implementation details and their interface - may change at any time. We mean it. -*/ diff --git a/Plugins/org.mitk.planarfigure/files.cmake b/Plugins/org.mitk.planarfigure/files.cmake deleted file mode 100644 index 044498f143..0000000000 --- a/Plugins/org.mitk.planarfigure/files.cmake +++ /dev/null @@ -1,22 +0,0 @@ -set(MOC_H_FILES - src/internal/mitkPlanarFigureActivator.h -) - -set(SRC_CPP_FILES - -) - -set(INTERNAL_CPP_FILES - mitkPlanarFigureActivator.cpp -) - -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.planarfigure/manifest_headers.cmake b/Plugins/org.mitk.planarfigure/manifest_headers.cmake deleted file mode 100644 index 1d6c61fc2f..0000000000 --- a/Plugins/org.mitk.planarfigure/manifest_headers.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(Plugin-Name "MITK PlanarFigure") -set(Plugin-Version "0.1") -set(Plugin-Vendor "German Cancer Research Center (DKFZ)") -set(Plugin-ContactAddress "https://www.mitk.org") -set(Require-Plugin org.mitk.gui.common) -set(Plugin-ActivationPolicy eager) diff --git a/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.cpp b/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.cpp deleted file mode 100644 index 904a0e918f..0000000000 --- a/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkPlanarFigureActivator.h" -#include "mitkPlanarFigureObjectFactory.h" - -#include "QmitkNodeDescriptorManager.h" -#include "mitkNodePredicateDataType.h" -#include "mitkNodePredicateProperty.h" -#include "mitkNodePredicateAnd.h" - -void -mitk::PlanarFigureActivator::start(ctkPluginContext* /*context*/) -{ - QmitkNodeDescriptorManager* descriptorManager = QmitkNodeDescriptorManager::GetInstance(); - - // Adding "PlanarLine" - mitk::NodePredicateDataType::Pointer isPlanarLine = mitk::NodePredicateDataType::New("PlanarLine"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarLine"), QString(":/QtWidgetsExt/PlanarLine_48.png"), isPlanarLine, descriptorManager)); - - // Adding "PlanarCircle" - mitk::NodePredicateDataType::Pointer isPlanarCircle = mitk::NodePredicateDataType::New("PlanarCircle"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarCircle"), QString(":/QtWidgetsExt/PlanarCircle_48.png"), isPlanarCircle, descriptorManager)); - - // Adding "PlanarEllipse" - mitk::NodePredicateDataType::Pointer isPlanarEllipse = mitk::NodePredicateDataType::New("PlanarEllipse"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarEllipse"), QString(":/QtWidgetsExt/PlanarEllipse_48.png"), isPlanarEllipse, descriptorManager)); - - // Adding "PlanarAngle" - mitk::NodePredicateDataType::Pointer isPlanarAngle = mitk::NodePredicateDataType::New("PlanarAngle"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarAngle"), QString(":/QtWidgetsExt/PlanarAngle_48.png"), isPlanarAngle, descriptorManager)); - - // Adding "PlanarFourPointAngle" - mitk::NodePredicateDataType::Pointer isPlanarFourPointAngle = mitk::NodePredicateDataType::New("PlanarFourPointAngle"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarFourPointAngle"), QString(":/QtWidgetsExt/PlanarFourPointAngle_48.png"), isPlanarFourPointAngle, descriptorManager)); - - // Adding "PlanarRectangle" - mitk::NodePredicateDataType::Pointer isPlanarRectangle = mitk::NodePredicateDataType::New("PlanarRectangle"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarRectangle"), QString(":/QtWidgetsExt/PlanarRectangle_48.png"), isPlanarRectangle, descriptorManager)); - - // Adding "PlanarPolygon" - mitk::NodePredicateDataType::Pointer isPlanarPolygon = mitk::NodePredicateDataType::New("PlanarPolygon"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarPolygon"), QString(":/QtWidgetsExt/PlanarPolygon_48.png"), isPlanarPolygon, descriptorManager)); - - // Adding "PlanarPath" - mitk::NodePredicateProperty::Pointer isNotClosedPolygon = mitk::NodePredicateProperty::New("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); - mitk::NodePredicateAnd::Pointer isPlanarPath = mitk::NodePredicateAnd::New(isNotClosedPolygon, isPlanarPolygon); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor(QObject::tr("PlanarPath"), QString(":/QtWidgetsExt/PlanarPath_48.png"), isPlanarPath, descriptorManager)); - - // Adding "PlanarDoubleEllipse" - mitk::NodePredicateDataType::Pointer isPlanarDoubleEllipse = mitk::NodePredicateDataType::New("PlanarDoubleEllipse"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor("PlanarDoubleEllipse", ":/QtWidgetsExt/PlanarDoubleEllipse_48.png", isPlanarDoubleEllipse, descriptorManager)); - - // Adding "PlanarBezierCurve" - mitk::NodePredicateDataType::Pointer isPlanarBezierCurve = mitk::NodePredicateDataType::New("PlanarBezierCurve"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor("PlanarBezierCurve", ":/QtWidgetsExt/PlanarBezierCurve_48.png", isPlanarBezierCurve, descriptorManager)); - - // Adding "PlanarSubdivisionPolygon" - mitk::NodePredicateDataType::Pointer isPlanarSubdivisionPolygon = mitk::NodePredicateDataType::New("PlanarSubdivisionPolygon"); - descriptorManager->AddDescriptor(new QmitkNodeDescriptor("PlanarSubdivisionPolygon", ":/QtWidgetsExt/PlanarSubdivisionPolygon_48.png", isPlanarSubdivisionPolygon, descriptorManager)); -} - -void -mitk::PlanarFigureActivator::stop(ctkPluginContext* /*context*/) -{ -} diff --git a/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.h b/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.h deleted file mode 100644 index d2c6c05575..0000000000 --- a/Plugins/org.mitk.planarfigure/src/internal/mitkPlanarFigureActivator.h +++ /dev/null @@ -1,49 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef mitkPlanarFigureActivator_h -#define mitkPlanarFigureActivator_h - -#include - - -namespace mitk -{ - -/** - * \ingroup org_mitk_planarfigure_internal - * - * \brief The plug-in activator for the planar figure module - * - * When the plug-in is started by the framework, it initializes planar figure specific things. - */ -class PlanarFigureActivator : - public QObject, public ctkPluginActivator -{ - - Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_planarfigure") - Q_INTERFACES(ctkPluginActivator) - -public: - - /** - * Registers sandbox core object factories. - */ - void start(ctkPluginContext* context) override; - void stop(ctkPluginContext* context) override; - -}; - -} -#endif