diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox index c8fbcc7f30..dc44f28335 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/BuildInstructions.dox @@ -1,236 +1,236 @@ /** \page BuildInstructionsPage Build Instructions \tableofcontents \section BuildInstructions_Introduction Introduction The CMake-based build system of MITK supports a "superbuild" process, meaning that it will download, configure, and build all required third-party libraries (except Qt) automatically. These instructions will show you how to use the MITK superbuild. \note This page explains explicitly how to build MITK itself. If you want to create your own project based on MITK, the process described below is completely automated. Please see \ref HowToNewProject. For more advanced users, the last sections explains how to inject custom build libraries into the superbuild process. \section BuildInstructions_Prerequisites Prerequisites You need: -# Git (there are also numerous third-party graphical clients available). We recommend using Git, but see below for a way how to get the current source code without using it. -# CMake (version \minimumCMakeVersion or higher) -# Qt \minimumQt5Version if you plan to develop Qt-based applications -# If you are using macOS you need an XCode installation and the Command Line Tools as it provides the neccessary compilers and SDKs To build MITK on Linux, install the following packages, e. g. with APT: \code{.unparsed} sudo apt install build-essential doxygen git graphviz libfreetype6-dev libglu1-mesa-dev libssl-dev libtiff5-dev libwrap0-dev libxcomposite1 libxcursor1 libxdamage-dev libxi-dev libxkbcommon-x11-0 libxt-dev mesa-common-dev \endcode For the optional and experimental (!) Python integration, install NumPy and SimpleITK v1.x, e. g.: \code{.unparsed} sudo apt install python3-numpy python3-pip -pip3 install SimpleITK==1.* +pip3 install SimpleITK \endcode \section BuildInstructions_Qt A note about Qt As we do not provide Qt in the MITK superbuild you need to install Qt manually. The Qt Company provides online installers for all supported platforms. We highly recommend to install Qt to the default location of the installer as it will allow MITK to automatically find Qt without any further action needed. Make sure to also select the following required components: - QtWebEngine - QtScript On Windows, the Qt installer offers a welcome and straight forward way to install OpenSSL. You find it under the Tools node. \section BuildInstructions_Get_Source Get a source tree Since MITK is under active development we recommend to use Git to check out the latest stable release from the homepage. If you decide to use the most current nightly release, make sure to get a stable tree: Check the MITK dashboard before checking out. If the build tree is not clean, you can specify an older revision for the checkout or get a stable tar ball from www.mitk.org. To clone MITK's current Git repository do: \code git clone https://phabricator.mitk.org/source/mitk.git MITK \endcode \section BuildInstructions_Build_With_CMake Build MITK with CMake Create a new directory for the superbuild binary tree, change to it and call CMake: In the shell (assuming your current directory is the same as the one where you issued the git clone command): \code mkdir MITK-superbuild cd MITK-superbuild ccmake ../MITK \endcode If you use Windows or prefer to use the CMake GUI, start the CMake GUI and enter the location of the source tree and binary tree, choose a suitable generator and configure the project. CMake will present you a couple of options, these are the most important ones: - CMAKE_PREFIX_PATH The path to your Qt installation, e.g., C:/Qt/5.12.9/msvc2017_64 or /home/user/Qt/5.12.9/gcc_64 - MITK_USE_BLUEBERRY Build the BlueBerry application framework - MITK_USE_Boost_LIBRARIES If you need binary Boost libraries, specify them here. - MITK_USE_OpenCV Build MITK code which depends on OpenCV (this will download and build OpenCV 2.4) - MITK_USE_Python3 Enables Python wrapping in MITK. This will also configure ITK, VTK, and OpenCV (if enabled) to build Python wrappers. - MITK_USE_Qt5 Build MITK code which depends on Qt 5 If you are satisfied with the configuration of your MITK superbuild, generate the project files with CMake by pressing "Generate". Linux and macOS users usually just enter "make" (optionally supplying the number threads to be used for a parallel build): \code make -j6 \endcode Windows users using Visual Studio can open the generated MITK-superbuild.sln solution file in the MITK-superbuild directory and start the build by building the BUILD_ALL project. \section BuildInstructions_Customize Customize your MITK superbuild The MITK superbuild configures MITK as well as all external libraries. The build directories of these libraries, and of MITK itself are located inside the MITK-superbuild directory. For example, the directory layout may look like: \code MITK-superbuild |- ep "external projects" |-bin |-lib |-include |-src |- MITK-build \endcode To change the configuration of the MITK build itself, choose the MITK-build directory as the binary directory in the CMake GUI (not the MITK-superbuild directory). After generating the project files, build the MITK project by either issuing "make" in the MITK-build directory (Linux, macOS), or by opening MITK-build/MITK.sln (Windows). You may also change the configuration of any project configured via the superbuild process. Make sure to also build the changed project and also the projects which depend on it. \section BuildInstructions_Running Running Applications On Linux, just execute the application you want to run. MITK executables are located in MITK-superbuild/MITK-build/bin On Windows, the PATH environment variable must contain the directories containing the third-party libraries. This is automatically done from Visual Studio. For running the applications directly use the generated batch files in the MITK-superbuild/MITK-build/bin. \section BuildInstructions_Documentation Documentation If you have the Doxygen documentation tool installed, you get a new project (Visual Studio) or "make" target named "doc". You can build this to generate the HTML documentation of MITK in the Documentation/Doxygen directory of your MITK-build binary tree or in the MITK_DOXYGEN_OUTPUT_DIR CMake variable (if specified). \section BuildInstructions_Extending Extend MITK on your own (using the application framework BlueBerry) Please see \ref NewPluginPage \section BuildInstructions_As_Toolkit Use MITK in your own project (as a toolkit) To use MITK in your external project, add the CMake command find_package(MITK REQUIRED) to your CMakeLists.txt and make use of the CMake macros mitk_create_module() and mitk_create_executable() provided by MITK. Here is a very basic example CMakeLists.txt including MITK as a project: \code cmake_minimum_required(VERSION 3.10 FATAL_ERROR) project(MyProject) find_package(MITK 2021.10 REQUIRED) add_executable(MyApp main.cpp) target_link_libraries(MyApp MitkCore) \endcode with the main.ccp being \code #include #include int main() { MITK_INFO << "Hello world!"; return 0; } \endcode \section BuildInstructions_Advanced_Customization Superbuild customization You can inject pre-build third-party libraries into the MITK superbuild by setting certain CMake variables before the first configure step. MITK will then use these third-party libraries instead of downloading and building them by itself. Note that you must take care of configuring those libraries with all options MITK requires. The variables listed below are provided for injecting third-party libraries. Their occurrence in the CMake GUI or in ccmake may depend on specific MITK_USE_* options set to ON. You may also use the variable names below without the EXTERNAL_ prefix, for example when providing their values on a command line call to CMake. - EXTERNAL_BOOST_ROOT Set this variable to your custom Boost installation - EXTERNAL_CTK_DIR Set this variable to your CTK binary tree (the directory containing the CTKConfig.cmake file) - EXTERNAL_CableSwig_DIR Set this variable to your CableSwig binary tree for Python wrapping (the directory containing the CableSwigConfig.cmake file) - EXTERNAL_DCMTK_DIR Set this variable to your DCMTK binary tree (the directory containing the DCMTKConfig.cmake file) - EXTERNAL_GDCM_DIR Set this variable to your GDCM binary tree (the directory containing the GDCMConfig.cmake file) - EXTERNAL_ITK_DIR Set this variable to your ITK binary tree (the directory containing the ITKConfig.cmake file) - EXTERNAL_OpenCV_DIR Set this variable to your OpenCV binary tree (the directory containing the OpenCVConfig.cmake file) - EXTERNAL_VTK_DIR Set this variable to your VTK binary tree (the directory containing the VTKConfig.cmake file) To set CMake options before the first configure step is invoked, supply them on the command line, i.e. \code ccmake -DITK_DIR:PATH=/opt/ITK-release ../MITK \endcode */ diff --git a/Modules/AppUtil/CMakeLists.txt b/Modules/AppUtil/CMakeLists.txt index 4685d36486..e306674c8d 100644 --- a/Modules/AppUtil/CMakeLists.txt +++ b/Modules/AppUtil/CMakeLists.txt @@ -1,14 +1,14 @@ -set(qt5_depends Qt5|Widgets+WebEngine) +set(qt5_depends Qt5|Widgets+WebEngineCore) if(UNIX AND NOT APPLE) set(qt5_depends "${qt5_depends}+X11Extras") endif() mitk_create_module( PACKAGE_DEPENDS PUBLIC CTK|CTKPluginFramework ${qt5_depends} Poco|Util PRIVATE VTK|GUISupportQt DEPENDS PUBLIC qtsingleapplication PRIVATE MitkCore ) diff --git a/Modules/CEST/files.cmake b/Modules/CEST/files.cmake index 8d29b23be8..0bf817db33 100644 --- a/Modules/CEST/files.cmake +++ b/Modules/CEST/files.cmake @@ -1,15 +1,23 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCESTImageNormalizationFilter.cpp mitkCustomTagParser.cpp mitkCESTImageDetectionHelper.cpp mitkExtractCESTOffset.cpp mitkCESTPropertyHelper.cpp ) set(RESOURCE_FILES 1416.json 1485.json 1494.json + 1500.json + 1503.json + 1504.json + 1519.json + 1520.json + 1521.json + 1522.json + 1583.json ) diff --git a/Modules/CEST/resource/1500.json b/Modules/CEST/resource/1500.json new file mode 100644 index 0000000000..bfd1c3d0a9 --- /dev/null +++ b/Modules/CEST/resource/1500.json @@ -0,0 +1,28 @@ +{ + "1500" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreSatMode", + "sWiPMemBlock.alFree[6]" : "PreparationType", + "sWiPMemBlock.alFree[7]" : "PulseType", + "sWiPMemBlock.alFree[8]" : "SamplingType", + "sWiPMemBlock.alFree[9]" : "SpoilingType", + "sWiPMemBlock.alFree[10]" : "measurements", + "sWiPMemBlock.alFree[11]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[12]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[13]" : "PulseDuration", + "sWiPMemBlock.alFree[14]" : "DutyCycle", + "sWiPMemBlock.alFree[15]" : "RecoveryTime", + "sWiPMemBlock.alFree[16]" : "RecoveryTimeM0", + "sWiPMemBlock.alFree[17]" : "ReadoutDelay", + "sWiPMemBlock.alFree[18]" : "NumberPreSatPulses", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp", + "sWiPMemBlock.adFree[7]" : "OffsetPreSatPulse" +} diff --git a/Modules/CEST/resource/1503.json b/Modules/CEST/resource/1503.json new file mode 100644 index 0000000000..4f5a1904ae --- /dev/null +++ b/Modules/CEST/resource/1503.json @@ -0,0 +1,24 @@ +{ + "1503" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1504.json b/Modules/CEST/resource/1504.json new file mode 100644 index 0000000000..3150f575fd --- /dev/null +++ b/Modules/CEST/resource/1504.json @@ -0,0 +1,24 @@ +{ + "1504" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1519.json b/Modules/CEST/resource/1519.json new file mode 100644 index 0000000000..5f34cbd684 --- /dev/null +++ b/Modules/CEST/resource/1519.json @@ -0,0 +1,24 @@ +{ + "1519" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1520.json b/Modules/CEST/resource/1520.json new file mode 100644 index 0000000000..20d5894d8b --- /dev/null +++ b/Modules/CEST/resource/1520.json @@ -0,0 +1,24 @@ +{ + "1520" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1521.json b/Modules/CEST/resource/1521.json new file mode 100644 index 0000000000..dd44bdeac9 --- /dev/null +++ b/Modules/CEST/resource/1521.json @@ -0,0 +1,24 @@ +{ + "1521" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1522.json b/Modules/CEST/resource/1522.json new file mode 100644 index 0000000000..1f298205c4 --- /dev/null +++ b/Modules/CEST/resource/1522.json @@ -0,0 +1,24 @@ +{ + "1522" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/CEST/resource/1583.json b/Modules/CEST/resource/1583.json new file mode 100644 index 0000000000..c6ac35a621 --- /dev/null +++ b/Modules/CEST/resource/1583.json @@ -0,0 +1,24 @@ +{ + "1583" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "MtMode", + "sWiPMemBlock.alFree[5]" : "PreparationType", + "sWiPMemBlock.alFree[6]" : "PulseType", + "sWiPMemBlock.alFree[7]" : "SamplingType", + "sWiPMemBlock.alFree[8]" : "SpoilingType", + "sWiPMemBlock.alFree[9]" : "measurements", + "sWiPMemBlock.alFree[10]" : "NumberRFBlocks", + "sWiPMemBlock.alFree[11]" : "PulsesPerRFBlock", + "sWiPMemBlock.alFree[12]" : "PulseDuration", + "sWiPMemBlock.alFree[13]" : "DutyCycle", + "sWiPMemBlock.alFree[14]" : "RecoveryTime", + "sWiPMemBlock.alFree[15]" : "RecoveryTimeM0", + "sWiPMemBlock.adFree[1]" : "Offset", + "sWiPMemBlock.adFree[2]" : "B1Amplitude", + "sWiPMemBlock.adFree[3]" : "AdiabaticPulseMu", + "sWiPMemBlock.adFree[4]" : "AdiabaticPulseBW", + "sWiPMemBlock.adFree[5]" : "AdiabaticPulseLength", + "sWiPMemBlock.adFree[6]" : "AdiabaticPulseAmp" +} diff --git a/Modules/Core/src/Controllers/mitkRenderingManager.cpp b/Modules/Core/src/Controllers/mitkRenderingManager.cpp index 5971ec93e7..1fe5bec198 100644 --- a/Modules/Core/src/Controllers/mitkRenderingManager.cpp +++ b/Modules/Core/src/Controllers/mitkRenderingManager.cpp @@ -1,778 +1,777 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkRenderingManager.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateProperty.h" #include "mitkProportionalTimeGeometry.h" #include "mitkRenderingManagerFactory.h" #include #include #include #include #include "mitkNumericTypes.h" #include #include #include #include namespace mitk { itkEventMacroDefinition(RenderingManagerEvent, itk::AnyEvent); itkEventMacroDefinition(RenderingManagerViewsInitializedEvent, RenderingManagerEvent); itkEventMacroDefinition(FocusChangedEvent, itk::AnyEvent); RenderingManager::Pointer RenderingManager::s_Instance = nullptr; RenderingManagerFactory *RenderingManager::s_RenderingManagerFactory = nullptr; RenderingManager::RenderingManager() : m_UpdatePending(false), m_MaxLOD(1), m_LODIncreaseBlocked(false), m_LODAbortMechanismEnabled(false), m_ClippingPlaneEnabled(false), m_TimeNavigationController(SliceNavigationController::New()), m_DataStorage(nullptr), m_ConstrainedPanningZooming(true), m_FocusedRenderWindow(nullptr), m_AntiAliasing(AntiAliasing::FastApproximate) { m_ShadingEnabled.assign(3, false); m_ShadingValues.assign(4, 0.0); InitializePropertyList(); } RenderingManager::~RenderingManager() { // Decrease reference counts of all registered vtkRenderWindows for // proper destruction RenderWindowVector::iterator it; for (it = m_AllRenderWindows.begin(); it != m_AllRenderWindows.end(); ++it) { (*it)->UnRegister(nullptr); auto callbacks_it = this->m_RenderWindowCallbacksList.find(*it); if (callbacks_it != this->m_RenderWindowCallbacksList.end()) { (*it)->RemoveObserver(callbacks_it->second.commands[0u]); (*it)->RemoveObserver(callbacks_it->second.commands[1u]); (*it)->RemoveObserver(callbacks_it->second.commands[2u]); } } } void RenderingManager::SetFactory(RenderingManagerFactory *factory) { s_RenderingManagerFactory = factory; } const RenderingManagerFactory *RenderingManager::GetFactory() { return s_RenderingManagerFactory; } bool RenderingManager::HasFactory() { if (RenderingManager::s_RenderingManagerFactory) { return true; } else { return false; } } RenderingManager::Pointer RenderingManager::New() { const RenderingManagerFactory *factory = GetFactory(); if (factory == nullptr) return nullptr; return factory->CreateRenderingManager(); } RenderingManager *RenderingManager::GetInstance() { if (!RenderingManager::s_Instance) { if (s_RenderingManagerFactory) { s_Instance = s_RenderingManagerFactory->CreateRenderingManager(); } } return s_Instance; } bool RenderingManager::IsInstantiated() { if (RenderingManager::s_Instance) return true; else return false; } void RenderingManager::AddRenderWindow(vtkRenderWindow *renderWindow) { if (renderWindow && (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.end())) { m_RenderWindowList[renderWindow] = RENDERING_INACTIVE; m_AllRenderWindows.push_back(renderWindow); if (m_DataStorage.IsNotNull()) mitk::BaseRenderer::GetInstance(renderWindow)->SetDataStorage(m_DataStorage.GetPointer()); // Register vtkRenderWindow instance renderWindow->Register(nullptr); // Add callbacks for rendering abort mechanism // BaseRenderer *renderer = BaseRenderer::GetInstance( renderWindow ); vtkCallbackCommand *startCallbackCommand = vtkCallbackCommand::New(); startCallbackCommand->SetCallback(RenderingManager::RenderingStartCallback); renderWindow->AddObserver(vtkCommand::StartEvent, startCallbackCommand); vtkCallbackCommand *progressCallbackCommand = vtkCallbackCommand::New(); progressCallbackCommand->SetCallback(RenderingManager::RenderingProgressCallback); renderWindow->AddObserver(vtkCommand::AbortCheckEvent, progressCallbackCommand); vtkCallbackCommand *endCallbackCommand = vtkCallbackCommand::New(); endCallbackCommand->SetCallback(RenderingManager::RenderingEndCallback); renderWindow->AddObserver(vtkCommand::EndEvent, endCallbackCommand); RenderWindowCallbacks callbacks; callbacks.commands[0u] = startCallbackCommand; callbacks.commands[1u] = progressCallbackCommand; callbacks.commands[2u] = endCallbackCommand; this->m_RenderWindowCallbacksList[renderWindow] = callbacks; // Delete vtk variables correctly startCallbackCommand->Delete(); progressCallbackCommand->Delete(); endCallbackCommand->Delete(); } } void RenderingManager::RemoveRenderWindow(vtkRenderWindow *renderWindow) { if (m_RenderWindowList.erase(renderWindow)) { auto callbacks_it = this->m_RenderWindowCallbacksList.find(renderWindow); if (callbacks_it != this->m_RenderWindowCallbacksList.end()) { renderWindow->RemoveObserver(callbacks_it->second.commands[0u]); renderWindow->RemoveObserver(callbacks_it->second.commands[1u]); renderWindow->RemoveObserver(callbacks_it->second.commands[2u]); this->m_RenderWindowCallbacksList.erase(callbacks_it); } auto rw_it = std::find(m_AllRenderWindows.begin(), m_AllRenderWindows.end(), renderWindow); if (rw_it != m_AllRenderWindows.cend()) { // Decrease reference count for proper destruction (*rw_it)->UnRegister(nullptr); m_AllRenderWindows.erase(rw_it); } } } const RenderingManager::RenderWindowVector &RenderingManager::GetAllRegisteredRenderWindows() { return m_AllRenderWindows; } void RenderingManager::RequestUpdate(vtkRenderWindow *renderWindow) { // If the renderWindow is not valid, we do not want to inadvertantly create // an entry in the m_RenderWindowList map. It is possible if the user is // regularly calling AddRenderer and RemoveRenderer for a rendering update // to come into this method with a renderWindow pointer that is valid in the // sense that the window does exist within the application, but that // renderWindow has been temporarily removed from this RenderingManager for // performance reasons. if (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.cend()) { return; } m_RenderWindowList[renderWindow] = RENDERING_REQUESTED; if (!m_UpdatePending) { m_UpdatePending = true; this->GenerateRenderingRequestEvent(); } } void RenderingManager::ForceImmediateUpdate(vtkRenderWindow *renderWindow) { // If the renderWindow is not valid, we do not want to inadvertantly create // an entry in the m_RenderWindowList map. It is possible if the user is // regularly calling AddRenderer and RemoveRenderer for a rendering update // to come into this method with a renderWindow pointer that is valid in the // sense that the window does exist within the application, but that // renderWindow has been temporarily removed from this RenderingManager for // performance reasons. if (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.cend()) { return; } - mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); - baseRenderer->SetConstrainZoomingAndPanning(m_ConstrainedPanningZooming); - // Erase potentially pending requests for this window m_RenderWindowList[renderWindow] = RENDERING_INACTIVE; m_UpdatePending = false; // Immediately repaint this window (implementation platform specific) // If the size is 0 it crashes int *size = renderWindow->GetSize(); if (0 != size[0] && 0 != size[1]) { // prepare the camera etc. before rendering // Note: this is a very important step which should be called before the VTK render! // If you modify the camera anywhere else or after the render call, the scene cannot be seen. - auto *vPR = dynamic_cast(baseRenderer); + auto *vPR = dynamic_cast(mitk::BaseRenderer::GetInstance(renderWindow)); if (vPR) vPR->PrepareRender(); // Execute rendering renderWindow->Render(); } } void RenderingManager::RequestUpdateAll(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { int id = BaseRenderer::GetInstance(it->first)->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { this->RequestUpdate(it->first); } } } void RenderingManager::ForceImmediateUpdateAll(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { int id = BaseRenderer::GetInstance(it->first)->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { // Immediately repaint this window (implementation platform specific) // If the size is 0, it crashes this->ForceImmediateUpdate(it->first); } } } void RenderingManager::InitializeViewsByBoundingObjects(const DataStorage *ds) { if (!ds) return; // get all nodes that have not set "includeInBoundingBox" to false mitk::NodePredicateNot::Pointer pred = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false))); mitk::DataStorage::SetOfObjects::ConstPointer rs = ds->GetSubset(pred); // calculate bounding geometry of these nodes auto bounds = ds->ComputeBoundingGeometry3D(rs, "visible"); // initialize the views to the bounding geometry this->InitializeViews(bounds); } // TODO_GOETZ // Remove old function, so only this one is working. bool RenderingManager::InitializeViews(const BaseGeometry *dataGeometry, RequestType type, bool resetCamera) { ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(dynamic_cast(dataGeometry->Clone().GetPointer()), 1); return InitializeViews(propTimeGeometry, type, resetCamera); } bool RenderingManager::InitializeViews(const TimeGeometry *dataGeometry, RequestType type, bool resetCamera) { MITK_DEBUG << "initializing views"; bool boundingBoxInitialized = false; TimeGeometry::ConstPointer timeGeometry = dataGeometry; TimeGeometry::Pointer modifiedGeometry = nullptr; if (dataGeometry != nullptr) { modifiedGeometry = dataGeometry->Clone(); } int warningLevel = vtkObject::GetGlobalWarningDisplay(); vtkObject::GlobalWarningDisplayOff(); if ((timeGeometry.IsNotNull()) && (timeGeometry->GetBoundingBoxInWorld()->GetDiagonalLength2() > mitk::eps)) { boundingBoxInitialized = true; } if (timeGeometry.IsNotNull()) { // make sure bounding box has an extent bigger than zero in any direction // clone the input geometry // Old Geometry3D::Pointer modifiedGeometry = dynamic_cast( dataGeometry->Clone().GetPointer() ); assert(modifiedGeometry.IsNotNull()); for (TimeStepType step = 0; step < modifiedGeometry->CountTimeSteps(); ++step) { BaseGeometry::BoundsArrayType newBounds = modifiedGeometry->GetGeometryForTimeStep(step)->GetBounds(); for (unsigned int dimension = 0; (2 * dimension) < newBounds.Size(); dimension++) { // check for equality but for an epsilon if (Equal(newBounds[2 * dimension], newBounds[2 * dimension + 1])) { newBounds[2 * dimension + 1] += 1; if (Equal( newBounds[2 * dimension], newBounds[2 * dimension + 1])) // newBounds will still be equal if values are beyond double precision { mitkThrow() << "One dimension of object data has zero length, please make sure you're not using numbers " "beyond double precision as coordinates."; } } } modifiedGeometry->GetGeometryForTimeStep(step)->SetBounds(newBounds); } } timeGeometry = modifiedGeometry; RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(it->first); + baseRenderer->SetConstrainZoomingAndPanning(m_ConstrainedPanningZooming); + int id = baseRenderer->GetMapperID(); if (((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2)))) { this->InternalViewInitialization(baseRenderer, timeGeometry, boundingBoxInitialized, id, resetCamera); } } if (boundingBoxInitialized) { m_TimeNavigationController->SetInputWorldTimeGeometry(timeGeometry); } m_TimeNavigationController->Update(); this->RequestUpdateAll(type); vtkObject::SetGlobalWarningDisplay(warningLevel); // Inform listeners that views have been initialized this->InvokeEvent(mitk::RenderingManagerViewsInitializedEvent()); return boundingBoxInitialized; } bool RenderingManager::InitializeViews(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(it->first); int id = baseRenderer->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); // Update the SNC nc->Update(); } } this->RequestUpdateAll(type); return true; } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow, const BaseGeometry *geometry, bool initializeGlobalTimeSNC, bool resetCamera) { ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(dynamic_cast(geometry->Clone().GetPointer()), 1); return InitializeView(renderWindow, propTimeGeometry, initializeGlobalTimeSNC, resetCamera); } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow, const TimeGeometry *geometry, bool initializeGlobalTimeSNC, bool resetCamera) { bool boundingBoxInitialized = false; int warningLevel = vtkObject::GetGlobalWarningDisplay(); vtkObject::GlobalWarningDisplayOff(); if ((geometry != nullptr) && (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() > mitk::eps)) { boundingBoxInitialized = true; } mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); int id = baseRenderer->GetMapperID(); this->InternalViewInitialization(baseRenderer, geometry, boundingBoxInitialized, id, resetCamera); if (boundingBoxInitialized && initializeGlobalTimeSNC) { m_TimeNavigationController->SetInputWorldTimeGeometry(geometry); } m_TimeNavigationController->Update(); this->RequestUpdate(renderWindow); vtkObject::SetGlobalWarningDisplay(warningLevel); return boundingBoxInitialized; } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); // Update the SNC nc->Update(); this->RequestUpdate(renderWindow); return true; } void RenderingManager::InternalViewInitialization(mitk::BaseRenderer *baseRenderer, const mitk::TimeGeometry *geometry, bool boundingBoxInitialized, int mapperID, bool resetCamera) { mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); if (boundingBoxInitialized) { // Set geometry for NC nc->SetInputWorldTimeGeometry(geometry); nc->Update(); if (resetCamera) { if (mapperID == BaseRenderer::Standard2D) { // For 2D SNCs, steppers are set so that the cross is centered in the image nc->GetSlice()->SetPos(nc->GetSlice()->GetSteps() / 2); baseRenderer->GetCameraController()->Fit(); } else if (mapperID == BaseRenderer::Standard3D) { baseRenderer->GetCameraController()->SetViewToAnterior(); } } } else { nc->Update(); } } const SliceNavigationController *RenderingManager::GetTimeNavigationController() const { return m_TimeNavigationController.GetPointer(); } SliceNavigationController *RenderingManager::GetTimeNavigationController() { return m_TimeNavigationController.GetPointer(); } void RenderingManager::ExecutePendingRequests() { m_UpdatePending = false; // Satisfy all pending update requests RenderWindowList::const_iterator it; int i = 0; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it, ++i) { if (it->second == RENDERING_REQUESTED) { this->ForceImmediateUpdate(it->first); } } } void RenderingManager::RenderingStartCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderingManager = RenderingManager::GetInstance(); auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) renderingManager->m_RenderWindowList[renderWindow] = RENDERING_INPROGRESS; renderingManager->m_UpdatePending = false; } void RenderingManager::RenderingProgressCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderingManager = RenderingManager::GetInstance(); if (renderingManager->m_LODAbortMechanismEnabled) { auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) { auto renderer = BaseRenderer::GetInstance(renderWindow); if (nullptr != renderer && 0 < renderer->GetNumberOfVisibleLODEnabledMappers()) renderingManager->DoMonitorRendering(); } } } void RenderingManager::RenderingEndCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) { auto renderer = BaseRenderer::GetInstance(renderWindow); if (nullptr != renderer) { auto renderingManager = RenderingManager::GetInstance(); renderingManager->m_RenderWindowList[renderer->GetRenderWindow()] = RENDERING_INACTIVE; if (0 < renderer->GetNumberOfVisibleLODEnabledMappers()) { if (0 == renderingManager->m_NextLODMap[renderer]) { renderingManager->StartOrResetTimer(); } else { renderingManager->m_NextLODMap[renderer] = 0; } } } } } bool RenderingManager::IsRendering() const { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { if (it->second == RENDERING_INPROGRESS) { return true; } } return false; } void RenderingManager::AbortRendering() { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { if (it->second == RENDERING_INPROGRESS) { it->first->SetAbortRender(true); m_RenderingAbortedMap[BaseRenderer::GetInstance(it->first)] = true; } } } int RenderingManager::GetNextLOD(BaseRenderer *renderer) { if (renderer != nullptr) { return m_NextLODMap[renderer]; } else { return 0; } } void RenderingManager::ExecutePendingHighResRenderingRequest() { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { BaseRenderer *renderer = BaseRenderer::GetInstance(it->first); if (renderer->GetNumberOfVisibleLODEnabledMappers() > 0) { if (m_NextLODMap[renderer] == 0) { m_NextLODMap[renderer] = 1; RequestUpdate(it->first); } } } } void RenderingManager::SetMaximumLOD(unsigned int max) { m_MaxLOD = max; } // enable/disable shading void RenderingManager::SetShading(bool state, unsigned int lod) { if (lod > m_MaxLOD) { itkWarningMacro(<< "LOD out of range requested: " << lod << " maxLOD: " << m_MaxLOD); return; } m_ShadingEnabled[lod] = state; } bool RenderingManager::GetShading(unsigned int lod) { if (lod > m_MaxLOD) { itkWarningMacro(<< "LOD out of range requested: " << lod << " maxLOD: " << m_MaxLOD); return false; } return m_ShadingEnabled[lod]; } // enable/disable the clipping plane void RenderingManager::SetClippingPlaneStatus(bool status) { m_ClippingPlaneEnabled = status; } bool RenderingManager::GetClippingPlaneStatus() { return m_ClippingPlaneEnabled; } void RenderingManager::SetShadingValues(float ambient, float diffuse, float specular, float specpower) { m_ShadingValues[0] = ambient; m_ShadingValues[1] = diffuse; m_ShadingValues[2] = specular; m_ShadingValues[3] = specpower; } RenderingManager::FloatVector &RenderingManager::GetShadingValues() { return m_ShadingValues; } void RenderingManager::InitializePropertyList() { if (m_PropertyList.IsNull()) { m_PropertyList = PropertyList::New(); } this->SetProperty("coupled-zoom", BoolProperty::New(false)); this->SetProperty("coupled-plane-rotation", BoolProperty::New(false)); this->SetProperty("MIP-slice-rendering", BoolProperty::New(false)); } PropertyList::Pointer RenderingManager::GetPropertyList() const { return m_PropertyList; } BaseProperty *RenderingManager::GetProperty(const char *propertyKey) const { return m_PropertyList->GetProperty(propertyKey); } void RenderingManager::SetProperty(const char *propertyKey, BaseProperty *propertyValue) { m_PropertyList->SetProperty(propertyKey, propertyValue); } void RenderingManager::SetDataStorage(DataStorage *storage) { if (storage != nullptr) { m_DataStorage = storage; RenderingManager::RenderWindowVector::const_iterator iter; for (iter = m_AllRenderWindows.cbegin(); iter < m_AllRenderWindows.cend(); ++iter) { mitk::BaseRenderer::GetInstance((*iter))->SetDataStorage(m_DataStorage.GetPointer()); } } } mitk::DataStorage *RenderingManager::GetDataStorage() { return m_DataStorage; } void RenderingManager::SetRenderWindowFocus(vtkRenderWindow *focusWindow) { if (focusWindow != m_FocusedRenderWindow) { if (!focusWindow || (m_RenderWindowList.find(focusWindow) != m_RenderWindowList.cend())) { m_FocusedRenderWindow = focusWindow; this->InvokeEvent(FocusChangedEvent()); return; } MITK_ERROR << "Tried to set a RenderWindow that does not exist."; } } void RenderingManager::SetAntiAliasing(AntiAliasing antiAliasing) { if (m_AntiAliasing != antiAliasing) { auto renderingManager = mitk::RenderingManager::GetInstance(); auto renderWindows = renderingManager->GetAllRegisteredRenderWindows(); for (auto renderWindow : renderWindows) { auto renderers = renderWindow->GetRenderers(); if (nullptr != renderers) { renderers->InitTraversal(); auto renderer = renderers->GetNextItem(); while (nullptr != renderer) { renderer->SetUseFXAA(AntiAliasing::FastApproximate == antiAliasing); renderer = renderers->GetNextItem(); } renderingManager->RequestUpdate(renderWindow); } } m_AntiAliasing = antiAliasing; } } // Create and register generic RenderingManagerFactory. TestingRenderingManagerFactory renderingManagerFactory; } // namespace diff --git a/Modules/CoreCmdApps/FileConverter.cpp b/Modules/CoreCmdApps/FileConverter.cpp index 009c67c1e1..197fa9fa67 100644 --- a/Modules/CoreCmdApps/FileConverter.cpp +++ b/Modules/CoreCmdApps/FileConverter.cpp @@ -1,112 +1,116 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkProperties.h" #include "mitkCommandLineParser.h" #include "mitkIOUtil.h" #include #include "mitkPreferenceListReaderOptionsFunctor.h" int main(int argc, char* argv[]) { mitkCommandLineParser parser; parser.setTitle("File Converter"); parser.setCategory("Basic Image Processing"); parser.setDescription(""); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--","-"); // Add command line argument names parser.addArgument("help", "h",mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file:", "Input File",us::Any(),false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file:", "Output file", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.addArgument("reader", "r", mitkCommandLineParser::String, "Reader Name", "Reader Name", us::Any()); parser.addArgument("list-readers", "lr", mitkCommandLineParser::Bool, "Reader Name", "Reader Name", us::Any()); std::map parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.size()==0) return EXIT_FAILURE; // Show a help message if ( parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } std::string inputFilename = us::any_cast(parsedArgs["input"]); std::string outputFilename = us::any_cast(parsedArgs["output"]); mitk::PreferenceListReaderOptionsFunctor::ListType preference = {}; if (parsedArgs.count("reader")) { preference.push_back(us::any_cast(parsedArgs["reader"])); } if (parsedArgs.count("list-readers")) { mitk::IOUtil::LoadInfo loadInfo(inputFilename); auto readers = loadInfo.m_ReaderSelector.Get(); std::string errMsg; if (readers.empty()) { if (!itksys::SystemTools::FileExists(loadInfo.m_Path.c_str())) { errMsg += "File '" + loadInfo.m_Path + "' does not exist\n"; } else { errMsg += "No reader available for '" + loadInfo.m_Path + "'\n"; } MITK_ERROR << errMsg; return 0; } std::cout << "Available Readers: "< 0) { - writeName = path + "/" + filename + "_" + std::to_string(count) + extension; + writeName = path + filename + "_" + std::to_string(count) + extension; } mitk::IOUtil::Save(node, writeName); ++count; } return EXIT_SUCCESS; } diff --git a/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp b/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp index ee9a32ef57..03cd67e759 100644 --- a/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp +++ b/Modules/MapperExt/src/mitkVolumeMapperVtkSmart3D.cpp @@ -1,237 +1,236 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkVolumeMapperVtkSmart3D.h" #include "mitkTransferFunctionProperty.h" #include "mitkTransferFunctionInitializer.h" #include "mitkLevelWindowProperty.h" #include #include #include #include void mitk::VolumeMapperVtkSmart3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { bool value; this->GetDataNode()->GetBoolProperty("volumerendering", value, renderer); if (!value) { m_Volume->VisibilityOff(); return; } else { + createMapper(GetInputImage()); m_Volume->VisibilityOn(); } UpdateTransferFunctions(renderer); UpdateRenderMode(renderer); this->Modified(); } vtkProp* mitk::VolumeMapperVtkSmart3D::GetVtkProp(mitk::BaseRenderer *) { if (!m_Volume->GetMapper()) { createMapper(GetInputImage()); createVolume(); createVolumeProperty(); } return m_Volume; } void mitk::VolumeMapperVtkSmart3D::ApplyProperties(vtkActor *, mitk::BaseRenderer *) { } void mitk::VolumeMapperVtkSmart3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // GPU_INFO << "SetDefaultProperties"; node->AddProperty("volumerendering", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.usemip", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.cpu.ambient", mitk::FloatProperty::New(0.10f), renderer, overwrite); node->AddProperty("volumerendering.cpu.diffuse", mitk::FloatProperty::New(0.50f), renderer, overwrite); node->AddProperty("volumerendering.cpu.specular", mitk::FloatProperty::New(0.40f), renderer, overwrite); node->AddProperty("volumerendering.cpu.specular.power", mitk::FloatProperty::New(16.0f), renderer, overwrite); node->AddProperty("volumerendering.usegpu", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.useray", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("volumerendering.gpu.ambient", mitk::FloatProperty::New(0.25f), renderer, overwrite); node->AddProperty("volumerendering.gpu.diffuse", mitk::FloatProperty::New(0.50f), renderer, overwrite); node->AddProperty("volumerendering.gpu.specular", mitk::FloatProperty::New(0.40f), renderer, overwrite); node->AddProperty("volumerendering.gpu.specular.power", mitk::FloatProperty::New(16.0f), renderer, overwrite); node->AddProperty("binary", mitk::BoolProperty::New(false), renderer, overwrite); mitk::Image::Pointer image = dynamic_cast(node->GetData()); if (image.IsNotNull() && image->IsInitialized()) { if ((overwrite) || (node->GetProperty("TransferFunction", renderer) == nullptr)) { // add a default transfer function mitk::TransferFunction::Pointer tf = mitk::TransferFunction::New(); mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(tf); tfInit->SetTransferFunctionMode(0); node->SetProperty("TransferFunction", mitk::TransferFunctionProperty::New(tf.GetPointer())); } } Superclass::SetDefaultProperties(node, renderer, overwrite); } vtkImageData* mitk::VolumeMapperVtkSmart3D::GetInputImage() { auto input = dynamic_cast(this->GetDataNode()->GetData()); return input->GetVtkImageData(this->GetTimestep()); } void mitk::VolumeMapperVtkSmart3D::createMapper(vtkImageData* imageData) { Vector3D spacing; FillVector3D(spacing, 1.0, 1.0, 1.0); m_ImageChangeInformation->SetInputData(imageData); m_ImageChangeInformation->SetOutputSpacing(spacing.GetDataPointer()); m_SmartVolumeMapper->SetBlendModeToComposite(); m_SmartVolumeMapper->SetInputConnection(m_ImageChangeInformation->GetOutputPort()); } void mitk::VolumeMapperVtkSmart3D::createVolume() { - m_Volume->VisibilityOff(); m_Volume->SetMapper(m_SmartVolumeMapper); m_Volume->SetProperty(m_VolumeProperty); } void mitk::VolumeMapperVtkSmart3D::createVolumeProperty() { m_VolumeProperty->ShadeOn(); m_VolumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); } void mitk::VolumeMapperVtkSmart3D::UpdateTransferFunctions(mitk::BaseRenderer *renderer) { vtkSmartPointer opacityTransferFunction; vtkSmartPointer gradientTransferFunction; vtkSmartPointer colorTransferFunction; bool isBinary = false; this->GetDataNode()->GetBoolProperty("binary", isBinary, renderer); if (isBinary) { colorTransferFunction = vtkSmartPointer::New(); float rgb[3]; if (!GetDataNode()->GetColor(rgb, renderer)) rgb[0] = rgb[1] = rgb[2] = 1; colorTransferFunction->AddRGBPoint(0, rgb[0], rgb[1], rgb[2]); colorTransferFunction->Modified(); opacityTransferFunction = vtkSmartPointer::New(); gradientTransferFunction = vtkSmartPointer::New(); } else { auto *transferFunctionProp = dynamic_cast(this->GetDataNode()->GetProperty("TransferFunction", renderer)); if (transferFunctionProp) { opacityTransferFunction = transferFunctionProp->GetValue()->GetScalarOpacityFunction(); gradientTransferFunction = transferFunctionProp->GetValue()->GetGradientOpacityFunction(); colorTransferFunction = transferFunctionProp->GetValue()->GetColorTransferFunction(); } else { opacityTransferFunction = vtkSmartPointer::New(); gradientTransferFunction = vtkSmartPointer::New(); colorTransferFunction = vtkSmartPointer::New(); } } - m_VolumeProperty->SetColor(colorTransferFunction); m_VolumeProperty->SetScalarOpacity(opacityTransferFunction); m_VolumeProperty->SetGradientOpacity(gradientTransferFunction); } void mitk::VolumeMapperVtkSmart3D::UpdateRenderMode(mitk::BaseRenderer *renderer) { bool usegpu = false; bool useray = false; bool usemip = false; this->GetDataNode()->GetBoolProperty("volumerendering.usegpu", usegpu); this->GetDataNode()->GetBoolProperty("volumerendering.useray", useray); this->GetDataNode()->GetBoolProperty("volumerendering.usemip", usemip); if (usegpu) m_SmartVolumeMapper->SetRequestedRenderModeToGPU(); else if (useray) m_SmartVolumeMapper->SetRequestedRenderModeToRayCast(); else m_SmartVolumeMapper->SetRequestedRenderModeToDefault(); int blendMode; if (this->GetDataNode()->GetIntProperty("volumerendering.blendmode", blendMode)) m_SmartVolumeMapper->SetBlendMode(blendMode); else if (usemip) m_SmartVolumeMapper->SetBlendMode(vtkSmartVolumeMapper::MAXIMUM_INTENSITY_BLEND); // shading parameter if (m_SmartVolumeMapper->GetRequestedRenderMode() == vtkSmartVolumeMapper::GPURenderMode) { float value = 0; if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.ambient", value, renderer)) m_VolumeProperty->SetAmbient(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.diffuse", value, renderer)) m_VolumeProperty->SetDiffuse(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.specular", value, renderer)) m_VolumeProperty->SetSpecular(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.gpu.specular.power", value, renderer)) m_VolumeProperty->SetSpecularPower(value); } else { float value = 0; if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.ambient", value, renderer)) m_VolumeProperty->SetAmbient(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.diffuse", value, renderer)) m_VolumeProperty->SetDiffuse(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.specular", value, renderer)) m_VolumeProperty->SetSpecular(value); if (this->GetDataNode()->GetFloatProperty("volumerendering.cpu.specular.power", value, renderer)) m_VolumeProperty->SetSpecularPower(value); } } mitk::VolumeMapperVtkSmart3D::VolumeMapperVtkSmart3D() { m_SmartVolumeMapper = vtkSmartPointer::New(); m_SmartVolumeMapper->SetBlendModeToComposite(); m_ImageChangeInformation = vtkSmartPointer::New(); m_VolumeProperty = vtkSmartPointer::New(); m_Volume = vtkSmartPointer::New(); } mitk::VolumeMapperVtkSmart3D::~VolumeMapperVtkSmart3D() { } diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index 3452a9a90b..47f5472fdf 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,363 +1,360 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSet.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.GetActiveLabel()->GetValue()), m_Layer(other.GetLayer()) { // clone Labels auto otherIt = other.IteratorConstBegin(); for (; otherIt != other.IteratorConstEnd(); ++otherIt) { m_LabelContainer[otherIt->first] = otherIt->second->Clone(); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); } } void mitk::LabelSet::OnLabelModified() { ModifyLabelEvent.Send(); Superclass::Modified(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const { return m_LabelContainer.begin(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() { return m_LabelContainer.begin(); } unsigned int mitk::LabelSet::GetNumberOfLabels() const { return m_LabelContainer.size(); } void mitk::LabelSet::SetLayer(unsigned int layer) { m_Layer = layer; Modified(); } void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) { m_ActiveLabelValue = pixelValue; ActiveLabelEvent.Send(pixelValue); Modified(); } bool mitk::LabelSet::ExistLabel(PixelType pixelValue) { return m_LabelContainer.count(pixelValue) > 0 ? true : false; } // TODO Parameter as Smartpointer void mitk::LabelSet::AddLabel(mitk::Label *label) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LabelContainer.size() >= max_size) return; mitk::Label::Pointer newLabel(label->Clone()); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); PixelType pixelValue; if (m_LabelContainer.empty()) { pixelValue = newLabel->GetValue(); } else { pixelValue = m_LabelContainer.rbegin()->first; if (pixelValue >= newLabel->GetValue() && m_LabelContainer.find(newLabel->GetValue()) != m_LabelContainer.end()) { ++pixelValue; newLabel->SetValue(pixelValue); } else { pixelValue = newLabel->GetValue(); } } // new map entry m_LabelContainer[pixelValue] = newLabel; UpdateLookupTable(pixelValue); // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); newLabel->AddObserver(itk::ModifiedEvent(), command); // newLabel->AddObserver(itk::ModifiedEvent(),command); SetActiveLabel(newLabel->GetValue()); AddLabelEvent.Send(); Modified(); } void mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { - if (m_LabelContainer.size() > 255) - return; - mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); AddLabel(newLabel); } void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) { mitk::Label *label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) { m_LookupTable = lut; Modified(); } void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const { } void mitk::LabelSet::RemoveLabel(PixelType pixelValue) { auto it = m_LabelContainer.rbegin(); PixelType nextActivePixelValue = it->first; for (; it != m_LabelContainer.rend(); ++it) { if (it->first == pixelValue) { it->second->RemoveAllObservers(); m_LabelContainer.erase(pixelValue); break; } nextActivePixelValue = it->first; } if (m_ActiveLabelValue == pixelValue) { if (ExistLabel(nextActivePixelValue)) SetActiveLabel(nextActivePixelValue); else SetActiveLabel(m_LabelContainer.rbegin()->first); } RemoveLabelEvent.Send(); Modified(); } void mitk::LabelSet::RemoveAllLabels() { auto _it = IteratorBegin(); for (; _it != IteratorConstEnd();) { RemoveLabelEvent.Send(); m_LabelContainer.erase(_it++); } AllLabelsModifiedEvent.Send(); } void mitk::LabelSet::SetNextActiveLabel() { auto it = m_LabelContainer.begin(); for (; it != m_LabelContainer.end(); ++it) { if (it->first == m_ActiveLabelValue) { // go to next label ++it; if (it == m_LabelContainer.end()) { // end of container; next label is first label it = m_LabelContainer.begin(); } break; // found the active label; finish loop } } SetActiveLabel(it->first); } void mitk::LabelSet::SetAllLabelsLocked(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) _it->second->SetLocked(value); AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::SetAllLabelsVisible(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) { _it->second->SetVisible(value); UpdateLookupTable(_it->first); } AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) { const mitk::Color &color = GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) { if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) return nullptr; return m_LabelContainer[pixelValue]; } const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const { auto it = m_LabelContainer.find(pixelValue); if (it == m_LabelContainer.end()) return nullptr; return it->second.GetPointer(); } bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // LabelSetmembers MITK_INFO(verbose) << "--- LabelSet Equal ---"; // m_LookupTable; const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // m_ActiveLabel; returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Active label not equal."; return returnValue; ; } // m_Layer; returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); if (!returnValue) { MITK_INFO(verbose) << "Layer index not equal."; return returnValue; ; } // container size; returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // Label container (map) // m_LabelContainer; auto lhsit = leftHandSide.IteratorConstBegin(); auto rhsit = rightHandSide.IteratorConstBegin(); for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) { returnValue = rhsit->first == lhsit->first; if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } diff --git a/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp b/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp index 4c53a5c238..dedc6d3917 100644 --- a/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp +++ b/Modules/Pharmacokinetics/src/Models/mitkExtendedToftsModel.cpp @@ -1,177 +1,182 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkExtendedToftsModel.h" #include "mitkConvolutionHelper.h" #include #include const std::string mitk::ExtendedToftsModel::MODEL_DISPLAY_NAME = "Extended Tofts Model"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_Ktrans = "KTrans"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_ve = "ve"; const std::string mitk::ExtendedToftsModel::NAME_PARAMETER_vp = "vp"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_Ktrans = "ml/min/100ml"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_ve = "ml/ml"; const std::string mitk::ExtendedToftsModel::UNIT_PARAMETER_vp = "ml/ml"; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_Ktrans = 0; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_ve = 1; const unsigned int mitk::ExtendedToftsModel::POSITION_PARAMETER_vp = 2; const unsigned int mitk::ExtendedToftsModel::NUMBER_OF_PARAMETERS = 3; std::string mitk::ExtendedToftsModel::GetModelDisplayName() const { return MODEL_DISPLAY_NAME; }; std::string mitk::ExtendedToftsModel::GetModelType() const { return "Perfusion.MR"; }; mitk::ExtendedToftsModel::ExtendedToftsModel() { } mitk::ExtendedToftsModel::~ExtendedToftsModel() { } mitk::ExtendedToftsModel::ParameterNamesType mitk::ExtendedToftsModel::GetParameterNames() const { ParameterNamesType result; result.push_back(NAME_PARAMETER_Ktrans); result.push_back(NAME_PARAMETER_ve); result.push_back(NAME_PARAMETER_vp); return result; } mitk::ExtendedToftsModel::ParametersSizeType mitk::ExtendedToftsModel::GetNumberOfParameters() const { return NUMBER_OF_PARAMETERS; } mitk::ExtendedToftsModel::ParamterUnitMapType mitk::ExtendedToftsModel::GetParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair(NAME_PARAMETER_Ktrans, UNIT_PARAMETER_Ktrans)); result.insert(std::make_pair(NAME_PARAMETER_vp, UNIT_PARAMETER_vp)); result.insert(std::make_pair(NAME_PARAMETER_ve, UNIT_PARAMETER_ve)); return result; }; mitk::ExtendedToftsModel::ParameterNamesType mitk::ExtendedToftsModel::GetDerivedParameterNames() const { ParameterNamesType result; result.push_back("kep"); return result; }; mitk::ExtendedToftsModel::ParametersSizeType mitk::ExtendedToftsModel::GetNumberOfDerivedParameters() const { return 1; }; mitk::ExtendedToftsModel::ParamterUnitMapType mitk::ExtendedToftsModel::GetDerivedParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair("kep", "1/min")); return result; }; mitk::ExtendedToftsModel::ModelResultType mitk::ExtendedToftsModel::ComputeModelfunction( const ParametersType& parameters) const { if (this->m_TimeGrid.GetSize() == 0) { itkExceptionMacro("No Time Grid Set! Cannot Calculate Signal"); } AterialInputFunctionType aterialInputFunction; aterialInputFunction = GetAterialInputFunction(this->m_TimeGrid); unsigned int timeSteps = this->m_TimeGrid.GetSize(); //Model Parameters double ktrans = parameters[POSITION_PARAMETER_Ktrans] / 6000.0; double ve = parameters[POSITION_PARAMETER_ve]; double vp = parameters[POSITION_PARAMETER_vp]; + if (ve == 0.0) + { + itkExceptionMacro("ve is 0! Cannot calculate signal"); + } + double lambda = ktrans / ve; mitk::ModelBase::ModelResultType convolution = mitk::convoluteAIFWithExponential(this->m_TimeGrid, aterialInputFunction, lambda); //Signal that will be returned by ComputeModelFunction mitk::ModelBase::ModelResultType signal(timeSteps); signal.fill(0.0); mitk::ModelBase::ModelResultType::iterator signalPos = signal.begin(); mitk::ModelBase::ModelResultType::const_iterator res = convolution.begin(); for (AterialInputFunctionType::iterator Cp = aterialInputFunction.begin(); Cp != aterialInputFunction.end(); ++res, ++signalPos, ++Cp) { *signalPos = (*Cp) * vp + ktrans * (*res); } return signal; } mitk::ModelBase::DerivedParameterMapType mitk::ExtendedToftsModel::ComputeDerivedParameters( const mitk::ModelBase::ParametersType& parameters) const { DerivedParameterMapType result; double kep = parameters[POSITION_PARAMETER_Ktrans] / parameters[POSITION_PARAMETER_ve]; result.insert(std::make_pair("kep", kep)); return result; }; itk::LightObject::Pointer mitk::ExtendedToftsModel::InternalClone() const { ExtendedToftsModel::Pointer newClone = ExtendedToftsModel::New(); newClone->SetTimeGrid(this->m_TimeGrid); return newClone.GetPointer(); }; void mitk::ExtendedToftsModel::PrintSelf(std::ostream& os, ::itk::Indent indent) const { Superclass::PrintSelf(os, indent); }; diff --git a/Modules/Pharmacokinetics/test/files.cmake b/Modules/Pharmacokinetics/test/files.cmake index 713e14913f..d6ce07a366 100644 --- a/Modules/Pharmacokinetics/test/files.cmake +++ b/Modules/Pharmacokinetics/test/files.cmake @@ -1,4 +1,7 @@ SET(MODULE_TESTS mitkDescriptivePharmacokineticBrixModelTest.cpp + mitkStandardToftsModelTest.cpp #ConvertToConcentrationTest.cpp + mitkTwoCompartmentExchangeModelTest.cpp + mitkExtendedToftsModelTest.cpp ) diff --git a/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp b/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp new file mode 100644 index 0000000000..5bd2a350e1 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkExtendedToftsModelTest.cpp @@ -0,0 +1,164 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// Testing +#include "mitkTestingMacros.h" +#include "mitkTestFixture.h" +#include "math.h" + +//MITK includes +#include "mitkVector.h" +#include "mitkExtendedToftsModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkExtendedToftsModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkExtendedToftsModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(GetDerivedParameterNamesTest); + MITK_TEST(GetNumberOfDerivedParametersTest); + MITK_TEST(GetDerivedParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + MITK_TEST(ComputeDerivedParametersTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::ExtendedToftsModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + mitk::ModelBase::DerivedParameterMapType m_derivedParameters; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(60); + mitk::ModelBase::ParametersType m_testparameters(3); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (60); + + m_testparameters[mitk::ExtendedToftsModel::POSITION_PARAMETER_Ktrans] = 35.0; + m_testparameters(mitk::ExtendedToftsModel::POSITION_PARAMETER_ve) = 0.5; + m_testparameters[mitk::ExtendedToftsModel::POSITION_PARAMETER_vp] = 0.05; + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + for (int i = 0; i < 22; ++i) + { + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::ExtendedToftsModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + m_derivedParameters = m_testmodel->GetDerivedParameters(m_testparameters); + } + + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + m_derivedParameters.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Extended Tofts Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::ExtendedToftsModel::ParameterNamesType parameterNames; + parameterNames.push_back("KTrans"); + parameterNames.push_back("ve"); + parameterNames.push_back("vp"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 3); + } + + + void GetParameterUnitsTest() + { + mitk::ExtendedToftsModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("KTrans", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("vp", "ml/ml")); + parameterUnits.insert(std::make_pair("ve","ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void GetDerivedParameterNamesTest() + { + mitk::ExtendedToftsModel::ParameterNamesType derivedParameterNames; + + derivedParameterNames.push_back("kep"); + + CPPUNIT_ASSERT_MESSAGE("Checking derived parameter names.", m_testmodel->GetDerivedParameterNames() == derivedParameterNames); + } + + void GetNumberOfDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfDerivedParameters() == 1); + } + + void GetDerivedParameterUnitsTest() + { + mitk::ExtendedToftsModel::ParamterUnitMapType derivedParameterUnits; + derivedParameterUnits.insert(std::make_pair("kep", "1/min")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetDerivedParameterUnits() == derivedParameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.438500, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(1.094436, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10", mitk::Equal(0.890956, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 15", mitk::Equal(0.580996, m_output[15], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20", mitk::Equal(0.342851, m_output[20], 1e-6, true) == true); + } + + void ComputeDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking kep.", mitk::Equal(70.00, m_derivedParameters["kep"], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkExtendedToftsModel) diff --git a/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp b/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp new file mode 100644 index 0000000000..5b0c907166 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkStandardToftsModelTest.cpp @@ -0,0 +1,162 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// Testing +#include "mitkTestingMacros.h" +#include "mitkTestFixture.h" + +//MITK includes +#include "mitkVector.h" +#include "mitkStandardToftsModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkStandardToftsModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkStandardToftsModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(GetDerivedParameterNamesTest); + MITK_TEST(GetNumberOfDerivedParametersTest); + MITK_TEST(GetDerivedParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + MITK_TEST(ComputeDerivedParametersTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::StandardToftsModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + mitk::ModelBase::DerivedParameterMapType m_derivedParameters; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(22); + mitk::ModelBase::ParametersType m_testparameters(2); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (22); + + m_testparameters[mitk::StandardToftsModel::POSITION_PARAMETER_Ktrans] = 35.0; + m_testparameters(mitk::StandardToftsModel::POSITION_PARAMETER_ve) = 0.5; + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + for (int i = 0; i < 22; ++i) + { + if (i < 5) + m_arterialInputFunction[i] = 0; + else + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::StandardToftsModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + m_derivedParameters = m_testmodel->GetDerivedParameters(m_testparameters); + } + + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + m_derivedParameters.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Standard Tofts Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::StandardToftsModel::ParameterNamesType parameterNames; + parameterNames.push_back("KTrans"); + parameterNames.push_back("ve"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 2); + } + + + void GetParameterUnitsTest() + { + mitk::StandardToftsModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("KTrans", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("ve", "ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void GetDerivedParameterNamesTest() + { + mitk::StandardToftsModel::ParameterNamesType derivedParameterNames; + + derivedParameterNames.push_back("kep"); + + CPPUNIT_ASSERT_MESSAGE("Checking derived parameter names.", m_testmodel->GetDerivedParameterNames() == derivedParameterNames); + } + + void GetNumberOfDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfDerivedParameters() == 1); + } + + void GetDerivedParameterUnitsTest() + { + mitk::StandardToftsModel::ParamterUnitMapType derivedParameterUnits; + derivedParameterUnits.insert(std::make_pair("kep", "1/min")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetDerivedParameterUnits() == derivedParameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.0, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(0.085056, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10.", mitk::Equal(0.442948, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20.", mitk::Equal(0.254551, m_output[20], 1e-6, true) == true); + } + + void ComputeDerivedParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking kep.", mitk::Equal(70.0, m_derivedParameters["kep"], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkStandardToftsModel) diff --git a/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp b/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp new file mode 100644 index 0000000000..652198ec20 --- /dev/null +++ b/Modules/Pharmacokinetics/test/mitkTwoCompartmentExchangeModelTest.cpp @@ -0,0 +1,137 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// Testing +#include "mitkTestingMacros.h" +#include "mitkTestFixture.h" + +//MITK includes +#include "mitkVector.h" +#include "mitkTwoCompartmentExchangeModel.h" +#include "mitkAIFBasedModelBase.h" + +class mitkTwoCompartmentExchangeModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkTwoCompartmentExchangeModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::TwoCompartmentExchangeModel::Pointer m_testmodel; + mitk::ModelBase::ModelResultType m_output; + +public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(22); + mitk::ModelBase::ParametersType m_testparameters(4); + mitk::AIFBasedModelBase::AterialInputFunctionType m_arterialInputFunction (22); + + m_testparameters[mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_F] = 35.0; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_PS) = 5.0; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_ve) = 0.5; + m_testparameters(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_vp) = 0.05; + + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + + // AIF from Weinmann, H. J., Laniado, M., and W.Mützel (1984). Pharmacokinetics of GD - DTPA / dimeglumine after intravenous injection into healthy volunteers. Phys Chem Phys Med NMR, 16(2) : 167–72. + int D = 1; + double a1 = 3.99; + double m1 = 0.144; + double a2 = 4.78; + double m2 = 0.0111; + + for (int i = 0; i < 22; ++i) + { + if (i < 5) + m_arterialInputFunction[i] = 0; + else + m_arterialInputFunction[i] = D * (a1 * exp(-m1 * m_grid[i]) + a2 * exp(-m2 * m_grid[i])); + } + + m_testmodel = mitk::TwoCompartmentExchangeModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetAterialInputFunctionValues(m_arterialInputFunction); + m_testmodel->SetAterialInputFunctionTimeGrid(m_grid); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + } + void tearDown() override + { + m_testmodel = nullptr; + m_output.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Two Compartment Exchange Model"); + } + + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + + void GetParameterNamesTest() + { + mitk::TwoCompartmentExchangeModel::ParameterNamesType parameterNames; + parameterNames.push_back("F"); + parameterNames.push_back("PS"); + parameterNames.push_back("ve"); + parameterNames.push_back("vp"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } + + + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 4); + } + + + void GetParameterUnitsTest() + { + mitk::TwoCompartmentExchangeModel::ParamterUnitMapType parameterUnits; + parameterUnits.insert(std::make_pair("F", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("PS", "ml/min/100ml")); + parameterUnits.insert(std::make_pair("ve", "ml/ml")); + parameterUnits.insert(std::make_pair("vp", "ml/ml")); + + CPPUNIT_ASSERT_MESSAGE("Checking parameter units.", m_testmodel->GetParameterUnits() == parameterUnits); + } + + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 0.", mitk::Equal(0.0, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 5.", mitk::Equal(0.057246, m_output[5], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 10.", mitk::Equal(0.127806, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 15.", mitk::Equal(0.131465, m_output[15], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal at time frame 20.", mitk::Equal(0.126101, m_output[20], 1e-6, true) == true); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkTwoCompartmentExchangeModel) diff --git a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h index 2e91c99d70..84a3f8892a 100644 --- a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h +++ b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h @@ -1,197 +1,198 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKPLANARFIGUREINTERACTOR_H #define MITKPLANARFIGUREINTERACTOR_H #include #include "mitkCommon.h" #include "mitkDataInteractor.h" #include "mitkNumericTypes.h" #pragma GCC visibility push(default) #include #pragma GCC visibility pop namespace mitk { class DataNode; class PlaneGeometry; class PlanarFigure; class PositionEvent; class BaseRenderer; class InteractionPositionEvent; class StateMachineAction; #pragma GCC visibility push(default) // Define events for PlanarFigure interaction notifications itkEventMacroDeclaration(PlanarFigureEvent, itk::AnyEvent); itkEventMacroDeclaration(StartPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(EndPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(SelectPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(StartInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(EndInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(StartHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(EndHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDeclaration(ContextMenuPlanarFigureEvent, PlanarFigureEvent); + itkEventMacroDeclaration(PointMovedPlanarFigureEvent, PlanarFigureEvent); #pragma GCC visibility pop /** * \brief Interaction with mitk::PlanarFigure objects via control-points * * @ingroup MitkPlanarFigureModule */ class MITKPLANARFIGURE_EXPORT PlanarFigureInteractor : public DataInteractor { public: mitkClassMacro(PlanarFigureInteractor, DataInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Sets the amount of precision */ void SetPrecision(ScalarType precision); /** \brief Sets the minimal distance between two control points. */ void SetMinimumPointDistance(ScalarType minimumDistance); protected: PlanarFigureInteractor(); ~PlanarFigureInteractor() override; void ConnectActionsAndFunctions() override; //////// Conditions //////// bool CheckFigurePlaced(const InteractionEvent *interactionEvent); bool CheckFigureHovering(const InteractionEvent *interactionEvent); bool CheckControlPointHovering(const InteractionEvent *interactionEvent); bool CheckSelection(const InteractionEvent *interactionEvent); bool CheckPointValidity(const InteractionEvent *interactionEvent); bool CheckFigureFinished(const InteractionEvent *interactionEvent); bool CheckResetOnPointSelect(const InteractionEvent *interactionEvent); bool CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent); bool CheckMinimalFigureFinished(const InteractionEvent *interactionEvent); bool CheckFigureIsExtendable(const InteractionEvent *interactionEvent); bool CheckFigureIsDeletable(const InteractionEvent *interactionEvent); bool CheckFigureIsEditable(const InteractionEvent *interactionEvent); //////// Actions //////// void FinalizeFigure(StateMachineAction *, InteractionEvent *interactionEvent); void MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent); void DeselectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent); void StartHovering(StateMachineAction *, InteractionEvent *interactionEvent); void EndHovering(StateMachineAction *, InteractionEvent *interactionEvent); void DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent); void PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *interactionEvent); void SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent); void HidePreviewPoint(StateMachineAction *, InteractionEvent *interactionEvent); void HideControlPoints(StateMachineAction *, InteractionEvent *interactionEvent); void RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent); void RequestContextMenu(StateMachineAction *, InteractionEvent *interactionEvent); void SelectFigure(StateMachineAction *, InteractionEvent *interactionEvent); void SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void EndInteraction(StateMachineAction *, InteractionEvent *interactionEvent); bool FilterEvents(InteractionEvent *interactionEvent, DataNode *) override; /** \brief Used when clicking to determine if a point is too close to the previous point. */ bool IsMousePositionAcceptableAsNewControlPoint(const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *); bool TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D); bool TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const; /** \brief Returns true if the first specified point is in proximity of the line defined * the other two point; false otherwise. * * Proximity is defined as the rectangle around the line with pre-defined distance * from the line. */ bool IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const; /** \brief Returns true if the point contained in the passed event (in display coordinates) * is over the planar figure (with a pre-defined tolerance range); false otherwise. */ int IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const; /** \brief Returns the index of the marker (control point) over which the point contained * in the passed event (in display coordinates) currently is; -1 if the point is not over * a marker. */ int IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const; void LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure); void ConfigurationChanged() override; private: /** \brief to store the value of precision to pick a point */ ScalarType m_Precision; /** \brief Store the minimal distance between two control points. */ ScalarType m_MinimumPointDistance; /** \brief True if the mouse is currently hovering over the image. */ bool m_IsHovering; }; } #endif // MITKPLANARFIGUREINTERACTOR_H diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 42916486c3..9556791b3f 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,1126 +1,1129 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" namespace mitk { itkEventMacroDefinition(PlanarFigureEvent, itk::AnyEvent); itkEventMacroDefinition(StartPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(EndPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(SelectPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(StartInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(EndInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(StartHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(EndHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacroDefinition(ContextMenuPlanarFigureEvent, PlanarFigureEvent); + itkEventMacroDefinition(PointMovedPlanarFigureEvent, PlanarFigureEvent); } mitk::PlanarFigureInteractor::PlanarFigureInteractor() : DataInteractor() , m_Precision(6.5) , m_MinimumPointDistance(25.0) , m_IsHovering(false) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) CONNECT_FUNCTION("set_preview_point_position", SetPreviewPointPosition) CONNECT_FUNCTION("move_current_point", MoveCurrentPoint); CONNECT_FUNCTION("deselect_point", DeselectPoint); CONNECT_FUNCTION("add_new_point", AddPoint); CONNECT_FUNCTION("add_initial_point", AddInitialPoint); CONNECT_FUNCTION("remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION("request_context_menu", RequestContextMenu); CONNECT_FUNCTION("select_figure", SelectFigure); CONNECT_FUNCTION("select_point", SelectPoint); CONNECT_FUNCTION("end_interaction", EndInteraction); CONNECT_FUNCTION("start_hovering", StartHovering) CONNECT_FUNCTION("end_hovering", EndHovering); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty("planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction); // hide the control points if necessary // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction); // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); + + planarFigure->InvokeEvent(PointMovedPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); planarFigure->InvokeEvent(EndPlacementPlanarFigureEvent()); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent(EndHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", false); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable(const InteractionEvent * /*interactionEvent*/) { bool isExtendable(false); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const bool wasSelected = planarFigure->DeselectControlPoint(); if (wasSelected) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are necessary because of a macOS specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ const DataNode::Pointer node = this->GetDataNode(); const BaseData::Pointer data = node->GetData(); bool isFigureFinished = false; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } auto planarFigure = dynamic_cast(data.GetPointer()); if (nullptr == planarFigure) { return; } // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } mitk::BaseRenderer *renderer = interactionEvent->GetSender(); auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planeGeometry, point2D)) { return; } // Place PlanarFigure at this point planarFigure->PlaceFigure(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } if (!m_IsHovering) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent(StartHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", true); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); if (selected && isExtendable && isEditable) { renderer->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); return isHovering; } bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); return pointIndex >= 0; } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return; } const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } bool mitk::PlanarFigureInteractor::CheckPointValidity(const InteractionEvent *interactionEvent) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } return IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return; } bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if (!selected) { planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } planarFigure->InvokeEvent(ContextMenuPlanarFigureEvent()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); auto planarFigure = dynamic_cast(GetDataNode()->GetData()); if (nullptr == planarFigure) { return false; } auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); if (nullptr != abstractTransformGeometry) { return false; } const double planeThickness = planarFigureGeometry->GetExtentInMM(2); return planarFigureGeometry->Distance(worldPoint3D) <= planeThickness; } void mitk::PlanarFigureInteractor::SetPrecision(mitk::ScalarType precision) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance(ScalarType minimumDistance) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D) { if (nullptr == positionEvent || nullptr == planarFigureGeometry) { return false; } const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if (planarFigureGeometry->Distance(worldPoint3D) > 0.1) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map(worldPoint3D, point2D); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { if (nullptr == objectGeometry || nullptr == rendererGeometry || nullptr == renderer) { return false; } mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, displayPoint); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const { if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry || nullptr == rendererGeometry) { return -1; } mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for (unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { break; // Poly line invalid (not on current 2D plane) --> skip it } if (firstPoint) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if (this->IsPointNearLine(displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine)) { // Point is close enough to line segment --> Return index of the segment return std::distance(polyLine.begin(), it); } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if (planarFigure->IsClosed() && this->IsPointNearLine(displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine)) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry || nullptr == rendererGeometry || nullptr == renderer) { return -1; } const mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // TODO: variable size of markers if (displayPosition.SquaredEuclideanDistanceTo(displayControlPoint) < 20.0) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure) { if (nullptr == planarFigure) { MITK_INFO << "PlanarFigure invalid."; } MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for (unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i) { MITK_INFO << "* " << planarFigure->GetFeatureName(i) << ": " << planarFigure->GetQuantity(i) << " " << planarFigure->GetFeatureUnit(i); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure) { if (nullptr == positionEvent || nullptr == planarFigure) { return false; } const BaseRenderer *renderer = positionEvent->GetSender(); if (nullptr == renderer) { return false; } // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); auto planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); if (nullptr == planarFigureGeometry) { return false; } auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); if (nullptr != abstractTransformGeometry) { return false; } Point2D point2D; // Get the point2D from the positionEvent if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. const Point2D correctedPoint = const_cast(planarFigure)->ApplyControlPointConstraints(0, point2D); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map(correctedPoint, newPoint3D); mitk::Point2D newDisplayPosition; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map(planarFigure->GetControlPoint(i), previousPoint3D); if (renderer->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance); } if (tooClose) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { const mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType)6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType)25.0; } } diff --git a/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp b/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp index 399903ff84..dfd18a0a43 100755 --- a/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp +++ b/Modules/QtPython/QmitkPythonVariableStackTableModel.cpp @@ -1,221 +1,214 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkPythonVariableStackTableModel.h" #include #include #include #include #include #include #include "QmitkMimeTypes.h" const QString QmitkPythonVariableStackTableModel::MITK_IMAGE_VAR_NAME = "mitkImage"; const QString QmitkPythonVariableStackTableModel::MITK_SURFACE_VAR_NAME = "mitkSurface"; QmitkPythonVariableStackTableModel::QmitkPythonVariableStackTableModel(QObject *parent) :QAbstractTableModel(parent) { us::ModuleContext* context = us::GetModuleContext(); m_PythonServiceRef = context->GetServiceReference(); m_PythonService = context->GetService(m_PythonServiceRef); m_PythonService->AddPythonCommandObserver( this ); } QmitkPythonVariableStackTableModel::~QmitkPythonVariableStackTableModel() { us::ModuleContext* context = us::GetModuleContext(); context->UngetService( m_PythonServiceRef ); m_PythonService->RemovePythonCommandObserver( this ); } bool QmitkPythonVariableStackTableModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int, int, const QModelIndex &) { // Early exit, returning true, but not actually doing anything (ignoring data). if (action == Qt::IgnoreAction) return true; // Note, we are returning true if we handled it, and false otherwise bool returnValue = false; if(data->hasFormat(QmitkMimeTypes::DataNodePtrs)) { - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "dropped MITK DataNode"; returnValue = true; int i = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode* node = nullptr; foreach(node, dataNodeList) { mitk::Image* mitkImage = dynamic_cast(node->GetData()); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "mitkImage is not null " << (mitkImage != nullptr? "true": "false"); QRegExp rx("^\\d"); QString varName(node->GetName().c_str()); // regex replace every character that is not allowed in a python variable varName = varName.replace(QRegExp("[.\\+\\-*\\s\\/\\n\\t\\r]"),QString("_")); if( mitkImage ) { if ( varName.isEmpty() ) varName = MITK_IMAGE_VAR_NAME; if ( rx.indexIn(varName) == 0) varName.prepend("_").prepend(MITK_IMAGE_VAR_NAME); if( i > 0 ) varName = QString("%1%2").arg(varName).arg(i); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "varName" << varName.toStdString(); bool exportAsCvImage = mitkImage->GetDimension() == 2 && m_PythonService->IsOpenCvPythonWrappingAvailable(); if( exportAsCvImage ) { int ret = QMessageBox::question(nullptr, "Export option", "2D image detected. Export as OpenCV image to Python instead of an SimpleITK image?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No); exportAsCvImage = ret == QMessageBox::Yes; if(exportAsCvImage) { m_PythonService->CopyToPythonAsCvImage( mitkImage, varName.toStdString() ); ++i; } } if( !exportAsCvImage ) { if( m_PythonService->IsSimpleItkPythonWrappingAvailable() ) { m_PythonService->CopyToPythonAsSimpleItkImage( mitkImage, varName.toStdString() ); ++i; } else { MITK_ERROR << "SimpleITK Python wrapping not available. Skipping export for image " << node->GetName(); } } } else { mitk::Surface* surface = dynamic_cast(node->GetData()); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "found surface"; if( surface ) { if (varName.isEmpty() ) varName = MITK_SURFACE_VAR_NAME; if ( rx.indexIn(varName) == 0) varName.prepend("_").prepend(MITK_SURFACE_VAR_NAME); - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "varName" << varName; - if( m_PythonService->IsVtkPythonWrappingAvailable() ) { m_PythonService->CopyToPythonAsVtkPolyData( surface, varName.toStdString() ); } else { MITK_ERROR << "VTK Python wrapping not available. Skipping export for surface " << node->GetName(); } } } } } return returnValue; } QVariant QmitkPythonVariableStackTableModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant headerData; // show only horizontal header if ( role == Qt::DisplayRole ) { if( orientation == Qt::Horizontal ) { // first column: "Attribute" if(section == 0) headerData = "Attribute"; else if(section == 1) headerData = "Type"; else if(section == 2) headerData = "Value"; } } return headerData; } Qt::ItemFlags QmitkPythonVariableStackTableModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if(index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | flags; else return Qt::ItemIsDropEnabled | flags; } int QmitkPythonVariableStackTableModel::rowCount(const QModelIndex &) const { return m_VariableStack.size(); } int QmitkPythonVariableStackTableModel::columnCount(const QModelIndex &) const { return 3; } QVariant QmitkPythonVariableStackTableModel::data(const QModelIndex &index, int role) const { if (index.isValid() && !m_VariableStack.empty()) { if(role == Qt::DisplayRole) { mitk::PythonVariable item = m_VariableStack.at(index.row()); if(index.column() == 0) return QString::fromStdString(item.m_Name); if(index.column() == 1) return QString::fromStdString(item.m_Type); if(index.column() == 2) return QString::fromStdString(item.m_Value); } } return QVariant(); } QStringList QmitkPythonVariableStackTableModel::mimeTypes() const { return QAbstractTableModel::mimeTypes(); QStringList types; types << "application/x-mitk-datanodes"; types << "application/x-qabstractitemmodeldatalist"; return types; } Qt::DropActions QmitkPythonVariableStackTableModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } -void QmitkPythonVariableStackTableModel::CommandExecuted(const std::string& pythonCommand) +void QmitkPythonVariableStackTableModel::CommandExecuted(const std::string&) { - MITK_DEBUG("QmitkPythonVariableStackTableModel") << "command was executed " << pythonCommand; m_VariableStack = m_PythonService->GetVariableStack(); QAbstractTableModel::beginResetModel(); QAbstractTableModel::endResetModel(); } std::vector QmitkPythonVariableStackTableModel::GetVariableStack() const { return m_VariableStack; } diff --git a/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h b/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h index 5bf7ea8d64..443f1b7cac 100644 --- a/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkTransferFunctionGeneratorWidget.h @@ -1,80 +1,80 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKTRANSFERFUNCTIONGENERATORWIDGET_H #define QMITKTRANSFERFUNCTIONGENERATORWIDGET_H #include "MitkQtWidgetsExtExports.h" #include "ui_QmitkTransferFunctionGeneratorWidget.h" #include #include #include #include class MITKQTWIDGETSEXT_EXPORT QmitkTransferFunctionGeneratorWidget : public QWidget, public Ui::QmitkTransferFunctionGeneratorWidget { Q_OBJECT public: QmitkTransferFunctionGeneratorWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); ~QmitkTransferFunctionGeneratorWidget() override; - void SetDataNode(mitk::DataNode *node); + void SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep = 0); int AddPreset(const QString &presetName); void SetPresetsTabEnabled(bool enable); void SetThresholdTabEnabled(bool enable); void SetBellTabEnabled(bool enable); public slots: void OnSavePreset(); void OnLoadPreset(); void OnDeltaLevelWindow(int dx, int dy); void OnDeltaThreshold(int dx, int dy); signals: void SignalTransferFunctionModeChanged(int); void SignalUpdateCanvas(); protected slots: void OnPreset(int mode); protected: mitk::TransferFunctionProperty::Pointer tfpToChange; double histoMinimum; double histoMaximum; double thPos; double thDelta; double deltaScale; double deltaMax; double deltaMin; const mitk::Image::HistogramType *histoGramm; QString presetFileName; double ScaleDelta(int d) const; }; #endif diff --git a/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h b/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h index d1d5a901d4..0ab5534657 100755 --- a/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkTransferFunctionWidget.h @@ -1,79 +1,79 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKTRANSFERFUNCTIONWIDGET_H #define QMITKTRANSFERFUNCTIONWIDGET_H #include "MitkQtWidgetsExtExports.h" #include "ui_QmitkTransferFunctionWidget.h" #include #include #include #include #include #include #include namespace mitk { class BaseRenderer; } class MITKQTWIDGETSEXT_EXPORT QmitkTransferFunctionWidget : public QWidget, public Ui::QmitkTransferFunctionWidget { Q_OBJECT public: QmitkTransferFunctionWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); ~QmitkTransferFunctionWidget() override; - void SetDataNode(mitk::DataNode *node, const mitk::BaseRenderer *renderer = nullptr); + void SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep = 0, const mitk::BaseRenderer *renderer = nullptr); void SetScalarLabel(const QString &scalarLabel); void ShowScalarOpacityFunction(bool show); void ShowColorFunction(bool show); void ShowGradientOpacityFunction(bool show); void SetScalarOpacityFunctionEnabled(bool enable); void SetColorFunctionEnabled(bool enable); void SetGradientOpacityFunctionEnabled(bool enable); public slots: void SetXValueScalar(const QString text); void SetYValueScalar(const QString text); void SetXValueGradient(const QString text); void SetYValueGradient(const QString text); void SetXValueColor(const QString text); void OnUpdateCanvas(); void UpdateRanges(); void OnResetSlider(); void OnSpanChanged(int lower, int upper); protected: mitk::TransferFunctionProperty::Pointer tfpToChange; int m_RangeSliderMin; int m_RangeSliderMax; mitk::SimpleHistogramCache histogramCache; }; #endif diff --git a/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp b/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp index e244bb77a1..861c103b23 100644 --- a/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkTransferFunctionGeneratorWidget.cpp @@ -1,353 +1,367 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkTransferFunctionGeneratorWidget.h" #include #include #include #include #include #include #include #include #include QmitkTransferFunctionGeneratorWidget::QmitkTransferFunctionGeneratorWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), deltaScale(1.0), deltaMax(1024), deltaMin(1) { histoGramm = nullptr; this->setupUi(this); // LevelWindow Tab { connect(m_CrossLevelWindow, SIGNAL(SignalDeltaMove(int, int)), this, SLOT(OnDeltaLevelWindow(int, int))); } // Threshold Tab { connect(m_CrossThreshold, SIGNAL(SignalDeltaMove(int, int)), this, SLOT(OnDeltaThreshold(int, int))); thDelta = 100; } // Presets Tab { m_TransferFunctionComboBox->setVisible(false); connect(m_TransferFunctionComboBox, SIGNAL(activated(int)), this, SIGNAL(SignalTransferFunctionModeChanged(int))); connect(m_TransferFunctionComboBox, SIGNAL(activated(int)), this, SLOT(OnPreset(int))); connect(m_SavePreset, SIGNAL(clicked()), this, SLOT(OnSavePreset())); connect(m_LoadPreset, SIGNAL(clicked()), this, SLOT(OnLoadPreset())); } presetFileName = "."; } int QmitkTransferFunctionGeneratorWidget::AddPreset(const QString &presetName) { m_TransferFunctionComboBox->setVisible(true); m_TransferFunctionComboBox->addItem(presetName); return m_TransferFunctionComboBox->count() - 1; } void QmitkTransferFunctionGeneratorWidget::SetPresetsTabEnabled(bool enable) { m_PresetTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::SetThresholdTabEnabled(bool enable) { m_ThresholdTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::SetBellTabEnabled(bool enable) { m_BellTab->setEnabled(enable); } void QmitkTransferFunctionGeneratorWidget::OnSavePreset() { if (tfpToChange.IsNull()) return; mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); presetFileName = QFileDialog::getSaveFileName( this, "Choose a filename to save the transfer function", presetFileName, "Transferfunction (*.xml)"); if (!presetFileName.endsWith(".xml")) presetFileName.append(".xml"); MITK_INFO << "Saving Transferfunction under path: " << presetFileName.toStdString(); if (mitk::TransferFunctionPropertySerializer::SerializeTransferFunction(presetFileName.toLatin1(), tf)) { QFontMetrics metrics(m_InfoPreset->font()); QString text = metrics.elidedText(presetFileName, Qt::ElideMiddle, m_InfoPreset->width()); m_InfoPreset->setText(QString("saved ") + text); } else { m_InfoPreset->setText(QString("saving failed")); } } void QmitkTransferFunctionGeneratorWidget::OnLoadPreset() { if (tfpToChange.IsNull()) return; presetFileName = QFileDialog::getOpenFileName( this, "Choose a file to open the transfer function from", presetFileName, "Transferfunction (*.xml)"); MITK_INFO << "Loading Transferfunction from path: " << presetFileName.toStdString(); mitk::TransferFunction::Pointer tf = mitk::TransferFunctionPropertySerializer::DeserializeTransferFunction(presetFileName.toLatin1()); if (tf.IsNotNull()) { tfpToChange->SetValue(tf); QFontMetrics metrics(m_InfoPreset->font()); QString text = metrics.elidedText(presetFileName, Qt::ElideMiddle, m_InfoPreset->width()); m_InfoPreset->setText(QString("loaded ") + text); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } } void QmitkTransferFunctionGeneratorWidget::OnPreset(int mode) { // first item is only information if (--mode == -1) return; m_InfoPreset->setText(QString("selected ") + m_TransferFunctionComboBox->currentText()); // revert to first item m_TransferFunctionComboBox->setCurrentIndex(0); } static double transformationGlocke(double x) { double z = 0.1; double a = 2 - 2 * z; double b = 2 * z - 1; x = a * x + b; return x; } static double stepFunctionGlocke(double x) { x = 1 - (2 * x - 1.0); // map [0.5;1] to [0,1] x = x * (3 * x - 2 * x * x); // apply smoothing function x = x * x; return x; } double QmitkTransferFunctionGeneratorWidget::ScaleDelta(int d) const { return deltaScale * (double)d; } void QmitkTransferFunctionGeneratorWidget::OnDeltaLevelWindow(int dx, int dy) // bell { if (tfpToChange.IsNull()) return; thPos += ScaleDelta(dx); thDelta -= ScaleDelta(dy); if (thDelta < deltaMin) thDelta = deltaMin; if (thDelta > deltaMax) thDelta = deltaMax; if (thPos < histoMinimum) thPos = histoMinimum; if (thPos > histoMaximum) thPos = histoMaximum; std::stringstream ss; ss << "Click on the cross and move the mouse" << "\n" << "\n" << "center at " << thPos << "\n" << "width " << thDelta * 2; m_InfoLevelWindow->setText(QString(ss.str().c_str())); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); // grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetScalarOpacityFunction(); f->RemoveAllPoints(); for (int r = 0; r <= 6; r++) { double relPos = (r / 6.0) * 0.5 + 0.5; f->AddPoint(thPos + thDelta * (-transformationGlocke(relPos)), stepFunctionGlocke(relPos)); f->AddPoint(thPos + thDelta * (transformationGlocke(relPos)), stepFunctionGlocke(relPos)); } f->Modified(); } // gradient at grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetGradientOpacityFunction(); f->RemoveAllPoints(); f->AddPoint(0, 1.0); f->Modified(); } tf->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } static double stepFunctionThreshold(double x) { x = 0.5 * x + 0.5; // map [-1;1] to [0,1] x = x * (3 * x - 2 * x * x); // apply smoothing function x = x * x; return x; } void QmitkTransferFunctionGeneratorWidget::OnDeltaThreshold(int dx, int dy) // LEVELWINDOW { if (tfpToChange.IsNull()) return; thPos += ScaleDelta(dx); thDelta += ScaleDelta(dy); if (thDelta < deltaMin) thDelta = deltaMin; if (thDelta > deltaMax) thDelta = deltaMax; if (thPos < histoMinimum) thPos = histoMinimum; if (thPos > histoMaximum) thPos = histoMaximum; std::stringstream ss; ss << "Click on the cross and move the mouse" << "\n" << "\n" << "threshold at " << thPos << "\n" << "width " << thDelta * 2; m_InfoThreshold->setText(QString(ss.str().c_str())); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); // grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetScalarOpacityFunction(); f->RemoveAllPoints(); for (int r = 1; r <= 4; r++) { double relPos = r / 4.0; f->AddPoint(thPos + thDelta * (-relPos), stepFunctionThreshold(-relPos)); f->AddPoint(thPos + thDelta * (relPos), stepFunctionThreshold(relPos)); } f->Modified(); } // gradient at grayvalue->opacity { vtkPiecewiseFunction *f = tf->GetGradientOpacityFunction(); f->RemoveAllPoints(); f->AddPoint(0, 1.0); f->Modified(); } tf->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit SignalUpdateCanvas(); } QmitkTransferFunctionGeneratorWidget::~QmitkTransferFunctionGeneratorWidget() { } -void QmitkTransferFunctionGeneratorWidget::SetDataNode(mitk::DataNode *node) +void QmitkTransferFunctionGeneratorWidget::SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep) { histoGramm = nullptr; if (node) { tfpToChange = dynamic_cast(node->GetProperty("TransferFunction")); if (!tfpToChange) node->SetProperty("TransferFunction", tfpToChange = mitk::TransferFunctionProperty::New()); mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); if (mitk::Image *image = dynamic_cast(node->GetData())) { - mitk::ImageStatisticsHolder *statistics = image->GetStatistics(); + mitk::Image::Pointer inputImage = image; + if (image->GetTimeSteps() > 1) + { + if (!image->GetTimeGeometry()->IsValidTimeStep(timestep)) + { + return; + } + mitk::ImageTimeSelector::Pointer timeselector = mitk::ImageTimeSelector::New(); + timeselector->SetInput(image); + timeselector->SetTimeNr(timestep); + timeselector->UpdateLargestPossibleRegion(); + inputImage = timeselector->GetOutput(); + } + + mitk::ImageStatisticsHolder *statistics = inputImage->GetStatistics(); histoMinimum = statistics->GetScalarValueMin(); histoMaximum = statistics->GetScalarValueMax(); } else if (mitk::UnstructuredGrid *grid = dynamic_cast(node->GetData())) { double *range = grid->GetVtkUnstructuredGrid()->GetScalarRange(); histoMinimum = range[0]; histoMaximum = range[1]; double histoRange = histoMaximum - histoMinimum; deltaMax = histoRange / 4.0; deltaMin = histoRange / 400.0; deltaScale = histoRange / 1024.0; } else { MITK_WARN << "QmitkTransferFunctonGeneratorWidget does not support " << node->GetData()->GetNameOfClass() << " instances"; } thPos = (histoMinimum + histoMaximum) / 2.0; } else { tfpToChange = nullptr; m_InfoPreset->setText(QString("")); } } diff --git a/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp b/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp index bde5c54854..2f5e727ba6 100755 --- a/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkTransferFunctionWidget.cpp @@ -1,246 +1,263 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkTransferFunctionWidget.h" +#include "mitkImageTimeSelector.h" #include QmitkTransferFunctionWidget::QmitkTransferFunctionWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { this->setupUi(this); // signals and slots connections connect(m_XEditScalarOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueScalar(const QString &))); connect(m_YEditScalarOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetYValueScalar(const QString &))); connect(m_XEditGradientOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueGradient(const QString &))); connect(m_YEditGradientOpacity, SIGNAL(textEdited(const QString &)), this, SLOT(SetYValueGradient(const QString &))); connect(m_XEditColor, SIGNAL(textEdited(const QString &)), this, SLOT(SetXValueColor(const QString &))); m_RangeSlider->setMinimum(-2048); m_RangeSlider->setMaximum(2048); connect(m_RangeSlider, SIGNAL(valuesChanged(int, int)), this, SLOT(OnSpanChanged(int, int))); // reset button connect(m_RangeSliderReset, SIGNAL(pressed()), this, SLOT(OnResetSlider())); m_ScalarOpacityFunctionCanvas->SetQLineEdits(m_XEditScalarOpacity, m_YEditScalarOpacity); m_GradientOpacityCanvas->SetQLineEdits(m_XEditGradientOpacity, m_YEditGradientOpacity); m_ColorTransferFunctionCanvas->SetQLineEdits(m_XEditColor, nullptr); m_ScalarOpacityFunctionCanvas->SetTitle("Grayvalue -> Opacity"); m_GradientOpacityCanvas->SetTitle("Grayvalue/Gradient -> Opacity"); m_ColorTransferFunctionCanvas->SetTitle("Grayvalue -> Color"); } QmitkTransferFunctionWidget::~QmitkTransferFunctionWidget() { } void QmitkTransferFunctionWidget::SetScalarLabel(const QString &scalarLabel) { m_textLabelX->setText(scalarLabel); m_textLabelX_2->setText(scalarLabel); m_textLabelX_3->setText(scalarLabel); m_ScalarOpacityFunctionCanvas->SetTitle(scalarLabel + " -> Opacity"); m_GradientOpacityCanvas->SetTitle(scalarLabel + "/Gradient -> Opacity"); m_ColorTransferFunctionCanvas->SetTitle(scalarLabel + " -> Color"); } void QmitkTransferFunctionWidget::ShowScalarOpacityFunction(bool show) { m_ScalarOpacityWidget->setVisible(show); } void QmitkTransferFunctionWidget::ShowColorFunction(bool show) { m_ColorWidget->setVisible(show); } void QmitkTransferFunctionWidget::ShowGradientOpacityFunction(bool show) { m_GradientOpacityWidget->setVisible(show); } void QmitkTransferFunctionWidget::SetScalarOpacityFunctionEnabled(bool enable) { m_ScalarOpacityWidget->setEnabled(enable); } void QmitkTransferFunctionWidget::SetColorFunctionEnabled(bool enable) { m_ColorWidget->setEnabled(enable); } void QmitkTransferFunctionWidget::SetGradientOpacityFunctionEnabled(bool enable) { m_GradientOpacityWidget->setEnabled(enable); } -void QmitkTransferFunctionWidget::SetDataNode(mitk::DataNode *node, const mitk::BaseRenderer *renderer) +void QmitkTransferFunctionWidget::SetDataNode(mitk::DataNode *node, mitk::TimeStepType timestep, const mitk::BaseRenderer *renderer) { if (node) { tfpToChange = dynamic_cast(node->GetProperty("TransferFunction", renderer)); - if (!tfpToChange) { if (!dynamic_cast(node->GetData())) { MITK_WARN << "QmitkTransferFunctionWidget::SetDataNode called with non-image node"; goto turnOff; } node->SetProperty("TransferFunction", tfpToChange = mitk::TransferFunctionProperty::New()); } mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); - if (mitk::BaseData *data = node->GetData()) + if (mitk::Image *data = dynamic_cast(node->GetData())) { - mitk::SimpleHistogram *h = histogramCache[data]; + mitk::SimpleHistogram *h = nullptr; + if (data->GetTimeSteps() > 1) + { + if (!data->GetTimeGeometry()->IsValidTimeStep(timestep)) + { + return; + } + mitk::ImageTimeSelector::Pointer timeselector = mitk::ImageTimeSelector::New(); + timeselector->SetInput(data); + timeselector->SetTimeNr(timestep); + timeselector->UpdateLargestPossibleRegion(); + auto inputImage = timeselector->GetOutput(); + h = histogramCache[inputImage]; + } + else + { + h = histogramCache[data]; + } m_RangeSliderMin = h->GetMin(); m_RangeSliderMax = h->GetMax(); m_RangeSlider->blockSignals(true); m_RangeSlider->setMinimum(m_RangeSliderMin); m_RangeSlider->setMaximum(m_RangeSliderMax); m_RangeSlider->setMinimumValue(m_RangeSliderMin); m_RangeSlider->setMaximumValue(m_RangeSliderMax); m_RangeSlider->blockSignals(false); m_ScalarOpacityFunctionCanvas->SetHistogram(h); m_GradientOpacityCanvas->SetHistogram(h); m_ColorTransferFunctionCanvas->SetHistogram(h); } OnUpdateCanvas(); return; } turnOff: m_ScalarOpacityFunctionCanvas->setEnabled(false); m_ScalarOpacityFunctionCanvas->SetHistogram(nullptr); m_GradientOpacityCanvas->setEnabled(false); m_GradientOpacityCanvas->SetHistogram(nullptr); m_ColorTransferFunctionCanvas->setEnabled(false); m_ColorTransferFunctionCanvas->SetHistogram(nullptr); tfpToChange = nullptr; } void QmitkTransferFunctionWidget::OnUpdateCanvas() { if (tfpToChange.IsNull()) return; mitk::TransferFunction::Pointer tf = tfpToChange->GetValue(); if (tf.IsNull()) return; m_ScalarOpacityFunctionCanvas->SetPiecewiseFunction(tf->GetScalarOpacityFunction()); m_GradientOpacityCanvas->SetPiecewiseFunction(tf->GetGradientOpacityFunction()); m_ColorTransferFunctionCanvas->SetColorTransferFunction(tf->GetColorTransferFunction()); UpdateRanges(); m_ScalarOpacityFunctionCanvas->update(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); } void QmitkTransferFunctionWidget::SetXValueScalar(const QString text) { if (!text.endsWith(".")) { m_ScalarOpacityFunctionCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetYValueScalar(const QString text) { if (!text.endsWith(".")) { m_ScalarOpacityFunctionCanvas->SetY(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetXValueGradient(const QString text) { if (!text.endsWith(".")) { m_GradientOpacityCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetYValueGradient(const QString text) { if (!text.endsWith(".")) { m_GradientOpacityCanvas->SetY(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::SetXValueColor(const QString text) { if (!text.endsWith(".")) { m_ColorTransferFunctionCanvas->SetX(text.toFloat()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkTransferFunctionWidget::UpdateRanges() { int lower = m_RangeSlider->minimumValue(); int upper = m_RangeSlider->maximumValue(); m_ScalarOpacityFunctionCanvas->SetMin(lower); m_ScalarOpacityFunctionCanvas->SetMax(upper); m_GradientOpacityCanvas->SetMin(lower); m_GradientOpacityCanvas->SetMax(upper); m_ColorTransferFunctionCanvas->SetMin(lower); m_ColorTransferFunctionCanvas->SetMax(upper); } void QmitkTransferFunctionWidget::OnSpanChanged(int, int) { UpdateRanges(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); m_ScalarOpacityFunctionCanvas->update(); } void QmitkTransferFunctionWidget::OnResetSlider() { m_RangeSlider->blockSignals(true); m_RangeSlider->setMaximumValue(m_RangeSliderMax); m_RangeSlider->setMinimumValue(m_RangeSliderMin); m_RangeSlider->blockSignals(false); UpdateRanges(); m_GradientOpacityCanvas->update(); m_ColorTransferFunctionCanvas->update(); m_ScalarOpacityFunctionCanvas->update(); } diff --git a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h index a2710f1d7b..19eca9289d 100644 --- a/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h +++ b/Modules/SceneSerializationBase/include/mitkVectorPropertySerializer.h @@ -1,153 +1,151 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkVectorPropertySerializer_h #define mitkVectorPropertySerializer_h #include "mitkBasePropertySerializer.h" #include "mitkVectorProperty.h" #include #include namespace mitk { /** \brief Serializes a VectorProperty Serializes an instance of VectorProperty into a XML structure like \verbatim \endverbatim This class is implemented as a template and makes use of std::stringstream for necessary conversions of specific data types to and from string. For numeric types, the class adds a precision token to stringstream that should usually suffice. */ template class MITKSCENESERIALIZATIONBASE_EXPORT VectorPropertySerializer : public BasePropertySerializer { public: // Expand manually most of mitkClassMacro: // mitkClassMacro(VectorProperty, mitk::BaseProperty); // This manual expansion is done to override explicitely // the GetNameOfClass methods typedef VectorProperty PropertyType; typedef VectorPropertySerializer Self; typedef BasePropertySerializer SuperClass; typedef itk::SmartPointer Pointer; typedef itk::SmartPointer ConstPointer; std::vector GetClassHierarchy() const override { return mitk::GetClassHierarchy(); } // This function must return different // strings in function of the template parameter! // Serialization depends on this feature. static const char *GetStaticNameOfClass() { // concatenate a prefix dependent on the template type and our own classname static std::string nameOfClass = std::string(VectorPropertyDataType::prefix()) + "VectorPropertySerializer"; return nameOfClass.c_str(); } const char *GetNameOfClass() const override { return this->GetStaticNameOfClass(); } itkFactorylessNewMacro(Self); itkCloneMacro(Self); //! Build an XML version of this property tinyxml2::XMLElement* Serialize(tinyxml2::XMLDocument& doc) override { auto *listElement = doc.NewElement("Values"); if (const PropertyType *prop = dynamic_cast(m_Property.GetPointer())) { typename PropertyType::VectorType elements = prop->GetValue(); unsigned int index(0); for (auto listEntry : elements) { std::stringstream indexS; indexS << index++; auto *entryElement = doc.NewElement("Value"); entryElement->SetAttribute("idx", indexS.str().c_str()); entryElement->SetAttribute("value", boost::lexical_cast(listEntry).c_str()); listElement->InsertEndChild(entryElement); } return listElement; } else { return nullptr; } } //! Construct a property from an XML serialization BaseProperty::Pointer Deserialize(const tinyxml2::XMLElement *listElement) override { typename PropertyType::VectorType datalist; if (listElement) { - MITK_DEBUG << "Deserializing " << *listElement; - unsigned int index(0); std::string valueString; DATATYPE value; for (auto *valueElement = listElement->FirstChildElement("Value"); valueElement; valueElement = valueElement->NextSiblingElement("Value")) { valueString = valueElement->Attribute("value"); if (valueString.empty()) { MITK_ERROR << "Missing value attribute in list"; return nullptr; } try { value = boost::lexical_cast(valueString); } catch (boost::bad_lexical_cast &e) { MITK_ERROR << "Could not parse '" << valueString << "' as number: " << e.what(); return nullptr; } datalist.push_back(value); ++index; } typename PropertyType::Pointer property = PropertyType::New(); property->SetValue(datalist); return property.GetPointer(); } else { MITK_ERROR << "Missing tag."; } return nullptr; } }; typedef VectorPropertySerializer DoubleVectorPropertySerializer; typedef VectorPropertySerializer IntVectorPropertySerializer; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp index 63c827db0c..881f688fc3 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp @@ -1,205 +1,209 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkAutoMLSegmentationWithPreviewTool.h" #include "mitkImageAccessByItk.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include // ITK #include #include mitk::AutoMLSegmentationWithPreviewTool::AutoMLSegmentationWithPreviewTool() : AutoSegmentationWithPreviewTool(true) { } void mitk::AutoMLSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& regions) { if (m_SelectedLabels != regions) { m_SelectedLabels = regions; //Note: we do not call this->Modified() on puprose. Reason: changing the //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. } } const mitk::LabelSetImage* mitk::AutoMLSegmentationWithPreviewTool::GetMLPreview() const { if (m_MLPreviewNode.IsNotNull()) { const auto mlPreviewImage = dynamic_cast(this->m_MLPreviewNode->GetData()); return mlPreviewImage; } return nullptr; } mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType mitk::AutoMLSegmentationWithPreviewTool::GetSelectedLabels() const { return this->m_SelectedLabels; } void mitk::AutoMLSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_SelectedLabels = {}; m_MLPreviewNode = mitk::DataNode::New(); m_MLPreviewNode->SetProperty("name", StringProperty::New(std::string(this->GetName()) + "ML preview")); m_MLPreviewNode->SetProperty("helper object", BoolProperty::New(true)); m_MLPreviewNode->SetVisibility(true); m_MLPreviewNode->SetOpacity(1.0); this->GetToolManager()->GetDataStorage()->Add(m_MLPreviewNode); } void mitk::AutoMLSegmentationWithPreviewTool::Deactivated() { this->GetToolManager()->GetDataStorage()->Remove(m_MLPreviewNode); m_MLPreviewNode = nullptr; Superclass::Deactivated(); } void mitk::AutoMLSegmentationWithPreviewTool::UpdateCleanUp() { if (m_MLPreviewNode.IsNotNull()) m_MLPreviewNode->SetVisibility(m_SelectedLabels.empty()); if (nullptr != this->GetPreviewSegmentationNode()) this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedLabels.empty()); if (m_SelectedLabels.empty()) { this->ResetPreviewNode(); } } -void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +void mitk::AutoMLSegmentationWithPreviewTool::SetNodeProperties(LabelSetImage::Pointer newMLPreview) { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (nullptr == m_MLPreviewNode->GetData() - || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() - || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic - //segmentations have to compute a preview - //for all time steps on confirmation - || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg - //previews work with dynamic images - //with avoiding unnecessary other computations - ) - { - if (nullptr == inputAtTimeStep) - { - MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; - return; - } - - this->m_LastMLTimeStep = timeStep; - - auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); - - if (newMLPreview.IsNotNull()) + if (newMLPreview.IsNotNull()) { this->m_MLPreviewNode->SetData(newMLPreview); this->m_MLPreviewNode->SetProperty("binary", mitk::BoolProperty::New(false)); mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); this->m_MLPreviewNode->SetProperty("Image Rendering.Mode", renderingMode); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetHueRange(1.0, 0.0); lookupTable->SetSaturationRange(1.0, 1.0); lookupTable->SetValueRange(1.0, 1.0); lookupTable->SetTableRange(-1.0, 1.0); lookupTable->Build(); lut->SetVtkLookupTable(lookupTable); prop->SetLookupTable(lut); this->m_MLPreviewNode->SetProperty("LookupTable", prop); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); mitk::LevelWindow levelwindow; levelwindow.SetRangeMinMax(0, newMLPreview->GetStatistics()->GetScalarValueMax()); levWinProp->SetLevelWindow(levelwindow); this->m_MLPreviewNode->SetProperty("levelwindow", levWinProp); } +} + +void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +{ + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + if (nullptr == m_MLPreviewNode->GetData() + || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() + || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic + //segmentations have to compute a preview + //for all time steps on confirmation + || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg + //previews work with dynamic images + //with avoiding unnecessary other computations + ) + { + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastMLTimeStep = timeStep; + + auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); + this->SetNodeProperties(newMLPreview); } if (!m_SelectedLabels.empty()) { const auto mlPreviewImage = this->GetMLPreview(); if (nullptr != mlPreviewImage) { AccessByItk_n(mlPreviewImage, CalculateMergedSimplePreview, (previewImage, timeStep)); } } } template void mitk::AutoMLSegmentationWithPreviewTool::CalculateMergedSimplePreview(const itk::Image* itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; typename OutputImageType::Pointer itkBinaryResultImage; filter->SetInput(itkImage); filter->SetLowerThreshold(m_SelectedLabels[0]); filter->SetUpperThreshold(m_SelectedLabels[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); itkBinaryResultImage = filter->GetOutput(); itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions for (const auto labelID : m_SelectedLabels) { if (labelID != m_SelectedLabels[0]) { filter->SetLowerThreshold(labelID); filter->SetUpperThreshold(labelID); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); typename OutputImageType::Pointer tempImage = filter->GetOutput(); typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(tempImage); orFilter->SetInput2(itkBinaryResultImage); orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); orFilter->UpdateLargestPossibleRegion(); itkBinaryResultImage = orFilter->GetOutput(); } } //---------------------------------------------------------------------------------------------------- segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h index 878fd5592c..60921765c6 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h @@ -1,82 +1,82 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #define MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkLabelSetImage.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation and generates segmentations with multiple labels. This tool class implements the basic logic to handle previews of multi label segmentations and to allow to pick arbitrary labels as selected and merge them to a single segmentation to store this segmentation as confirmed segmentation. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoMLSegmentationWithPreviewTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(AutoMLSegmentationWithPreviewTool, AutoSegmentationWithPreviewTool); void Activated() override; void Deactivated() override; using SelectedLabelVectorType = std::vector; void SetSelectedLabels(const SelectedLabelVectorType& regions); SelectedLabelVectorType GetSelectedLabels() const; const LabelSetImage* GetMLPreview() const; protected: AutoMLSegmentationWithPreviewTool(); ~AutoMLSegmentationWithPreviewTool() = default; void UpdateCleanUp() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - + virtual void SetNodeProperties(LabelSetImage::Pointer); /** Function to generate the new multi lable preview for a given time step input image. * The function must be implemented by derived tools. * This function is called by DoUpdatePreview if needed. * Reasons are: * - ML preview does not exist * - Modify time of tools is newer then of ML preview * - ML preview was not generated for the current selected timestep of input image or for the current selected timepoint.*/ virtual LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) = 0; private: /** Function to generate a simple (single lable) preview by merging all labels of the ML preview that are selected and * copies that single label preview to the passed previewImage. * This function is called by DoUpdatePreview if needed. * @param mlPreviewImage Multi label preview that is the source. * @param previewImage Pointer to the single label preview image that should receive the merged selected labels. * @timeStep Time step of the previewImage that should be filled.*/ template void CalculateMergedSimplePreview(const itk::Image* mlImage, mitk::Image* segmentation, unsigned int timeStep); SelectedLabelVectorType m_SelectedLabels = {}; // holds the multilabel result as a preview image mitk::DataNode::Pointer m_MLPreviewNode; TimeStepType m_LastMLTimeStep = 0; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp new file mode 100644 index 0000000000..d65969bb8a --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.cpp @@ -0,0 +1,153 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkProcessExecutor.h" + +#include +#include +#include +#include + +namespace mitk +{ + std::string ProcessExecutor::GetOSDependendExecutableName(const std::string &name) + { +#if defined(_WIN32) + + if (itksys::SystemTools::GetFilenameLastExtension(name).empty()) + { + return name + ".exe"; + } + + return name; + +#else + auto result = itksys::SystemTools::GetFilenamePath(name); + if (EnsureCorrectOSPathSeparator(result).empty()) + { + return "./" + name; + } + else + { + return name; + } + +#endif + } + + std::string ProcessExecutor::EnsureCorrectOSPathSeparator(const std::string &path) + { + std::string ret = path; + +#ifdef _WIN32 + const std::string curSep = "\\"; + const char wrongSep = '/'; +#else + const std::string curSep = "/"; + const char wrongSep = '\\'; +#endif + + std::string::size_type pos = ret.find_first_of(wrongSep); + + while (pos != std::string::npos) + { + ret.replace(pos, 1, curSep); + + pos = ret.find_first_of(wrongSep); + } + + return ret; + } + + int ProcessExecutor::GetExitValue() { return this->m_ExitValue; }; + + bool ProcessExecutor::Execute(const std::string &executionPath, const ArgumentListType &argumentList) + { + std::vector pArguments_(argumentList.size() + 1); + + for (ArgumentListType::size_type index = 0; index < argumentList.size(); ++index) + { + pArguments_[index] = argumentList[index].c_str(); + } + pArguments_.push_back(nullptr); //terminating null element as required by ITK + + bool normalExit = false; + + try + { + itksysProcess *processID = itksysProcess_New(); + itksysProcess_SetCommand(processID, pArguments_.data()); + + itksysProcess_SetWorkingDirectory(processID, executionPath.c_str()); + + if (this->m_SharedOutputPipes) + { + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDOUT, 1); + itksysProcess_SetPipeShared(processID, itksysProcess_Pipe_STDERR, 1); + } + + itksysProcess_Execute(processID); + + char *rawOutput = nullptr; + int outputLength = 0; + while (true) + { + int dataStatus = itksysProcess_WaitForData(processID, &rawOutput, &outputLength, nullptr); + + if (dataStatus == itksysProcess_Pipe_STDOUT) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdOutEvent(data)); + } + else if (dataStatus == itksysProcess_Pipe_STDERR) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdErrEvent(data)); + } + else + { + break; + } + } + + itksysProcess_WaitForExit(processID, nullptr); + + auto state = static_cast(itksysProcess_GetState(processID)); + + normalExit = (state == itksysProcess_State_Exited); + this->m_ExitValue = itksysProcess_GetExitValue(processID); + } + catch (...) + { + throw; + } + return normalExit; + }; + + bool ProcessExecutor::Execute(const std::string &executionPath, + const std::string &executableName, + ArgumentListType argumentList) + { + std::string executableName_OS = GetOSDependendExecutableName(executableName); + argumentList.insert(argumentList.begin(), executableName_OS); + + return Execute(executionPath, argumentList); + } + + ProcessExecutor::ProcessExecutor() + { + this->m_ExitValue = 0; + this->m_SharedOutputPipes = false; + } + + ProcessExecutor::~ProcessExecutor() = default; +} // namespace mitk diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.h b/Modules/Segmentation/Interactions/mitkProcessExecutor.h new file mode 100644 index 0000000000..79856889b4 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.h @@ -0,0 +1,110 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +// Class is adapted from MatchPoint ProcessExecutor + +#ifndef __MITK_PROCESS_EXECUTOR_H +#define __MITK_PROCESS_EXECUTOR_H + +#include +#include +#include + +namespace mitk +{ + class ExternalProcessOutputEvent : public itk::AnyEvent + { + public: + typedef ExternalProcessOutputEvent Self; + typedef itk::AnyEvent Superclass; + + explicit ExternalProcessOutputEvent(const std::string &output = "") : m_Output(output) {} + ~ExternalProcessOutputEvent() override {} + + const char *GetEventName() const override { return "ExternalProcessOutputEvent"; } + bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } + itk::EventObject *MakeObject() const override { return new Self(m_Output); } + std::string GetOutput() const { return m_Output; } + + private: + std::string m_Output; + }; + +#define mitkProcessExecutorEventMacro(classname) \ + class classname : public ExternalProcessOutputEvent \ + { \ + public: \ + typedef classname Self; \ + typedef ExternalProcessOutputEvent Superclass; \ + \ + explicit classname(const std::string &output) : Superclass(output) {} \ + ~classname() override {} \ + \ + virtual const char *GetEventName() const { return #classname; } \ + virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ + virtual ::itk::EventObject *MakeObject() const { return new Self(this->GetOutput()); } \ + }; + + mitkProcessExecutorEventMacro(ExternalProcessStdOutEvent); + mitkProcessExecutorEventMacro(ExternalProcessStdErrEvent); + + /** + * @brief You may register an observer for an ExternalProcessOutputEvent, ExternalProcessStdOutEvent or + * ExternalProcessStdErrEvent in order to get notified of any output. + * @remark The events will only be invoked if the pipes are NOT(!) shared. By default the pipes are not shared. + * + */ + class MITKSEGMENTATION_EXPORT ProcessExecutor : public itk::Object + { + public: + using Self = ProcessExecutor; + using Superclass = ::itk::Object; + using Pointer = ::itk::SmartPointer; + using ConstPointer = ::itk::SmartPointer; + + itkTypeMacro(ProcessExecutor, ::itk::Object); + itkFactorylessNewMacro(Self); + + itkSetMacro(SharedOutputPipes, bool); + itkGetConstMacro(SharedOutputPipes, bool); + + using ArgumentListType = std::vector; + + bool Execute(const std::string &executionPath, const std::string &executableName, ArgumentListType argumentList); + + /** + * @brief Executes the process. This version assumes that the executable name is the first argument in the argument + * list and has already been converted to its OS dependent name via the static convert function of this class. + */ + bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); + + int GetExitValue(); + static std::string EnsureCorrectOSPathSeparator(const std::string &); + + static std::string GetOSDependendExecutableName(const std::string &name); + + protected: + ProcessExecutor(); + ~ProcessExecutor() override; + + int m_ExitValue; + + /** + * @brief Specifies if the child process should share the output pipes (true) or not (false). + * If pipes are not shared the output will be passed by invoking ExternalProcessOutputEvents + * @remark The events will only be invoked if the pipes are NOT(!) shared. + */ + bool m_SharedOutputPipes; + }; + +} // namespace mitk +#endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp new file mode 100644 index 0000000000..c2db0bcca3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -0,0 +1,322 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitknnUnetTool.h" + +#include "mitkIOUtil.h" +#include "mitkProcessExecutor.h" +#include +#include +#include +#include +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); +} + +mitk::nnUNetTool::nnUNetTool() +{ + this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); +} + +mitk::nnUNetTool::~nnUNetTool() +{ + itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); +} + +void mitk::nnUNetTool::Activated() +{ + Superclass::Activated(); + m_InputOutputPair = std::make_pair(nullptr, nullptr); +} + +void mitk::nnUNetTool::UpdateCleanUp() +{ + // This overriden method is intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. +} + +void mitk::nnUNetTool::RenderOutputBuffer() +{ + if (this->m_OutputBuffer != nullptr) + { + Superclass::SetNodeProperties(this->m_OutputBuffer); + this->ClearOutputBuffer(); + try + { + if (nullptr != this->GetPreviewSegmentationNode()) + { + this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); + } + if (this->GetSelectedLabels().empty()) + { + this->ResetPreviewNode(); + } + } + catch (const mitk::Exception &e) + { + MITK_INFO << e.GetDescription(); + } + } +} + +void mitk::nnUNetTool::SetNodeProperties(LabelSetImage::Pointer segmentation) +{ + // This overriden method doesn't set node properties. Intentionally left out for setting later upon demand + // in the `RenderOutputBuffer` method. + this->m_OutputBuffer = segmentation; +} + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() +{ + return this->m_OutputBuffer; +} + +void mitk::nnUNetTool::ClearOutputBuffer() +{ + this->m_OutputBuffer = nullptr; +} + +us::ModuleResource mitk::nnUNetTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); + return resource; +} + +const char **mitk::nnUNetTool::GetXPM() const +{ + return nullptr; +} + +const char *mitk::nnUNetTool::GetName() const +{ + return "nnUNet"; +} + +mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() +{ + return this->GetToolManager()->GetDataStorage(); +} + +mitk::DataNode *mitk::nnUNetTool::GetRefNode() +{ + return this->GetToolManager()->GetReferenceData(0); +} + +namespace +{ + void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) + { + std::string testCOUT; + std::string testCERR; + const auto *pEvent = dynamic_cast(&e); + + if (pEvent) + { + testCOUT = testCOUT + pEvent->GetOutput(); + MITK_INFO << testCOUT; + } + + const auto *pErrEvent = dynamic_cast(&e); + + if (pErrEvent) + { + testCERR = testCERR + pErrEvent->GetOutput(); + MITK_ERROR << testCERR; + } + } +} // namespace + +mitk::LabelSetImage::Pointer mitk::nnUNetTool::ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType /*timeStep*/) +{ + if (m_InputOutputPair.first == inputAtTimeStep) + { + return m_InputOutputPair.second; + } + std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; + std::string templateFilename = "XXXXXX_000_0000.nii.gz"; + + ProcessExecutor::Pointer spExec = ProcessExecutor::New(); + itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&onPythonProcessEvent); + spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + ProcessExecutor::ArgumentListType args; + + inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); + std::ofstream tmpStream; + inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, templateFilename, inDir + IOUtil::GetDirectorySeparator()); + tmpStream.close(); + std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); + std::string fileName = inputImagePath.substr(found + 1); + std::string token = fileName.substr(0, fileName.find("_")); + + if (this->GetNoPip()) + { + scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; + } + + try + { + IOUtil::Save(inputAtTimeStep, inputImagePath); + + if (this->GetMultiModal()) + { + for (size_t i = 0; i < this->m_OtherModalPaths.size(); ++i) + { + mitk::Image::ConstPointer modalImage = this->m_OtherModalPaths[i]; + std::string outModalFile = + inDir + IOUtil::GetDirectorySeparator() + token + "_000_000" + std::to_string(i + 1) + ".nii.gz"; + IOUtil::Save(modalImage.GetPointer(), outModalFile); + } + } + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + // Code calls external process + std::string command = "nnUNet_predict"; + if (this->GetNoPip()) + { +#ifdef _WIN32 + command = "python"; +#else + command = "python3"; +#endif + } + for (ModelParams &modelparam : m_ParamQ) + { + outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + modelparam.outputDir = outDir; + args.clear(); + if (this->GetNoPip()) + { + args.push_back(scriptPath); + } + args.push_back("-i"); + args.push_back(inDir); + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-t"); + args.push_back(modelparam.task); + + if (modelparam.model.find("cascade") != std::string::npos) + { + args.push_back("-ctr"); + } + else + { + args.push_back("-tr"); + } + args.push_back(modelparam.trainer); + + args.push_back("-m"); + args.push_back(modelparam.model); + + args.push_back("-p"); + args.push_back(modelparam.planId); + + if (!modelparam.folds.empty()) + { + args.push_back("-f"); + for (auto fold : modelparam.folds) + { + args.push_back(fold); + } + } + + args.push_back("--num_threads_nifti_save"); + args.push_back("1"); // fixing to 1 + + if (!this->GetMirror()) + { + args.push_back("--disable_tta"); + } + + if (!this->GetMixedPrecision()) + { + args.push_back("--disable_mixed_precision"); + } + + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.push_back("--save_npz"); + } + + try + { + std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); + itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); + std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); + itksys::SystemTools::PutEnv(cudaEnv.c_str()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } + } + if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) + { + args.clear(); + command = "nnUNet_ensemble"; + outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); + outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; + + args.push_back("-f"); + for (ModelParams &modelparam : m_ParamQ) + { + args.push_back(modelparam.outputDir); + } + + args.push_back("-o"); + args.push_back(outDir); + + args.push_back("-pp"); + args.push_back(this->GetPostProcessingJsonDirectory()); + + spExec->Execute(this->GetPythonPath(), command, args); + } + try + { + LabelSetImage::Pointer resultImage = LabelSetImage::New(); + Image::Pointer outputImage = IOUtil::Load(outputImagePath); + resultImage->InitializeByLabeledImage(outputImage); + resultImage->SetGeometry(inputAtTimeStep->GetGeometry()); + m_InputOutputPair = std::make_pair(inputAtTimeStep, resultImage); + return resultImage; + } + catch (const mitk::Exception &e) + { + /* + Can't throw mitk exception to the caller. Refer: T28691 + */ + MITK_ERROR << e.GetDescription(); + return nullptr; + } +} diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h new file mode 100644 index 0000000000..9e5a31cac3 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -0,0 +1,196 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitknnUnetTool_h_Included +#define mitknnUnetTool_h_Included + +#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkCommon.h" +#include "mitkToolManager.h" +#include +#include +#include + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + /** + * @brief nnUNet parameter request object holding all model parameters for input. + * Also holds output temporary directory path. + */ + struct ModelParams + { + std::string task; + std::vector folds; + std::string model; + std::string trainer; + std::string planId; + std::string outputDir; + }; + + /** + \brief nnUNet segmentation tool. + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + */ + class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoMLSegmentationWithPreviewTool + { + public: + mitkClassMacro(nnUNetTool, AutoMLSegmentationWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + const char **GetXPM() const override; + const char *GetName() const override; + us::ModuleResource GetIconResource() const override; + + void Activated() override; + + itkSetMacro(nnUNetDirectory, std::string); + itkGetConstMacro(nnUNetDirectory, std::string); + + itkSetMacro(ModelDirectory, std::string); + itkGetConstMacro(ModelDirectory, std::string); + + itkSetMacro(PythonPath, std::string); + itkGetConstMacro(PythonPath, std::string); + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + itkSetMacro(PostProcessingJsonDirectory, std::string); + itkGetConstMacro(PostProcessingJsonDirectory, std::string); + + itkSetMacro(MixedPrecision, bool); + itkGetConstMacro(MixedPrecision, bool); + itkBooleanMacro(MixedPrecision); + + itkSetMacro(Mirror, bool); + itkGetConstMacro(Mirror, bool); + itkBooleanMacro(Mirror); + + itkSetMacro(MultiModal, bool); + itkGetConstMacro(MultiModal, bool); + itkBooleanMacro(MultiModal); + + itkSetMacro(NoPip, bool); + itkGetConstMacro(NoPip, bool); + itkBooleanMacro(NoPip); + + itkSetMacro(Ensemble, bool); + itkGetConstMacro(Ensemble, bool); + itkBooleanMacro(Ensemble); + + itkSetMacro(Predict, bool); + itkGetConstMacro(Predict, bool); + itkBooleanMacro(Predict); + + itkSetMacro(GpuId, unsigned int); + itkGetConstMacro(GpuId, unsigned int); + + /** + * @brief vector of ModelParams. + * Size > 1 only for ensemble prediction. + */ + std::vector m_ParamQ; + + /** + * @brief Holds paths to other input image modalities. + * + */ + std::vector m_OtherModalPaths; + + std::pair m_InputOutputPair; + + /** + * @brief Renders the output LabelSetImage. + * To called in the main thread. + */ + void RenderOutputBuffer(); + + /** + * @brief Get the Output Buffer object + * + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer GetOutputBuffer(); + + /** + * @brief Sets the outputBuffer to nullptr + * + */ + void ClearOutputBuffer(); + + /** + * @brief Returns the DataStorage from the ToolManager + */ + mitk::DataStorage *GetDataStorage(); + + mitk::DataNode *GetRefNode(); + + protected: + /** + * @brief Construct a new nnUNet Tool object and temp directory. + * + */ + nnUNetTool(); + + /** + * @brief Destroy the nnUNet Tool object and deletes the temp directory. + * + */ + ~nnUNetTool(); + + /** + * @brief Overriden method from the tool manager to execute the segmentation + * Implementation: + * 1. Saves the inputAtTimeStep in a temporary directory. + * 2. Copies other modalities, renames and saves in the temporary directory, if required. + * 3. Sets RESULTS_FOLDER and CUDA_VISIBLE_DEVICES variables in the environment. + * 3. Iterates through the parameter queue (m_ParamQ) and executes "nnUNet_predict" command with the parameters + * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as + * LabelSetImage and returns. + * + * @param inputAtTimeStep + * @param timeStep + * @return LabelSetImage::Pointer + */ + LabelSetImage::Pointer ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType timeStep) override; + void UpdateCleanUp() override; + void SetNodeProperties(LabelSetImage::Pointer) override; + + private: + std::string m_MitkTempDir; + std::string m_nnUNetDirectory; + std::string m_ModelDirectory; + std::string m_PythonPath; + std::string m_PostProcessingJsonDirectory; + // bool m_UseGPU; kept for future + // bool m_AllInGPU; + bool m_MixedPrecision; + bool m_Mirror; + bool m_NoPip; + bool m_MultiModal; + bool m_Ensemble = false; + bool m_Predict; + LabelSetImage::Pointer m_OutputBuffer; + unsigned int m_GpuId; + }; +} // namespace mitk +#endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index c4c3643e85..02b759d7b4 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,115 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingBaseTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp + Interactions/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO + Interactions/mitkProcessExecutor.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml Interactions/PickingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h new file mode 100644 index 0000000000..881cb52567 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetEnsembleLayout.h @@ -0,0 +1,86 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file.s + +============================================================================*/ + +#ifndef QmitknnUNetEnsembleLayout_h_Included +#define QmitknnUNetEnsembleLayout_h_Included + + +#include +#include +#include +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetTaskParamsUITemplate +{ + +public: + QLabel* trainerLabel; + ctkComboBox* trainerBox; + QLabel* plannerLabel; + ctkComboBox* plannerBox; + QLabel* foldLabel; + ctkCheckableComboBox* foldBox; + QLabel* modelLabel; + ctkComboBox* modelBox; + QWidget* parent; + + QmitknnUNetTaskParamsUITemplate(QWidget* inputGroupBox_1) + { + this->parent = inputGroupBox_1; + QVBoxLayout* verticalLayout_x = new QVBoxLayout(inputGroupBox_1); + verticalLayout_x->setObjectName(QString::fromUtf8("verticalLayout_x")); + QGridLayout* g_x = new QGridLayout(); +#ifndef Q_OS_MAC + g_x->setSpacing(6); +#endif +#ifndef Q_OS_MAC + g_x->setContentsMargins(0, 0, 0, 0); +#endif + g_x->setObjectName(QString::fromUtf8("g_2")); + + modelLabel = new QLabel("Configuration", inputGroupBox_1); + g_x->addWidget(modelLabel, 0, 0, 1, 1); + trainerLabel = new QLabel("Trainer", inputGroupBox_1); + g_x->addWidget(trainerLabel, 0, 1, 1, 1); + + modelBox = new ctkComboBox(inputGroupBox_1); + modelBox->setObjectName(QString::fromUtf8("modelBox_1")); + g_x->addWidget(modelBox, 1, 0, 1, 1); + trainerBox = new ctkComboBox(inputGroupBox_1); + trainerBox->setObjectName(QString::fromUtf8("trainerBox_1")); + g_x->addWidget(trainerBox, 1, 1, 1, 1); + + plannerLabel = new QLabel("Planner", inputGroupBox_1); + g_x->addWidget(plannerLabel, 2, 0, 1, 1); + foldLabel = new QLabel("Fold", inputGroupBox_1); + g_x->addWidget(foldLabel, 2, 1, 1, 1); + + plannerBox = new ctkComboBox(inputGroupBox_1); + plannerBox->setObjectName(QString::fromUtf8("plannerBox_1")); + g_x->addWidget(plannerBox, 3, 0, 1, 1); + foldBox = new ctkCheckableComboBox(inputGroupBox_1); + foldBox->setObjectName(QString::fromUtf8("foldBox_1")); + g_x->addWidget(foldBox, 3, 1, 1, 1); + + verticalLayout_x->addLayout(g_x); + } +}; +QT_END_NAMESPACE + +#endif + diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h new file mode 100644 index 0000000000..d005f62eb3 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h @@ -0,0 +1,281 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file.s + +============================================================================*/ + +#ifndef QmitknnUNetFolderParser_h_Included +#define QmitknnUNetFolderParser_h_Included + +#include "QmitknnUNetToolGUI.h" +#include +#include +#include + +/** + * @brief Struct to store each (Folder) Node of the hierarchy tree structure. + * + */ +struct FolderNode +{ + QString name; + QString path; // parent + std::vector> subFolders; +}; + +/** + * @brief Class to store and retreive folder hierarchy information + * of RESULTS_FOLDER. Only Root node is explicitly stored in m_RootNode. + * No. of sub levels in the hierachry is defined in the LEVEL constant. + * + */ +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetFolderParser +{ +public: + + /** + * @brief Construct a new QmitknnUNetFolderParser object + * Initializes root folder node object pointer calls + * @param parentFolder + */ + QmitknnUNetFolderParser(const QString parentFolder) + { + m_RootNode = std::make_shared(); + m_RootNode->path = parentFolder; + m_RootNode->name = QString("nnUNet"); + m_RootNode->subFolders.clear(); + InitDirs(m_RootNode, 0); + } + + /** + * @brief Destroy the QmitknnUNetFolderParser object + * + */ + ~QmitknnUNetFolderParser() = default; /*{ DeleteDirs(m_RootNode, LEVEL); }*/ + + /** + * @brief Returns the "Results Folder" string which is parent path of the root node. + * + * @return QString + */ + QString getResultsFolder() { return m_RootNode->path; } + + /** + * @brief Returns the Model Names from root node. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelNames() + { + auto models = GetSubFolderNamesFromNode(m_RootNode); + return models; + } + + /** + * @brief Returns the task names for a given model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTasksForModel(const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + auto tasks = GetSubFolderNamesFromNode(modelNode); + return tasks; + } + + /** + * @brief Returns the models names for a given task. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getModelsForTask(const QString &taskName) + { + T modelsForTask; + auto models = GetSubFolderNamesFromNode(m_RootNode); + foreach (QString model, models) + { + QStringList taskList = getTasksForModel(model); + if (taskList.contains(taskName, Qt::CaseInsensitive)) + { + modelsForTask << model; + } + } + return modelsForTask; + } + + /** + * @brief Returns the trainer / planner names for a given task & model. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getTrainerPlannersForTask(const QString &taskName, const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + auto tps = GetSubFolderNamesFromNode(taskNode); + return tps; + } + + /** + * @brief Returns the Folds names for a given trainer,planner,task & model name. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param trainer + * @param planner + * @param taskName + * @param modelName + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T getFoldsForTrainerPlanner(const QString &trainer, + const QString &planner, + const QString &taskName, + const QString &modelName) + { + std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); + std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); + QString trainerPlanner = trainer + QString("__") + planner; + std::shared_ptr tpNode = GetSubNodeMatchingNameCrietria(trainerPlanner, taskNode); + auto folds = GetSubFolderNamesFromNode(tpNode); + return folds; + } + +private: + const int m_LEVEL = 4; + std::shared_ptr m_RootNode; + + /** + * @brief Iterates through the root node and returns the sub FolderNode object Matching Name Crietria + * + * @param queryName + * @param parentNode + * @return std::shared_ptr + */ + std::shared_ptr GetSubNodeMatchingNameCrietria(const QString &queryName, + std::shared_ptr parentNode) + { + std::shared_ptr retNode; + std::vector> subNodes = parentNode->subFolders; + for (std::shared_ptr node : subNodes) + { + if (node->name == queryName) + { + retNode = node; + break; + } + } + return retNode; + } + + /** + * @brief Returns the sub folder names for a folder node object. Template function, + * type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param std::shared_ptr + * @return T (any of stl or Qt containers which supports push_back call) + */ + template + T GetSubFolderNamesFromNode(const std::shared_ptr parent) + { + T folders; + std::vector> subNodes = parent->subFolders; + for (std::shared_ptr folder : subNodes) + { + folders.push_back(folder->name); + } + return folders; + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and create a tree structure. + * + * @param parent + * @param level + */ + void InitDirs(std::shared_ptr parent, int level) + { + QString searchFolder = parent->path + QDir::separator() + parent->name; + auto subFolders = FetchFoldersFromDir(searchFolder); + level++; + foreach (QString folder, subFolders) + { + std::shared_ptr fp = std::make_shared(); + fp->path = searchFolder; + fp->name = folder; + if (level < this->m_LEVEL) + { + InitDirs(fp, level); + } + parent->subFolders.push_back(fp); + } + } + + /** + * @brief Iterates through the sub folder hierarchy upto a level provided + * and clears the sub folder std::vector from each node. + * + * @param parent + * @param level + */ + void DeleteDirs(std::shared_ptr parent, int level) + { + level++; + for (std::shared_ptr subFolder : parent->subFolders) + { + if (level < m_LEVEL) + { + DeleteDirs(subFolder, level); + } + parent->subFolders.clear(); + } + } + + /** + * @brief Template function to fetch all folders inside a given path. + * The type can be any of stl or Qt containers which supports push_back call. + * + * @tparam T + * @param path + * @return T + */ + template + T FetchFoldersFromDir(const QString &path) + { + T folders; + for (QDirIterator it(path, QDir::AllDirs, QDirIterator::NoIteratorFlags); it.hasNext();) + { + it.next(); + if (!it.fileName().startsWith('.')) + { + folders.push_back(it.fileName()); + } + } + return folders; + } +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h new file mode 100644 index 0000000000..6cd9532f46 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.h @@ -0,0 +1,83 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGPU_h_Included +#define QmitknnUNetToolGPU_h_Included + +#include +#include +#include +#include + +/** + * @brief Struct to store GPU info. + * + */ +struct QmitkGPUSpec +{ + QString name; + int memoryFree; + unsigned int id; +}; + +/** + * @brief Class to load and save GPU information + * for further validation + */ +class QmitkGPULoader : public QObject +{ + Q_OBJECT + +private: + std::vector m_Gpus; + +public: + /** + * @brief Construct a new Qmitk GPU Loader object. + * Parses GPU info using `nvidia-smi` command and saves it as QmitkGPUSpec objects. + */ + QmitkGPULoader() + { + QProcess process; + process.start("nvidia-smi --query-gpu=name,memory.free --format=csv"); + process.waitForFinished(-1); + QStringList infoStringList; + while (process.canReadLine()) + { + QString line = process.readLine(); + if (!line.startsWith("name")) + { + infoStringList << line; + } + } + foreach (QString infoString, infoStringList) + { + QmitkGPUSpec spec; + QStringList gpuDetails; + gpuDetails = infoString.split(","); + spec.name = gpuDetails.at(0); + // spec.id = id; + spec.memoryFree = gpuDetails.at(1).split(" ")[0].toInt(); + this->m_Gpus.push_back(spec); + } + } + ~QmitkGPULoader() = default; + + /** + * @brief Returns the number of GPUs parsed and saved as QmitkGPUSpec objects. + * + * @return int + */ + int GetGPUCount() { return static_cast(m_Gpus.size()); } +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp new file mode 100644 index 0000000000..ea0b15c38b --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -0,0 +1,328 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitknnUNetToolGUI.h" + +#include "mitknnUnetTool.h" +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") + +QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkAutoMLSegmentationToolGUIBase() +{ + // Nvidia-smi command returning zero doesn't alway mean lack of GPUs. + // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. + if (m_GpuLoader.GetGPUCount() == 0) + { + std::string warning = "WARNING: No GPUs were detected on your machine. The nnUNet tool might not work."; + ShowErrorMessage(warning); + } + + // define predicates for multi modal data selection combobox + auto imageType = mitk::TNodePredicateDataType::New(); + auto labelSetImageType = mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New()); + this->m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); +} + +void QmitknnUNetToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) +{ + Superclass::ConnectNewTool(newTool); + newTool->IsTimePointChangeAwareOff(); +} + +void QmitknnUNetToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); +#ifndef _WIN32 + m_Controls.pythonEnvComboBox->addItem("/usr/bin"); +#endif + m_Controls.pythonEnvComboBox->addItem("Select"); + AutoParsePythonPaths(); + connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewRequested())); + connect(m_Controls.modeldirectoryBox, + SIGNAL(directoryChanged(const QString &)), + this, + SLOT(OnDirectoryChanged(const QString &))); + connect( + m_Controls.modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); + connect(m_Controls.taskBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTaskChanged(const QString &))); + connect( + m_Controls.plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); + connect(m_Controls.nopipBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); + connect(m_Controls.multiModalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalitiesNumberChanged(int))); + connect(m_Controls.posSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalPositionChanged(int))); + connect(m_Controls.pythonEnvComboBox, +#if QT_VERSION >= 0x050F00 // 5.15 + SIGNAL(textActivated(const QString &)), +#elif QT_VERSION >= 0x050C00 // 5.12 + SIGNAL(activated(const QString &)), +#endif + this, + SLOT(OnPythonPathChanged(const QString &))); + connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); + + m_Controls.codedirectoryBox->setVisible(false); + m_Controls.nnUnetdirLabel->setVisible(false); + m_Controls.multiModalSpinBox->setVisible(false); + m_Controls.multiModalSpinLabel->setVisible(false); + m_Controls.posSpinBoxLabel->setVisible(false); + m_Controls.posSpinBox->setVisible(false); + m_Controls.previewButton->setEnabled(false); + + QSize sz(16, 16); + m_Controls.stopButton->setVisible(false); + + QIcon refreshIcon; + refreshIcon.addPixmap(style()->standardIcon(QStyle::SP_BrowserReload).pixmap(sz), QIcon::Normal, QIcon::Off); + m_Controls.refreshdirectoryBox->setIcon(refreshIcon); + m_Controls.refreshdirectoryBox->setEnabled(true); + + m_Controls.statusLabel->setTextFormat(Qt::RichText); + m_Controls.statusLabel->setText("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + + " GPUs were detected."); + + if (m_GpuLoader.GetGPUCount() != 0) + { + m_Controls.gpuSpinBox->setMaximum(m_GpuLoader.GetGPUCount() - 1); + } + mainLayout->addLayout(m_Controls.verticalLayout); + Superclass::InitializeUI(mainLayout); + m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. +} + +void QmitknnUNetToolGUI::OnPreviewRequested() +{ + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + try + { + m_Controls.previewButton->setEnabled(false); // To prevent misclicked back2back prediction. + qApp->processEvents(); + tool->PredictOn(); // purposefully placed to make tool->GetMTime different than before. + QString modelName = m_Controls.modelBox->currentText(); + if (modelName.startsWith("ensemble", Qt::CaseInsensitive)) + { + ProcessEnsembleModelsParams(tool); + } + else + { + ProcessModelParams(tool); + } + QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); + QString pythonPath = pythonPathTextItem.mid(pythonPathTextItem.indexOf(" ") + 1); + bool isNoPip = m_Controls.nopipBox->isChecked(); +#ifdef _WIN32 + if (!isNoPip && !(pythonPath.endsWith("Scripts", Qt::CaseInsensitive) || + pythonPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(pythonPath.endsWith("bin", Qt::CaseInsensitive) || pythonPath.endsWith("bin/", Qt::CaseInsensitive))) + { + pythonPath += QDir::separator() + QString("bin"); + } +#endif + std::string nnUNetDirectory; + if (isNoPip) + { + nnUNetDirectory = m_Controls.codedirectoryBox->directory().toStdString(); + } + else if (!IsNNUNetInstalled(pythonPath)) + { + throw std::runtime_error("nnUNet is not detected in the selected python environment. Please select a valid " + "python environment or install nnUNet."); + } + + tool->SetnnUNetDirectory(nnUNetDirectory); + tool->SetPythonPath(pythonPath.toStdString()); + tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); + // checkboxes + tool->SetMirror(m_Controls.mirrorBox->isChecked()); + tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); + tool->SetNoPip(isNoPip); + tool->SetMultiModal(m_Controls.multiModalBox->isChecked()); + // Spinboxes + tool->SetGpuId(static_cast(m_Controls.gpuSpinBox->value())); + // Multi-Modal + tool->MultiModalOff(); + if (m_Controls.multiModalBox->isChecked()) + { + tool->m_OtherModalPaths.clear(); + tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); + tool->MultiModalOn(); + } + tool->m_InputOutputPair = std::make_pair(nullptr, nullptr); + m_Controls.statusLabel->setText("STATUS: Starting Segmentation task... This might take a while."); + tool->UpdatePreview(); + if (tool->GetOutputBuffer() == nullptr) + { + SegmentationProcessFailed(); + } + SegmentationResultHandler(tool); + tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. + m_Controls.previewButton->setEnabled(true); + } + catch (const std::exception &e) + { + std::stringstream errorMsg; + errorMsg << "Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); + ShowErrorMessage(errorMsg.str()); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + catch (...) + { + std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; + ShowErrorMessage(errorMsg); + m_Controls.previewButton->setEnabled(true); + tool->PredictOff(); + return; + } + } +} + +std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() +{ + std::vector paths; + if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) + { + for (QmitkDataStorageComboBox *modality : m_Modalities) + { + if (modality->objectName() != "multiModal_0") + { + paths.push_back(dynamic_cast(modality->GetSelectedNode()->GetData())); + } + } + } + return paths; +} + +bool QmitknnUNetToolGUI::IsNNUNetInstalled(const QString &pythonPath) +{ + QString fullPath = pythonPath; +#ifdef _WIN32 + if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + } +#else + if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + } +#endif + fullPath = fullPath.mid(fullPath.indexOf(" ") + 1); + return QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")); +} + +void QmitknnUNetToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) +{ + this->setCursor(Qt::ArrowCursor); + QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); + messageBox->exec(); + delete messageBox; + MITK_WARN << message; +} + +void QmitknnUNetToolGUI::ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer tool) +{ + if (m_EnsembleParams[0]->modelBox->currentText() == m_EnsembleParams[1]->modelBox->currentText()) + { + throw std::runtime_error("Both models you have selected for ensembling are the same."); + } + QString taskName = m_Controls.taskBox->currentText(); + std::vector requestQ; + QString ppDirFolderNamePart1 = "ensemble_"; + QStringList ppDirFolderNameParts; + for (std::unique_ptr &layout : m_EnsembleParams) + { + QStringList ppDirFolderName; + QString modelName = layout->modelBox->currentText(); + ppDirFolderName << modelName; + ppDirFolderName << "__"; + QString trainer = layout->trainerBox->currentText(); + ppDirFolderName << trainer; + ppDirFolderName << "__"; + QString planId = layout->plannerBox->currentText(); + ppDirFolderName << planId; + + if (!IsModelExists(modelName, taskName, QString(trainer + "__" + planId))) + { + std::string errorMsg = "The configuration " + modelName.toStdString() + + " you have selected doesn't exist. Check your Results Folder again."; + throw std::runtime_error(errorMsg); + } + std::vector testfold; // empty vector to consider all folds for inferencing. + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); + requestQ.push_back(modelObject); + ppDirFolderNameParts << ppDirFolderName.join(QString("")); + } + tool->EnsembleOn(); + + QString ppJsonFilePossibility1 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.first() + "--" + + ppDirFolderNameParts.last() + QDir::separator() + "postprocessing.json"); + QString ppJsonFilePossibility2 = QDir::cleanPath( + m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.last() + "--" + + ppDirFolderNameParts.first() + QDir::separator() + "postprocessing.json"); + + if (QFile(ppJsonFilePossibility1).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility1.toStdString()); + } + else if (QFile(ppJsonFilePossibility2).exists()) + { + tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); + } + else + { + // warning message + } + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +void QmitknnUNetToolGUI::ProcessModelParams(mitk::nnUNetTool::Pointer tool) +{ + tool->EnsembleOff(); + std::vector requestQ; + QString modelName = m_Controls.modelBox->currentText(); + QString taskName = m_Controls.taskBox->currentText(); + QString trainer = m_Controls.trainerBox->currentText(); + QString planId = m_Controls.plannerBox->currentText(); + std::vector fetchedFolds = FetchSelectedFoldsFromUI(); + mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, fetchedFolds); + requestQ.push_back(modelObject); + tool->m_ParamQ.clear(); + tool->m_ParamQ = requestQ; +} + +bool QmitknnUNetToolGUI::IsModelExists(const QString &modelName, const QString &taskName, const QString &trainerPlanner) +{ + QString modelSearchPath = + QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + modelName + + QDir::separator() + taskName + QDir::separator() + trainerPlanner); + if (QDir(modelSearchPath).exists()) + { + return true; + } + return false; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h new file mode 100644 index 0000000000..583151c47a --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h @@ -0,0 +1,222 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file.s + +============================================================================*/ + +#ifndef QmitknnUNetToolGUI_h_Included +#define QmitknnUNetToolGUI_h_Included + +#include "QmitkAutoMLSegmentationToolGUIBase.h" +#include "QmitknnUNetFolderParser.h" +#include "QmitknnUNetGPU.h" +#include "mitknnUnetTool.h" +#include "ui_QmitknnUNetToolGUIControls.h" +#include +#include +#include +#include + +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetToolGUI : public QmitkAutoMLSegmentationToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitknnUNetToolGUI, QmitkAutoMLSegmentationToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots: + + /** + * @brief Qt slot + * + */ + void OnPreviewRequested(); + + /** + * @brief Qt slot + * + */ + void OnDirectoryChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnModelChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTaskChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnTrainerChanged(const QString &); + + /** + * @brief Qt slot + * + */ + void OnCheckBoxChanged(int); + + /** + * @brief Qthread slot to capture failures from thread worker and + * shows error message + * + */ + void SegmentationProcessFailed(); + + /** + * @brief Qthread to capture sucessfull nnUNet segmentation. + * Further, renders the LabelSet image + */ + void SegmentationResultHandler(mitk::nnUNetTool *); + + /** + * @brief Qt Slot + * + */ + void OnModalitiesNumberChanged(int); + + /** + * @brief Qt Slot + * + */ + void OnPythonPathChanged(const QString &); + + /** + * @brief Qt Slot + * + */ + void OnModalPositionChanged(int); + + /** + * @brief Qt slot + * + */ + void OnRefreshPresssed(); + +protected: + QmitknnUNetToolGUI(); + ~QmitknnUNetToolGUI() = default; + + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) override; + void InitializeUI(QBoxLayout *mainLayout) override; + void EnableWidgets(bool enabled) override; + +private: + /** + * @brief Parses the ensemble UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Parses the UI elements and sets to nnUNetTool object pointer. + * + */ + void ProcessModelParams(mitk::nnUNetTool::Pointer); + + /** + * @brief Creates and renders QmitknnUNetTaskParamsUITemplate layout for ensemble input. + */ + void ShowEnsembleLayout(bool visible = true); + + /** + * @brief Creates a QMessage object and shows on screen. + */ + void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); + + /** + * @brief Searches and parses paths of python virtual enviroments + * from predefined lookout locations + */ + void AutoParsePythonPaths(); + + /** + * @brief Check if pretrained model sub folder inside RESULTS FOLDER exist. + * + */ + bool IsModelExists(const QString&, const QString&, const QString&); + + /** + * @brief Clears all combo boxes + * Any new combo box added in the future can be featured here for clearance. + * + */ + void ClearAllComboBoxes(); + + /** + * @brief Checks if nnUNet_predict command is valid in the selected python virtual environment. + * + * @return bool + */ + bool IsNNUNetInstalled(const QString &); + + /** + * @brief Mapper function to map QString entries from UI to ModelParam attributes. + * + * @return mitk::ModelParams + */ + mitk::ModelParams MapToRequest( + const QString &, const QString &, const QString &, const QString &, const std::vector &); + + /** + * @brief Returns checked fold names from the ctk-Checkable-ComboBox. + * + * @return std::vector + */ + std::vector FetchSelectedFoldsFromUI(); + + /** + * @brief Returns all paths from the dynamically generated ctk-path-line-edit boxes. + * + * @return std::vector + */ + std::vector FetchMultiModalImagesFromUI(); + + Ui_QmitknnUNetToolGUIControls m_Controls; + QmitkGPULoader m_GpuLoader; + + /** + * @brief Stores all dynamically added ctk-path-line-edit UI elements. + * + */ + std::vector m_Modalities; + + std::vector> m_EnsembleParams; + + mitk::NodePredicateBase::Pointer m_MultiModalPredicate; + + /** + * @brief Stores row count of the "advancedSettingsLayout" layout element. This value helps dynamically add + * ctk-path-line-edit UI elements at the right place. Forced to initialize in the InitializeUI method since there is + * no guarantee of retrieving exact row count anywhere else. + * + */ + int m_UI_ROWS; + + /** + * @brief Stores path of the model director (RESULTS_FOLDER appended by "nnUNet"). + * + */ + std::shared_ptr m_ParentFolder = nullptr; + + /** + * @brief Valid list of models supported by nnUNet + * + */ + const QStringList m_VALID_MODELS = {"2d", "3d_lowres", "3d_fullres", "3d_cascade_fullres", "ensembles"}; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui new file mode 100644 index 0000000000..534b398ddf --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUIControls.ui @@ -0,0 +1,440 @@ + + + QmitknnUNetToolGUIControls + + + + 0 + 0 + 192 + 352 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitknnUNetToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + nnUNet Results Folder: + + + + + + + + + + + + Refresh Results Folder + + + + + + + + + Configuration: + + + + + + + + + + + 0 + 0 + + + + Task: + + + + + + + + + + + + + 0 + 0 + + + + Trainer: + + + + + + + + + + + + 0 + 0 + + + + Plan: + + + + + + + + + + + Fold: + + + + + + + + + + Stop + + + + + + + + + 0 + 0 + + + + Multi-Modal: + + + + + + + + + + + 0 + 0 + + + + No. of Extra Modalities: + + + + + + + + + + + + + 0 + 0 + + + + Reference Image Position + + + + + + + + + + + + + + + + 0 + 0 + + + + 5 + + + Advanced + + + true + + + true + + + Qt::AlignRight + + + + + + + 6 + + + 0 + + + + + + + 0 + 0 + + + + No Pip + + + + + + + + + + + 0 + 0 + + + + Mixed Precision + + + + + + + true + + + + + + + + + 0 + 0 + + + + GPU Id: + + + + + + + + + + + + 0 + 0 + + + + Enable Mirroring + + + + + + + true + + + + + + + + + 0 + 0 + + + + Python Path: + + + + + + + + + + + + 0 + 0 + + + + nnUNet Path: + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Preview + + + + + + + + 0 + 0 + + + + + + + + + + + ctkDirectoryButton + QWidget +
ctkDirectoryButton.h
+ 1 +
+ + ctkComboBox + QComboBox +
ctkComboBox.h
+ 1 +
+ + ctkCheckableComboBox + QComboBox +
ctkCheckableComboBox.h
+ 1 +
+ + ctkCheckBox + QCheckBox +
ctkCheckBox.h
+ 1 +
+ + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+
+ + +
diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp new file mode 100644 index 0000000000..b04034d042 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp @@ -0,0 +1,434 @@ +#include "QmitknnUNetToolGUI.h" +#include +#include +#include +#include +#include + +void QmitknnUNetToolGUI::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); +} + +void QmitknnUNetToolGUI::ClearAllComboBoxes() +{ + m_Controls.modelBox->clear(); + m_Controls.taskBox->clear(); + m_Controls.foldBox->clear(); + m_Controls.trainerBox->clear(); + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + } +} + +void QmitknnUNetToolGUI::OnRefreshPresssed() +{ + const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); + OnDirectoryChanged(resultsFolder); +} + +void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) +{ + m_Controls.previewButton->setEnabled(false); + this->ClearAllComboBoxes(); + m_ParentFolder = std::make_shared(resultsFolder); + auto models = m_ParentFolder->getModelNames(); + std::for_each(models.begin(), + models.end(), + [this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + m_Controls.modelBox->addItem(model); + }); +} + +void QmitknnUNetToolGUI::OnModelChanged(const QString &model) +{ + if (model.isEmpty()) + { + return; + } + m_Controls.taskBox->clear(); + auto tasks = m_ParentFolder->getTasksForModel(model); + std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); +} + +void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) +{ + if (task.isEmpty()) + { + return; + } + m_Controls.trainerBox->clear(); + m_Controls.plannerBox->clear(); + QStringList trainerPlanners = + m_ParentFolder->getTrainerPlannersForTask(task, m_Controls.modelBox->currentText()); + if (m_Controls.modelBox->currentText() == "ensembles") + { + m_Controls.trainerBox->setVisible(false); + m_Controls.trainerLabel->setVisible(false); + m_Controls.plannerBox->setVisible(false); + m_Controls.plannerLabel->setVisible(false); + m_Controls.foldBox->setVisible(false); + m_Controls.foldLabel->setVisible(false); + ShowEnsembleLayout(true); + QString splitterString = "--"; + QStringList models, trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + QStringList trainerSplitParts = trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts); + foreach (QString modelSet, trainerSplitParts) + { + modelSet.remove("ensemble_", Qt::CaseInsensitive); + QStringList splitParts = modelSet.split("__", QString::SplitBehavior::SkipEmptyParts); + QString modelName = splitParts.first(); + QString trainer = splitParts.at(1); + QString planId = splitParts.at(2); + models << modelName; + trainers << trainer; + planners << planId; + } + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + models.removeDuplicates(); + + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->modelBox->clear(); + layout->trainerBox->clear(); + layout->plannerBox->clear(); + std::for_each(models.begin(), + models.end(), + [&layout, this](QString model) + { + if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) + layout->modelBox->addItem(model); + }); + std::for_each( + trainers.begin(), trainers.end(), [&layout](QString trainer) { layout->trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [&layout](QString planner) { layout->plannerBox->addItem(planner); }); + } + m_Controls.previewButton->setEnabled(true); + } + else + { + m_Controls.trainerBox->setVisible(true); + m_Controls.trainerLabel->setVisible(true); + m_Controls.plannerBox->setVisible(true); + m_Controls.plannerLabel->setVisible(true); + m_Controls.foldBox->setVisible(true); + m_Controls.foldLabel->setVisible(true); + m_Controls.previewButton->setEnabled(false); + ShowEnsembleLayout(false); + QString splitterString = "__"; + QStringList trainers, planners; + foreach (QString trainerPlanner, trainerPlanners) + { + trainers << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).first(); + planners << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).last(); + } + trainers.removeDuplicates(); + planners.removeDuplicates(); + std::for_each( + trainers.begin(), trainers.end(), [this](QString trainer) { m_Controls.trainerBox->addItem(trainer); }); + std::for_each( + planners.begin(), planners.end(), [this](QString planner) { m_Controls.plannerBox->addItem(planner); }); + } +} + +void QmitknnUNetToolGUI::OnTrainerChanged(const QString &plannerSelected) +{ + if (plannerSelected.isEmpty()) + { + return; + } + m_Controls.foldBox->clear(); + if (m_Controls.modelBox->currentText() != "ensembles") + { + auto selectedTrainer = m_Controls.trainerBox->currentText(); + auto selectedTask = m_Controls.taskBox->currentText(); + auto selectedModel = m_Controls.modelBox->currentText(); + auto folds = m_ParentFolder->getFoldsForTrainerPlanner( + selectedTrainer, plannerSelected, selectedTask, selectedModel); + std::for_each(folds.begin(), + folds.end(), + [this](QString fold) + { + if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet + m_Controls.foldBox->addItem(fold); + }); + if (m_Controls.foldBox->count() != 0) + { + // Now recalling all added items to check-mark it. + const QAbstractItemModel *qaim = m_Controls.foldBox->checkableModel(); + for (int i = 0; i < folds.size(); ++i) + { + const QModelIndex mi = qaim->index(i, 0); + m_Controls.foldBox->setCheckState(mi, Qt::Checked); + } + m_Controls.previewButton->setEnabled(true); + } + } + else + { + m_Controls.previewButton->setEnabled(true); + } +} + +void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) +{ + if (pyEnv == QString("Select")) + { + QString path = + QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + m_Controls.pythonEnvComboBox->insertItem(0, path); + m_Controls.pythonEnvComboBox->setCurrentIndex(0); + } + } + else if (!IsNNUNetInstalled(pyEnv)) + { + std::string warning = + "WARNING: nnUNet is not detected on the Python environment you selected. Please select another " + "environment or create one. For more info refer https://github.com/MIC-DKFZ/nnUNet"; + ShowErrorMessage(warning); + } +} + +void QmitknnUNetToolGUI::OnCheckBoxChanged(int state) +{ + bool visibility = false; + if (state == Qt::Checked) + { + visibility = true; + } + ctkCheckBox *box = qobject_cast(sender()); + if (box != nullptr) + { + if (box->objectName() == QString("nopipBox")) + { + m_Controls.codedirectoryBox->setVisible(visibility); + m_Controls.nnUnetdirLabel->setVisible(visibility); + } + else if (box->objectName() == QString("multiModalBox")) + { + m_Controls.multiModalSpinLabel->setVisible(visibility); + m_Controls.multiModalSpinBox->setVisible(visibility); + m_Controls.posSpinBoxLabel->setVisible(visibility); + m_Controls.posSpinBox->setVisible(visibility); + if (visibility) + { + QmitkDataStorageComboBox *defaultImage = new QmitkDataStorageComboBox(this, true); + defaultImage->setObjectName(QString("multiModal_" + QString::number(0))); + defaultImage->SetPredicate(this->m_MultiModalPredicate); + defaultImage->setDisabled(true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + if (tool != nullptr) + { + defaultImage->SetDataStorage(tool->GetDataStorage()); + defaultImage->SetSelectedNode(tool->GetRefNode()); + } + m_Controls.advancedSettingsLayout->addWidget(defaultImage, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(defaultImage); + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_UI_ROWS++; + } + else + { + OnModalitiesNumberChanged(0); + m_Controls.multiModalSpinBox->setValue(0); + m_Controls.posSpinBox->setMaximum(0); + delete this->m_Modalities[0]; + m_Modalities.pop_back(); + } + } + } +} + +void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) +{ + while (num > static_cast(this->m_Modalities.size() - 1)) + { + QmitkDataStorageComboBox *multiModalBox = new QmitkDataStorageComboBox(this, true); + mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); + multiModalBox->SetDataStorage(tool->GetDataStorage()); + multiModalBox->SetPredicate(this->m_MultiModalPredicate); + multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, this->m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); + m_Modalities.push_back(multiModalBox); + } + while (num < static_cast(this->m_Modalities.size() - 1) && !m_Modalities.empty()) + { + QmitkDataStorageComboBox *child = m_Modalities.back(); + if (child->objectName() == "multiModal_0") + { + std::iter_swap(this->m_Modalities.end() - 2, this->m_Modalities.end() - 1); + child = m_Modalities.back(); + } + delete child; // delete the layout item + m_Modalities.pop_back(); + } + m_Controls.posSpinBox->setMaximum(this->m_Modalities.size() - 1); + m_Controls.advancedSettingsLayout->update(); +} + +void QmitknnUNetToolGUI::OnModalPositionChanged(int posIdx) +{ + if (posIdx < static_cast(m_Modalities.size())) + { + int currPos = 0; + bool stopCheck = false; + // for-loop clears all widgets from the QGridLayout and also, finds the position of loaded-image widget. + for (QmitkDataStorageComboBox *multiModalBox : m_Modalities) + { + m_Controls.advancedSettingsLayout->removeWidget(multiModalBox); + multiModalBox->setParent(nullptr); + if (multiModalBox->objectName() != "multiModal_0" && !stopCheck) + { + currPos++; + } + else + { + stopCheck = true; + } + } + // moving the loaded-image widget to the required position + std::iter_swap(this->m_Modalities.begin() + currPos, m_Modalities.begin() + posIdx); + // re-adding all widgets in the order + for (int i = 0; i < static_cast(m_Modalities.size()); ++i) + { + QmitkDataStorageComboBox *multiModalBox = m_Modalities[i]; + m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + i + 1, 1, 1, 3); + } + m_Controls.advancedSettingsLayout->update(); + } +} + +void QmitknnUNetToolGUI::AutoParsePythonPaths() +{ + QString homeDir = QDir::homePath(); + std::vector searchDirs; +#ifdef _WIN32 + searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + + QString("anaconda3")); +#else + // Add search locations for possible standard python paths here + searchDirs.push_back(homeDir + QDir::separator() + "environments"); + searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); +#endif + for (QString searchDir : searchDirs) + { + if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) + { + if (QDir(searchDir).exists()) + { + m_Controls.pythonEnvComboBox->insertItem(0, "(base): " + searchDir); + searchDir.append((QDir::separator() + QString("envs"))); + } + } + for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) + { + subIt.next(); + QString envName = subIt.fileName(); + if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. + { + m_Controls.pythonEnvComboBox->insertItem(0, "(" + envName + "): " + subIt.filePath()); + } + } + } + m_Controls.pythonEnvComboBox->setCurrentIndex(-1); +} + +std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI() +{ + std::vector folds; + if (!(m_Controls.foldBox->allChecked() || m_Controls.foldBox->noneChecked())) + { + QModelIndexList foldList = m_Controls.foldBox->checkedIndexes(); + foreach (QModelIndex index, foldList) + { + QString foldQString = + m_Controls.foldBox->itemText(index.row()).split("_", QString::SplitBehavior::SkipEmptyParts).last(); + folds.push_back(foldQString.toStdString()); + } + } + return folds; +} + +mitk::ModelParams QmitknnUNetToolGUI::MapToRequest(const QString &modelName, + const QString &taskName, + const QString &trainer, + const QString &planId, + const std::vector &folds) +{ + mitk::ModelParams requestObject; + requestObject.model = modelName.toStdString(); + requestObject.trainer = trainer.toStdString(); + requestObject.planId = planId.toStdString(); + requestObject.task = taskName.toStdString(); + requestObject.folds = folds; + return requestObject; +} + +void QmitknnUNetToolGUI::SegmentationProcessFailed() +{ + m_Controls.statusLabel->setText( + "STATUS: Error in the segmentation process. No resulting segmentation can be loaded."); + this->setCursor(Qt::ArrowCursor); + std::stringstream stream; + stream << "Error in the segmentation process. No resulting segmentation can be loaded."; + ShowErrorMessage(stream.str()); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool) +{ + tool->RenderOutputBuffer(); + this->SetLabelSetPreview(tool->GetMLPreview()); + m_Controls.statusLabel->setText("STATUS: Segmentation task finished successfully. Please Confirm the " + "segmentation else will result in data loss"); + m_Controls.stopButton->setEnabled(false); +} + +void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) +{ + if (m_EnsembleParams.empty()) + { + ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); + auto lay1 = std::make_unique(groupBoxModel1); + m_EnsembleParams.push_back(std::move(lay1)); + groupBoxModel1->setObjectName(QString::fromUtf8("model_1_Box")); + groupBoxModel1->setTitle(QString::fromUtf8("Model 1")); + groupBoxModel1->setMinimumSize(QSize(0, 0)); + groupBoxModel1->setCollapsedHeight(5); + groupBoxModel1->setCollapsed(false); + groupBoxModel1->setFlat(true); + groupBoxModel1->setAlignment(Qt::AlignRight); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel1, 3, 0, 1, 2); + + ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); + auto lay2 = std::make_unique(groupBoxModel2); + m_EnsembleParams.push_back(std::move(lay2)); + groupBoxModel2->setObjectName(QString::fromUtf8("model_2_Box")); + groupBoxModel2->setTitle(QString::fromUtf8("Model 2")); + groupBoxModel2->setMinimumSize(QSize(0, 0)); + groupBoxModel2->setCollapsedHeight(5); + groupBoxModel2->setCollapsed(false); + groupBoxModel2->setFlat(true); + groupBoxModel2->setAlignment(Qt::AlignLeft); + m_Controls.advancedSettingsLayout->addWidget(groupBoxModel2, 3, 2, 1, 2); + } + for (std::unique_ptr &layout : m_EnsembleParams) + { + layout->parent->setVisible(visible); + } +} diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index e71eb77792..63a50b2923 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,90 +1,97 @@ set( CPP_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp Qmitk/QmitkAutoSegmentationToolGUIBase.cpp Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkFastMarchingToolGUIBase.cpp Qmitk/QmitkFastMarchingTool3DGUI.cpp Qmitk/QmitkFastMarchingToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkNewSegmentationDialog.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitkWatershedToolGUI.cpp +Qmitk/QmitknnUNetToolGUI.cpp +Qmitk/QmitknnUNetToolSlots.cpp #Added from ML Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSearchLabelDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h Qmitk/QmitkAutoSegmentationToolGUIBase.h Qmitk/QmitkAutoMLSegmentationToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkFastMarchingToolGUIBase.h Qmitk/QmitkFastMarchingTool3DGUI.h Qmitk/QmitkFastMarchingToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkNewSegmentationDialog.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitkWatershedToolGUI.h +Qmitk/QmitknnUNetToolGUI.h +Qmitk/QmitknnUNetGPU.h +Qmitk/QmitknnUNetEnsembleLayout.h +Qmitk/QmitknnUNetFolderParser.h #Added from ML Qmitk/QmitkLabelSetWidget.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSearchLabelDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkWatershedToolGUIControls.ui #Added from ML Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSearchLabelDialogGUI.ui +Qmitk/QmitknnUNetToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp index f3a4196c61..98ccf8a397 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp @@ -1,1432 +1,1432 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkExtWorkbenchWindowAdvisor.h" #include "QmitkExtActionBarAdvisor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // UGLYYY #include "internal/QmitkExtWorkbenchWindowAdvisorHack.h" #include "internal/QmitkCommonExtPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include QmitkExtWorkbenchWindowAdvisorHack* QmitkExtWorkbenchWindowAdvisorHack::undohack = new QmitkExtWorkbenchWindowAdvisorHack(); QString QmitkExtWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; static bool USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS = false; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForViewNavigator: public berry::IPartListener { public: PartListenerForViewNavigator(QAction* act) : viewNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { - if (ref->GetId()=="org.mitk.views.viewnavigatorview") + if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } private: QAction* viewNavigatorAction; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED; } else { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } //GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->closeProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->viewNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(true); } } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->closeProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->viewNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(false); } } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; QmitkExtWorkbenchWindowAdvisor::QmitkExtWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showPerspectiveToolbar(false) , showVersionInfo(true) , showMitkVersionInfo(true) , showViewMenuItem(true) , showNewWindowMenuItem(false) , showClosePerspectiveMenuItem(true) , viewNavigatorFound(false) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); - viewExcludeList.push_back("org.mitk.views.viewnavigatorview"); + viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkExtWorkbenchWindowAdvisor::~QmitkExtWorkbenchWindowAdvisor() { } berry::ActionBarAdvisor::Pointer QmitkExtWorkbenchWindowAdvisor::CreateActionBarAdvisor(berry::IActionBarConfigurer::Pointer configurer) { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { berry::ActionBarAdvisor::Pointer actionBarAdvisor(new QmitkExtActionBarAdvisor(configurer)); return actionBarAdvisor; } else { return berry::WorkbenchWindowAdvisor::CreateActionBarAdvisor(configurer); } } QWidget* QmitkExtWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkExtWorkbenchWindowAdvisor::ShowClosePerspectiveMenuItem(bool show) { showClosePerspectiveMenuItem = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowClosePerspectiveMenuItem() { return showClosePerspectiveMenuItem; } void QmitkExtWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkExtWorkbenchWindowAdvisor::ShowNewWindowMenuItem(bool show) { showNewWindowMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewMenuItem(bool show) { showViewMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowPerspectiveToolbar(bool show) { showPerspectiveToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkExtWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkExtWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); auto fileOpenAction = new QmitkFileOpenAction(berry::QtStyleManager::ThemeIcon(basePath + "document-open.svg"), window); fileOpenAction->setShortcut(QKeySequence::Open); auto fileSaveAction = new QmitkFileSaveAction(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg"), window); fileSaveAction->setShortcut(QKeySequence::Save); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); closeProjectAction = new QmitkCloseProjectAction(window); closeProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "edit-delete.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; - if ((*iter)->GetId() == "org.mitk.views.viewnavigatorview") + if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } if (!USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileOpenAction); fileMenu->addAction(fileSaveAction); fileMenu->addAction(fileSaveProjectAction); fileMenu->addAction(closeProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); if (showNewWindowMenuItem) { windowMenu->addAction("&New Window", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onNewWindow())); windowMenu->addSeparator(); } QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); QMenu* viewMenu = nullptr; if (showViewMenuItem) { viewMenu = windowMenu->addMenu("Show &View"); viewMenu->setObjectName("Show View"); } windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); if(showClosePerspectiveMenuItem) closePerspAction = windowMenu->addAction("&Close Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onClosePerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); if (showViewMenuItem) { for (auto viewAction : qAsConst(viewActions)) { viewMenu->addAction(viewAction); } } // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== } else { undoAction = new QmitkUndoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), nullptr); undoAction->setShortcut(QKeySequence::Undo); redoAction = new QmitkRedoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), nullptr); redoAction->setShortcut(QKeySequence::Redo); } // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { openDicomEditorAction = new QmitkOpenDicomEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "dicom.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { openStdMultiWidgetEditorAction = new QmitkOpenStdMultiWidgetEditorAction(QIcon(":/org.mitk.gui.qt.ext/Editor.png"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { openMxNMultiWidgetEditorAction = new QmitkOpenMxNMultiWidgetEditorAction(QIcon(":/org.mitk.gui.qt.ext/Editor.png"), window); } if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } viewNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.ext/view-manager.svg")),"&View Navigator", nullptr); - viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigatorview"); + viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigator"); if (viewNavigatorFound) { QObject::connect(viewNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onViewNavigator())); viewNavigatorAction->setCheckable(true); // add part listener for view navigator viewNavigatorPartListener.reset(new PartListenerForViewNavigator(viewNavigatorAction)); window->GetPartService()->AddPartListener(viewNavigatorPartListener.data()); - berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigatorview"); + berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigator"); viewNavigatorAction->setChecked(false); if (viewnavigatorview) { bool isViewNavigatorVisible = window->GetActivePage()->IsPartVisible(viewnavigatorview); if (isViewNavigatorVisible) viewNavigatorAction->setChecked(true); } viewNavigatorAction->setToolTip("Toggle View Navigator"); } mainActionsToolBar->addAction(fileOpenAction); mainActionsToolBar->addAction(fileSaveProjectAction); mainActionsToolBar->addAction(closeProjectAction); mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if(this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { mainActionsToolBar->addAction(openDicomEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { mainActionsToolBar->addAction(openStdMultiWidgetEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { mainActionsToolBar->addAction(openMxNMultiWidgetEditorAction); } if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } if (viewNavigatorFound) { mainActionsToolBar->addAction(viewNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== Perspective Toolbar ================================== auto qPerspectiveToolbar = new QToolBar; qPerspectiveToolbar->setObjectName("perspectiveToolBar"); if (showPerspectiveToolbar) { qPerspectiveToolbar->addActions(perspGroup->actions()); mainWindow->addToolBar(qPerspectiveToolbar); } else delete qPerspectiveToolbar; if (showViewToolbar) { auto prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); berry::IPreferences::Pointer stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (const auto &labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (const auto &category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); if (!viewDescriptorsInCurrentCategory.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (const auto &viewDescriptor : qAsConst(viewDescriptorsInCurrentCategory)) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); // progBar->AddStepsToDo(2); // progBar->Progress(1); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkExtWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); // show the shortcut bar and progress indicator, which are hidden by // default //configurer->SetShowPerspectiveBar(true); //configurer->SetShowFastViewBars(true); //configurer->SetShowProgressIndicator(true); // // add the drag and drop support for the editor area // configurer.addEditorAreaTransfer(EditorInputTransfer.getInstance()); // configurer.addEditorAreaTransfer(ResourceTransfer.getInstance()); // configurer.addEditorAreaTransfer(FileTransfer.getInstance()); // configurer.addEditorAreaTransfer(MarkerTransfer.getInstance()); // configurer.configureEditorAreaDropListener(new EditorAreaDropAdapter( // configurer.getWindow())); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkExtWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } auto introPart = configurer->GetWindow()->GetWorkbench()->GetIntroManager()->GetIntro(); if (introPart.IsNotNull()) { configurer->GetWindow()->GetWorkbench()->GetIntroManager()->ShowIntro(GetWindowConfigurer()->GetWindow(), false); } } void QmitkExtWorkbenchWindowAdvisor::onIntro() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkExtWorkbenchWindowAdvisor::onHelp() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkExtWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkExtWorkbenchWindowAdvisor::onAbout() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onAbout(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkExtWorkbenchWindowAdvisorHack::QmitkExtWorkbenchWindowAdvisorHack() : QObject() { } QmitkExtWorkbenchWindowAdvisorHack::~QmitkExtWorkbenchWindowAdvisorHack() { } void QmitkExtWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkExtWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if( wbench == nullptr ) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if( wbench_window.IsNull() ) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if( wbench_page.IsNull() ) return; auto wbench_view = wbench_page->FindView( view_query_name ); if( wbench_view.IsNotNull() ) { bool isViewVisible = wbench_page->IsPartVisible( wbench_view ); if( isViewVisible ) { wbench_page->HideView( wbench_view ); return; } } wbench_page->ShowView( view_query_name ); } void QmitkExtWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onViewNavigator() { // show/hide viewnavigatorView - SafeHandleNavigatorView("org.mitk.views.viewnavigatorview"); + SafeHandleNavigatorView("org.mitk.views.viewnavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkExtWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkExtWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkExtWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkExtWorkbenchWindowAdvisorHack::onNewWindow() { berry::PlatformUI::GetWorkbench()->OpenWorkbenchWindow(nullptr); } void QmitkExtWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkExtWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(),nullptr); aboutDialog->open(); } void QmitkExtWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkExtWorkbenchWindowAdvisor>(this, &QmitkExtWorkbenchWindowAdvisor::PropertyChange)); // configurer.getWindow().addPageListener(new IPageListener() { // public void pageActivated(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageClosed(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageOpened(IWorkbenchPage page) { // // do nothing // } // }); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkExtWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkExtWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkExtWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } if (!lastActiveEditor.Expired()) { lastActiveEditor.Lock()->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkExtWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { if (!lastActiveEditor.Expired()) { QString newTitle = lastActiveEditor.Lock()->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkExtWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkExtWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkExtWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); QSettings settings(GetQSettingsFile(), QSettings::IniFormat); settings.setValue("ToolbarPosition", mainWindow->saveState()); } QString QmitkExtWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkCommonExtPlugin::getContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } diff --git a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp index 826357b8b4..7b2e41bc87 100644 --- a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationWorkbenchWindowAdvisor.cpp @@ -1,1146 +1,1146 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkFlowApplicationWorkbenchWindowAdvisor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QmitkExtFileSaveProjectAction.h" #include #include #include #include #include #include #include #include // UGLYYY #include "QmitkFlowApplicationWorkbenchWindowAdvisorHack.h" #include "QmitkFlowApplicationPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include QmitkFlowApplicationWorkbenchWindowAdvisorHack* QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack = new QmitkFlowApplicationWorkbenchWindowAdvisorHack(); QString QmitkFlowApplicationWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (!windowAdvisor->lastActiveEditor.Expired() && ref->GetPart(false) == windowAdvisor->lastActiveEditor.Lock()) { windowAdvisor->UpdateTitle(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkFlowApplicationWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkFlowApplicationWorkbenchWindowAdvisor* windowAdvisor; }; QmitkFlowApplicationWorkbenchWindowAdvisor::QmitkFlowApplicationWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showVersionInfo(true) , showMitkVersionInfo(true) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); - viewExcludeList.push_back("org.mitk.views.viewnavigatorview"); + viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkFlowApplicationWorkbenchWindowAdvisor::~QmitkFlowApplicationWorkbenchWindowAdvisor() { } QWidget* QmitkFlowApplicationWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkFlowApplicationWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; - if ((*iter)->GetId() == "org.mitk.views.viewnavigatorview") + if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileSaveProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo()), QKeySequence("CTRL+Z")); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo()), QKeySequence("CTRL+Y")); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences()), QKeySequence("CTRL+P")); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help",this, SLOT(onHelp()), QKeySequence("F1")); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== View Toolbar ================================== if (showViewToolbar) { auto prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); berry::IPreferences::Pointer stylePrefs = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); bool showCategoryNames = stylePrefs->GetBool(berry::QtPreferences::QT_SHOW_TOOLBAR_CATEGORY_NAMES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (auto labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (auto category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); QList > relevantViewDescriptors; for (auto viewDescriptor : viewDescriptorsInCurrentCategory) { if (viewDescriptor->GetId() != "org.mitk.views.flow.control") { relevantViewDescriptors.push_back(viewDescriptor); } } if (!relevantViewDescriptors.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category + " View Toolbar"); mainWindow->addToolBar(toolbar); if (showCategoryNames && !category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); toolbar->addWidget(categoryButton); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (auto viewDescriptor : relevantViewDescriptors) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::onIntro() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelp() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::onAbout() { QmitkFlowApplicationWorkbenchWindowAdvisorHack::undohack->onAbout(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkFlowApplicationWorkbenchWindowAdvisor>(this, &QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange)); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkFlowApplicationWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkFlowApplicationWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkFlowApplicationWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } if (!lastActiveEditor.Expired()) { lastActiveEditor.Lock()->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkFlowApplicationWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { if (!lastActiveEditor.Expired()) { QString newTitle = lastActiveEditor.Lock()->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkFlowApplicationWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkFlowApplicationWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); QSettings settings(GetQSettingsFile(), QSettings::IniFormat); settings.setValue("ToolbarPosition", mainWindow->saveState()); } QString QmitkFlowApplicationWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkFlowApplicationWorkbenchWindowAdvisorHack::QmitkFlowApplicationWorkbenchWindowAdvisorHack() : QObject() { } QmitkFlowApplicationWorkbenchWindowAdvisorHack::~QmitkFlowApplicationWorkbenchWindowAdvisorHack() { } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast(model)) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if (wbench == nullptr) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if (wbench_window.IsNull()) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if (wbench_page.IsNull()) return; auto wbench_view = wbench_page->FindView(view_query_name); if (wbench_view.IsNotNull()) { bool isViewVisible = wbench_page->IsPartVisible(wbench_view); if (isViewVisible) { wbench_page->HideView(wbench_view); return; } } wbench_page->ShowView(view_query_name); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onEditPreferences() { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); _PreferencesDialog.exec(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onIntro() { bool hasIntro = berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro(); if (!hasIntro) { QRegExp reg("(.*)(\\n)*"); QRegExp reg2("(\\n)*(.*)"); QFile file(":/org.mitk.gui.qt.ext/index.html"); file.open(QIODevice::ReadOnly | QIODevice::Text); //text file only for reading QString text = QString(file.readAll()); file.close(); QString title = text; title.replace(reg, ""); title.replace(reg2, ""); std::cout << title.toStdString() << std::endl; QMessageBox::information(nullptr, title, text, "Close"); } else { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkFlowApplicationPlugin::GetDefault()->GetPluginContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkFlowApplicationWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(), nullptr); aboutDialog->open(); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index 98a6bb780f..23872a6f83 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,877 +1,888 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMeasurementView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctkDoubleSpinBox.h" #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), - m_EndInteractionObserverTag(0) + m_EndInteractionObserverTag(0), + m_DuringInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; + unsigned int m_DuringInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SingleNodeSelectionWidget(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), m_Layout(nullptr), m_Radius(nullptr), m_Thickness(nullptr), m_FixedParameterBox(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; QLabel* m_SelectedImageLabel; QmitkSingleNodeSelectionWidget* m_SingleNodeSelectionWidget; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; ctkDoubleSpinBox* m_Radius; ctkDoubleSpinBox* m_Thickness; QGroupBox* m_FixedParameterBox; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; d->m_SingleNodeSelectionWidget = new QmitkSingleNodeSelectionWidget(); d->m_SingleNodeSelectionWidget->SetDataStorage(GetDataStorage()); d->m_SingleNodeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); d->m_SingleNodeSelectionWidget->SetSelectionIsOptional(true); d->m_SingleNodeSelectionWidget->SetEmptyInfo(QStringLiteral("Please select a reference image")); d->m_SingleNodeSelectionWidget->SetPopUpTitel(QStringLiteral("Select a reference image")); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; d->m_DrawActionsToolBar->setEnabled(false); // fixed parameter section auto fixedLayout = new QGridLayout(); d->m_FixedParameterBox = new QGroupBox(); d->m_FixedParameterBox->setCheckable(true); d->m_FixedParameterBox->setChecked(false); + d->m_FixedParameterBox->setEnabled(false); d->m_FixedParameterBox->setTitle("Fixed sized circle/double ellipse"); d->m_FixedParameterBox->setToolTip("If activated, circles and double ellipses (as rings) figures will always be created with the set parameters as fixed size."); d->m_FixedParameterBox->setAlignment(Qt::AlignLeft); auto labelRadius1 = new QLabel(QString("Radius")); d->m_Radius = new ctkDoubleSpinBox(); d->m_Radius->setMinimum(0); d->m_Radius->setValue(10); d->m_Radius->setSuffix(" mm"); d->m_Radius->setAlignment(Qt::AlignLeft); d->m_Radius->setToolTip("Sets the radius for following planar figures: circle, double ellipse (as ring)."); auto labelThickness = new QLabel(QString("Thickness")); d->m_Thickness = new ctkDoubleSpinBox(); d->m_Thickness->setMinimum(0); d->m_Thickness->setMaximum(10); d->m_Thickness->setValue(5); d->m_Thickness->setSuffix(" mm"); d->m_Thickness->setAlignment(Qt::AlignLeft); d->m_Thickness->setToolTip("Sets the thickness for following planar figures: double ellipse (as ring)."); fixedLayout->addWidget(labelRadius1,0,0); fixedLayout->addWidget(d->m_Radius,0,1); fixedLayout->addWidget(labelThickness,1,0); fixedLayout->addWidget(d->m_Thickness,1,1); d->m_FixedParameterBox->setLayout(fixedLayout); // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); d->m_SelectedImageLabel = new QLabel("Selected Image"); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(d->m_SelectedImageLabel, 0, 0); d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 1, 1, 1); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); // placed after CreateConnections to trigger update of the current selection d->m_SingleNodeSelectionWidget->SetAutoSelectNewNodes(true); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); + d->m_FixedParameterBox->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); + d->m_FixedParameterBox->setEnabled(true); } } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); - // add observer for event when interaction with figure starts + // add observer for event when interaction with figure ends auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); + // add observer for event during interaction when a point is moved + auto duringInteractionCommand = SimpleCommandType::New(); + duringInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::UpdateMeasurementText); + data.m_DuringInteractionObserverTag = planarFigure->AddObserver(mitk::PointMovedPlanarFigureEvent(), duringInteractionCommand); + // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { auto it = std::find(d->m_CurrentSelection.begin(), d->m_CurrentSelection.end(), node); if (it != d->m_CurrentSelection.end()) { this->UpdateMeasurementText(); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); + data.m_Figure->RemoveObserver(data.m_DuringInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { mitk::CoreServicePointer propertyFilters(mitk::CoreServices::GetPropertyFilters()); mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp index 0d02206bb5..762e8e5dee 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp @@ -1,1213 +1,1168 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiLabelSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include // Qmitk #include "QmitkCreateMultiLabelPresetAction.h" #include "QmitkLoadMultiLabelPresetAction.h" #include #include #include #include // us #include #include #include #include #include // Qt #include #include #include #include #include const std::string QmitkMultiLabelSegmentationView::VIEW_ID = "org.mitk.views.multilabelsegmentation"; QmitkMultiLabelSegmentationView::QmitkMultiLabelSegmentationView() : m_Parent(nullptr), m_RenderWindowPart(nullptr), m_ToolManager(nullptr), m_ReferenceNode(nullptr), m_WorkingNode(nullptr), m_AutoSelectionEnabled(false), m_MouseCursorSet(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkMultiLabelSegmentationView::~QmitkMultiLabelSegmentationView() { OnLooseLabelSetConnection(); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls.m_WorkingNodeSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); + // Remove observer if one was registered + auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); + if (finding != m_WorkingDataObserverTags.end()) + { + m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); + m_WorkingDataObserverTags.erase(m_WorkingNode); + } + if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } if (m_WorkingNode.IsNotNull()) { this->OnLooseLabelSetConnection(); } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); this->OnEstablishLabelSetConnection(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); + m_WorkingDataObserverTags.insert(std::pair(m_WorkingNode, + m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); bool isVisible = false; workingNode->GetBoolProperty("visible", isVisible); workingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkMultiLabelSegmentationView::OnNewLabel() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { QMessageBox::information( m_Parent, "New Segmentation", "Please load and select an image before starting some action."); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Reference data needs to be an image in order to create a new segmentation."); return; } mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select a multilabel segmentation before starting some action."); return; } mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select a multilabel segmentation before starting some action."); return; } // ask about the name and organ type of the new segmentation auto dialog = new QmitkNewSegmentationDialog(m_Parent); QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); dialog->SetSuggestionList(organColors); dialog->setWindowTitle("New Label"); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } QString segName = dialog->GetSegmentationName(); if (segName.isEmpty()) { segName = "Unnamed"; } workingImage->GetActiveLabelSet()->AddLabel(segName.toStdString(), dialog->GetColor()); UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - - this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkMultiLabelSegmentationView::OnSavePreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkCreateMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnLoadPreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkLoadMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnShowLabelTable(bool value) { if (value) m_Controls.m_LabelSetWidget->show(); else m_Controls.m_LabelSetWidget->hide(); } void QmitkMultiLabelSegmentationView::OnNewSegmentationSession() { mitk::DataNode::Pointer referenceNode = m_ToolManager->GetReferenceData(0); if (referenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information(m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The selected image has multiple time steps.\n\nDo you want to create a static " "segmentation that is identical for all time steps or do you want to create a " "dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); bool ok = false; newName = QInputDialog::getText(m_Parent, "New segmentation", "New name:", QLineEdit::Normal, newName, &ok); if (!ok) { return; } if (newName.isEmpty()) { newName = "Unnamed"; } this->WaitCursorOn(); mitk::LabelSetImage::Pointer workingImage = mitk::LabelSetImage::New(); try { workingImage->Initialize(segTemplateImage); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Parent, tr("New segmentation"), tr("Could not create a new segmentation.\n")); return; } this->WaitCursorOff(); mitk::DataNode::Pointer workingNode = mitk::DataNode::New(); workingNode->SetData(workingImage); workingNode->SetName(newName.toStdString()); workingImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(referenceNode->GetName().c_str())); workingImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New(newName.toStdString().c_str())); this->GetDataStorage()->Add(workingNode, referenceNode); m_Controls.m_WorkingNodeSelector->SetCurrentSelectedNode(workingNode); OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) m_RenderWindowPart->SetSelectedPosition(pos); } void QmitkMultiLabelSegmentationView::OnResetView() { if (m_RenderWindowPart) m_RenderWindowPart->ForceImmediateUpdate(); } void QmitkMultiLabelSegmentationView::OnAddLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } QString question = "Do you really want to add a layer to the current segmentation session?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Add layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) return; try { WaitCursorOn(); workingImage->AddLayer(); WaitCursorOff(); } catch ( mitk::Exception& e ) { WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( m_Controls.m_LabelSetWidget, "Add Layer", "Could not add a new layer. See error log for details.\n"); return; } OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnDeleteLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } if (workingImage->GetNumberOfLayers() < 2) return; QString question = "Do you really want to delete the current layer?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } try { this->WaitCursorOn(); workingImage->RemoveLayer(); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Controls.m_LabelSetWidget, "Delete Layer", "Could not delete the currently active layer. See error log for details.\n"); return; } UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnPreviousLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } OnChangeLayer(workingImage->GetActiveLayer() - 1); } void QmitkMultiLabelSegmentationView::OnNextLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } OnChangeLayer(workingImage->GetActiveLayer() + 1); } void QmitkMultiLabelSegmentationView::OnChangeLayer(int layer) { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->SetActiveLayer(layer); this->WaitCursorOff(); UpdateGUI(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnLockExteriorToggled(bool checked) { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } workingImage->GetLabel(0)->SetLocked(checked); } void QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged(int index) { if (index == 1) { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false);//OnToggleWidgetActivation(false); m_Controls.m_swInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->show(); } else if (index == 2) { m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(1); m_Controls.m_swInterpolation->show(); } else { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(2); m_Controls.m_swInterpolation->hide(); } } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkMultiLabelSegmentationView::CreateQtPartControl(QWidget *parent) { m_Parent = parent; m_Controls.setupUi(parent); m_Controls.m_tbSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls.m_tbLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); // *------------------------ // * Shortcuts // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls.m_ReferenceNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_ReferenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls.m_ReferenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls.m_WorkingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls.m_WorkingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls.m_ReferenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged); connect(m_Controls.m_WorkingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * ToolManager // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); m_Controls.m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls.m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); // *------------------------ // * LabelSetWidget // *------------------------ m_Controls.m_LabelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls.m_LabelSetWidget->SetOrganColors(mitk::OrganNamesHandling::GetDefaultOrganColorString()); m_Controls.m_LabelSetWidget->hide(); // *------------------------ // * Interpolation // *------------------------ m_Controls.m_SurfaceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); m_Controls.m_SliceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); connect(m_Controls.m_cbInterpolation, QOverload::of(&QComboBox::activated), this, &QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged); m_Controls.m_cbInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->hide(); m_Controls.m_gbInterpolation->hide(); // See T27436 QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold"); std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // *------------------------ // * ToolSelection 2D // *------------------------ m_Controls.m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer2D); m_Controls.m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls.m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls.m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkMultiLabelSegmentationView::OnManualTool2DSelected); // *------------------------ // * ToolSelection 3D // *------------------------ m_Controls.m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer3D); m_Controls.m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); // todo add : FastMarching3D RegionGrowing Watershed m_Controls.m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // *------------------------* // * Connect Buttons // *------------------------* connect(m_Controls.m_pbNewLabel, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNewLabel); connect(m_Controls.m_tbSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnSavePreset); connect(m_Controls.m_tbLoadPreset, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnLoadPreset); connect(m_Controls.m_pbNewSegmentationSession, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNewSegmentationSession); connect(m_Controls.m_pbShowLabelTable, &QToolButton::toggled, this, &QmitkMultiLabelSegmentationView::OnShowLabelTable); // *------------------------* // * Connect LabelSetWidget // *------------------------* connect(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkMultiLabelSegmentationView::OnGoToLabel); connect(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::resetView, this, &QmitkMultiLabelSegmentationView::OnResetView); // *------------------------* // * DATA SLECTION WIDGET // *------------------------* connect(m_Controls.m_btAddLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnAddLayer); connect(m_Controls.m_btDeleteLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnDeleteLayer); connect(m_Controls.m_btPreviousLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnPreviousLayer); connect(m_Controls.m_btNextLayer, &QToolButton::clicked, this, &QmitkMultiLabelSegmentationView::OnNextLayer); connect(m_Controls.m_btLockExterior, &QToolButton::toggled, this, &QmitkMultiLabelSegmentationView::OnLockExteriorToggled); connect(m_Controls.m_cbActiveLayer, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiLabelSegmentationView::OnChangeLayer); - m_Controls.m_btAddLayer->setEnabled(false); - m_Controls.m_btDeleteLayer->setEnabled(false); - m_Controls.m_btNextLayer->setEnabled(false); - m_Controls.m_btPreviousLayer->setEnabled(false); - m_Controls.m_cbActiveLayer->setEnabled(false); - - m_Controls.m_pbNewLabel->setEnabled(false); - m_Controls.m_btLockExterior->setEnabled(false); - m_Controls.m_tbSavePreset->setEnabled(false); - m_Controls.m_tbLoadPreset->setEnabled(false); - m_Controls.m_pbShowLabelTable->setEnabled(false); - - // set callback function for already existing segmentation nodes - mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(m_SegmentationPredicate); - for (mitk::DataStorage::SetOfObjects::const_iterator iter = allSegmentations->begin(); iter != allSegmentations->end(); ++iter) - { - mitk::DataNode* node = *iter; - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - } - auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls.m_ReferenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls.m_WorkingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkMultiLabelSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part QList controllers; controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } void QmitkMultiLabelSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkMultiLabelSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { bool slimView = prefs->GetBool("slim view", false); m_Controls.m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls.m_ManualToolSelectionBox3D->SetShowNames(!slimView); m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); this->ApplyDisplayOptions(); } void QmitkMultiLabelSegmentationView::NodeAdded(const mitk::DataNode* node) { - if (!m_SegmentationPredicate->CheckNode(node)) + if (m_SegmentationPredicate->CheckNode(node)) { - return; + this->ApplyDisplayOptions(const_cast(node)); } - - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkMultiLabelSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - - ApplyDisplayOptions(const_cast(node)); } void QmitkMultiLabelSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext *context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService *service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; } - - mitk::DataNode* tempNode = const_cast(node); - //Remove observer if one was registered - auto finding = m_WorkingDataObserverTags.find(tempNode); - if (finding != m_WorkingDataObserverTags.end()) - { - node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); - m_WorkingDataObserverTags.erase(tempNode); - } } void QmitkMultiLabelSegmentationView::ApplyDisplayOptions() { if (!m_Parent) { return; } mitk::DataNode::Pointer workingData = m_ToolManager->GetWorkingData(0); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is a multi label segmentation node->SetProperty("labelset.contour.active", drawOutline); // force render window update to show outline node->GetData()->Modified(); } else if (nullptr != node->GetData()) { // node is actually a 'single label' segmentation, // but its outline property can be set in the 'multi label' segmentation preference page as well bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkMultiLabelSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { // node is a "single label" / "binary" image --> no label set return; } workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateGUI); } void QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { // data (type) was changed in-place, e.g. LabelSetImage -> (binary) image return; } // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateGUI); } void QmitkMultiLabelSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkMultiLabelSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkMultiLabelSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls.m_pbNewSegmentationSession->setEnabled(false); - m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_gbInterpolation->setEnabled(false); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(false); - m_Controls.m_LabelSetWidget->setEnabled(false); - m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btLockExterior->setChecked(false); - m_Controls.m_btLockExterior->setEnabled(false); - m_Controls.m_tbSavePreset->setEnabled(false); - m_Controls.m_tbLoadPreset->setEnabled(false); m_Controls.m_pbShowLabelTable->setChecked(false); - m_Controls.m_pbShowLabelTable->setEnabled(false); if (hasReferenceNode) { m_Controls.m_pbNewSegmentationSession->setEnabled(true); } if (hasWorkingNode) { - m_Controls.m_pbNewLabel->setEnabled(true); - m_Controls.m_btLockExterior->setEnabled(true); - m_Controls.m_tbSavePreset->setEnabled(true); - m_Controls.m_tbLoadPreset->setEnabled(true); - m_Controls.m_pbShowLabelTable->setEnabled(true); - m_Controls.m_LabelSetWidget->setEnabled(true); - m_Controls.m_btAddLayer->setEnabled(true); - m_Controls.m_cbActiveLayer->blockSignals(true); m_Controls.m_cbActiveLayer->clear(); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (nullptr != workingImage) { int numberOfLayers = workingImage->GetNumberOfLayers(); for (unsigned int lidx = 0; lidx < workingImage->GetNumberOfLayers(); ++lidx) { m_Controls.m_cbActiveLayer->addItem(QString::number(lidx)); } int activeLayer = workingImage->GetActiveLayer(); m_Controls.m_cbActiveLayer->setCurrentIndex(activeLayer); m_Controls.m_cbActiveLayer->blockSignals(false); - m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btDeleteLayer->setEnabled(numberOfLayers > 1); + m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btPreviousLayer->setEnabled(activeLayer > 0); m_Controls.m_btNextLayer->setEnabled(activeLayer != numberOfLayers - 1); m_Controls.m_btLockExterior->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); m_Controls.m_pbShowLabelTable->setChecked(workingImage->GetNumberOfLabels() > 1 /*1st is exterior*/); } } if (hasWorkingNode && hasReferenceNode) { m_Controls.m_gbInterpolation->setEnabled(true); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(true); int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkMultiLabelSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); + m_Controls.groupBox_Layer->setEnabled(false); + m_Controls.groupBox_Labels->setEnabled(false); + m_Controls.m_LabelSetWidget->setEnabled(false); // the argument is actually not used // enable status depends on the tool manager selection m_Controls.m_ManualToolSelectionBox2D->setEnabled(false); m_Controls.m_ManualToolSelectionBox3D->setEnabled(false); mitk::DataNode* referenceNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls.m_WorkingNodeSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); - auto referenceNodeIsVisible = renderWindowPart && - referenceNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); - if (!referenceNodeIsVisible) - { - this->UpdateWarningLabel(tr("The selected reference image is currently not visible!")); - return; - } - auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry *worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); + m_Controls.groupBox_Layer->setEnabled(true); + m_Controls.groupBox_Labels->setEnabled(true); + m_Controls.m_LabelSetWidget->setEnabled(true); m_Controls.m_ManualToolSelectionBox2D->setEnabled(true); m_Controls.m_ManualToolSelectionBox3D->setEnabled(true); return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkMultiLabelSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls.lblSegmentationWarnings->hide(); } else { m_Controls.lblSegmentationWarnings->show(); } m_Controls.lblSegmentationWarnings->setText("" + text + ""); } diff --git a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp index cd5613a0ab..a6c3dd90d8 100644 --- a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp +++ b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp @@ -1,395 +1,395 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkAddNewPropertyDialog.h" #include "QmitkPropertyItemDelegate.h" #include "QmitkPropertyItemModel.h" #include "QmitkPropertyItemSortFilterProxyModel.h" #include "QmitkPropertyTreeView.h" #include #include #include #include #include #include #include #include namespace { QmitkAbstractNodeSelectionWidget::NodeList GetInitialSelection(berry::ISelection::ConstPointer selection) { if (selection.IsNotNull() && !selection->IsEmpty()) { auto* dataNodeSelection = dynamic_cast(selection.GetPointer()); if (nullptr != dataNodeSelection) { auto firstSelectedDataNode = dataNodeSelection->GetSelectedDataNodes().front(); if (firstSelectedDataNode.IsNotNull()) { QmitkAbstractNodeSelectionWidget::NodeList initialSelection; initialSelection.push_back(firstSelectedDataNode); return initialSelection; } } } return QmitkAbstractNodeSelectionWidget::NodeList(); } } const std::string QmitkPropertyTreeView::VIEW_ID = "org.mitk.views.properties"; QmitkPropertyTreeView::QmitkPropertyTreeView() : m_PropertyAliases(mitk::CoreServices::GetPropertyAliases(nullptr), nullptr), m_PropertyDescriptions(mitk::CoreServices::GetPropertyDescriptions(nullptr), nullptr), m_PropertyPersistence(mitk::CoreServices::GetPropertyPersistence(nullptr), nullptr), m_ProxyModel(nullptr), m_Model(nullptr), m_Delegate(nullptr), m_Renderer(nullptr) { } QmitkPropertyTreeView::~QmitkPropertyTreeView() { } void QmitkPropertyTreeView::SetFocus() { m_Controls.filterLineEdit->setFocus(); } void QmitkPropertyTreeView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_Controls.propertyListComboBox->count() == 2) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); Q_FOREACH(QString renderWindow, renderWindows.keys()) { m_Controls.propertyListComboBox->insertItem(m_Controls.propertyListComboBox->count() - 1, QString("Data node: ") + renderWindow); } } } void QmitkPropertyTreeView::RenderWindowPartDeactivated(mitk::IRenderWindowPart*) { if (m_Controls.propertyListComboBox->count() > 2) { m_Controls.propertyListComboBox->clear(); m_Controls.propertyListComboBox->addItem("Data node: common"); m_Controls.propertyListComboBox->addItem("Base data"); } } void QmitkPropertyTreeView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.propertyListComboBox->addItem("Data node: common"); mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); if (renderWindowPart != nullptr) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); for(const auto& renderWindow : renderWindows.keys()) { m_Controls.propertyListComboBox->addItem(QString("Data node: ") + renderWindow); } } m_Controls.propertyListComboBox->addItem("Base data"); m_Controls.newButton->setEnabled(false); this->HideAllIcons(); m_ProxyModel = new QmitkPropertyItemSortFilterProxyModel(m_Controls.treeView); m_Model = new QmitkPropertyItemModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_Model); m_ProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setDynamicSortFilter(true); m_Delegate = new QmitkPropertyItemDelegate(m_Controls.treeView); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); - auto selection = this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); - auto currentSelection = GetInitialSelection(selection); - - if (!currentSelection.isEmpty()) - m_Controls.singleSlot->SetCurrentSelection(currentSelection); - m_Controls.filterLineEdit->setClearButtonEnabled(true); m_Controls.treeView->setItemDelegateForColumn(1, m_Delegate); m_Controls.treeView->setModel(m_ProxyModel); m_Controls.treeView->setColumnWidth(0, 160); m_Controls.treeView->sortByColumn(0, Qt::AscendingOrder); m_Controls.treeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.treeView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.treeView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked); const int ICON_SIZE = 32; auto icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tags.svg")); m_Controls.tagsLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tag.svg")); m_Controls.tagLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg")); m_Controls.saveLabel->setPixmap(icon.pixmap(ICON_SIZE)); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkPropertyTreeView::OnCurrentSelectionChanged); connect(m_Controls.filterLineEdit, &QLineEdit::textChanged, this, &QmitkPropertyTreeView::OnFilterTextChanged); connect(m_Controls.propertyListComboBox, static_cast(&QComboBox::currentIndexChanged), this, &QmitkPropertyTreeView::OnPropertyListChanged); connect(m_Controls.newButton, &QPushButton::clicked, this, &QmitkPropertyTreeView::OnAddNewProperty); connect(m_Controls.treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QmitkPropertyTreeView::OnCurrentRowChanged); connect(m_Model, &QmitkPropertyItemModel::modelReset, this, &QmitkPropertyTreeView::OnModelReset); + + auto selection = this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + auto currentSelection = GetInitialSelection(selection); + + if (!currentSelection.isEmpty()) + m_Controls.singleSlot->SetCurrentSelection(currentSelection); } void QmitkPropertyTreeView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } QString QmitkPropertyTreeView::GetPropertyNameOrAlias(const QModelIndex& index) { QString propertyName; if (index.isValid()) { QModelIndex current = index; while (current.isValid()) { QString name = m_ProxyModel->data(m_ProxyModel->index(current.row(), 0, current.parent())).toString(); propertyName.prepend(propertyName.isEmpty() ? name : name.append('.')); current = current.parent(); } } return propertyName; } void QmitkPropertyTreeView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; this->SetPartName("Properties"); m_Model->SetPropertyList(nullptr); m_Delegate->SetPropertyList(nullptr); m_Controls.newButton->setEnabled(false); return; } // node is selected, create tree with node properties m_SelectedNode = nodes.front(); mitk::PropertyList* propertyList = m_Model->GetPropertyList(); if (m_Renderer == nullptr && m_Controls.propertyListComboBox->currentText() == "Base data") { propertyList = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetPropertyList() : nullptr; } else { propertyList = m_SelectedNode->GetPropertyList(m_Renderer); } QString selectionClassName = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetNameOfClass() : ""; m_SelectionClassName = selectionClassName.toStdString(); m_Model->SetPropertyList(propertyList, selectionClassName); m_Delegate->SetPropertyList(propertyList); m_Controls.newButton->setEnabled(true); m_Controls.treeView->expandAll(); } void QmitkPropertyTreeView::HideAllIcons() { m_Controls.tagLabel->hide(); m_Controls.tagsLabel->hide(); m_Controls.saveLabel->hide(); } void QmitkPropertyTreeView::OnCurrentRowChanged(const QModelIndex& current, const QModelIndex&) { if (current.isValid()) { QString name = this->GetPropertyNameOrAlias(current); if (!name.isEmpty()) { QString alias; bool isTrueName = true; std::string trueName = m_PropertyAliases->GetPropertyName(name.toStdString()); if (trueName.empty() && !m_SelectionClassName.empty()) trueName = m_PropertyAliases->GetPropertyName(name.toStdString(), m_SelectionClassName); if (!trueName.empty()) { alias = name; name = QString::fromStdString(trueName); isTrueName = false; } QString description = QString::fromStdString(m_PropertyDescriptions->GetDescription(name.toStdString())); std::vector aliases; if (!isTrueName) { aliases = m_PropertyAliases->GetAliases(name.toStdString(), m_SelectionClassName); if (aliases.empty() && !m_SelectionClassName.empty()) aliases = m_PropertyAliases->GetAliases(name.toStdString()); } bool isPersistent = m_PropertyPersistence->HasInfo(name.toStdString()); if (!description.isEmpty() || !aliases.empty() || isPersistent) { QString customizedDescription; if (!aliases.empty()) { customizedDescription = "

" + name + "

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

" + name + "

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

" + description + "

"; m_Controls.tagsLabel->setVisible(!aliases.empty() && aliases.size() > 1); m_Controls.tagLabel->setVisible(!aliases.empty() && aliases.size() == 1); m_Controls.saveLabel->setVisible(isPersistent); m_Controls.descriptionLabel->setText(customizedDescription); m_Controls.descriptionLabel->show(); return; } } } m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } void QmitkPropertyTreeView::OnPropertyListChanged(int index) { if (index == -1) return; QString renderer = m_Controls.propertyListComboBox->itemText(index); if (renderer.startsWith("Data node: ")) renderer = QString::fromStdString(renderer.toStdString().substr(11)); m_Renderer = nullptr; if (renderer != "common" && renderer != "Base data") { auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) m_Renderer = renderWindowPart->GetQmitkRenderWindow(renderer)->GetRenderer(); } QList nodes; if (m_SelectedNode.IsNotNull()) nodes << m_SelectedNode; this->OnCurrentSelectionChanged(nodes); } void QmitkPropertyTreeView::OnAddNewProperty() { std::unique_ptr dialog(m_Controls.propertyListComboBox->currentText() != "Base data" ? new QmitkAddNewPropertyDialog(m_SelectedNode, m_Renderer) : new QmitkAddNewPropertyDialog(m_SelectedNode->GetData())); if (dialog->exec() == QDialog::Accepted) this->m_Model->Update(); } void QmitkPropertyTreeView::OnFilterTextChanged(const QString& filter) { m_ProxyModel->setFilterWildcard(filter); m_Controls.treeView->expandAll(); } void QmitkPropertyTreeView::OnModelReset() { m_Controls.treeView->expandAll(); m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index efcc12808f..ec740d3f74 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,285 +1,321 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the segmentation view",2.00} Some of the features described below are closed source additions to the open source toolkit MITK and are not available in every application. \tableofcontents \section org_mitk_views_segmentationUserManualOverview Overview Segmentation is the act of partitioning an image into subsets by either manual or automated delineation to create i.e. a distinction between foreground and background. The MITK segmentation plugin allows you to create segmentations of anatomical and pathological structures in medical images. The plugin consists of a number of views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Segmentation post-processing
In this documentation, the features and usage of the segmentation view will be described. For an introduction to the segmentation utilities and clipping plane views, please be referred to the respective documentation pages. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation plugin overview", 16.00} \section org_mitk_views_segmentationPreferences Preferences The segmentation plugin offers a number of preferences which can be set via the MITK Workbench application preferences: \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Slim view: Allows you to show or hide the tool button description of the segmentation view
  • 2D display: Specify whether the segmentation is drawn as outline or as a transparent overlay
  • 3D display: Activate 3D volume rendering for your segmentation
  • Data node selection mode: If activated the segmentation image is automatically chosen from the data manager selection.
  • Smoothed surface creation: Set certain smoothing parameters for surface creation
\section org_mitk_views_segmentationUserManualTechnical Technical Issues The segmentation plugin makes a number of assumptions:
  • Images must be 2D, 3D, or 3D+t.
  • Images must be single-values, i.e. CT, MRI or "normal" ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations are handled as binary images of the same extent as the original image.
\section org_mitk_views_segmentationUserManualImageSelection Data Selection & Creating New Segmentations To select a reference image for the segmentation, click on the Patient Image selection widget and choose a suitable image from the selection available in the data manager. By default the auto selection mode is enabled (see \ref org_mitk_views_segmentationPreferences).\n Once a patient image is selected, a new segmentation can be created on this reference image by clicking the New... button to the right of the Segmentation selection widget. An input field will appear which allows you to set the name and display color of the segmentation. Notice that the input field suggests names once you start typing and that it also suggests colors for known organ names. Once generated the segmentation will be added with "binary mask" icon to the data manager as sub-node of the reference image. This item is automatically selected for you, allowing you to start editing the new segmentation right away. \subsection org_mitk_views_segmentationUserManualManualKringeling2 Selecting Segmentations for Editing Alternatively to creating a new segmentation, an existing one can be edited as well. As you might have segmented multiple structures within a single image, the application needs to know which of them to use for editing. For that you click the segmentation selection widget and a selection field will open, containing all suitable segmentations for the parent dataset available in the data manager. \section org_mitk_views_segmentationUserManualToolOverview Segmentation Tool Overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting/adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentationUserManualManualKringeling 2D Segmentation Tools With 2D manual contouring you define which voxels are part of the segmentation and which ones are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. \subsection org_mitk_views_segmentationUserManualManualKringeling3 Selecting Editing Tools To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). \subsection org_mitk_views_segmentationUserManualManualKringeling4 Using Editing Tools All of the editing tools work by the same principle: you use the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), move the mouse while holding the mouse button and release to finish the editing action. Multi-step undo and redo is fully supported by all editing tools. Use the application-wide undo button in the toolbar to revert erroneous %actions. Remark: If you are familiar with the MITK Workbench, you know that clicking and moving the mouse in any of the 2D render windows will move around the crosshair that defines what part of the image is displayed. This behavior is disabled whilst any of the manual segmentation tools are active- otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationUserManualAddSubtractTools Add and Subtract Tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and Subtract Tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationUserManualPaintWipeTools Paint and Wipe Tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and Wipe Tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationUserManualRegionGrowingTool Region Growing Tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region Growing Tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This enables you to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up/down is different from left/right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_IMGLeakage.png,"Leakage correction feature of the Region Growing tool",11.28} \endif \subsection org_mitk_views_segmentationUserManualFillTool Fill Tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill Tool",3.81} Left-click inside a segmentation with holes to completely fill all holes. Left-click inside a hole to fill only this specific hole. \subsection org_mitk_views_segmentationUserManualEraseTool Erase Tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase Tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmentations (left-click on specific segmentation) or to clear a whole slice at once (left-click outside a segmentation). \subsection org_mitk_views_segmentationUserManualLiveWireTool Live Wire Tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live Wire Tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00}
  • (1) To start the tool you have to double-click near the edge of the object you want to segment. The initial anchor point will snap to the edge within a 3x3 region.
  • (2) Move the mouse. You don't have trace the edge of the object. The contour will automatically snap to it.
  • (3) To fix a segment you can set anchor points by single left mouse button click.
  • (4) Go on with moving the mouse and setting anchor points.
  • (5) To close the contour double-click on the initial anchor point.
  • (6) After closing, the contour can be edited by moving, inserting and deleting anchor points.
The contour will be transferred to its binary image representation by deactivating the tool. \subsection org_mitk_views_segmentationUserManual2DFastMarchingTool 2D Fast Marching Tool \imageMacro{QmitkSegmentation_IMG2DFastMarchingUsage.png,"2D Fast Marching Tool",3.01} Provides a fast marching based 2D interaction segmentation tool. You start with setting seed points in an image slice. Via several sliders you can adapt parameters and see the fast marching result instantly. \subsection org_mitk_views_segmentationUserManualManualKringeling5 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D Interpolation Usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D Interpolation Usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since Redo/Undo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentationUserManual3DSegmentationTools 3D Segmentation Tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentationUserManual3DThresholdTool 3D Threshold Tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentationUserManual3DULTool 3D Upper/Lower Threshold Tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D Upper/Lower Threshold tool",10.00} \subsection org_mitk_views_segmentationUserManual3DOtsuTool 3D Otsu Tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentationUserManual3DFMTool 3D Fast Marching Tool The 3D Fast Marching tool works similar to the 2D pendant but on the whole image. Depending on your image size the calculation might take some time. You can interactively set the parameters of the algorithm via the GUI. The resulting segmentation will be automatically updated. \imageMacro{QmitkSegmentation_3DFMTool.png,"3D Fast Marching tool",10.00} \subsection org_mitk_views_segmentationUserManual3DRGTool 3D Region Growing Tool The 3D Region Growing tool works similar to the 2D pendant. At the beginning you have to place a seedpoint and define a threshold interval. If you press Run Segmentation a preview is calculated. By moving the Adapt region growing slider you can interactively adapt the segmentation result. \imageMacro{QmitkSegmentation_3DRGTool.png,"3D Region Growing tool",10.00} \subsection org_mitk_views_segmentationUserManual3DWatershedTool 3D Watershed Tool This tool provides a watershed based segmentation algorithm. For a detailed explanation of the parameters level and threshold, please be referred to https://itk.org/Doxygen/html/classitk_1_1WatershedImageFilter.html . Remark: The tool is (due to its implementation) faster if you use lower levels. So in doubt you should start with high levels (supposed to undersegment) and then lower the levels until you are happy. \imageMacro{QmitkSegmentation_3DWatershedTool.png,"3D Watershed tool",10.00} \subsection org_mitk_views_segmentationUserManualPickingTool Picking Tool The Picking tool allows you to select islands within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} +\subsection org_mitk_views_segmentationUserManualnnUNetTool nnU-Net Tool (Windows & Ubuntu only) + +This tool provides a GUI to the deep learning-based segmentation algorithm called the nnUNet. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) +for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
+Remark: The tool assumes that you have a Python3 environment with nnUNet (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. +\imageMacro{QmitkSegmentation_3DWatershedTool.png,"nnUNet tool",10.00} + +\subsubsection org_mitk_views_segmentationUserManualnnUNetToolWorkflow Workflow: + -# Click on the "nnUNet Results Folder" directory icon and navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as + per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. + \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} + -# Choose your required configuration-task-trainer-fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). + Note that, if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. + -# For ensemble predictions, you will get the option to select parameters based on postprocessing files available in the ensembles configuration of RESULTS_FOLDER. + Note that, all folds for both configurations will be considered for ensemble inferencing. + \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} + -# If your task requires multi-modal inputs, then check the "Multi-Modal" checkbox and enter the no.of modalities in the "No. of Extra Modalities" spinbox you'd like to refer to. + Instantly, as much node selectors should appear below to select other image nodes from the Data Manager along with a deactivated selector with preselected with the reference node. + Certain pre-trained configurations are trained in a particular modal order. Please be aware of the order beforehand & select the image nodes in the node selectors accordingly for accurate inferencing. + If the reference image node for segmentation is not the first in the multi-modal order, then you may reposition it using the "Reference Image Position" spinbox. + \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} + -# Click on "Advanced" to choose a Python environment. Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. + Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder. + No need to select all the way until "../myenv/bin/python", for example. + -# Click on "Preview". + \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} + -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. + -# Use "GPU Id" to change the preferred GPU for inferencing. This is equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. + +\subsubsection org_mitk_views_segmentationUserManualnnUNetToolMisc Miscellaneous: + -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. + -# If you have a nnUNet code-base yet to be pip-installed into any environment (eg. developer modified nnUNet forks), click checkbox "No pip". + Once the checkbox is checked a new directory selector appears in the section. You may click and navigate to your nnUNet fork folder to select the code from which you want inference. + \imageMacro{QmitkSegmentation_nnUNet_nopip.png,"nnUNet Advanced Settings with option to select nnUNet local folder",10.00} + + \section org_mitk_views_segmentationUserManualPostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations.",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3DTImages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationUserManualTechnicalDetail Technical Information for Developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions . */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png new file mode 100644 index 0000000000..f084fba26e Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Advanced.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png new file mode 100644 index 0000000000..5736d3d8da Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_Settings.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png new file mode 100644 index 0000000000..7354cb28e8 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_ensemble.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png new file mode 100644 index 0000000000..803d126ec7 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_multimodal.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png new file mode 100644 index 0000000000..593c91f57f Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_nnUNet_nopip.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml index a72db4b004..a628b56818 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml +++ b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml @@ -1,80 +1,81 @@ Allows the segmentation of images using different tools. Edit segmentations using standard operations. + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 1c1181fdf1..b26097ebce 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,822 +1,809 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_AutoSelectionEnabled(false) , m_MouseCursorSet(false) { mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); if (nodes.empty()) { m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls->segImageSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } this->UpdateGUI(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); + // Remove observer if one was registered + auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); + if (finding != m_WorkingDataObserverTags.end()) + { + m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); + m_WorkingDataObserverTags.erase(m_WorkingNode); + } + if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); + + auto command = itk::SimpleMemberCommand::New(); + command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); + m_WorkingDataObserverTags.insert(std::pair(m_WorkingNode, + m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer referenceNode = m_ToolManager->GetReferenceData(0); if (referenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information(m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } m_ToolManager->ActivateTool(-1); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The selected image has multiple time steps.\n\nDo you want to create a static " "segmentation that is identical for all time steps or do you want to create a " "dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); // ask about the name and organ type of the new segmentation auto dialog = new QmitkNewSegmentationDialog(m_Parent); QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); dialog->SetSuggestionList(organColors); dialog->SetSegmentationName(newName); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } std::string newNodeName = dialog->GetSegmentationName().toStdString(); if (newNodeName.empty()) { newNodeName = "Unnamed"; } // create a new image of the same dimensions and smallest possible pixel type auto firstTool = m_ToolManager->GetToolById(0); if (nullptr == firstTool) { return; } mitk::DataNode::Pointer emptySegmentation = nullptr; try { emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); } catch (const std::bad_alloc &) { QMessageBox::warning(m_Parent, tr("New segmentation"), tr("Could not allocate memory for new segmentation")); } if (nullptr == emptySegmentation) { return; // could have been aborted by user } // initialize "showVolume"-property to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); // escape ';' here (replace by '\;') QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->Put("Organ-Color-List", stringForStorage); this->GetPreferences()->Flush(); this->GetDataStorage()->Add(emptySegmentation, referenceNode); if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } emptySegmentation->SetSelected(true); m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); - - this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (manualSegmentationTool) { if (state == true) { manualSegmentationTool->SetShowMarkerNodes(true); } else { manualSegmentationTool->SetShowMarkerNodes(false); } } } } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->patImageSelector->SetDataStorage(GetDataStorage()); m_Controls->patImageSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->patImageSelector->SetInvalidInfo("Select an image"); m_Controls->patImageSelector->SetPopUpTitel("Select an image"); m_Controls->patImageSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->segImageSelector->SetDataStorage(GetDataStorage()); m_Controls->segImageSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->segImageSelector->SetInvalidInfo("Select a segmentation"); m_Controls->segImageSelector->SetPopUpTitel("Select a segmentation"); m_Controls->segImageSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->patImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->segImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' 'Live Wire' '2D Fast Marching'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Fast Marching 3D' 'Region Growing 3D' Watershed Picking"); - +#ifdef __linux__ + segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows +#endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // all part of open source MITK m_Controls->m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer2D); m_Controls->m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); //setup 3D Tools m_Controls->m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer3D); m_Controls->m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // create signal/slot connections connect(m_Controls->btnNewSegmentation, &QToolButton::clicked, this, &QmitkSegmentationView::CreateNewSegmentation); connect(m_Controls->m_SlicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); - // set callback function for already existing segmentation nodes - mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(m_SegmentationPredicate); - for (mitk::DataStorage::SetOfObjects::const_iterator iter = allSegmentations->begin(); iter != allSegmentations->end(); ++iter) - { - mitk::DataNode* node = *iter; - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - } - auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->patImageSelector->SetAutoSelectNewNodes(true); m_Controls->segImageSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part if (m_Controls) { m_Controls->m_SlicesInterpolator->SetDataStorage(this->GetDataStorage()); QList controllers; controllers.push_back(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->m_SlicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Controls != nullptr) { bool slimView = prefs->GetBool("slim view", false); m_Controls->m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls->m_ManualToolSelectionBox3D->SetShowNames(!slimView); } m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { - if (!m_SegmentationPredicate->CheckNode(node)) + if (m_SegmentationPredicate->CheckNode(node)) { - return; + this->ApplyDisplayOptions(const_cast(node)); } - - auto command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); - m_WorkingDataObserverTags.insert(std::pair( - const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); - - this->ApplyDisplayOptions(const_cast(node)); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } - - mitk::DataNode* tempNode = const_cast(node); - //Remove observer if one was registered - auto finding = m_WorkingDataObserverTags.find(tempNode); - if (finding != m_WorkingDataObserverTags.end()) - { - node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); - m_WorkingDataObserverTags.erase(tempNode); - } } void QmitkSegmentationView::ApplyDisplayOptions() { if (!m_Parent) { return; } if (!m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataNode::Pointer workingData = m_ToolManager->GetWorkingData(0); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is actually a multi label segmentation, // but its outline property can be set in the 'single label' segmentation preference page as well node->SetProperty("labelset.contour.active", drawOutline); // force render window update to show outline node->GetData()->Modified(); } else { // node is a 'single label' segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* _3DRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, _3DRenderWindow->GetRenderer())) { selectedRenderWindow = _3DRenderWindow; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; { ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); } selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (nodes.size() != 0) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->btnNewSegmentation->setEnabled(false); m_Controls->m_SlicesInterpolator->setEnabled(false); if (hasReferenceNode) { m_Controls->btnNewSegmentation->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { m_Controls->m_SlicesInterpolator->setEnabled(true); int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->m_ManualToolSelectionBox2D->setEnabled(false); m_Controls->m_ManualToolSelectionBox3D->setEnabled(false); mitk::DataNode* referenceNode = m_Controls->patImageSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->segImageSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry *worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->m_ManualToolSelectionBox2D->setEnabled(true); m_Controls->m_ManualToolSelectionBox3D->setEnabled(true); return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls->lblSegmentationWarnings->hide(); } else { m_Controls->lblSegmentationWarnings->show(); } m_Controls->lblSegmentationWarnings->setText("" + text + ""); } diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake b/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake index 5b58d0228e..a4e8dac4c9 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake +++ b/Plugins/org.mitk.gui.qt.viewnavigator/files.cmake @@ -1,47 +1,45 @@ set(SRC_CPP_FILES QmitkViewNavigatorWidget.cpp - -# mitkQtPerspectiveItem.h -# mitkQtViewItem.h + QmitkPerspectiveItem.h + QmitkViewItem.h ) set(INTERNAL_CPP_FILES - org_mitk_gui_qt_viewnavigator_Activator.cpp - ViewNavigatorView.cpp + mitkPluginActivator.cpp + QmitkViewNavigatorView.cpp ) set(UI_FILES src/QmitkViewNavigatorWidgetControls.ui ) set(MOC_H_FILES - src/internal/org_mitk_gui_qt_viewnavigator_Activator.h - src/internal/ViewNavigatorView.h - + src/internal/mitkPluginActivator.h + src/internal/QmitkViewNavigatorView.h src/QmitkViewNavigatorWidget.h ) # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench set(CACHED_RESOURCE_FILES resources/view-manager.svg plugin.xml ) # list of Qt .qrc files which contain additional resources # specific to this plugin set(QRC_FILES ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml b/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml index c2a48b96b9..f08d78ca51 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml +++ b/Plugins/org.mitk.gui.qt.viewnavigator/plugin.xml @@ -1,12 +1,12 @@ - diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h similarity index 51% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h index 644aa14611..10ecc84c24 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtPerspectiveItem.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkPerspectiveItem.h @@ -1,44 +1,38 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef _PerspectiveItem -#define _PerspectiveItem +#ifndef QMITKPERSPECTIVEITEM_H +#define QMITKPERSPECTIVEITEM_H #include #include -namespace mitk -{ - -class QtPerspectiveItem : public QStandardItem +class QmitkPerspectiveItem : public QStandardItem { public: - QtPerspectiveItem(QString string) : - QStandardItem(string) - { - } - QtPerspectiveItem(const QIcon& icon, QString string) : - QStandardItem(icon, string) - { - } - berry::IPerspectiveDescriptor::Pointer m_Perspective; + QmitkPerspectiveItem(const QString& string) + : QStandardItem(string) + { + } - QStringList m_Tags; - QString m_Description; -private: + QmitkPerspectiveItem(const QIcon& icon, const QString& string) + : QStandardItem(icon, string) + { + } + berry::IPerspectiveDescriptor::Pointer m_ItemDescriptor; + QStringList m_Tags; + QString m_Description; }; -} - -#endif +#endif // QMITKPERSPECTIVEITEM_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h similarity index 53% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h index 34e71b487d..26dd93c9c5 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/mitkQtViewItem.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewItem.h @@ -1,45 +1,38 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef _ViewItem -#define _ViewItem +#ifndef QMITKVIEWITEM_H +#define QMITKVIEWITEM_H #include -#include #include -namespace mitk -{ - -class QtViewItem : public QStandardItem +class QmitkViewItem : public QStandardItem { public: - QtViewItem(QString string) : - QStandardItem(string) - { - } - QtViewItem(const QIcon& icon, QString string) : - QStandardItem(icon, string) - { - } - berry::IViewDescriptor::Pointer m_View; - QStringList m_Tags; - QString m_Description; + QmitkViewItem(const QString& string) + : QStandardItem(string) + { + } -private: + QmitkViewItem(const QIcon& icon, const QString& string) + : QStandardItem(icon, string) + { + } + berry::IViewDescriptor::Pointer m_ItemDescriptor; + QStringList m_Tags; + QString m_Description; }; -} - -#endif +#endif // QMITKVIEWITEM_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp index fc58de6095..1b378a4c41 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.cpp @@ -1,697 +1,699 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -//Qmitk headers -#include "QmitkViewNavigatorWidget.h" +// View navigator plugin +#include +#include +#include // Blueberry -#include #include #include -#include #include #include #include #include #include #include // MITK #include // Qt #include #include -#include #include #include class KeywordRegistry { public: + KeywordRegistry() { berry::IExtensionRegistry* extensionPointService = berry::Platform::GetExtensionRegistry(); auto keywordExts = extensionPointService->GetConfigurationElementsFor("org.blueberry.ui.keywords"); for (auto keywordExtsIt = keywordExts.begin(); keywordExtsIt != keywordExts.end(); ++keywordExtsIt) { QString keywordId = (*keywordExtsIt)->GetAttribute("id"); QString keywordLabels = (*keywordExtsIt)->GetAttribute("label"); m_Keywords[keywordId].push_back(keywordLabels); } } QStringList GetKeywords(const QString& id) { return m_Keywords[id]; } QStringList GetKeywords(const QStringList& ids) { QStringList result; - for (int i = 0; i < ids.size(); ++i) + for (const auto& id : ids) { - result.append(this->GetKeywords(ids[i])); + result.append(this->GetKeywords(id)); } return result; } private: + QHash m_Keywords; }; - class ClassFilterProxyModel : public QSortFilterProxyModel { -private : - bool hasToBeDisplayed(const QModelIndex index) const; - bool displayElement(const QModelIndex index) const; public: - ClassFilterProxyModel(QObject *parent = nullptr); - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; -}; -ClassFilterProxyModel::ClassFilterProxyModel(QObject *parent): - QSortFilterProxyModel(parent) -{ -} -bool ClassFilterProxyModel::filterAcceptsRow(int sourceRow, - const QModelIndex &sourceParent) const -{ + ClassFilterProxyModel(QObject* parent = nullptr) + : QSortFilterProxyModel(parent) + { + } + + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); return hasToBeDisplayed(index); -} + } -bool ClassFilterProxyModel::displayElement(const QModelIndex index) const -{ - bool result = false; +private: + + bool displayElement(const QModelIndex index) const + { QString type = sourceModel()->data(index, Qt::DisplayRole).toString(); - QStandardItem * item = dynamic_cast(sourceModel())->itemFromIndex(index); + QStandardItem* item = dynamic_cast(sourceModel())->itemFromIndex(index); if (type.contains(filterRegExp())) { return true; } + + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - mitk::QtViewItem* viewItem = dynamic_cast(item); - if (viewItem) + for (const auto& tag : viewItem->m_Tags) { - for (int i = 0; i < viewItem->m_Tags.size(); ++i) - { - if (viewItem->m_Tags[i].contains(filterRegExp())) - { - return true; - } - } - if (viewItem->m_Description.contains(filterRegExp())) + if (tag.contains(filterRegExp())) { return true; } } + if (viewItem->m_Description.contains(filterRegExp())) + { + return true; + } } + + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { - mitk::QtPerspectiveItem* viewItem = dynamic_cast(item); - if (viewItem) + for (const auto& tag : perspectiveItem->m_Tags) { - for (int i = 0; i < viewItem->m_Tags.size(); ++i) - { - if (viewItem->m_Tags[i].contains(filterRegExp())) - { - return true; - } - } - if (viewItem->m_Description.contains(filterRegExp())) + if (tag.contains(filterRegExp())) { return true; } } + if (perspectiveItem->m_Description.contains(filterRegExp())) + { + return true; + } } - return result; -} + return false; + } -bool ClassFilterProxyModel::hasToBeDisplayed(const QModelIndex index) const -{ + bool hasToBeDisplayed(const QModelIndex index) const + { bool result = false; - if ( sourceModel()->rowCount(index) > 0 ) + if (sourceModel()->rowCount(index) > 0) { - for( int ii = 0; ii < sourceModel()->rowCount(index); ii++) + for (int i = 0; i < sourceModel()->rowCount(index); i++) { - QModelIndex childIndex = sourceModel()->index(ii,0,index); - if ( ! childIndex.isValid() ) + QModelIndex childIndex = sourceModel()->index(i, 0, index); + if (!childIndex.isValid()) + { break; + } + result = hasToBeDisplayed(childIndex); result |= displayElement(index); + if (result) { break; } } } else { result = displayElement(index); } return result; -} + } +}; class ViewNavigatorPerspectiveListener: public berry::IPerspectiveListener { public: - ViewNavigatorPerspectiveListener(QmitkViewNavigatorWidget* p) : - parentWidget(p) + ViewNavigatorPerspectiveListener(QmitkViewNavigatorWidget* parent) + : m_ParentWidget(parent) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED // remove the following line when command framework is finished | Events::CLOSED | Events::OPENED | Events::PART_CHANGED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { - parentWidget->m_ActivePerspective = perspective; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = perspective; + m_ParentWidget->UpdateTreeList(); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& newPerspective) override { - parentWidget->m_ActivePerspective = newPerspective; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = newPerspective; + m_ParentWidget->UpdateTreeList(); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->m_ActivePerspective = nullptr; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = nullptr; } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->UpdateTreeList(); + m_ParentWidget->UpdateTreeList(); } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { - parentWidget->m_ActivePerspective = nullptr; - parentWidget->UpdateTreeList(); + m_ParentWidget->m_ActivePerspective = nullptr; } using IPerspectiveListener::PerspectiveChanged; - void PerspectiveChanged(const berry::IWorkbenchPage::Pointer&, - const berry::IPerspectiveDescriptor::Pointer&, + void PerspectiveChanged(const berry::IWorkbenchPage::Pointer& /*page*/, + const berry::IPerspectiveDescriptor::Pointer& /*perspective*/, const berry::IWorkbenchPartReference::Pointer& partRef, const std::string& changeId) { - if (changeId=="viewHide" && partRef->GetId()=="org.mitk.views.viewnavigatorview") - berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->RemovePerspectiveListener(parentWidget->m_PerspectiveListener.data()); + if (changeId == "viewHide" && partRef->GetId() == "org.mitk.views.viewnavigator") + berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->RemovePerspectiveListener(m_ParentWidget->m_PerspectiveListener.data()); else - parentWidget->UpdateTreeList(nullptr, partRef.GetPointer(), changeId); + m_ParentWidget->UpdateTreeList(); } private: - QmitkViewNavigatorWidget* parentWidget; + + QmitkViewNavigatorWidget* m_ParentWidget; +}; + +class ViewNavigatorViewListener: public berry::IPartListener +{ +public: + + ViewNavigatorViewListener(QmitkViewNavigatorWidget* parent) + : m_ParentWidget(parent) + { + } + + Events::Types GetPartEventTypes() const override + { + return Events::OPENED | Events::CLOSED; + } + + void PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) override + { + if (partRef->GetId() != "org.mitk.views.viewnavigator") + { + m_ParentWidget->UpdateTreeList((partRef->GetPart(false)).GetPointer()); + } + else + { + m_ParentWidget->FillTreeList(); + m_ParentWidget->UpdateTreeList(); + } + } + + void PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) override + { + if (partRef->GetId() != "org.mitk.views.viewnavigator") + { + m_ParentWidget->UpdateTreeList(); + } + } + +private: + + QmitkViewNavigatorWidget* m_ParentWidget; }; bool compareViews(const berry::IViewDescriptor::Pointer& a, const berry::IViewDescriptor::Pointer& b) { if (a.IsNull() || b.IsNull()) + { return false; + } + return a->GetLabel().compare(b->GetLabel()) < 0; } bool comparePerspectives(const berry::IPerspectiveDescriptor::Pointer& a, const berry::IPerspectiveDescriptor::Pointer& b) { if (a.IsNull() || b.IsNull()) + { return false; + } + return a->GetLabel().compare(b->GetLabel()) < 0; } bool compareQStandardItems(const QStandardItem* a, const QStandardItem* b) { - if (a==nullptr || b==nullptr) + if (nullptr == a || nullptr== b) + { return false; + } + return a->text().compare(b->text()) < 0; } QmitkViewNavigatorWidget::QmitkViewNavigatorWidget(berry::IWorkbenchWindow::Pointer window, - QWidget * parent, Qt::WindowFlags ) + QWidget* parent, + Qt::WindowFlags) : QWidget(parent) , m_Window(window) { - m_Generated = false; this->CreateQtPartControl(this); } QmitkViewNavigatorWidget::~QmitkViewNavigatorWidget() { m_Window->RemovePerspectiveListener(m_PerspectiveListener.data()); + m_Window->GetPartService()->RemovePartListener(m_ViewPartListener.data()); } -void QmitkViewNavigatorWidget::setFocus() +void QmitkViewNavigatorWidget::SetFocus() { m_Controls.lineEdit->setFocus(); } -void QmitkViewNavigatorWidget::CreateQtPartControl( QWidget *parent ) -{ - // create GUI widgets from the Qt Designer's .ui file - m_PerspectiveListener.reset(new ViewNavigatorPerspectiveListener(this)); - m_Window->AddPerspectiveListener(m_PerspectiveListener.data()); - - m_Parent = parent; - m_Controls.setupUi( parent ); - connect( m_Controls.m_PluginTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(CustomMenuRequested(QPoint))); - connect( m_Controls.m_PluginTreeView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(ItemClicked(const QModelIndex&))); - connect( m_Controls.lineEdit, SIGNAL(textChanged(QString)), SLOT(FilterChanged())); - - m_ContextMenu = new QMenu(m_Controls.m_PluginTreeView); - m_Controls.m_PluginTreeView->setContextMenuPolicy(Qt::CustomContextMenu); - - // Create a new TreeModel for the data - m_TreeModel = new QStandardItemModel(); - m_FilterProxyModel = new ClassFilterProxyModel(this); - m_FilterProxyModel->setSourceModel(m_TreeModel); - //proxyModel->setFilterFixedString("Diff"); - m_Controls.m_PluginTreeView->setModel(m_FilterProxyModel); - - this->UpdateTreeList(); -} - -void QmitkViewNavigatorWidget::UpdateTreeList(QStandardItem* root, berry::IWorkbenchPartReference *partRef, const std::string &changeId) +void QmitkViewNavigatorWidget::UpdateTreeList(berry::IWorkbenchPart* workbenchPart) { - berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); - if (page.IsNull()) - return; + berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); + if (page.IsNull()) + { + return; + } - if( !m_Generated ) - { - m_Generated = FillTreeList(); - } + berry::IPerspectiveDescriptor::Pointer currentPerspective = page->GetPerspective(); + QList viewParts = page->GetViews(); - if (root==nullptr) - root = m_TreeModel->invisibleRootItem(); - for (int i=0; irowCount(); i++) + // iterate over all tree items + for (const auto& item : m_TreeModel->findItems("*", Qt::MatchWildcard | Qt::MatchRecursive)) + { + QFont font; + // check if the item is a view item and if it is equal to any opened view + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - QStandardItem* item = root->child(i); - QFont font; - if (dynamic_cast(item)) + if (nullptr != workbenchPart && workbenchPart->GetPartName() == viewItem->m_ItemDescriptor->GetLabel()) + { + font.setBold(true); + } + else + { + for (const auto& viewPart : viewParts) { - mitk::QtPerspectiveItem* pItem = dynamic_cast(item); - berry::IPerspectiveDescriptor::Pointer currentPersp = page->GetPerspective(); - - if (currentPersp.IsNotNull() && currentPersp->GetId()==pItem->m_Perspective->GetId()) - font.setBold(true); - pItem->setFont(font); + if (viewPart->GetPartName() == viewItem->m_ItemDescriptor->GetLabel()) + { + font.setBold(true); + break; + } } - mitk::QtViewItem* vItem = dynamic_cast(item); - if (vItem) + } + + viewItem->setFont(font); + } + else + { + // check if the item is a perspective item and if it is equal to the current perspective + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) + { + if (currentPerspective.IsNotNull() && currentPerspective->GetId() == perspectiveItem->m_ItemDescriptor->GetId()) { - QList viewParts(page->GetViews()); - for (int i=0; iGetPartName()==vItem->m_View->GetLabel()) - { - font.setBold(true); - break; - } - - if( partRef!=nullptr && partRef->GetId()==vItem->m_View->GetId() && changeId=="viewHide") - font.setBold(false); - - vItem->setFont(font); + font.setBold(true); } - UpdateTreeList(item, partRef, changeId); + + perspectiveItem->setFont(font); + } } + } } bool QmitkViewNavigatorWidget::FillTreeList() { - // active workbench window available? - if (m_Window.IsNull()) - return false; - - // active page available? - berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); - if (page.IsNull()) - return false; - - // everything is fine and we can remove the window listener - if (m_WindowListener != nullptr) - berry::PlatformUI::GetWorkbench()->RemoveWindowListener(m_WindowListener.data()); - // initialize tree model m_TreeModel->clear(); - QStandardItem *treeRootItem = m_TreeModel->invisibleRootItem(); - - // get all available perspectives - berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - QList perspectiveDescriptors(perspRegistry->GetPerspectives()); - qSort(perspectiveDescriptors.begin(), perspectiveDescriptors.end(), comparePerspectives); - // get all Keywords - KeywordRegistry keywordRegistry; + // add all available perspectives + this->AddPerspectivesToTree(); - berry::IPerspectiveDescriptor::Pointer currentPersp = page->GetPerspective(); - //QStringList perspectiveExcludeList = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetPerspectiveExcludeList(); - - std::vector< QStandardItem* > categoryItems; - QStandardItem *perspectiveRootItem = new QStandardItem("Perspectives"); - perspectiveRootItem->setEditable(false); - perspectiveRootItem->setFont(QFont("", 12, QFont::Normal)); - treeRootItem->appendRow(perspectiveRootItem); - - for (int i=0; iGetId()) - { - skipPerspective = true; - break; - } - if (skipPerspective) - continue; -*/ - //QIcon* pIcon = static_cast(p->GetImageDescriptor()->CreateImage()); - mitk::QtPerspectiveItem* pItem = new mitk::QtPerspectiveItem(p->GetLabel()); - pItem->m_Perspective = p; - pItem->m_Description = p->GetDescription(); - QStringList keylist = p->GetKeywordReferences(); - pItem->m_Tags = keywordRegistry.GetKeywords(keylist); - pItem->setEditable(false); - - QFont font; font.setBold(true); - if (currentPersp.IsNotNull() && currentPersp->GetId()==p->GetId()) - pItem->setFont(font); - - QStringList catPath = p->GetCategoryPath(); - if (catPath.isEmpty()) - { - perspectiveRootItem->appendRow(pItem); - } - else - { - QStandardItem* categoryItem = nullptr; - - for (unsigned int c=0; ctext() == catPath.front()) - { - categoryItem = categoryItems.at(c); - break; - } - } - - if (categoryItem==nullptr) - { - categoryItem = new QStandardItem(QIcon(),catPath.front()); - categoryItems.push_back(categoryItem); - } - categoryItem->setEditable(false); - categoryItem->appendRow(pItem); - categoryItem->setFont(QFont("", 12, QFont::Normal)); - } - } - std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); - for (unsigned int i=0; iappendRow(categoryItems.at(i)); - - // get all available views - berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); - QList viewDescriptors(viewRegistry->GetViews()); - QList viewParts(page->GetViews()); - qSort(viewDescriptors.begin(), viewDescriptors.end(), compareViews); - auto emptyItem = new QStandardItem(); - emptyItem->setFlags(Qt::ItemIsEnabled); - treeRootItem->appendRow(emptyItem); - //QStringList viewExcludeList = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetViewExcludeList(); - - //There currently is no way to get the list of excluded views at application start - QStringList viewExcludeList; - // internal view used for the intro screen, will crash when opened directly, see T22352 - viewExcludeList.append(QString("org.blueberry.ui.internal.introview")); - viewExcludeList.append(QString("org.mitk.views.controlvisualizationpropertiesview")); - viewExcludeList.append(QString("org.mitk.views.modules")); - viewExcludeList.append(QString("org.mitk.views.viewnavigatorview")); - - QStandardItem* viewRootItem = new QStandardItem(QIcon(),"Views"); - viewRootItem->setFont(QFont("", 12, QFont::Normal)); - viewRootItem->setEditable(false); - treeRootItem->appendRow(viewRootItem); - - categoryItems.clear(); - QStandardItem* noCategoryItem = new QStandardItem(QIcon(),"Miscellaneous"); - noCategoryItem->setEditable(false); - noCategoryItem->setFont(QFont("", 12, QFont::Normal)); - - for (int i = 0; i < viewDescriptors.size(); ++i) - { - berry::IViewDescriptor::Pointer v = viewDescriptors[i]; - bool skipView = false; - for(int e=0; eGetId()) - { - skipView = true; - break; - } - if (skipView) - continue; - - QStringList catPath = v->GetCategoryPath(); - - QIcon icon = v->GetImageDescriptor(); - mitk::QtViewItem* vItem = new mitk::QtViewItem(icon, v->GetLabel()); - vItem->m_View = v; - vItem->setToolTip(v->GetDescription()); - vItem->m_Description = v->GetDescription(); - - QStringList keylist = v->GetKeywordReferences(); - vItem->m_Tags = keywordRegistry.GetKeywords(keylist); - vItem->setEditable(false); - - for (int i=0; iGetPartName()==v->GetLabel()) - { - QFont font; font.setBold(true); - vItem->setFont(font); - break; - } - - if (catPath.empty()) - noCategoryItem->appendRow(vItem); - else - { - QStandardItem* categoryItem = nullptr; - - for (unsigned int c=0; ctext() == catPath.front()) - { - categoryItem = categoryItems.at(c); - break; - } - - if (categoryItem==nullptr) - { - categoryItem = new QStandardItem(QIcon(),catPath.front()); - categoryItems.push_back(categoryItem); - } - categoryItem->setEditable(false); - categoryItem->appendRow(vItem); - categoryItem->setFont(QFont("", 12, QFont::Normal)); - } - } - std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); - - for (unsigned int i=0; iappendRow(categoryItems.at(i)); - if (noCategoryItem->hasChildren()) - viewRootItem->appendRow(noCategoryItem); + // add all available views + this->AddViewsToTree(); m_Controls.m_PluginTreeView->expandAll(); return true; } void QmitkViewNavigatorWidget::FilterChanged() { QString filterString = m_Controls.lineEdit->text(); -// if (filterString.size() > 0 ) m_Controls.m_PluginTreeView->expandAll(); -// else -// m_Controls.m_PluginTreeView->collapseAll(); - // QRegExp::PatternSyntax syntax = QRegExp::RegExp; Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; QString strPattern = "^*" + filterString; QRegExp regExp(strPattern, caseSensitivity); m_FilterProxyModel->setFilterRegExp(regExp); } void QmitkViewNavigatorWidget::ItemClicked(const QModelIndex &index) { QStandardItem* item = m_TreeModel->itemFromIndex(m_FilterProxyModel->mapToSource(index)); - if ( dynamic_cast< mitk::QtPerspectiveItem* >(item) ) + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { try { - mitk::QtPerspectiveItem* pItem = dynamic_cast< mitk::QtPerspectiveItem* >(item); - berry::PlatformUI::GetWorkbench()->ShowPerspective( pItem->m_Perspective->GetId(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); + berry::PlatformUI::GetWorkbench()->ShowPerspective( + perspectiveItem->m_ItemDescriptor->GetId(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } catch (...) { - QMessageBox::critical(nullptr, "Opening Perspective Failed", QString("The requested perspective could not be opened.\nSee the log for details.")); + QMessageBox::critical(nullptr, "Opening Perspective Failed", + QString("The requested perspective could not be opened.\nSee the log for details.")); } + + return; } - else if ( dynamic_cast< mitk::QtViewItem* >(item) ) + + QmitkViewItem* viewItem = dynamic_cast(item); + if (nullptr != viewItem) { - berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); + berry::IWorkbenchPage::Pointer page = + berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); if (page.IsNotNull()) { try { - mitk::QtViewItem* vItem = dynamic_cast< mitk::QtViewItem* >(item); - page->ShowView(vItem->m_View->GetId()); + page->ShowView(viewItem->m_ItemDescriptor->GetId()); } catch (const berry::PartInitException& e) { BERRY_ERROR << "Error: " << e.what() << std::endl; } } } } void QmitkViewNavigatorWidget::SaveCurrentPerspectiveAs() { berry::IHandlerService* handlerService = m_Window->GetService(); try { handlerService->ExecuteCommand(berry::IWorkbenchCommandConstants::WINDOW_SAVE_PERSPECTIVE_AS, berry::UIElement::Pointer()); FillTreeList(); } catch(const berry::NotHandledException&) - {} + { + } catch(const berry::CommandException& e) { MITK_ERROR << e.what(); } } void QmitkViewNavigatorWidget::ResetCurrentPerspective() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to reset the current perspective?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to reset the current perspective?", + QMessageBox::Yes | QMessageBox::No).exec()) + { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); + } } void QmitkViewNavigatorWidget::ClosePerspective() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to close the current perspective?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to close the current perspective?", + QMessageBox::Yes | QMessageBox::No).exec()) { berry::IWorkbenchPage::Pointer page = m_Window->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); - - // if ( page->GetPerspective().IsNull() ) - // { - // berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - // berry::PlatformUI::GetWorkbench()->ShowPerspective( perspRegistry->GetDefaultPerspective(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); - // } } } void QmitkViewNavigatorWidget::CloseAllPerspectives() { - if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", "Do you really want to close all perspectives?", QMessageBox::Yes|QMessageBox::No).exec()) + if (QMessageBox::Yes == QMessageBox(QMessageBox::Question, "Please confirm", + "Do you really want to close all perspectives?", + QMessageBox::Yes | QMessageBox::No).exec()) { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->CloseAllPerspectives(true, true); - - // berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); - // berry::PlatformUI::GetWorkbench()->ShowPerspective( perspRegistry->GetDefaultPerspective(), berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow() ); } } void QmitkViewNavigatorWidget::ExpandAll() { m_Controls.m_PluginTreeView->expandAll(); } void QmitkViewNavigatorWidget::CollapseAll() { m_Controls.m_PluginTreeView->collapseAll(); } void QmitkViewNavigatorWidget::CustomMenuRequested(QPoint pos) { QModelIndex index = m_Controls.m_PluginTreeView->indexAt(pos); QStandardItem* item = m_TreeModel->itemFromIndex(m_FilterProxyModel->mapToSource(index)); - if (m_ContextMenu==nullptr) + if (nullptr == m_ContextMenu) return; m_ContextMenu->clear(); - QAction* expandAction = new QAction("Expand tree", this); - m_ContextMenu->addAction(expandAction); - connect(expandAction, SIGNAL(triggered()), SLOT(ExpandAll())); - - QAction* collapseAction = new QAction("Collapse tree", this); - m_ContextMenu->addAction(collapseAction); - connect(collapseAction, SIGNAL(triggered()), SLOT(CollapseAll())); - - m_ContextMenu->addSeparator(); - - - if ( item!=nullptr && dynamic_cast< mitk::QtPerspectiveItem* >(item) ) + QmitkPerspectiveItem* perspectiveItem = dynamic_cast(item); + if (nullptr != perspectiveItem) { - berry::IPerspectiveDescriptor::Pointer persp = dynamic_cast< mitk::QtPerspectiveItem* >(item)->m_Perspective; - if (this->m_ActivePerspective.IsNotNull() && this->m_ActivePerspective == persp) + berry::IPerspectiveDescriptor::Pointer perspectiveDescriptor = perspectiveItem->m_ItemDescriptor; + if (this->m_ActivePerspective.IsNotNull() && this->m_ActivePerspective == perspectiveDescriptor) { - //m_ContextMenu->addSeparator(); - QAction* saveAsAction = new QAction("Save As...", this); m_ContextMenu->addAction(saveAsAction); connect(saveAsAction, SIGNAL(triggered()), SLOT(SaveCurrentPerspectiveAs())); m_ContextMenu->addSeparator(); } } QAction* resetAction = new QAction("Reset current perspective", this); m_ContextMenu->addAction(resetAction); connect(resetAction, SIGNAL(triggered()), SLOT(ResetCurrentPerspective())); QAction* closeAction = new QAction("Close perspective", this); m_ContextMenu->addAction(closeAction); connect(closeAction, SIGNAL(triggered()), SLOT(ClosePerspective())); - m_ContextMenu->addSeparator(); - QAction* closeAllAction = new QAction("Close all perspectives", this); m_ContextMenu->addAction(closeAllAction); connect(closeAllAction, SIGNAL(triggered()), SLOT(CloseAllPerspectives())); + m_ContextMenu->addSeparator(); + + QAction* expandAction = new QAction("Expand tree", this); + m_ContextMenu->addAction(expandAction); + connect(expandAction, SIGNAL(triggered()), SLOT(ExpandAll())); + + QAction* collapseAction = new QAction("Collapse tree", this); + m_ContextMenu->addAction(collapseAction); + connect(collapseAction, SIGNAL(triggered()), SLOT(CollapseAll())); + m_ContextMenu->popup(m_Controls.m_PluginTreeView->viewport()->mapToGlobal(pos)); } + +void QmitkViewNavigatorWidget::CreateQtPartControl(QWidget* parent) +{ + // active workbench window available? + if (m_Window.IsNull()) + { + return; + } + + m_Parent = parent; + m_Controls.setupUi(parent); + connect(m_Controls.m_PluginTreeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(CustomMenuRequested(QPoint))); + connect(m_Controls.m_PluginTreeView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(ItemClicked(const QModelIndex&))); + connect(m_Controls.lineEdit, SIGNAL(textChanged(QString)), SLOT(FilterChanged())); + + m_ContextMenu = new QMenu(m_Controls.m_PluginTreeView); + m_Controls.m_PluginTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + + // Create a new TreeModel for the data + m_TreeModel = new QStandardItemModel(); + m_FilterProxyModel = new ClassFilterProxyModel(this); + m_FilterProxyModel->setSourceModel(m_TreeModel); + m_Controls.m_PluginTreeView->setModel(m_FilterProxyModel); + + m_PerspectiveListener.reset(new ViewNavigatorPerspectiveListener(this)); + m_Window->AddPerspectiveListener(m_PerspectiveListener.data()); + + m_ViewPartListener.reset(new ViewNavigatorViewListener(this)); + m_Window->GetPartService()->AddPartListener(m_ViewPartListener.data()); +} + +void QmitkViewNavigatorWidget::AddPerspectivesToTree() +{ + berry::IPerspectiveRegistry* perspRegistry = berry::PlatformUI::GetWorkbench()->GetPerspectiveRegistry(); + QList perspectiveDescriptors(perspRegistry->GetPerspectives()); + qSort(perspectiveDescriptors.begin(), perspectiveDescriptors.end(), comparePerspectives); + + QStandardItem* perspectiveRootItem = new QStandardItem("Perspectives"); + perspectiveRootItem->setFont(QFont("", 12, QFont::Normal)); + perspectiveRootItem->setEditable(false); + QStandardItem* treeRootItem = m_TreeModel->invisibleRootItem(); + treeRootItem->appendRow(perspectiveRootItem); + + this->AddItemsToTree, QmitkPerspectiveItem>( + perspectiveDescriptors, perspectiveRootItem); +} + +void QmitkViewNavigatorWidget::AddViewsToTree() +{ + berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); + QList viewDescriptors(viewRegistry->GetViews()); + qSort(viewDescriptors.begin(), viewDescriptors.end(), compareViews); + + QStandardItem* viewRootItem = new QStandardItem("Views"); + viewRootItem->setFont(QFont("", 12, QFont::Normal)); + viewRootItem->setEditable(false); + QStandardItem* treeRootItem = m_TreeModel->invisibleRootItem(); + treeRootItem->appendRow(viewRootItem); + + QStandardItem* miscellaneousCategoryItem = new QStandardItem("Miscellaneous"); + miscellaneousCategoryItem->setFont(QFont("", 12, QFont::Normal)); + miscellaneousCategoryItem->setEditable(false); + + QStringList viewExcludeList; + // internal view used for the intro screen, will crash when opened directly, see T22352 + viewExcludeList.append(QString("org.blueberry.ui.internal.introview")); + viewExcludeList.append(QString("org.mitk.views.controlvisualizationpropertiesview")); + viewExcludeList.append(QString("org.mitk.views.modules")); + viewExcludeList.append(QString("org.mitk.views.viewnavigator")); + + this->AddItemsToTree, QmitkViewItem>( + viewDescriptors, viewRootItem, miscellaneousCategoryItem, viewExcludeList); +} + +template +void QmitkViewNavigatorWidget::AddItemsToTree(D itemDescriptors, QStandardItem* rootItem, + QStandardItem* miscellaneousItem, const QStringList& itemExcludeList) +{ + KeywordRegistry keywordRegistry; + std::vector categoryItems; + + for (const auto& itemDescriptor : itemDescriptors) + { + bool excludeView = itemExcludeList.contains(itemDescriptor->GetId()); + if (excludeView) + { + continue; + } + + QIcon icon = itemDescriptor->GetImageDescriptor(); + I* item = new I(icon, itemDescriptor->GetLabel()); + item->m_ItemDescriptor = itemDescriptor; + item->m_Description = itemDescriptor->GetDescription(); + item->setToolTip(itemDescriptor->GetDescription()); + + QStringList keylist = itemDescriptor->GetKeywordReferences(); + item->m_Tags = keywordRegistry.GetKeywords(keylist); + item->setEditable(false); + + QStringList categoryPath = itemDescriptor->GetCategoryPath(); + if (categoryPath.empty()) + { + // If a root item for general / non-categorized item views is given, use it. + // Otherwise put the non-categorized item views into the top root item. + if (nullptr != miscellaneousItem) + { + miscellaneousItem->appendRow(item); + } + else + { + rootItem->appendRow(item); + } + } + else + { + QStandardItem* categoryItem = nullptr; + for (const auto& currentCategoryItem : categoryItems) + { + if (currentCategoryItem->text() == categoryPath.front()) + { + categoryItem = currentCategoryItem; + break; + } + } + + if (nullptr == categoryItem) + { + categoryItem = new QStandardItem(QIcon(), categoryPath.front()); + categoryItems.push_back(categoryItem); + } + + categoryItem->setFont(QFont("", 12, QFont::Normal)); + categoryItem->setEditable(false); + categoryItem->appendRow(item); + } + } + + std::sort(categoryItems.begin(), categoryItems.end(), compareQStandardItems); + for (const auto& categoryItem : categoryItems) + { + rootItem->appendRow(categoryItem); + } + + if (nullptr != miscellaneousItem && miscellaneousItem->hasChildren()) + { + rootItem->appendRow(miscellaneousItem); + } +} diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h index 2e81f31644..d55bcedba4 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/QmitkViewNavigatorWidget.h @@ -1,88 +1,87 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef _QMITKViewNavigatorWidget_H_INCLUDED -#define _QMITKViewNavigatorWidget_H_INCLUDED +#ifndef QMIKVIEWNAVIGATORWIDGET_H +#define QMIKVIEWNAVIGATORWIDGET_H -//QT headers -#include -#include - -#include #include "ui_QmitkViewNavigatorWidgetControls.h" -#include -#include -#include -#include -#include +//QT headers +#include #include -#include #include -#include -#include + +#include +#include +#include class ClassFilterProxyModel; -/** @brief - */ +/** +* @brief +* +*/ class QmitkViewNavigatorWidget : public QWidget { - //this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: - QmitkViewNavigatorWidget (berry::IWorkbenchWindow::Pointer window, - QWidget* parent = nullptr, Qt::WindowFlags f = nullptr); + QmitkViewNavigatorWidget(berry::IWorkbenchWindow::Pointer window, + QWidget* parent = nullptr, + Qt::WindowFlags f = nullptr); ~QmitkViewNavigatorWidget() override; - virtual void CreateQtPartControl(QWidget *parent); - void setFocus(); - - bool FillTreeList(); - void UpdateTreeList(QStandardItem* item = nullptr, berry::IWorkbenchPartReference* partRef=nullptr, const std::string& changeId=""); - - QScopedPointer m_PerspectiveListener; - QScopedPointer m_WindowListener; + void SetFocus(); -public slots: +public Q_SLOTS: - void CustomMenuRequested(QPoint pos); - void ItemClicked(const QModelIndex &index); + void FilterChanged(); + void ItemClicked(const QModelIndex& index); void SaveCurrentPerspectiveAs(); void ResetCurrentPerspective(); - void CloseAllPerspectives(); void ClosePerspective(); + void CloseAllPerspectives(); void ExpandAll(); void CollapseAll(); - void FilterChanged(); + void CustomMenuRequested(QPoint pos); protected: friend class ViewNavigatorPerspectiveListener; + friend class ViewNavigatorViewListener; - // member variables Ui::QmitkViewNavigatorWidgetControls m_Controls; QWidget* m_Parent; QStandardItemModel* m_TreeModel; ClassFilterProxyModel* m_FilterProxyModel; QMenu* m_ContextMenu; berry::IPerspectiveDescriptor::Pointer m_ActivePerspective; - bool m_Generated; private: + void CreateQtPartControl(QWidget* parent); + bool FillTreeList(); + void UpdateTreeList(berry::IWorkbenchPart* workbenchPart = nullptr); + + void AddPerspectivesToTree(); + void AddViewsToTree(); + template + void AddItemsToTree(D itemDescriptors, QStandardItem* rootItem, + QStandardItem* miscellaneousItem = nullptr, const QStringList& itemExcludeList = QStringList()); + + QScopedPointer m_PerspectiveListener; + QScopedPointer m_ViewPartListener; + berry::IWorkbenchWindow::Pointer m_Window; }; -#endif // _QMITKViewNavigatorWidget_H_INCLUDED - +#endif // QMIKVIEWNAVIGATORWIDGET_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp similarity index 74% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp index 11e625d938..3443fe6ba3 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.cpp @@ -1,36 +1,34 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - -#include "ViewNavigatorView.h" - +#include "QmitkViewNavigatorView.h" #include "QmitkViewNavigatorWidget.h" // BlueBerry #include #include -const std::string ViewNavigatorView::VIEW_ID = "org.mitk.views.viewnavigatorview"; +const std::string QmitkViewNavigatorView::VIEW_ID = "org.mitk.views.viewnavigator"; -void ViewNavigatorView::SetFocus() +void QmitkViewNavigatorView::SetFocus() { - m_ViewNavigatorWidget->setFocus(); + m_ViewNavigatorWidget->SetFocus(); } -void ViewNavigatorView::CreateQtPartControl( QWidget *parent ) +void QmitkViewNavigatorView::CreateQtPartControl(QWidget* parent) { parent->setLayout(new QVBoxLayout); parent->layout()->setContentsMargins(0, 0, 0, 0); m_ViewNavigatorWidget = new QmitkViewNavigatorWidget(this->GetSite()->GetWorkbenchWindow(), parent); parent->layout()->addWidget(m_ViewNavigatorWidget); } diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h similarity index 58% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h index 62c7f24120..18f72929ad 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/ViewNavigatorView.h +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/QmitkViewNavigatorView.h @@ -1,50 +1,44 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ - -#ifndef ViewNavigatorView_h -#define ViewNavigatorView_h +#ifndef QMITKVIEWNAVIGATORVIEW_H +#define QMITKVIEWNAVIGATORVIEW_H #include class QmitkViewNavigatorWidget; /** - \brief ViewNavigatorView - - \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. - - \sa QmitkAbstractView - \ingroup ${plugin_target}_internal +* @brief +* */ -class ViewNavigatorView : public QmitkAbstractView +class QmitkViewNavigatorView : public QmitkAbstractView { - // this is needed for all Qt objects that should have a Qt meta-object - // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; protected: void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; private: QmitkViewNavigatorWidget* m_ViewNavigatorWidget; + }; -#endif // ViewNavigatorView_h +#endif // QMITKVIEWNAVIGATORVIEW_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp similarity index 56% rename from Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp rename to Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp index 2f2991cbbf..c0312d1037 100644 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.cpp +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.cpp @@ -1,29 +1,24 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ +#include "mitkPluginActivator.h" +#include "QmitkViewNavigatorView.h" -#include "org_mitk_gui_qt_viewnavigator_Activator.h" -#include "ViewNavigatorView.h" - -namespace mitk { - -void org_mitk_gui_qt_viewnavigator_Activator::start(ctkPluginContext* context) +void mitk::PluginActivator::start(ctkPluginContext* context) { - BERRY_REGISTER_EXTENSION_CLASS(ViewNavigatorView, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkViewNavigatorView, context) } -void org_mitk_gui_qt_viewnavigator_Activator::stop(ctkPluginContext* context) +void mitk::PluginActivator::stop(ctkPluginContext* context) { Q_UNUSED(context) } - -} diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h new file mode 100644 index 0000000000..c2daea8f73 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/mitkPluginActivator.h @@ -0,0 +1,33 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef MITKPLUGINACTIVATOR_H +#define MITKPLUGINACTIVATOR_H + +#include + +namespace mitk +{ + class PluginActivator : public QObject, public ctkPluginActivator + { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_viewnavigator") + Q_INTERFACES(ctkPluginActivator) + + public: + void start(ctkPluginContext *context) override; + void stop(ctkPluginContext *context) override; + + }; +} + +#endif // MITKPLUGINACTIVATOR_H diff --git a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h b/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h deleted file mode 100644 index a14a2a6286..0000000000 --- a/Plugins/org.mitk.gui.qt.viewnavigator/src/internal/org_mitk_gui_qt_viewnavigator_Activator.h +++ /dev/null @@ -1,37 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef org_mitk_gui_qt_viewnavigator_Activator_h -#define org_mitk_gui_qt_viewnavigator_Activator_h - -#include - -namespace mitk { - -class org_mitk_gui_qt_viewnavigator_Activator : - public QObject, public ctkPluginActivator -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_viewnavigator") - Q_INTERFACES(ctkPluginActivator) - -public: - - void start(ctkPluginContext* context) override; - void stop(ctkPluginContext* context) override; - -}; // org_mitk_gui_qt_viewnavigator_Activator - -} - -#endif // org_mitk_gui_qt_viewnavigator_Activator_h diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp index 2e931cec19..90e0d7ecf0 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp @@ -1,293 +1,313 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkVolumeVisualizationView.h" #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkVolumeVisualizationView::VIEW_ID = "org.mitk.views.volumevisualization"; enum { DEFAULT_RENDERMODE = 0, RAYCAST_RENDERMODE = 1, GPU_RENDERMODE = 2 }; QmitkVolumeVisualizationView::QmitkVolumeVisualizationView() : QmitkAbstractView() , m_Controls(nullptr) { } void QmitkVolumeVisualizationView::SetFocus() { - UpdateInterface(); } void QmitkVolumeVisualizationView::CreateQtPartControl(QWidget* parent) { m_Controls = new Ui::QmitkVolumeVisualizationViewControls; m_Controls->setupUi(parent); m_Controls->volumeSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls->volumeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls->volumeSelectionWidget->SetSelectionIsOptional(true); m_Controls->volumeSelectionWidget->SetEmptyInfo(QString("Please select a 3D / 4D image volume")); m_Controls->volumeSelectionWidget->SetPopUpTitel(QString("Select image volume")); // Fill the transfer function presets in the generator widget std::vector names; mitk::TransferFunctionInitializer::GetPresetNames(names); for (const auto& name : names) { m_Controls->transferFunctionGeneratorWidget->AddPreset(QString::fromStdString(name)); } // see enum in vtkSmartVolumeMapper m_Controls->renderMode->addItem("Default"); m_Controls->renderMode->addItem("RayCast"); m_Controls->renderMode->addItem("GPU"); // see vtkVolumeMapper::BlendModes m_Controls->blendMode->addItem("Comp"); m_Controls->blendMode->addItem("Max"); m_Controls->blendMode->addItem("Min"); m_Controls->blendMode->addItem("Avg"); m_Controls->blendMode->addItem("Add"); connect(m_Controls->volumeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkVolumeVisualizationView::OnCurrentSelectionChanged); connect(m_Controls->enableRenderingCB, SIGNAL(toggled(bool)), this, SLOT(OnEnableRendering(bool))); connect(m_Controls->renderMode, SIGNAL(activated(int)), this, SLOT(OnRenderMode(int))); connect(m_Controls->blendMode, SIGNAL(activated(int)), this, SLOT(OnBlendMode(int))); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalUpdateCanvas()), m_Controls->transferFunctionWidget, SLOT(OnUpdateCanvas())); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalTransferFunctionModeChanged(int)), SLOT(OnMitkInternalPreset(int))); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); m_Controls->volumeSelectionWidget->SetAutoSelectNewNodes(true); + + this->m_TimePointChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); + connect(&m_TimePointChangeListener, + &QmitkSliceNavigationListener::SelectedTimePointChanged, + this, + &QmitkVolumeVisualizationView::OnSelectedTimePointChanged); +} + +void QmitkVolumeVisualizationView::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) +{ + this->m_TimePointChangeListener.RenderWindowPartActivated(renderWindowPart); +} + +void QmitkVolumeVisualizationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) +{ + this->m_TimePointChangeListener.RenderWindowPartDeactivated(renderWindowPart); } void QmitkVolumeVisualizationView::OnMitkInternalPreset(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto node = m_SelectedNode.Lock(); mitk::TransferFunctionProperty::Pointer transferFuncProp; if (node->GetProperty(transferFuncProp, "TransferFunction")) { // first item is only information if (--mode == -1) return; // -- Creat new TransferFunction mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(transferFuncProp->GetValue()); tfInit->SetTransferFunctionMode(mode); RequestRenderWindowUpdate(); m_Controls->transferFunctionWidget->OnUpdateCanvas(); } } void QmitkVolumeVisualizationView::OnCurrentSelectionChanged(QList nodes) { m_SelectedNode = nullptr; if (nodes.empty() || nodes.front().IsNull()) { UpdateInterface(); return; } auto selectedNode = nodes.front(); auto image = dynamic_cast(selectedNode->GetData()); if (nullptr != image) { m_SelectedNode = selectedNode; } UpdateInterface(); } void QmitkVolumeVisualizationView::OnEnableRendering(bool state) { if (m_SelectedNode.IsExpired()) { return; } m_SelectedNode.Lock()->SetProperty("volumerendering", mitk::BoolProperty::New(state)); UpdateInterface(); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnRenderMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usegpu = false; bool useray = false; if (DEFAULT_RENDERMODE == mode) { useray = true; usegpu = true; } else if (GPU_RENDERMODE == mode) { usegpu = true; } else if (RAYCAST_RENDERMODE == mode) { useray = true; } selectedNode->SetProperty("volumerendering.usegpu", mitk::BoolProperty::New(usegpu)); selectedNode->SetProperty("volumerendering.useray", mitk::BoolProperty::New(useray)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnBlendMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usemip = false; if (vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND == mode) { usemip = true; } selectedNode->SetProperty("volumerendering.usemip", mitk::BoolProperty::New(usemip)); selectedNode->SetProperty("volumerendering.blendmode", mitk::IntProperty::New(mode)); RequestRenderWindowUpdate(); } +void QmitkVolumeVisualizationView::OnSelectedTimePointChanged(const mitk::TimePointType & /*newTimePoint*/) +{ + this->UpdateInterface(); +} + void QmitkVolumeVisualizationView::UpdateInterface() { if (m_SelectedNode.IsExpired()) { // turnoff all m_Controls->enableRenderingCB->setChecked(false); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } bool enabled = false; auto selectedNode = m_SelectedNode.Lock(); selectedNode->GetBoolProperty("volumerendering", enabled); m_Controls->enableRenderingCB->setEnabled(true); m_Controls->enableRenderingCB->setChecked(enabled); if (!enabled) { // turnoff all except volumerendering checkbox m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } // otherwise we can activate em all m_Controls->blendMode->setEnabled(true); m_Controls->renderMode->setEnabled(true); // Determine Combo Box mode { bool usegpu = false; bool useray = false; bool usemip = false; selectedNode->GetBoolProperty("volumerendering.usegpu", usegpu); selectedNode->GetBoolProperty("volumerendering.useray", useray); selectedNode->GetBoolProperty("volumerendering.usemip", usemip); int blendMode; if (selectedNode->GetIntProperty("volumerendering.blendmode", blendMode)) m_Controls->blendMode->setCurrentIndex(blendMode); if (usemip) m_Controls->blendMode->setCurrentIndex(vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND); int mode = DEFAULT_RENDERMODE; if (useray) mode = RAYCAST_RENDERMODE; else if (usegpu) mode = GPU_RENDERMODE; m_Controls->renderMode->setCurrentIndex(mode); } - - m_Controls->transferFunctionWidget->SetDataNode(selectedNode); + auto time = this->GetRenderWindowPart()->GetTimeNavigationController()->GetSelectedTimeStep(); + m_Controls->transferFunctionWidget->SetDataNode(selectedNode, time); m_Controls->transferFunctionWidget->setEnabled(true); - m_Controls->transferFunctionGeneratorWidget->SetDataNode(selectedNode); + m_Controls->transferFunctionGeneratorWidget->SetDataNode(selectedNode, time); m_Controls->transferFunctionGeneratorWidget->setEnabled(true); } diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h index 5d413576f2..658d5fc133 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.h @@ -1,61 +1,69 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKVOLUMEVISUALIZATIONVIEW_H #define QMITKVOLUMEVISUALIZATIONVIEW_H #include "ui_QmitkVolumeVisualizationViewControls.h" // mitk core #include #include #include +#include +#include /** * @brief */ -class QmitkVolumeVisualizationView : public QmitkAbstractView +class QmitkVolumeVisualizationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkVolumeVisualizationView(); ~QmitkVolumeVisualizationView() override = default; void SetFocus() override; private Q_SLOTS: void OnCurrentSelectionChanged(QList nodes); void OnMitkInternalPreset(int mode); void OnEnableRendering(bool state); void OnRenderMode(int mode); void OnBlendMode(int mode); + void OnSelectedTimePointChanged(const mitk::TimePointType &); + +protected: + void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) override; + void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) override; private: void CreateQtPartControl(QWidget* parent) override; void UpdateInterface(); Ui::QmitkVolumeVisualizationViewControls* m_Controls; mitk::WeakPointer m_SelectedNode; + QmitkSliceNavigationListener m_TimePointChangeListener; }; #endif // QMITKVOLUMEVISUALIZATIONVIEW_H