diff --git a/CMake/BuildConfigurations/WorkbenchRelease.cmake b/CMake/BuildConfigurations/WorkbenchRelease.cmake index 694b4b9e35..a0742477e2 100644 --- a/CMake/BuildConfigurations/WorkbenchRelease.cmake +++ b/CMake/BuildConfigurations/WorkbenchRelease.cmake @@ -1,28 +1,29 @@ include(${CMAKE_CURRENT_LIST_DIR}/Default.cmake) set(MITK_CONFIG_PACKAGES ${MITK_CONFIG_PACKAGES} MatchPoint ) set(MITK_CONFIG_PLUGINS ${MITK_CONFIG_PLUGINS} org.mitk.gui.qt.multilabelsegmentation org.mitk.matchpoint.core.helper org.mitk.gui.qt.matchpoint.algorithm.browser org.mitk.gui.qt.matchpoint.algorithm.control org.mitk.gui.qt.matchpoint.mapper org.mitk.gui.qt.matchpoint.framereg org.mitk.gui.qt.matchpoint.visualizer org.mitk.gui.qt.matchpoint.evaluator org.mitk.gui.qt.matchpoint.manipulator ) if(NOT MITK_USE_SUPERBUILD) set(BUILD_CoreCmdApps ON CACHE BOOL "" FORCE) + set(BUILD_MatchPointCmdApps ON CACHE BOOL "" FORCE) endif() set(MITK_VTK_DEBUG_LEAKS OFF CACHE BOOL "Enable VTK Debug Leaks" FORCE) find_package(Doxygen REQUIRED) # Ensure that the in-application help can be build set(BLUEBERRY_QT_HELP_REQUIRED ON CACHE BOOL "Required Qt help documentation in plug-ins" FORCE) diff --git a/CMake/PackageDepends/MITK_nlohmann_json_Config.cmake b/CMake/PackageDepends/MITK_nlohmann_json_Config.cmake new file mode 100644 index 0000000000..d434c19b31 --- /dev/null +++ b/CMake/PackageDepends/MITK_nlohmann_json_Config.cmake @@ -0,0 +1 @@ +set(ALL_LIBRARIES "nlohmann_json::nlohmann_json") diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index e275f8b0f4..afff18b5ec 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,32 +1,33 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME OpenIGTLink OFF) mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) mitkFunctionAddExternalProject(NAME Eigen ON ADVANCED DOC "Use the Eigen library") mitkFunctionAddExternalProject(NAME ANN ON ADVANCED DOC "Use Approximate Nearest Neighbor Library") mitkFunctionAddExternalProject(NAME CppUnit ON ADVANCED DOC "Use CppUnit for unit tests") mitkFunctionAddExternalProject(NAME HDF5 ON ADVANCED) mitkFunctionAddExternalProject(NAME OpenCV OFF) mitkFunctionAddExternalProject(NAME Vigra OFF DEPENDS HDF5) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) mitkFunctionAddExternalProject(NAME ZLIB OFF ADVANCED) mitkFunctionAddExternalProject(NAME lz4 ON ADVANCED) mitkFunctionAddExternalProject(NAME cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) mitkFunctionAddExternalProject(NAME OpenMesh OFF) mitkFunctionAddExternalProject(NAME CTK ON DEPENDS Qt5 DCMTK DOC "Use CTK in MITK") mitkFunctionAddExternalProject(NAME DCMQI ON DEPENDS DCMTK ITK DOC "Use dcmqi in MITK") mitkFunctionAddExternalProject(NAME MatchPoint OFF ADVANCED DEPENDS ITK DOC "Use the MatchPoint translation image registration library") +mitkFunctionAddExternalProject(NAME nlohmann_json ON ADVANCED) if(MITK_USE_Qt5) mitkFunctionAddExternalProject(NAME Qwt ON ADVANCED DEPENDS Qt5) endif() if(UNIX AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/CMakeExternals/nlohmann_json.cmake b/CMakeExternals/nlohmann_json.cmake new file mode 100644 index 0000000000..fbca098b3c --- /dev/null +++ b/CMakeExternals/nlohmann_json.cmake @@ -0,0 +1,48 @@ +#----------------------------------------------------------------------------- +# nlohmann_json +#----------------------------------------------------------------------------- + +if(MITK_USE_nlohmann_json) + # Sanity checks + if(DEFINED nlohmann_json_DIR AND NOT EXISTS "${nlohmann_json_DIR}") + message(FATAL_ERROR "nlohmann_json_DIR variable is defined but corresponds to non-existing directory") + endif() + + set(proj nlohmann_json) + set(proj_DEPENDENCIES ) + set(nlohmann_json_DEPENDS ${proj}) + + if(NOT DEFINED nlohmann_json_DIR) + + set(additional_args ) + + if(CTEST_USE_LAUNCHERS) + list(APPEND additional_args + "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" + ) + endif() + + ExternalProject_Add(${proj} + LIST_SEPARATOR ${sep} + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.10.5 + CMAKE_GENERATOR ${gen} + CMAKE_GENERATOR_PLATFORM ${gen_platform} + CMAKE_ARGS + ${ep_common_args} + ${additional_args} + CMAKE_CACHE_ARGS + ${ep_common_cache_args} + -DJSON_BuildTests:BOOL=OFF + CMAKE_CACHE_DEFAULT_ARGS + ${ep_common_cache_default_args} + DEPENDS ${proj_DEPENDENCIES} + ) + + set(nlohmann_json_DIR "${ep_prefix}") + mitkFunctionInstallExternalCMakeProject(${proj}) + + else() + mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") + endif() +endif() diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox index 5d760fedaa..c9d1bd489a 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox @@ -1,111 +1,115 @@ /** \page thirdpartylibs Third-party libraries The following third-party libraries can be used with MITK by default and can, in part, be automatically downloaded during superbuild. \par ANN https://www.cs.umd.edu/~mount/ANN/ \par Boost https://www.boost.org/ \par C++ REST SDK https://github.com/Microsoft/cpprestsdk/ \par CppUnit https://sourceforge.net/projects/cppunit/ \par CTK https://commontk.org/ \par DCMTK https://dicom.offis.de/dcmtk \par Eigen http://eigen.tuxfamily.org/index.php?title=Main_Page \par GDCM https://gdcm.sourceforge.net/ \par HDF5 https://support.hdfgroup.org/HDF5/ \par ITK https://itk.org/ +\par JSON for Modern C++ + +https://github.com/nlohmann/json + \par lz4 https://github.com/lz4/lz4 \par MatchPoint https://www.dkfz.de/en/sidt/projects/MatchPoint/info.html \par OpenCL https://www.khronos.org/opencl/ \par OpenCV https://opencv.org/ \par OpenIGTLink http://openigtlink.org/ \par OpenMesh https://www.openmesh.org/ \par PCRE https://www.pcre.org/ \par POCO https://pocoproject.org/ \par Python https://www.python.org/ \par Qt https://www.qt.io/ \par Qwt http://qwt.sourceforge.net/ \par SWIG http://www.swig.org/ \par TinyXML-2 http://www.grinninglizard.com/tinyxml2/ \par VIGRA https://ukoethe.github.io/vigra/ \par VTK https://vtk.org/ \par zlib https://zlib.net/ For copyright information on any of the above toolkits see the corresponding home page or the corresponding source folder. */ diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/isosurface/QmitkIsoSurface.cpp b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/isosurface/QmitkIsoSurface.cpp index 2935ce2e5d..692bee7b14 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/isosurface/QmitkIsoSurface.cpp +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/isosurface/QmitkIsoSurface.cpp @@ -1,164 +1,163 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkIsoSurface.h" // MITK headers #include #include #include // Qmitk headers #include -#include // Qt-GUI headers #include #include #include QmitkIsoSurface::QmitkIsoSurface(QObject * /*parent*/, const char * /*name*/) : m_Controls(nullptr), m_MitkImage(nullptr), m_SurfaceCounter(0) { } void QmitkIsoSurface::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { m_Controls = new Ui::QmitkIsoSurfaceControls; m_Controls->setupUi(parent); this->CreateConnections(); m_Controls->m_ImageSelector->SetDataStorage(this->GetDataStorage()); m_Controls->m_ImageSelector->SetPredicate(mitk::NodePredicateDataType::New("Image")); berry::IPreferences::Pointer prefs = this->GetPreferences(); if (prefs.IsNotNull()) m_Controls->thresholdLineEdit->setText(prefs->Get("defaultThreshold", "0")); } } void QmitkIsoSurface::SetFocus() { m_Controls->m_ImageSelector->setFocus(); } void QmitkIsoSurface::CreateConnections() { if (m_Controls) { connect(m_Controls->m_ImageSelector, SIGNAL(OnSelectionChanged(const mitk::DataNode *)), this, SLOT(ImageSelected(const mitk::DataNode *))); connect(m_Controls->createSurfacePushButton, SIGNAL(clicked()), this, SLOT(CreateSurface())); } } void QmitkIsoSurface::ImageSelected(const mitk::DataNode *item) { // nothing selected (nullptr selection) if (item == nullptr || item->GetData() == nullptr) return; m_MitkImage = dynamic_cast(item->GetData()); } void QmitkIsoSurface::CreateSurface() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (m_MitkImage != nullptr) { // Value Gauss // float gsDev = 1.5; // Value for DecimatePro float targetReduction = 0.05; // ImageToSurface Instance mitk::DataNode::Pointer node = m_Controls->m_ImageSelector->GetSelectedNode(); mitk::ManualSegmentationToSurfaceFilter::Pointer filter = mitk::ManualSegmentationToSurfaceFilter::New(); if (filter.IsNull()) { std::cout << "nullptr Pointer for ManualSegmentationToSurfaceFilter" << std::endl; return; } filter->SetInput(m_MitkImage); filter->SetGaussianStandardDeviation(0.5); filter->SetUseGaussianImageSmooth(true); filter->SetThreshold(getThreshold()); // if( Gauss ) --> TH manipulated for vtkMarchingCube filter->SetTargetReduction(targetReduction); int numOfPolys = filter->GetOutput()->GetVtkPolyData()->GetNumberOfPolys(); if (numOfPolys > 2000000) { QApplication::restoreOverrideCursor(); if (QMessageBox::question(nullptr, "CAUTION!!!", "The number of polygons is greater than 2 000 000. If you continue, the program might " "crash. How do you want to go on?", "Proceed anyway!", "Cancel immediately! (maybe you want to insert an other threshold)!", QString::null, 0, 1) == 1) { return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } mitk::DataNode::Pointer surfaceNode = mitk::DataNode::New(); surfaceNode->SetData(filter->GetOutput()); int layer = 0; ++m_SurfaceCounter; std::ostringstream buffer; buffer << m_SurfaceCounter; std::string surfaceNodeName = "Surface " + buffer.str(); node->GetIntProperty("layer", layer); surfaceNode->SetIntProperty("layer", layer + 1); surfaceNode->SetProperty("Surface", mitk::BoolProperty::New(true)); surfaceNode->SetProperty("name", mitk::StringProperty::New(surfaceNodeName)); this->GetDataStorage()->Add(surfaceNode, node); // to show surfaceContur surfaceNode->SetColor(m_RainbowColor.GetNextColor()); surfaceNode->SetVisibility(true); mitk::IRenderWindowPart *renderPart = this->GetRenderWindowPart(); if (renderPart) { renderPart->RequestUpdate(); } } QApplication::restoreOverrideCursor(); } float QmitkIsoSurface::getThreshold() { return m_Controls->thresholdLineEdit->text().toFloat(); } QmitkIsoSurface::~QmitkIsoSurface() { berry::IPreferences::Pointer prefs = this->GetPreferences(); if (prefs.IsNotNull()) prefs->Put("defaultThreshold", m_Controls->thresholdLineEdit->text()); } diff --git a/Examples/Plugins/org.mitk.example.gui.opencv/src/internal/videoplayer/QmitkVideoPlayer.cpp b/Examples/Plugins/org.mitk.example.gui.opencv/src/internal/videoplayer/QmitkVideoPlayer.cpp index 1ae80a56e5..5d58b70e00 100644 --- a/Examples/Plugins/org.mitk.example.gui.opencv/src/internal/videoplayer/QmitkVideoPlayer.cpp +++ b/Examples/Plugins/org.mitk.example.gui.opencv/src/internal/videoplayer/QmitkVideoPlayer.cpp @@ -1,60 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkVideoPlayer.h" #include #include -#include #include QmitkVideoPlayer::QmitkVideoPlayer() : m_VideoSource(nullptr), m_VideoBackground(new QmitkVideoBackground(m_VideoSource)) { } QmitkVideoPlayer::~QmitkVideoPlayer() { // save video preferences } void QmitkVideoPlayer::CreateQtPartControl(QWidget *parent) { // retrieve old preferences m_VideoSource = mitk::OpenCVVideoSource::New(); m_VideoBackground = new QmitkVideoBackground(m_VideoSource); m_VideoBackground->setParent(parent); QVBoxLayout *layout = new QVBoxLayout; QmitkRenderWindow *renderWindow = nullptr; if (this->GetRenderWindowPart()) { renderWindow = this->GetRenderWindowPart()->GetActiveQmitkRenderWindow(); } m_OpenCVVideoControls = new QmitkOpenCVVideoControls(m_VideoBackground, renderWindow); layout->addWidget(m_OpenCVVideoControls); parent->setLayout(layout); } void QmitkVideoPlayer::SetFocus() { m_OpenCVVideoControls->setFocus(); } void QmitkVideoPlayer::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) { m_OpenCVVideoControls->SetRenderWindow(renderWindowPart->GetActiveQmitkRenderWindow()); } void QmitkVideoPlayer::RenderWindowPartDeactivated(mitk::IRenderWindowPart * /*renderWindowPart*/) { m_OpenCVVideoControls->SetRenderWindow(nullptr); } diff --git a/Modules/AppUtil/include/mitkBaseApplication.h b/Modules/AppUtil/include/mitkBaseApplication.h index ad11d6f08a..00cca3ef7d 100644 --- a/Modules/AppUtil/include/mitkBaseApplication.h +++ b/Modules/AppUtil/include/mitkBaseApplication.h @@ -1,319 +1,320 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 mitkBaseApplication_h #define mitkBaseApplication_h #include #include #include #include class ctkPluginContext; class ctkPluginFramework; class QCoreApplication; class QTranslator; namespace mitk { /** * A utility class for starting BlueBerry applications. * * In the simplest case, create an instance of this class and call run(). * This will launch a CTK plugin framework instance and execute the * default application registered by a plug-in via the * org.blueberry.osgi.applications extension point. * * This class contains many convenience methods to: * - Put the application in safe mode which catches unhandled * exceptions thrown in the Qt event loop and displays an error * message. * - Put the application in single mode which by default * sends the command line arguments to an already running instance * of the same application instead of creating a second instance. * - Add a list of library names which should be pre-loaded at * application start-up, e.g. to speed up the initial launch during * the caching process of the plug-in meta-data. * - Set a custom provisioning file to start a specific set of CTK * plug-ins during application start-up. * - Set and get CTK plugin framework properties * * The behavior can further be customized by deriving from BaseApplication * and overriding specific methods, such as: * - initializeLibraryPaths() to add specific library / plugin search paths * - defineOptions(Poco::Util::OptionSet&) to define a custom set of * command line options * - getQApplication() to provide a custom QCoreApplication instance * * A simple but complete example: * \code * #include * * int main(int argc, char* argv[]) * { * mitk::BaseApplication app(argc, argv); * app.setApplicationName("MyApp"); * app.setOrganizationName("MyOrganization"); * * // Run the workbench * return app.run(); * } * \endcode */ class MITKAPPUTIL_EXPORT BaseApplication : public Poco::Util::Application { public: // Command line arguments static const QString ARG_APPLICATION; static const QString ARG_CLEAN; static const QString ARG_CONSOLELOG; static const QString ARG_DEBUG; static const QString ARG_FORCE_PLUGIN_INSTALL; static const QString ARG_HOME; static const QString ARG_NEWINSTANCE; static const QString ARG_NO_LAZY_REGISTRY_CACHE_LOADING; static const QString ARG_NO_REGISTRY_CACHE; static const QString ARG_PLUGIN_CACHE; static const QString ARG_PLUGIN_DIRS; static const QString ARG_PRELOAD_LIBRARY; static const QString ARG_PRODUCT; static const QString ARG_PROVISIONING; static const QString ARG_REGISTRY_MULTI_LANGUAGE; static const QString ARG_SPLASH_IMAGE; static const QString ARG_STORAGE_DIR; static const QString ARG_XARGS; static const QString ARG_LOG_QT_MESSAGES; static const QString ARG_SEGMENTATION_LABELSET_PRESET; + static const QString ARG_SEGMENTATION_LABEL_SUGGESTIONS; // BlueBerry specific plugin framework properties static const QString PROP_APPLICATION; static const QString PROP_FORCE_PLUGIN_INSTALL; static const QString PROP_NEWINSTANCE; static const QString PROP_NO_LAZY_REGISTRY_CACHE_LOADING; static const QString PROP_NO_REGISTRY_CACHE; static const QString PROP_PRODUCT; static const QString PROP_REGISTRY_MULTI_LANGUAGE; BaseApplication(int argc, char **argv); ~BaseApplication() override; /** * Initialize the Qt library such that a QCoreApplication * instance is available and e.g. Qt widgets can be created. * * This is usually not called directly by the user. */ void initializeQt(); /** * Launches the BlueBerry framework and runs the default application * or the one specified in the PROP_APPLICATION framework property. * * @return The return code of the application after it was shut down. */ int run() override; void printHelp(const std::string &name, const std::string &value); /** * Set the application name. Same as QCoreApplication::setApplicationName. * @param name The application name. */ void setApplicationName(const QString &name); QString getApplicationName() const; /** * Set the organization name. Same as QCoreApplication::setOrganizationName. * @param name The organization name. */ void setOrganizationName(const QString &name); QString getOrganizationName() const; /** * Set the organization domain. Same as QCoreApplication::setOrganizationDomain. * @param name The organization domain. */ void setOrganizationDomain(const QString &name); QString getOrganizationDomain() const; /** * Put the application in single mode, which by default only allows * a single instance of the application to be created. * * Calling this method after run() has been called has no effect. * * @param singleMode */ void setSingleMode(bool singleMode); bool getSingleMode() const; /** * Put the application in safe mode, catching exceptions from the * Qt event loop. * * @param safeMode */ void setSafeMode(bool safeMode); bool getSafeMode() const; /** * Set a list of library names or absoulte file paths * which should be loaded at application start-up. The name * and file path may contain a library version appended at the * end and separated by a '$' charactger. * * For example liborg_mitk_gui_qt_common$1.0. * Platform specific suffixes are appended automatically. * * @param libraryBaseNames A list of library base names. */ void setPreloadLibraries(const QStringList &libraryBaseNames); /** * Get the list of library base names which should be pre-loaded. * * @return A list of pre-loaded libraries. */ QStringList getPreloadLibraries() const; /** * Set the path to the provisioning file. * * By default a provisioning file located in the same directory * as the executable and named \.provisioning * is loaded if it exists. To disable parsing of provisioning * files, use an empty string as the argument. Use a * null QString (\c QString::null ) to reset to the * default behaviour. * * @param filePath An absolute file path to the provisioning file. */ void setProvisioningFilePath(const QString &filePath); /** * Get the file path to the provisioning file. * @return The provisioning file path. */ QString getProvisioningFilePath() const; void setProperty(const QString &property, const QVariant &value); QVariant getProperty(const QString &property) const; void installTranslator(QTranslator*); bool isRunning(); void sendMessage(const QByteArray); protected: void initialize(Poco::Util::Application &self) override; void uninitialize() override; int getArgc() const; char **getArgv() const; /** * Get the framework storage directory for the CTK plugin * framework. This method is called in the initialize(Poco::Util::Application&) * method. It must not be called without a QCoreApplications instance. * * @return The CTK Plugin Framework storage directory. */ virtual QString getCTKFrameworkStorageDir() const; /** * Initialize the CppMicroServices library. * * The default implementation set the CppMicroServices storage * path to the current ctkPluginConstants::FRAMEWORK_STORAGE property * value. * * This method is called in the initialize(Poco::Util::Application&) * after the CTK Plugin Framework storage directory property * was set. */ virtual void initializeCppMicroServices(); /** * Get the QCoreApplication object. * * This method is called in the initialize(Poco::Util::Application&) * method and must create a QCoreApplication instance if the * global qApp variable is not initialized yet. * * @return The current QCoreApplication instance. This method * never returns null. */ virtual QCoreApplication *getQApplication() const; /** * Add plugin library search paths to the CTK Plugin Framework. * * This method is called in the nitialize(Poco::Util::Application&) * method after getQApplication() was called. */ virtual void initializeLibraryPaths(); /** * Runs the application for which the platform was started. The platform * must be running. *

* The given argument is passed to the application being run. If it is an invalid QVariant * then the command line arguments used in starting the platform, and not consumed * by the platform code, are passed to the application as a QStringList. *

* @param args the argument passed to the application. May be invalid * @return the result of running the application * @throws std::exception if anything goes wrong */ int main(const std::vector &args) override; /** * Define command line arguments * @param options */ void defineOptions(Poco::Util::OptionSet &options) override; QSharedPointer getFramework() const; ctkPluginContext *getFrameworkContext() const; /** * Get the initial properties for the CTK plugin framework. * * The returned map contains the initial framework properties for * initializing the CTK plugin framework. The value of specific * properties may change at runtime and differ from the initial * value. * * @return The initial CTK Plugin Framework properties. */ QHash getFrameworkProperties() const; /* * Initialize and display the splash screen if an image filename is given * */ void initializeSplashScreen(QCoreApplication * application) const; private: struct Impl; Impl* d; }; } #endif // MITKBASEAPPLICATION_H diff --git a/Modules/AppUtil/src/mitkBaseApplication.cpp b/Modules/AppUtil/src/mitkBaseApplication.cpp index 28aa7a35b0..0b8cfb0814 100644 --- a/Modules/AppUtil/src/mitkBaseApplication.cpp +++ b/Modules/AppUtil/src/mitkBaseApplication.cpp @@ -1,880 +1,885 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { void outputQtMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) { auto message = msg.toStdString(); switch (type) { case QtDebugMsg: MITK_DEBUG << message; break; case QtInfoMsg: MITK_INFO << message; break; case QtWarningMsg: MITK_WARN << message; break; case QtCriticalMsg: MITK_ERROR << message; break; case QtFatalMsg: MITK_ERROR << message; abort(); default: MITK_INFO << message; break; } } } namespace mitk { const QString BaseApplication::ARG_APPLICATION = "BlueBerry.application"; const QString BaseApplication::ARG_CLEAN = "BlueBerry.clean"; const QString BaseApplication::ARG_CONSOLELOG = "BlueBerry.consoleLog"; const QString BaseApplication::ARG_DEBUG = "BlueBerry.debug"; const QString BaseApplication::ARG_FORCE_PLUGIN_INSTALL = "BlueBerry.forcePlugins"; const QString BaseApplication::ARG_HOME = "BlueBerry.home"; const QString BaseApplication::ARG_NEWINSTANCE = "BlueBerry.newInstance"; const QString BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING = "BlueBerry.noLazyRegistryCacheLoading"; const QString BaseApplication::ARG_NO_REGISTRY_CACHE = "BlueBerry.noRegistryCache"; const QString BaseApplication::ARG_PLUGIN_CACHE = "BlueBerry.plugin_cache_dir"; const QString BaseApplication::ARG_PLUGIN_DIRS = "BlueBerry.plugin_dirs"; const QString BaseApplication::ARG_PRELOAD_LIBRARY = "BlueBerry.preloadLibrary"; const QString BaseApplication::ARG_PRODUCT = "BlueBerry.product"; const QString BaseApplication::ARG_PROVISIONING = "BlueBerry.provisioning"; const QString BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE = "BlueBerry.registryMultiLanguage"; const QString BaseApplication::ARG_SPLASH_IMAGE = "BlueBerry.splashscreen"; const QString BaseApplication::ARG_STORAGE_DIR = "BlueBerry.storageDir"; const QString BaseApplication::ARG_XARGS = "xargs"; const QString BaseApplication::ARG_LOG_QT_MESSAGES = "Qt.logMessages"; const QString BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET = "Segmentation.labelSetPreset"; + const QString BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS = "Segmentation.labelSuggestions"; const QString BaseApplication::PROP_APPLICATION = "blueberry.application"; const QString BaseApplication::PROP_FORCE_PLUGIN_INSTALL = BaseApplication::ARG_FORCE_PLUGIN_INSTALL; const QString BaseApplication::PROP_NEWINSTANCE = BaseApplication::ARG_NEWINSTANCE; const QString BaseApplication::PROP_NO_LAZY_REGISTRY_CACHE_LOADING = BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING; const QString BaseApplication::PROP_NO_REGISTRY_CACHE = BaseApplication::ARG_NO_REGISTRY_CACHE; const QString BaseApplication::PROP_PRODUCT = "blueberry.product"; const QString BaseApplication::PROP_REGISTRY_MULTI_LANGUAGE = BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE; class SplashCloserCallback : public QRunnable { public: SplashCloserCallback(QSplashScreen* splashscreen) : m_Splashscreen(splashscreen) { } void run() override { this->m_Splashscreen->close(); } private: QSplashScreen *m_Splashscreen; // Owned by BaseApplication::Impl }; struct BaseApplication::Impl { ctkProperties m_FWProps; QCoreApplication *m_QApp; int m_Argc; char **m_Argv; #ifdef Q_OS_MAC std::vector m_Argv_macOS; #endif QString m_AppName; QString m_OrgaName; QString m_OrgaDomain; bool m_SingleMode; bool m_SafeMode; QSplashScreen *m_Splashscreen; SplashCloserCallback *m_SplashscreenClosingCallback; bool m_LogQtMessages; QStringList m_PreloadLibs; QString m_ProvFile; Impl(int argc, char **argv) : m_Argc(argc), m_Argv(argv), #ifdef Q_OS_MAC m_Argv_macOS(), #endif m_SingleMode(false), m_SafeMode(true), m_Splashscreen(nullptr), m_SplashscreenClosingCallback(nullptr), m_LogQtMessages(false) { #ifdef Q_OS_MAC /* On macOS the process serial number is passed as an command line argument (-psn_) in certain circumstances. This option causes a Poco exception. We remove it, if present. */ m_Argv_macOS.reserve(argc + 1); const char psn[] = "-psn"; for (int i = 0; i < argc; ++i) { if (0 == strncmp(argv[i], psn, sizeof(psn) - 1)) continue; m_Argv_macOS.push_back(argv[i]); } m_Argv_macOS.push_back(nullptr); m_Argc = static_cast(m_Argv_macOS.size() - 1); m_Argv = m_Argv_macOS.data(); #endif } ~Impl() { delete m_SplashscreenClosingCallback; delete m_Splashscreen; delete m_QApp; } QVariant getProperty(const QString &property) const { auto iter = m_FWProps.find(property); return m_FWProps.end() != iter ? iter.value() : QVariant(); } void handleBooleanOption(const std::string &name, const std::string &) { if (ARG_LOG_QT_MESSAGES.toStdString() == name) { m_LogQtMessages = true; return; } auto fwKey = QString::fromStdString(name); // Translate some keys to proper framework properties if (ARG_CONSOLELOG == fwKey) fwKey = ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG; // For all other options we use the command line option name as the // framework property key. m_FWProps[fwKey] = true; } void handlePreloadLibraryOption(const std::string &, const std::string &value) { m_PreloadLibs.push_back(QString::fromStdString(value)); } void handleClean(const std::string &, const std::string &) { m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT; } void initializeCTKPluginFrameworkProperties(Poco::Util::LayeredConfiguration &configuration) { // Add all configuration key/value pairs as framework properties Poco::Util::LayeredConfiguration::Keys keys; Poco::Util::LayeredConfiguration::Keys keyStack; configuration.keys(keyStack); std::vector keyChain; while (!keyStack.empty()) { const auto currSubKey = keyStack.back(); if (!keyChain.empty() && keyChain.back() == currSubKey) { keyChain.pop_back(); keyStack.pop_back(); continue; } Poco::Util::LayeredConfiguration::Keys subKeys; configuration.keys(currSubKey, subKeys); if (subKeys.empty()) { std::string finalKey; keyStack.pop_back(); for (const auto& key : keyChain) finalKey += key + '.'; finalKey += currSubKey; keys.push_back(finalKey); } else { keyChain.push_back(currSubKey); for (const auto& key : subKeys) keyStack.push_back(key); } } for (const auto& key : keys) { if (configuration.hasProperty(key)) { // .ini and command line options overwrite already inserted keys auto qKey = QString::fromStdString(key); m_FWProps[qKey] = QString::fromStdString(configuration.getString(key)); } } } void parseProvisioningFile(const QString &filePath) { // Skip parsing if the file path is empty if (filePath.isEmpty()) return; auto consoleLog = this->getProperty(ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG).toBool(); // Read initial plugins from a provisioning file QFileInfo provFile(filePath); QStringList pluginsToStart; if (provFile.exists()) { MITK_INFO(consoleLog) << "Using provisioning file: " << qPrintable(provFile.absoluteFilePath()); ProvisioningInfo provInfo(provFile.absoluteFilePath()); // It can still happen that the encoding is not compatible with the fromUtf8 function (i.e. when // manipulating the LANG variable). The QStringList in provInfo is empty then. if (provInfo.getPluginDirs().empty()) { MITK_ERROR << "Cannot search for provisioning file, the retrieved directory list is empty.\n" << "This can happen if there are some special non-ASCII characters in the install path."; } else { for(const auto& pluginPath : provInfo.getPluginDirs()) ctkPluginFrameworkLauncher::addSearchPath(pluginPath); auto pluginUrlsToStart = provInfo.getPluginsToStart(); for (const auto& url : qAsConst(pluginUrlsToStart)) pluginsToStart.push_back(url.toString()); } } else { MITK_INFO(consoleLog) << "Provisionig file does not exist."; } if (!pluginsToStart.isEmpty()) { m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS] = pluginsToStart; // Use transient start with declared activation policy (this helps when the provisioning file // changes and some plug-ins should not be installed in the application any more). ctkPlugin::StartOptions startOptions(ctkPlugin::START_TRANSIENT | ctkPlugin::START_ACTIVATION_POLICY); m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS_START_OPTIONS] = static_cast(startOptions); } } }; BaseApplication::BaseApplication(int argc, char **argv) : Application(), d(new Impl(argc, argv)) { } BaseApplication::~BaseApplication() { delete d; } void BaseApplication::printHelp(const std::string &, const std::string &) { Poco::Util::HelpFormatter help(this->options()); help.setAutoIndent(); help.setCommand(this->commandName()); help.format(std::cout); exit(EXIT_OK); } void BaseApplication::setApplicationName(const QString &name) { if (nullptr != qApp) qApp->setApplicationName(name); d->m_AppName = name; } QString BaseApplication::getApplicationName() const { return nullptr != qApp ? qApp->applicationName() : d->m_AppName; } void BaseApplication::setOrganizationName(const QString &name) { if (nullptr != qApp) qApp->setOrganizationName(name); d->m_OrgaName = name; } QString BaseApplication::getOrganizationName() const { return nullptr != qApp ? qApp->organizationName() : d->m_OrgaName; } void BaseApplication::setOrganizationDomain(const QString &domain) { if (nullptr != qApp) qApp->setOrganizationDomain(domain); d->m_OrgaDomain = domain; } QString BaseApplication::getOrganizationDomain() const { return nullptr != qApp ? qApp->organizationDomain() : d->m_OrgaDomain; } void BaseApplication::setSingleMode(bool singleMode) { if (nullptr != qApp) return; d->m_SingleMode = singleMode; } bool BaseApplication::getSingleMode() const { return d->m_SingleMode; } void BaseApplication::setSafeMode(bool safeMode) { if (nullptr != qApp && nullptr == d->m_QApp) return; d->m_SafeMode = safeMode; nullptr == d->m_QApp && getSingleMode() ? static_cast(d->m_QApp)->setSafeMode(safeMode) : static_cast(d->m_QApp)->setSafeMode(safeMode); } bool BaseApplication::getSafeMode() const { return d->m_SafeMode; } void BaseApplication::setPreloadLibraries(const QStringList &libraryBaseNames) { d->m_PreloadLibs = libraryBaseNames; } QStringList BaseApplication::getPreloadLibraries() const { return d->m_PreloadLibs; } void BaseApplication::setProvisioningFilePath(const QString &filePath) { d->m_ProvFile = filePath; } QString BaseApplication::getProvisioningFilePath() const { auto provFilePath = d->m_ProvFile; // A null QString means look up a default provisioning file if (provFilePath.isNull() && nullptr != qApp) { QFileInfo appFilePath(QCoreApplication::applicationFilePath()); QDir basePath(QCoreApplication::applicationDirPath()); auto provFileName = appFilePath.baseName() + ".provisioning"; QFileInfo provFile(basePath.absoluteFilePath(provFileName)); #ifdef Q_OS_MAC /* * On macOS, if started from the build directory, the .provisioning file is located at: * * The executable path is: * * In this case we have to cdUp threetimes. * * During packaging the MitkWorkbench.provisioning file is placed at the same * level like the executable. Nothing has to be done. */ if (!provFile.exists()) { basePath.cdUp(); basePath.cdUp(); basePath.cdUp(); provFile = basePath.absoluteFilePath(provFileName); } #endif if (provFile.exists()) { provFilePath = provFile.absoluteFilePath(); } #ifdef CMAKE_INTDIR else { basePath.cdUp(); provFile.setFile(basePath.absoluteFilePath(provFileName)); if (provFile.exists()) provFilePath = provFile.absoluteFilePath(); } #endif } return provFilePath; } void BaseApplication::initializeQt() { if (nullptr != qApp) return; // If parameters have been set before, we have to store them to hand them // through to the application auto appName = this->getApplicationName(); auto orgName = this->getOrganizationName(); auto orgDomain = this->getOrganizationDomain(); // Create a QCoreApplication instance this->getQApplication(); // Provide parameters to QCoreApplication this->setApplicationName(appName); this->setOrganizationName(orgName); this->setOrganizationDomain(orgDomain); if (d->m_LogQtMessages) qInstallMessageHandler(outputQtMessage); QWebEngineUrlScheme qtHelpScheme("qthelp"); qtHelpScheme.setFlags(QWebEngineUrlScheme::LocalScheme | QWebEngineUrlScheme::LocalAccessAllowed); QWebEngineUrlScheme::registerScheme(qtHelpScheme); } void BaseApplication::initialize(Poco::Util::Application &self) { // 1. Call the super-class method Poco::Util::Application::initialize(self); // 2. Initialize the Qt framework (by creating a QCoreApplication) this->initializeQt(); // 3. Seed the random number generator, once at startup. QTime time = QTime::currentTime(); qsrand((uint)time.msec()); // 4. Load the "default" configuration, which involves parsing // an optional .ini file and parsing any // command line arguments this->loadConfiguration(); // 5. Add configuration data from the command line and the // optional .ini file as CTK plugin // framework properties. d->initializeCTKPluginFrameworkProperties(this->config()); // 6. Initialize splash screen if an image path is provided // in the .ini file this->initializeSplashScreen(qApp); // 7. Set the custom CTK Plugin Framework storage directory QString storageDir = this->getCTKFrameworkStorageDir(); if (!storageDir.isEmpty()) d->m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storageDir; // 8. Set the library search paths and the pre-load library property this->initializeLibraryPaths(); auto preloadLibs = this->getPreloadLibraries(); if (!preloadLibs.isEmpty()) d->m_FWProps[ctkPluginConstants::FRAMEWORK_PRELOAD_LIBRARIES] = preloadLibs; // 9. Initialize the CppMicroServices library. // The initializeCppMicroServices() method reuses the // FRAMEWORK_STORAGE property, so we call it after the // getCTKFrameworkStorageDir method. this->initializeCppMicroServices(); // 10. Parse the (optional) provisioning file and set the // correct framework properties. d->parseProvisioningFile(this->getProvisioningFilePath()); // 11. Set the CTK Plugin Framework properties ctkPluginFrameworkLauncher::setFrameworkProperties(d->m_FWProps); } void BaseApplication::uninitialize() { auto pfw = this->getFramework(); if (pfw) { pfw->stop(); // Wait for up to 10 seconds for the CTK plugin framework to stop pfw->waitForStop(10000); } Poco::Util::Application::uninitialize(); } int BaseApplication::getArgc() const { return d->m_Argc; } char **BaseApplication::getArgv() const { return d->m_Argv; } QString BaseApplication::getCTKFrameworkStorageDir() const { QString storageDir; if (this->getSingleMode()) { // This function checks if an instance is already running and either sends a message to // it containing the command line arguments or checks if a new instance was forced by // providing the BlueBerry.newInstance command line argument. In the latter case, a path // to a temporary directory for the new application's storage directory is returned. storageDir = handleNewAppInstance(static_cast(d->m_QApp), d->m_Argc, d->m_Argv, ARG_NEWINSTANCE); } if (storageDir.isEmpty()) { // This is a new instance and no other instance is already running. We specify the // storage directory here (this is the same code as in berryInternalPlatform.cpp) // so that we can re-use the location for the persistent data location of the // the CppMicroServices library. // Append a hash value of the absolute path of the executable to the data location. // This allows to start the same application from different build or install trees. storageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + this->getOrganizationName() + "/" + this->getApplicationName() + '_'; storageDir += QString::number(qHash(QCoreApplication::applicationDirPath())) + "/"; } return storageDir; } void BaseApplication::initializeCppMicroServices() { auto storageDir = this->getProperty(ctkPluginConstants::FRAMEWORK_STORAGE).toString(); if (!storageDir.isEmpty()) us::ModuleSettings::SetStoragePath((storageDir + "us" + QDir::separator()).toStdString()); } QCoreApplication *BaseApplication::getQApplication() const { if (nullptr == qApp) { vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0); auto defaultFormat = QVTKOpenGLNativeWidget::defaultFormat(); defaultFormat.setSamples(0); QSurfaceFormat::setDefaultFormat(defaultFormat); #ifdef Q_OS_OSX QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #endif QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); d->m_QApp = this->getSingleMode() ? static_cast(new QmitkSingleApplication(d->m_Argc, d->m_Argv, this->getSafeMode())) : static_cast(new QmitkSafeApplication(d->m_Argc, d->m_Argv, this->getSafeMode())); } return qApp; } void BaseApplication::initializeLibraryPaths() { QStringList suffixes; suffixes << "plugins"; #ifdef Q_OS_WINDOWS suffixes << "bin/plugins"; #ifdef CMAKE_INTDIR suffixes << "bin/" CMAKE_INTDIR "/plugins"; #endif #else suffixes << "lib/plugins"; #ifdef CMAKE_INTDIR suffixes << "lib/" CMAKE_INTDIR "/plugins"; #endif #endif #ifdef Q_OS_MAC suffixes << "../../plugins"; #endif // We add a couple of standard library search paths for plug-ins QDir appDir(QCoreApplication::applicationDirPath()); // Walk one directory up and add bin and lib sub-dirs; this might be redundant appDir.cdUp(); for (const auto& suffix : qAsConst(suffixes)) ctkPluginFrameworkLauncher::addSearchPath(appDir.absoluteFilePath(suffix)); } int BaseApplication::main(const std::vector &args) { // Start the plugin framework and all installed plug-ins according to their auto-start setting QStringList arguments; for (auto const &arg : args) arguments.push_back(QString::fromStdString(arg)); if (nullptr != d->m_Splashscreen) { // A splash screen is displayed. Create the closing callback. d->m_SplashscreenClosingCallback = new SplashCloserCallback(d->m_Splashscreen); } return ctkPluginFrameworkLauncher::run(d->m_SplashscreenClosingCallback, QVariant::fromValue(arguments)).toInt(); } void BaseApplication::defineOptions(Poco::Util::OptionSet &options) { Poco::Util::Option helpOption("help", "h", "print this help text"); helpOption.callback(Poco::Util::OptionCallback(this, &BaseApplication::printHelp)); options.addOption(helpOption); Poco::Util::Option newInstanceOption(ARG_NEWINSTANCE.toStdString(), "", "forces a new instance of this application"); newInstanceOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(newInstanceOption); Poco::Util::Option cleanOption(ARG_CLEAN.toStdString(), "", "cleans the plugin cache"); cleanOption.callback(Poco::Util::OptionCallback(d, &Impl::handleClean)); options.addOption(cleanOption); Poco::Util::Option productOption(ARG_PRODUCT.toStdString(), "", "the id of the product to be launched"); productOption.argument("").binding(PROP_PRODUCT.toStdString()); options.addOption(productOption); Poco::Util::Option appOption(ARG_APPLICATION.toStdString(), "", "the id of the application extension to be executed"); appOption.argument("").binding(PROP_APPLICATION.toStdString()); options.addOption(appOption); Poco::Util::Option provOption(ARG_PROVISIONING.toStdString(), "", "the location of a provisioning file"); provOption.argument("").binding(ARG_PROVISIONING.toStdString()); options.addOption(provOption); Poco::Util::Option storageDirOption(ARG_STORAGE_DIR.toStdString(), "", "the location for storing persistent application data"); storageDirOption.argument("").binding(ctkPluginConstants::FRAMEWORK_STORAGE.toStdString()); options.addOption(storageDirOption); Poco::Util::Option consoleLogOption(ARG_CONSOLELOG.toStdString(), "", "log messages to the console"); consoleLogOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(consoleLogOption); Poco::Util::Option debugOption(ARG_DEBUG.toStdString(), "", "enable debug mode"); debugOption.argument("", false).binding(ctkPluginFrameworkLauncher::PROP_DEBUG.toStdString()); options.addOption(debugOption); Poco::Util::Option forcePluginOption(ARG_FORCE_PLUGIN_INSTALL.toStdString(), "", "force installing plug-ins with same symbolic name"); forcePluginOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(forcePluginOption); Poco::Util::Option preloadLibsOption(ARG_PRELOAD_LIBRARY.toStdString(), "", "preload a library"); preloadLibsOption.argument("") .repeatable(true) .callback(Poco::Util::OptionCallback(d, &Impl::handlePreloadLibraryOption)); options.addOption(preloadLibsOption); Poco::Util::Option noRegistryCacheOption(ARG_NO_REGISTRY_CACHE.toStdString(), "", "do not use a cache for the registry"); noRegistryCacheOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noRegistryCacheOption); Poco::Util::Option noLazyRegistryCacheLoadingOption(ARG_NO_LAZY_REGISTRY_CACHE_LOADING.toStdString(), "", "do not use lazy cache loading for the registry"); noLazyRegistryCacheLoadingOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(noLazyRegistryCacheLoadingOption); Poco::Util::Option registryMultiLanguageOption(ARG_REGISTRY_MULTI_LANGUAGE.toStdString(), "", "enable multi-language support for the registry"); registryMultiLanguageOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(registryMultiLanguageOption); Poco::Util::Option splashScreenOption(ARG_SPLASH_IMAGE.toStdString(), "", "optional picture to use as a splash screen"); splashScreenOption.argument("").binding(ARG_SPLASH_IMAGE.toStdString()); options.addOption(splashScreenOption); Poco::Util::Option xargsOption(ARG_XARGS.toStdString(), "", "Extended argument list"); xargsOption.argument("").binding(ARG_XARGS.toStdString()); options.addOption(xargsOption); Poco::Util::Option logQtMessagesOption(ARG_LOG_QT_MESSAGES.toStdString(), "", "log Qt messages"); logQtMessagesOption.callback(Poco::Util::OptionCallback(d, &Impl::handleBooleanOption)); options.addOption(logQtMessagesOption); Poco::Util::Option labelSetPresetOption(ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), "", "use this label set preset for new segmentations"); labelSetPresetOption.argument("").binding(ARG_SEGMENTATION_LABELSET_PRESET.toStdString()); options.addOption(labelSetPresetOption); + Poco::Util::Option labelSuggestionsOption(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), "", "use this list of predefined suggestions for segmentation labels"); + labelSuggestionsOption.argument("").binding(ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString()); + options.addOption(labelSuggestionsOption); + Poco::Util::Application::defineOptions(options); } QSharedPointer BaseApplication::getFramework() const { return ctkPluginFrameworkLauncher::getPluginFramework(); } ctkPluginContext *BaseApplication::getFrameworkContext() const { auto framework = getFramework(); return framework ? framework->getPluginContext() : nullptr; } void BaseApplication::initializeSplashScreen(QCoreApplication * application) const { auto pixmapFileNameProp = d->getProperty(ARG_SPLASH_IMAGE); if (!pixmapFileNameProp.isNull()) { auto pixmapFileName = pixmapFileNameProp.toString(); QFileInfo checkFile(pixmapFileName); if (checkFile.exists() && checkFile.isFile()) { QPixmap pixmap(checkFile.absoluteFilePath()); d->m_Splashscreen = new QSplashScreen(pixmap, Qt::WindowStaysOnTopHint); d->m_Splashscreen->show(); application->processEvents(); } } } QHash BaseApplication::getFrameworkProperties() const { return d->m_FWProps; } int BaseApplication::run() { this->init(d->m_Argc, d->m_Argv); return Application::run(); } void BaseApplication::setProperty(const QString &property, const QVariant &value) { d->m_FWProps[property] = value; } QVariant BaseApplication::getProperty(const QString &property) const { return d->getProperty(property); } void BaseApplication::installTranslator(QTranslator* translator) { this->getQApplication()->installTranslator(translator); } bool BaseApplication::isRunning() { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->isRunning(); mitkThrow() << "Method not implemented."; } void BaseApplication::sendMessage(const QByteArray msg) { auto app = dynamic_cast(this->getQApplication()); if (nullptr != app) app->sendMessage(msg); mitkThrow() << "Method not implemented."; } } diff --git a/Modules/CEST/CMakeLists.txt b/Modules/CEST/CMakeLists.txt index a35980ee61..467700c299 100644 --- a/Modules/CEST/CMakeLists.txt +++ b/Modules/CEST/CMakeLists.txt @@ -1,9 +1,9 @@ MITK_CREATE_MODULE( DEPENDS MitkCore PRIVATE MitkDICOM PACKAGE_DEPENDS - PRIVATE Poco + PRIVATE Poco nlohmann_json ) add_subdirectory(autoload/IO) add_subdirectory(test) diff --git a/Modules/CEST/autoload/IO/CMakeLists.txt b/Modules/CEST/autoload/IO/CMakeLists.txt index 9b6aa610fa..73ef3d9ca0 100644 --- a/Modules/CEST/autoload/IO/CMakeLists.txt +++ b/Modules/CEST/autoload/IO/CMakeLists.txt @@ -1,4 +1,5 @@ MITK_CREATE_MODULE( CESTIO DEPENDS MitkCEST MitkDICOM + PACKAGE_DEPENDS PRIVATE nlohmann_json AUTOLOAD_WITH MitkDICOM ) diff --git a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp index 0276670704..3070b9d2b1 100644 --- a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp +++ b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp @@ -1,407 +1,409 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTGenericDICOMReaderService.h" #include "mitkIOMimeTypes.h" #include #include #include #include #include #include #include "mitkCESTImageNormalizationFilter.h" #include "itksys/SystemTools.hxx" #include #include #include -#include -#include +#include + +using namespace nlohmann; namespace { std::string OPTION_NAME_B1() { return "B1 amplitude"; } std::string OPTION_NAME_PULSE() { return "Pulse duration [us]"; } std::string OPTION_NAME_DC() { return "Duty cycle [%]"; } std::string OPTION_NAME_NORMALIZE() { return "Normalize data"; } std::string OPTION_NAME_NORMALIZE_AUTOMATIC() { return "Automatic"; } std::string OPTION_NAME_NORMALIZE_NO() { return "No"; } std::string OPTION_NAME_MERGE() { return "Merge all series"; } std::string OPTION_NAME_MERGE_YES() { return "Yes"; } std::string OPTION_NAME_MERGE_NO() { return "No"; } std::string META_FILE_OPTION_NAME_MERGE() { return "CEST.MergeAllSeries"; } } namespace mitk { DICOMTagPath DICOM_IMAGING_FREQUENCY_PATH() { return mitk::DICOMTagPath(0x0018, 0x0084); } CESTDICOMManualReaderService::CESTDICOMManualReaderService(const CustomMimeType& mimeType, const std::string& description) : BaseDICOMReaderService(mimeType, description) { IFileIO::Options options; options[OPTION_NAME_B1()] = 0.0; options[OPTION_NAME_PULSE()] = 0.0; options[OPTION_NAME_DC()] = 0.0; std::vector normalizationStrategy; normalizationStrategy.push_back(OPTION_NAME_NORMALIZE_AUTOMATIC()); normalizationStrategy.push_back(OPTION_NAME_NORMALIZE_NO()); options[OPTION_NAME_NORMALIZE()] = normalizationStrategy; std::vector mergeStrategy; mergeStrategy.push_back(OPTION_NAME_MERGE_NO()); mergeStrategy.push_back(OPTION_NAME_MERGE_YES()); options[OPTION_NAME_MERGE()] = mergeStrategy; this->SetDefaultOptions(options); this->RegisterService(); } namespace { - void ExtractOptionFromPropertyTree(const std::string& key, boost::property_tree::ptree& root, std::map& options) + void ExtractOptionFromPropertyTree(const std::string& key, const json& root, std::map& options) { - auto finding = root.find(key); - if (finding != root.not_found()) + if (root.contains(key)) { - try + const auto& value = root[key]; + + if (value.is_number_float()) { - options[key] = finding->second.get_value(); + options[key] = value.get(); } - catch (const boost::property_tree::ptree_bad_data& /*e*/) + else { - options[key] = finding->second.get_value(); + options[key] = value.get(); } } } IFileIO::Options ExtractOptionsFromFile(const std::string& file) { - boost::property_tree::ptree root; + json root; if (itksys::SystemTools::FileExists(file)) { try { - boost::property_tree::read_json(file, root, std::locale("C")); + root = json::parse(file); } - catch (const boost::property_tree::json_parser_error & e) + catch (const json::exception& e) { MITK_WARN << "Could not parse CEST meta file. Fall back to default values. Error was:\n" << e.what(); } } else { MITK_DEBUG << "CEST meta file does not exist. Fall back to default values. CEST meta file path: " << file; } IFileIO::Options options; ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_B1Amplitude(), root, options); ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_PULSEDURATION(), root, options); ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_DutyCycle(), root, options); ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_OFFSETS(), root, options); ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_TREC(), root, options); ExtractOptionFromPropertyTree(META_FILE_OPTION_NAME_MERGE(), root, options); return options; } void TransferOption(const mitk::IFileIO::Options& sourceOptions, const std::string& sourceName, mitk::IFileIO::Options& options, const std::string& newName) { auto sourceFinding = sourceOptions.find(sourceName); auto finding = options.find(newName); bool replaceValue = finding == options.end(); if (!replaceValue) { replaceValue = us::any_cast(finding->second) == 0.; } if (sourceFinding != sourceOptions.end() && us::any_cast(sourceFinding->second) != 0. && replaceValue) { options[newName] = sourceFinding->second; } } void TransferMergeOption(const mitk::IFileIO::Options& sourceOptions, const std::string& sourceName, mitk::IFileIO::Options& options, const std::string& newName) { auto sourceFinding = sourceOptions.find(sourceName); auto finding = options.find(newName); bool replaceValue = finding == options.end(); if (!replaceValue) { try { us::any_cast(finding->second); } catch (const us::BadAnyCastException& /*e*/) { replaceValue = true; //if we cannot cast in string the user has not made a selection yet } } if (sourceFinding != sourceOptions.end() && us::any_cast(sourceFinding->second) != OPTION_NAME_MERGE_NO() && replaceValue) { options[newName] = sourceFinding->second; } } } std::string CESTDICOMManualReaderService::GetCESTMetaFilePath() const { auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); std::string metafile = dir + "/" + "CEST_META.json"; return metafile; } std::string CESTDICOMManualReaderService::GetTRECFilePath() const { auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); std::string metafile = dir + "/" + "TREC.txt"; return metafile; } std::string CESTDICOMManualReaderService::GetLISTFilePath() const { auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); std::string metafile = dir + "/" + "LIST.txt"; return metafile; } IFileIO::Options CESTDICOMManualReaderService::GetOptions() const { auto options = AbstractFileReader::GetOptions(); if (!this->GetInputLocation().empty()) { auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, OPTION_NAME_B1()); TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, OPTION_NAME_PULSE()); TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, OPTION_NAME_DC()); TransferMergeOption(fileOptions, META_FILE_OPTION_NAME_MERGE(), options, OPTION_NAME_MERGE()); } return options; } us::Any CESTDICOMManualReaderService::GetOption(const std::string& name) const { this->GetOptions(); //ensure (default) options are set. return AbstractFileReader::GetOption(name); } DICOMFileReader::Pointer CESTDICOMManualReaderService::GetReader(const mitk::StringList& relevantFiles) const { auto selector = mitk::DICOMFileReaderSelector::New(); const std::string mergeStrategy = this->GetOption(OPTION_NAME_MERGE()).ToString(); if (mergeStrategy == OPTION_NAME_MERGE_YES()) { auto r = ::us::GetModuleContext()->GetModule()->GetResource("cest_DKFZ.xml"); selector->AddConfigFromResource(r); } selector->LoadBuiltIn3DnTConfigs(); selector->SetInputFiles(relevantFiles); mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNotNull()) { //reset tag cache to ensure that additional tags of interest //will be regarded by the reader if set later on. reader->SetTagCache(nullptr); } return reader; } std::vector> CESTDICOMManualReaderService::Read() { const Options userOptions = this->GetOptions(); const std::string mergeStrategy = userOptions.find(OPTION_NAME_MERGE())->second.ToString(); this->SetOnlyRegardOwnSeries(mergeStrategy != OPTION_NAME_MERGE_YES()); std::vector result; std::vector dicomResult = BaseDICOMReaderService::Read(); const std::string normalizationStrategy = userOptions.find(OPTION_NAME_NORMALIZE())->second.ToString(); for (const auto &item : dicomResult) { auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); IFileIO::Options options; TransferOption(userOptions, OPTION_NAME_B1(), options, CEST_PROPERTY_NAME_B1Amplitude()); TransferOption(userOptions, OPTION_NAME_PULSE(), options, CEST_PROPERTY_NAME_PULSEDURATION()); TransferOption(userOptions, OPTION_NAME_DC(), options, CEST_PROPERTY_NAME_DutyCycle()); TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, CEST_PROPERTY_NAME_B1Amplitude()); TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, CEST_PROPERTY_NAME_PULSEDURATION()); TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, CEST_PROPERTY_NAME_DutyCycle()); TransferOption(fileOptions, CEST_PROPERTY_NAME_OFFSETS(), options, CEST_PROPERTY_NAME_OFFSETS()); TransferOption(fileOptions, CEST_PROPERTY_NAME_TREC(), options, CEST_PROPERTY_NAME_TREC()); auto trecValues = CustomTagParser::ReadListFromFile(this->GetTRECFilePath()); auto offsetValues = CustomTagParser::ReadListFromFile(this->GetLISTFilePath()); bool isCEST = !offsetValues.empty(); bool isT1 = !trecValues.empty(); if (!isCEST && !isT1) {//check if there are settings in the metafile auto finding = fileOptions.find(CEST_PROPERTY_NAME_OFFSETS()); if (finding != fileOptions.end()) { isCEST = true; offsetValues = finding->second.ToString(); }; finding = fileOptions.find(CEST_PROPERTY_NAME_TREC()); if (finding != fileOptions.end()) { isT1 = true; trecValues = finding->second.ToString(); }; } if (isCEST) { MITK_INFO << "CEST image detected due to LIST.txt or offset property in CEST_META.json"; options[CEST_PROPERTY_NAME_OFFSETS()] = offsetValues; } else if (isT1) { MITK_INFO << "T1 image detected due to TREC.txt or trec property in CEST_META.json"; options[CEST_PROPERTY_NAME_TREC()] = trecValues; } else { mitkThrow() << "Cannot load CEST/T1 file. No CEST offsets or T1 trec values specified. LIST.txt/TREC.txt or information in CEST_META.json is missing."; } for (const auto& option : options) { item->GetPropertyList()->SetStringProperty(option.first.c_str(), option.second.ToString().c_str()); } auto freqProp = item->GetProperty(mitk::DICOMTagPathToPropertyName(DICOM_IMAGING_FREQUENCY_PATH()).c_str()); if (freqProp.IsNull()) { mitkThrow() << "Loaded image in invalid state. Does not contain the DICOM Imaging Frequency tag."; } SetCESTFrequencyMHz(item, mitk::ConvertDICOMStrToValue(freqProp->GetValueAsString())); auto image = dynamic_cast(item.GetPointer()); if (isCEST) { try { auto offsets = ExtractCESTOffset(image); } catch (...) { mitkThrow() << "Cannot load CEST file. Number of CEST offsets do not equal the number of image time steps. Image time steps: " << image->GetTimeSteps() << "; offset values: " << offsetValues; } } else if (isT1) { try { auto t1s = ExtractCESTT1Time(image); } catch (...) { mitkThrow() << "Cannot load T1 file. Number of T1 times do not equal the number of image time steps. Image time steps: " << image->GetTimeSteps() << "; T1 values: " << trecValues; } } if (normalizationStrategy == OPTION_NAME_NORMALIZE_AUTOMATIC() && mitk::IsNotNormalizedCESTImage(image)) { MITK_INFO << "Unnormalized CEST image was loaded and will be normalized automatically."; auto normalizationFilter = mitk::CESTImageNormalizationFilter::New(); normalizationFilter->SetInput(image); normalizationFilter->Update(); auto normalizedImage = normalizationFilter->GetOutput(); auto nameProp = item->GetProperty("name"); if (!nameProp) { mitkThrow() << "Cannot load CEST file. Property \"name\" is missing after BaseDICOMReaderService::Read()."; } normalizedImage->SetProperty("name", mitk::StringProperty::New(nameProp->GetValueAsString() + "_normalized")); result.push_back(normalizedImage); } else { result.push_back(item); } } return result; } CESTDICOMManualReaderService *CESTDICOMManualReaderService::Clone() const { return new CESTDICOMManualReaderService(*this); } } diff --git a/Modules/CEST/src/mitkCustomTagParser.cpp b/Modules/CEST/src/mitkCustomTagParser.cpp index 49bbc08b59..ae68040759 100644 --- a/Modules/CEST/src/mitkCustomTagParser.cpp +++ b/Modules/CEST/src/mitkCustomTagParser.cpp @@ -1,855 +1,855 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCustomTagParser.h" #include #include #include "mitkCESTPropertyHelper.h" #include "mitkIPropertyPersistence.h" #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" #include "usModuleResource.h" #include "usModuleResourceStream.h" #include #include #include -#include -#include #include #include #include #include #include #include +#include + +using namespace nlohmann; + namespace { mitk::IPropertyPersistence *GetPersistenceService() { mitk::IPropertyPersistence *result = nullptr; std::vector> persRegisters = us::GetModuleContext()->GetServiceReferences(); if (!persRegisters.empty()) { if (persRegisters.size() > 1) { MITK_WARN << "Multiple property description services found. Using just one."; } result = us::GetModuleContext()->GetService(persRegisters.front()); } return result; }; } const std::string mitk::CustomTagParser::m_CESTPropertyPrefix = "CEST."; const std::string mitk::CustomTagParser::m_RevisionPropertyName = m_CESTPropertyPrefix + "Revision"; const std::string mitk::CustomTagParser::m_JSONRevisionPropertyName = m_CESTPropertyPrefix + "revision_json"; -const std::string mitk::CustomTagParser::m_RevisionIndependentMapping = -"\n" -" \"sProtConsistencyInfo.tSystemType\" : \"SysType\",\n" -" \"sProtConsistencyInfo.flNominalB0\" : \"NominalB0\",\n" -" \"sTXSPEC.asNucleusInfo[0].lFrequency\" : \"FREQ\",\n" -" \"sTXSPEC.asNucleusInfo[0].flReferenceAmplitude\" : \"RefAmp\",\n" -" \"alTR[0]\" : \"TR\",\n" -" \"alTE[0]\" : \"TE\",\n" -" \"lAverages\" : \"averages\",\n" -" \"lRepetitions\" : \"repetitions\",\n" -" \"adFlipAngleDegree[0]\" : \"ImageFlipAngle\",\n" -" \"lTotalScanTimeSec\" : \"TotalScanTime\","; -const std::string mitk::CustomTagParser::m_DefaultJsonString = -"{\n" -" \"default mapping, corresponds to revision 1416\" : \"revision_json\",\n" -" \"sWiPMemBlock.alFree[1]\" : \"AdvancedMode\",\n" -" \"sWiPMemBlock.alFree[2]\" : \"RecoveryMode\",\n" -" \"sWiPMemBlock.alFree[3]\" : \"DoubleIrrMode\",\n" -" \"sWiPMemBlock.alFree[4]\" : \"BinomMode\",\n" -" \"sWiPMemBlock.alFree[5]\" : \"MtMode\",\n" -" \"sWiPMemBlock.alFree[6]\" : \"PreparationType\",\n" -" \"sWiPMemBlock.alFree[7]\" : \"PulseType\",\n" -" \"sWiPMemBlock.alFree[8]\" : \"SamplingType\",\n" -" \"sWiPMemBlock.alFree[9]\" : \"SpoilingType\",\n" -" \"sWiPMemBlock.alFree[10]\" : \"measurements\",\n" -" \"sWiPMemBlock.alFree[11]\" : \"NumberOfPulses\",\n" -" \"sWiPMemBlock.alFree[12]\" : \"NumberOfLockingPulses\",\n" -" \"sWiPMemBlock.alFree[13]\" : \"PulseDuration\",\n" -" \"sWiPMemBlock.alFree[14]\" : \"DutyCycle\",\n" -" \"sWiPMemBlock.alFree[15]\" : \"RecoveryTime\",\n" -" \"sWiPMemBlock.alFree[16]\" : \"RecoveryTimeM0\",\n" -" \"sWiPMemBlock.alFree[17]\" : \"ReadoutDelay\",\n" -" \"sWiPMemBlock.alFree[18]\" : \"BinomDuration\",\n" -" \"sWiPMemBlock.alFree[19]\" : \"BinomDistance\",\n" -" \"sWiPMemBlock.alFree[20]\" : \"BinomNumberofPulses\",\n" -" \"sWiPMemBlock.alFree[21]\" : \"BinomPreRepetions\",\n" -" \"sWiPMemBlock.alFree[22]\" : \"BinomType\",\n" -" \"sWiPMemBlock.adFree[1]\" : \"Offset\",\n" -" \"sWiPMemBlock.adFree[2]\" : \"B1Amplitude\",\n" -" \"sWiPMemBlock.adFree[3]\" : \"AdiabaticPulseMu\",\n" -" \"sWiPMemBlock.adFree[4]\" : \"AdiabaticPulseBW\",\n" -" \"sWiPMemBlock.adFree[5]\" : \"AdiabaticPulseLength\",\n" -" \"sWiPMemBlock.adFree[6]\" : \"AdiabaticPulseAmp\",\n" -" \"sWiPMemBlock.adFree[7]\" : \"FermiSlope\",\n" -" \"sWiPMemBlock.adFree[8]\" : \"FermiFWHM\",\n" -" \"sWiPMemBlock.adFree[9]\" : \"DoubleIrrDuration\",\n" -" \"sWiPMemBlock.adFree[10]\" : \"DoubleIrrAmplitude\",\n" -" \"sWiPMemBlock.adFree[11]\" : \"DoubleIrrRepetitions\",\n" -" \"sWiPMemBlock.adFree[12]\" : \"DoubleIrrPreRepetitions\"\n" -"}"; +const std::string mitk::CustomTagParser::m_RevisionIndependentMapping = R"( + "sProtConsistencyInfo.tSystemType" : "SysType", + "sProtConsistencyInfo.flNominalB0" : "NominalB0", + "sTXSPEC.asNucleusInfo[0].lFrequency" : "FREQ", + "sTXSPEC.asNucleusInfo[0].flReferenceAmplitude" : "RefAmp", + "alTR[0]" : "TR", + "alTE[0]" : "TE", + "lAverages" : "averages", + "lRepetitions" : "repetitions", + "adFlipAngleDegree[0]" : "ImageFlipAngle", + "lTotalScanTimeSec" : "TotalScanTime", +)"; +const std::string mitk::CustomTagParser::m_DefaultJsonString = R"({ + "default mapping, corresponds to revision 1416" : "revision_json", + "sWiPMemBlock.alFree[1]" : "AdvancedMode", + "sWiPMemBlock.alFree[2]" : "RecoveryMode", + "sWiPMemBlock.alFree[3]" : "DoubleIrrMode", + "sWiPMemBlock.alFree[4]" : "BinomMode", + "sWiPMemBlock.alFree[5]" : "MtMode", + "sWiPMemBlock.alFree[6]" : "PreparationType", + "sWiPMemBlock.alFree[7]" : "PulseType", + "sWiPMemBlock.alFree[8]" : "SamplingType", + "sWiPMemBlock.alFree[9]" : "SpoilingType", + "sWiPMemBlock.alFree[10]" : "measurements", + "sWiPMemBlock.alFree[11]" : "NumberOfPulses", + "sWiPMemBlock.alFree[12]" : "NumberOfLockingPulses", + "sWiPMemBlock.alFree[13]" : "PulseDuration", + "sWiPMemBlock.alFree[14]" : "DutyCycle", + "sWiPMemBlock.alFree[15]" : "RecoveryTime", + "sWiPMemBlock.alFree[16]" : "RecoveryTimeM0", + "sWiPMemBlock.alFree[17]" : "ReadoutDelay", + "sWiPMemBlock.alFree[18]" : "BinomDuration", + "sWiPMemBlock.alFree[19]" : "BinomDistance", + "sWiPMemBlock.alFree[20]" : "BinomNumberofPulses", + "sWiPMemBlock.alFree[21]" : "BinomPreRepetions", + "sWiPMemBlock.alFree[22]" : "BinomType", + "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]" : "FermiSlope", + "sWiPMemBlock.adFree[8]" : "FermiFWHM", + "sWiPMemBlock.adFree[9]" : "DoubleIrrDuration", + "sWiPMemBlock.adFree[10]" : "DoubleIrrAmplitude", + "sWiPMemBlock.adFree[11]" : "DoubleIrrRepetitions", + "sWiPMemBlock.adFree[12]" : "DoubleIrrPreRepetitions" +})"; mitk::CustomTagParser::CustomTagParser(std::string relevantFile) : m_ClosestInternalRevision(""), m_ClosestExternalRevision("") { std::string pathToDirectory; std::string fileName; itksys::SystemTools::SplitProgramPath(relevantFile, pathToDirectory, fileName); m_DicomDataPath = pathToDirectory; m_ParseStrategy = "Automatic"; m_RevisionMappingStrategy = "Fuzzy"; } std::string mitk::CustomTagParser::ExtractRevision(std::string sequenceFileName) { //all rules are case insesitive. Thus we convert everything to lower case //in order to check everything only once. std::string cestPrefix = "cest"; std::string cestPrefix2 = "_cest"; std::string cestPrefix3 = "\\cest"; //this version covers the fact that the strings extracted //from the SIEMENS tag has an additional prefix that is seperated by backslash. std::string revisionPrefix = "_rev"; std::transform(sequenceFileName.begin(), sequenceFileName.end(), sequenceFileName.begin(), ::tolower); bool isCEST = sequenceFileName.compare(0, cestPrefix.length(), cestPrefix) == 0; std::size_t foundPosition = 0; if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix2); isCEST = foundPosition != std::string::npos; } if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix3); isCEST = foundPosition != std::string::npos; } if (!isCEST) { mitkThrow() << "Invalid CEST sequence file name. No CEST prefix found. Could not extract revision."; } foundPosition = sequenceFileName.find(revisionPrefix, foundPosition); if (foundPosition == std::string::npos) { mitkThrow() << "Invalid CEST sequence file name. No revision prefix was found in CEST sequence file name. Could not extract revision."; } std::string revisionString = sequenceFileName.substr(foundPosition + revisionPrefix.length(), std::string::npos); std::size_t firstNoneNumber = revisionString.find_first_not_of("0123456789"); if (firstNoneNumber != std::string::npos) { revisionString.erase(firstNoneNumber, std::string::npos); } return revisionString; } bool mitk::CustomTagParser::IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString) { bool isT1 = false; // if a forced parse strategy is set, use that one if ("T1" == m_ParseStrategy) { return true; } if ("CEST/WASABI" == m_ParseStrategy) { return false; } if (("T1Recovery" == preparationType) || ("T1Inversion" == preparationType)) { isT1 = true; } // How to interpret the recoveryMode depends on the age of the sequence // older sequences use 0 = false and 1 = true, newer ones 1 = false and 2 = true. // A rough rule of thumb is to assume that if the SpoilingType is 0, then the first // convention is chosen, if it is 1, then the second applies. Otherwise // we assume revision 1485 and newer to follow the new convention. // This unfortunate heuristic is due to somewhat arbitrary CEST sequence implementations. if (!isT1) { std::string thisIsTrue = "1"; std::string thisIsFalse = "0"; if ("0" == spoilingType) { thisIsFalse = "0"; thisIsTrue = "1"; } else if ("1" == spoilingType) { thisIsFalse = "1"; thisIsTrue = "2"; } else { int revisionNrWeAssumeToBeDifferenciating = 1485; if (std::stoi(revisionString) - revisionNrWeAssumeToBeDifferenciating < 0) { thisIsFalse = "0"; thisIsTrue = "1"; } else { thisIsFalse = "1"; thisIsTrue = "2"; } } if (thisIsFalse == recoveryMode) { isT1 = false; } else if (thisIsTrue == recoveryMode) { isT1 = true; } } return isT1; } mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomPropertyString(std::string dicomPropertyString) { auto results = mitk::PropertyList::New(); if ("" == dicomPropertyString) { //MITK_ERROR << "Could not parse empty custom dicom string"; return results; } auto comp = [](const std::string& s1, const std::string& s2) { return boost::algorithm::lexicographical_compare(s1, s2, boost::algorithm::is_iless()); }; std::map privateParameters(comp); // The Siemens private tag contains information like "43\52\23\34". // We jump over each "\" and convert the number; std::string bytes; { const std::size_t SUBSTR_LENGTH = 2; const std::size_t INPUT_LENGTH = dicomPropertyString.length(); if (INPUT_LENGTH < SUBSTR_LENGTH) return results; const std::size_t MAX_INPUT_OFFSET = INPUT_LENGTH - SUBSTR_LENGTH; bytes.reserve(INPUT_LENGTH / 3 + 1); try { for (std::size_t i = 0; i <= MAX_INPUT_OFFSET; i += 3) { std::string byte_string = dicomPropertyString.substr(i, SUBSTR_LENGTH); int byte = static_cast(std::stoi(byte_string.c_str(), nullptr, 16)); bytes.push_back(byte); } } catch (const std::invalid_argument&) // std::stoi() could not perform conversion { return results; } } // extract parameter list std::string parameterListString; { const std::string ASCCONV_MARKER = "###"; const std::string ASCCONV_BEGIN = "### ASCCONV BEGIN"; const std::string ASCCONV_END = "### ASCCONV END"; auto ascconvBeginPos = bytes.find(ASCCONV_BEGIN); if (std::string::npos == ascconvBeginPos) return results; ascconvBeginPos += ASCCONV_BEGIN.length(); ascconvBeginPos = bytes.find(ASCCONV_MARKER, ascconvBeginPos); if (std::string::npos == ascconvBeginPos) return results; ascconvBeginPos += ASCCONV_MARKER.length(); // closing "###" auto ascconvEndPos = bytes.find(ASCCONV_END, ascconvBeginPos); if (std::string::npos == ascconvEndPos) return results; auto count = ascconvEndPos - ascconvBeginPos; parameterListString = bytes.substr(ascconvBeginPos, count); } boost::replace_all(parameterListString, "\r\n", "\n"); boost::replace_all(parameterListString, "\t", ""); boost::char_separator newlineSeparator("\n"); boost::tokenizer> parameters(parameterListString, newlineSeparator); for (const auto ¶meter : parameters) { std::vector parts; boost::split(parts, parameter, boost::is_any_of("=")); if (parts.size() == 2) { parts[0].erase(std::remove(parts[0].begin(), parts[0].end(), ' '), parts[0].end()); parts[1].erase(parts[1].begin(), parts[1].begin() + 1); // first character is a space privateParameters[parts[0]] = parts[1]; } } std::string revisionString = ""; try { revisionString = ExtractRevision(privateParameters["tSequenceFileName"]); } catch (const std::exception &e) { MITK_ERROR << "Cannot deduce revision information. Reason: "<< e.what(); return results; } results->SetProperty(m_RevisionPropertyName, mitk::StringProperty::New(revisionString)); std::string jsonString = GetRevisionAppropriateJSONString(revisionString); - boost::property_tree::ptree root; - std::istringstream jsonStream(jsonString); + json root; try { - boost::property_tree::read_json(jsonStream, root); + root = json::parse(jsonString); } - catch (const boost::property_tree::json_parser_error &e) + catch (const json::exception &e) { mitkThrow() << "Could not parse json file. Error was:\n" << e.what(); } - for (const auto &it : root) + for (const auto &it : root.items()) { - if (it.second.empty()) + if (it.value().is_string()) { - std::string propertyName = m_CESTPropertyPrefix + it.second.data(); + auto propertyName = m_CESTPropertyPrefix + it.value().get(); if (m_JSONRevisionPropertyName == propertyName) { - results->SetProperty(propertyName, mitk::StringProperty::New(it.first)); + results->SetProperty(propertyName, mitk::StringProperty::New(it.key())); } else { - results->SetProperty(propertyName, mitk::StringProperty::New(privateParameters[it.first])); + results->SetProperty(propertyName, mitk::StringProperty::New(privateParameters[it.key()])); } } else { MITK_ERROR << "Currently no support for nested dicom tag descriptors in json file."; } } std::string offset = ""; std::string measurements = ""; results->GetStringProperty("CEST.Offset", offset); results->GetStringProperty("CEST.measurements", measurements); if (measurements.empty()) { std::string stringRepetitions = ""; results->GetStringProperty("CEST.repetitions", stringRepetitions); std::string stringAverages = ""; results->GetStringProperty("CEST.averages", stringAverages); const auto ERROR_STRING = "Could not find measurements, fallback assumption of repetitions + averages could not be determined either."; if (!stringRepetitions.empty() && !stringAverages.empty()) { std::stringstream measurementStream; try { measurementStream << std::stoi(stringRepetitions) + std::stoi(stringAverages); measurements = measurementStream.str(); MITK_INFO << "Could not find measurements, assuming repetitions + averages. That is: " << measurements; } catch (const std::invalid_argument&) { MITK_ERROR << ERROR_STRING; } } else { MITK_WARN << ERROR_STRING; } } std::string preparationType = ""; std::string recoveryMode = ""; std::string spoilingType = ""; results->GetStringProperty(CEST_PROPERTY_NAME_PREPERATIONTYPE().c_str(), preparationType); results->GetStringProperty(CEST_PROPERTY_NAME_RECOVERYMODE().c_str(), recoveryMode); results->GetStringProperty(CEST_PROPERTY_NAME_SPOILINGTYPE().c_str(), spoilingType); if (this->IsT1Sequence(preparationType, recoveryMode, spoilingType, revisionString)) { MITK_INFO << "Parsed as T1 image"; std::stringstream trecStream; std::string trecPath = m_DicomDataPath + "/TREC.txt"; auto trec = ReadListFromFile(trecPath); if(trec.empty()) { MITK_WARN << "Assumed T1, but could not load TREC at " << trecPath; } results->SetStringProperty(CEST_PROPERTY_NAME_TREC().c_str(), trec.c_str()); } else { MITK_INFO << "Parsed as CEST or WASABI image"; std::string sampling = ""; bool hasSamplingInformation = results->GetStringProperty("CEST.SamplingType", sampling); if (hasSamplingInformation) { std::string offsets = GetOffsetString(sampling, offset, measurements); results->SetStringProperty(CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets.c_str()); } else { MITK_WARN << "Could not determine sampling type."; } } //persist all properties mitk::IPropertyPersistence *persSrv = GetPersistenceService(); if (persSrv) { auto propertyMap = results->GetMap(); for (auto const &prop : *propertyMap) { PropertyPersistenceInfo::Pointer info = PropertyPersistenceInfo::New(); std::string key = prop.first; std::replace(key.begin(), key.end(), '.', '_'); info->SetNameAndKey(prop.first, key); persSrv->AddInfo(info); } } return results; } std::string mitk::CustomTagParser::ReadListFromFile(const std::string& filePath) { std::stringstream listStream; std::ifstream list(filePath.c_str()); list.imbue(std::locale("C")); if (list.good()) { std::string currentValue; while (std::getline(list, currentValue)) { listStream << currentValue << " "; } } return listStream.str(); } mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty) { if (!dicomProperty) { MITK_ERROR << "DICOM property empty"; } auto results = mitk::PropertyList::New(); if (dicomProperty) { results = ParseDicomPropertyString(dicomProperty->GetValue()); } return results; } std::vector mitk::CustomTagParser::GetInternalRevisions() { const std::vector configs = us::GetModuleContext()->GetModule()->FindResources("/", "*.json", false); std::vector availableRevisionsVector; for (const auto& resource : configs) { availableRevisionsVector.push_back(std::stoi(resource.GetBaseName())); } return availableRevisionsVector; } std::vector mitk::CustomTagParser::GetExternalRevisions() { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonsPath = stringToJSONDirectory + "/*.json"; std::set JsonFiles; Poco::Glob::glob(prospectiveJsonsPath, JsonFiles, Poco::Glob::GLOB_CASELESS); std::vector availableRevisionsVector; for (const auto& jsonpath : JsonFiles) { std::string jsonDir; std::string jsonName; itksys::SystemTools::SplitProgramPath(jsonpath, jsonDir, jsonName); std::string revision = itksys::SystemTools::GetFilenameWithoutExtension(jsonName); // disregard jsons which contain letters in their name bool onlyNumbers = (revision.find_first_not_of("0123456789") == std::string::npos); if(onlyNumbers) { availableRevisionsVector.push_back(std::stoi(revision)); } } return availableRevisionsVector; } std::string mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString, std::vector availableRevisionsVector) { // descending order std::sort(availableRevisionsVector.begin(), availableRevisionsVector.end(), std::greater<>()); int revision = std::stoi(revisionString); int index = 0; int numberOfRevisions = availableRevisionsVector.size(); while (index < numberOfRevisions) { // current mapping still has a higher revision number if ((availableRevisionsVector[index] - revision) > 0) { ++index; } else { break; } } if (index < numberOfRevisions) { std::stringstream foundRevisionStream; foundRevisionStream << availableRevisionsVector[index]; return foundRevisionStream.str(); } return ""; } void mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString) { m_ClosestInternalRevision = GetClosestLowerRevision(revisionString, GetInternalRevisions()); m_ClosestExternalRevision = GetClosestLowerRevision(revisionString, GetExternalRevisions()); if ("Strict" == m_RevisionMappingStrategy && !((0 == m_ClosestInternalRevision.compare(revisionString)) || (0 == m_ClosestExternalRevision.compare(revisionString)))) { // strict revision mapping and neither revision does match the dicom meta data std::stringstream errorMessageStream; errorMessageStream << "\nCould not parse dicom data in strict mode, data revision " << revisionString << " has no known matching parameter mapping. To use the closest known older parameter mapping select the " << "\"Fuzzy\" revision mapping option when loading the data.\n" << "\nCurrently known revision mappings are:\n Precompiled:"; for (const auto revision : GetInternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n External:"; for (const auto revision : GetExternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n\nExternal revision mapping descriptions should be located at\n\n"; std::string stringToJSONDirectory = GetExternalJSONDirectory(); errorMessageStream << stringToJSONDirectory; errorMessageStream << "\n\nTo provide an external mapping for this revision create a " << revisionString << ".json there. You might need to create the directory first."; mitkThrow() << errorMessageStream.str(); } } std::string mitk::CustomTagParser::GetRevisionAppropriateJSONString(std::string revisionString) { std::string returnValue = ""; if ("" == revisionString) { MITK_WARN << "Could not extract revision"; } else { GetClosestLowerRevision(revisionString); bool useExternal = false; bool useInternal = false; if ("" != m_ClosestExternalRevision) { useExternal = true; } if ("" != m_ClosestInternalRevision) { useInternal = true; } if (useExternal && useInternal) { if (std::stoi(m_ClosestInternalRevision) > std::stoi(m_ClosestExternalRevision)) { useExternal = false; } } if (useExternal) { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonPath = stringToJSONDirectory + "/" + m_ClosestExternalRevision + ".json"; std::ifstream externalJSON(prospectiveJsonPath.c_str()); if (externalJSON.good()) { MITK_INFO << "Found external json for CEST parameters at " << prospectiveJsonPath; std::stringstream buffer; buffer << externalJSON.rdbuf(); returnValue = buffer.str(); useInternal = false; } } if (useInternal) { std::string filename = m_ClosestInternalRevision + ".json"; us::ModuleResource jsonResource = us::GetModuleContext()->GetModule()->GetResource(filename); if (jsonResource.IsValid() && jsonResource.IsFile()) { MITK_INFO << "Found no external json for CEST parameters. Closest internal mapping is for revision " << m_ClosestInternalRevision; us::ModuleResourceStream jsonStream(jsonResource); std::stringstream buffer; buffer << jsonStream.rdbuf(); returnValue = buffer.str(); } } } if ("" == returnValue) { MITK_WARN << "Could not identify parameter mapping for the given revision " << revisionString << ", using default mapping."; returnValue = m_DefaultJsonString; } // inject the revision independent mapping before the first newline { returnValue.insert(returnValue.find("\n"), m_RevisionIndependentMapping); } return returnValue; } std::string mitk::CustomTagParser::GetOffsetString(std::string samplingType, std::string offset, std::string measurements) { std::stringstream results; results.imbue(std::locale("C")); std::string normalizationIndicatingOffset = "-300"; double offsetDouble = 0.0; int measurementsInt = 0; bool validOffset = false; bool validMeasurements = false; if ("" != offset) { validOffset = true; offsetDouble = std::stod(offset); } if ("" != measurements) { validMeasurements = true; measurementsInt = std::stoi(measurements); } std::vector offsetVector; if (validOffset && validMeasurements) { for (int step = 0; step < measurementsInt -1; ++step) { double currentOffset = -offsetDouble + 2 * step * offsetDouble / (measurementsInt - 2.0); offsetVector.push_back(currentOffset); } } else { MITK_WARN << "Invalid offset or measurements, offset calculation will only work for list sampling type."; } if (samplingType == "1" || samplingType == "Regular") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (const auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "2" || samplingType == "Alternating") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (auto& entry : offsetVector) { entry = std::abs(entry); } std::sort(offsetVector.begin(), offsetVector.end(), std::greater<>()); for (unsigned int index = 0; index < offsetVector.size(); ++index) { offsetVector[index] = std::pow(-1, index) * offsetVector[index]; } for (auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "3" || samplingType == "List") { std::string listPath = m_DicomDataPath + "/LIST.txt"; auto values = ReadListFromFile(listPath); if (!values.empty()) { results << values; } else { MITK_ERROR << "Could not load list at " << listPath; } } else if (samplingType == "4" || samplingType == "SingleOffset") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (int step = 0; step < measurementsInt - 1; ++step) { results << offsetDouble << " "; } } } else { MITK_WARN << "Encountered unknown sampling type."; } std::string resultString = results.str(); // replace multiple spaces by a single space std::string::iterator newEnditerator = std::unique(resultString.begin(), resultString.end(), [=](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); } ); resultString.erase(newEnditerator, resultString.end()); if ((resultString.length() > 0) && (resultString.at(resultString.length() - 1) == ' ')) { resultString.erase(resultString.end() - 1, resultString.end()); } if ((resultString.length() > 0) && (resultString.at(0) == ' ')) { resultString.erase(resultString.begin(), ++(resultString.begin())); } return resultString; } void mitk::CustomTagParser::SetParseStrategy(std::string parseStrategy) { m_ParseStrategy = parseStrategy; } void mitk::CustomTagParser::SetRevisionMappingStrategy(std::string revisionMappingStrategy) { m_RevisionMappingStrategy = revisionMappingStrategy; } std::string mitk::CustomTagParser::GetExternalJSONDirectory() { std::string moduleLocation = us::GetModuleContext()->GetModule()->GetLocation(); std::string stringToModule; std::string libraryName; itksys::SystemTools::SplitProgramPath(moduleLocation, stringToModule, libraryName); std::stringstream jsonDirectory; jsonDirectory << stringToModule << "/CESTRevisionMapping"; return jsonDirectory.str(); } diff --git a/Modules/Core/CMakeLists.txt b/Modules/Core/CMakeLists.txt index 27d967637d..0d72d5511a 100644 --- a/Modules/Core/CMakeLists.txt +++ b/Modules/Core/CMakeLists.txt @@ -1,77 +1,78 @@ set(TOOL_CPPS "") # temporary suppress warnings in the following files until image accessors are fully integrated. set_source_files_properties( src/DataManagement/mitkImage.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) set_source_files_properties( src/Controllers/mitkSliceNavigationController.cpp COMPILE_FLAGS -DMITK_NO_DEPRECATED_WARNINGS ) #if(MSVC) # set(optional_private_package_depends psapi) #endif() mitk_create_module( INCLUDE_DIRS PUBLIC ${MITK_BINARY_DIR} PRIVATE src/Algorithms src/Controllers src/DataManagement src/Interactions src/IO src/Rendering DEPENDS PUBLIC mbilog CppMicroServices PACKAGE_DEPENDS PUBLIC Boost ITK|IOImageBase+SpatialObjects+Statistics #ITK|Statistics+Transform VTK|FiltersTexture+FiltersParallel+ImagingStencil+ImagingMath+InteractionStyle+RenderingOpenGL2+RenderingVolumeOpenGL2+RenderingFreeType+RenderingLabel+InteractionWidgets+IOGeometry+IOXML PRIVATE ITK|IOBioRad+IOBMP+IOBruker+IOCSV+IOGDCM+IOGE+IOGIPL+IOHDF5+IOIPL+IOJPEG+IOJPEG2000+IOLSM+IOMesh+IOMeta+IOMINC+IOMRC+IONIFTI+IONRRD+IOPNG+IOSiemens+IOSpatialObjects+IOStimulate+IOTIFF+IOTransformBase+IOTransformHDF5+IOTransformInsightLegacy+IOTransformMatlab+IOVTK+IOXML + nlohmann_json tinyxml2 ${optional_private_package_depends} # Do not automatically create CppMicroServices initialization code. # Because the VTK "auto-init" functionality injects file-local static # initialization code in every cpp file which includes a VTK header, # static initialization order becomes an issue again. For the Mitk # core library, we need to ensure that the VTK static initialization stuff # happens before the CppMicroServices initialization, since the latter # might already use VTK code which needs to access VTK object factories. # Hence, CppMicroServices initialization code is placed manually within # the mitkCoreActivator.cpp file. NO_INIT ) if(NOT TARGET ${MODULE_TARGET}) message(SEND_ERROR "Core target ${MODULE_TARGET} does not exist") endif() function(_itk_create_factory_register_manager) # In MITK_ITK_Config.cmake, we do *not* include ITK_USE_FILE, which # prevents multiple registrations/unregistrations of ITK IO factories # during library loading/unloading (of MITK libraries). However, we need # "one" place where the IO factories are registered at # least once. This could be the application executable, but every executable would # need to take care of that itself. Instead, we allow the auto registration in the # Mitk Core library. set(NO_DIRECTORY_SCOPED_ITK_COMPILE_DEFINITION 1) find_package(ITK) include(${ITK_USE_FILE}) if(NOT ITK_NO_IO_FACTORY_REGISTER_MANAGER) # We manually add the define which will be of target scope. MITK # patches ITK_USE_FILE to remove the directory scoped compile # definition since it would be propagated to other targets in the # same directory scope but these targets might want to *not* # use the ITK factory manager stuff. target_compile_definitions(${MODULE_TARGET} PRIVATE ITK_IO_FACTORY_REGISTER_MANAGER) endif() endfunction() _itk_create_factory_register_manager() if(BUILD_TESTING) add_subdirectory(TestingHelper) add_subdirectory(test) endif() diff --git a/Modules/Core/src/DataManagement/mitkAnatomicalStructureColorPresets.cpp b/Modules/Core/src/DataManagement/mitkAnatomicalStructureColorPresets.cpp index 6ae41cd508..d5d5735216 100644 --- a/Modules/Core/src/DataManagement/mitkAnatomicalStructureColorPresets.cpp +++ b/Modules/Core/src/DataManagement/mitkAnatomicalStructureColorPresets.cpp @@ -1,180 +1,180 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkAnatomicalStructureColorPresets.h" #include #include "usGetModuleContext.h" #include "usModuleContext.h" #include "usModule.h" #include "usModuleResource.h" #include "usModuleResourceStream.h" namespace mitk { const std::string AnatomicalStructureColorPresets::PRESET = "preset"; const std::string AnatomicalStructureColorPresets::CATEGORY = "category"; const std::string AnatomicalStructureColorPresets::TYPE = "type"; const std::string AnatomicalStructureColorPresets::MODIFIER = "modifier"; const std::string AnatomicalStructureColorPresets::COLOR = "color"; const std::string AnatomicalStructureColorPresets::CODE_VALUE = "code_value"; const std::string AnatomicalStructureColorPresets::CODE_SCHEME = "coding_scheme"; const std::string AnatomicalStructureColorPresets::CODE_NAME = "code_name"; const std::string AnatomicalStructureColorPresets::COLOR_R = "rgb_r"; const std::string AnatomicalStructureColorPresets::COLOR_G = "rgb_g"; const std::string AnatomicalStructureColorPresets::COLOR_B = "rgb_b"; vtkStandardNewMacro(AnatomicalStructureColorPresets); bool AnatomicalStructureColorPresets::LoadPreset() { us::ModuleResource presetResource = us::GetModuleContext()->GetModule()->GetResource("mitkAnatomicalStructureColorPresets.xml"); if (!presetResource) return false; us::ModuleResourceStream presetStream(presetResource); vtkXMLParser::SetStream(&presetStream); if (!vtkXMLParser::Parse()) { #ifdef INTERDEBUG MITK_INFO<<"AnatomicalStructureColorPresets::LoadPreset xml file cannot parse!"< const AnatomicalStructureColorPresets::GetCategoryPresets() { return m_Category; } std::map const AnatomicalStructureColorPresets::GetTypePresets() { return m_Type; } std::map const AnatomicalStructureColorPresets::GetColorPresets() { return m_Color; } void AnatomicalStructureColorPresets::Save() { //Not yet implemented } void AnatomicalStructureColorPresets::NewPresets(std::map& newCategory, std::map& newType, std::map& newColor) { m_Category = newCategory; m_Type = newType; m_Color = newColor; this->Save(); } } diff --git a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp index 845d8bd4d3..5d9649c5b8 100644 --- a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp @@ -1,499 +1,493 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include +#include #include "mitkTemporoSpatialStringProperty.h" -#include -#include -#include +#include + +using namespace nlohmann; mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const char *s) { if (s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const std::string &s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const TemporoSpatialStringProperty &other) : BaseProperty(other), m_Values(other.m_Values) { } bool mitk::TemporoSpatialStringProperty::IsEqual(const BaseProperty &property) const { return this->m_Values == static_cast(property).m_Values; } bool mitk::TemporoSpatialStringProperty::Assign(const BaseProperty &property) { this->m_Values = static_cast(property).m_Values; return true; } std::string mitk::TemporoSpatialStringProperty::GetValueAsString() const { return GetValue(); } bool mitk::TemporoSpatialStringProperty::IsUniform() const { auto refValue = this->GetValue(); for (const auto& timeStep : m_Values) { auto finding = std::find_if_not(timeStep.second.begin(), timeStep.second.end(), [&refValue](const mitk::TemporoSpatialStringProperty::SliceMapType::value_type& val) { return val.second == refValue; }); if (finding != timeStep.second.end()) { return false; } } return true; } itk::LightObject::Pointer mitk::TemporoSpatialStringProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue() const { std::string result = ""; if (!m_Values.empty()) { if (!m_Values.begin()->second.empty()) { result = m_Values.begin()->second.begin()->second; } } return result; }; std::pair mitk::TemporoSpatialStringProperty::CheckValue( const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { std::string value = ""; bool found = false; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd && allowCloseTime) { // search for closest time step (earlier preverd) timeIter = m_Values.upper_bound(timeStep); if (timeIter != m_Values.begin()) { // there is a key lower than time step timeIter = std::prev(timeIter); } } if (timeIter != timeEnd) { const SliceMapType &slices = timeIter->second; auto sliceIter = slices.find(zSlice); auto sliceEnd = slices.end(); if (sliceIter == sliceEnd && allowCloseSlice) { // search for closest slice (earlier preverd) sliceIter = slices.upper_bound(zSlice); if (sliceIter != slices.begin()) { // there is a key lower than slice sliceIter = std::prev(sliceIter); } } if (sliceIter != sliceEnd) { value = sliceIter->second; found = true; } } return std::make_pair(found, value); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).second; }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueBySlice( const IndexValueType &zSlice, bool allowClose) const { return GetValue(0, zSlice, true, allowClose); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueByTimeStep( const TimeStepType &timeStep, bool allowClose) const { return GetValue(timeStep, 0, allowClose, true); }; bool mitk::TemporoSpatialStringProperty::HasValue() const { return !m_Values.empty(); }; bool mitk::TemporoSpatialStringProperty::HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).first; }; bool mitk::TemporoSpatialStringProperty::HasValueBySlice(const IndexValueType &zSlice, bool allowClose) const { return HasValue(0, zSlice, true, allowClose); }; bool mitk::TemporoSpatialStringProperty::HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose) const { return HasValue(timeStep, 0, allowClose, true); }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices() const { std::set uniqueSlices; for (const auto& timeStep : m_Values) { for (const auto& slice : timeStep.second) { uniqueSlices.insert(slice.first); } } return std::vector(std::begin(uniqueSlices), std::end(uniqueSlices)); } std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices( const TimeStepType &timeStep) const { std::vector result; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter != timeEnd) { for (auto const &element : timeIter->second) { result.push_back(element.first); } } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps() const { std::vector result; for (auto const &element : m_Values) { result.push_back(element.first); } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps(const IndexValueType& slice) const { std::vector result; for (const auto& timeStep : m_Values) { if (timeStep.second.find(slice) != std::end(timeStep.second)) { result.push_back(timeStep.first); } } return result; } void mitk::TemporoSpatialStringProperty::SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value) { auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd) { SliceMapType slices{{zSlice, value}}; m_Values.insert(std::make_pair(timeStep, slices)); } else { timeIter->second[zSlice] = value; } this->Modified(); }; void mitk::TemporoSpatialStringProperty::SetValue(const ValueType &value) { this->Modified(); m_Values.clear(); this->SetValue(0, 0, value); }; // Create necessary escape sequences from illegal characters // REMARK: This code is based upon code from boost::ptree::json_writer. // The corresponding boost function was not used directly, because it is not part of // the public interface of ptree::json_writer. :( // A own serialization strategy was implemented instead of using boost::ptree::json_write because // currently (<= boost 1.60) everything (even numbers) are converted into string representations // by the writer, so e.g. it becomes "t":"2" instaed of "t":2 template std::basic_string CreateJSONEscapes(const std::basic_string &s) { std::basic_string result; typename std::basic_string::const_iterator b = s.begin(); typename std::basic_string::const_iterator e = s.end(); while (b != e) { - typedef typename boost::make_unsigned::type UCh; + using UCh = std::make_unsigned_t; UCh c(*b); // This assumes an ASCII superset. // We escape everything outside ASCII, because this code can't // handle high unicode characters. if (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x2E) || (c >= 0x30 && c <= 0x5B) || (c >= 0x5D && c <= 0x7F)) result += *b; else if (*b == Ch('\b')) result += Ch('\\'), result += Ch('b'); else if (*b == Ch('\f')) result += Ch('\\'), result += Ch('f'); else if (*b == Ch('\n')) result += Ch('\\'), result += Ch('n'); else if (*b == Ch('\r')) result += Ch('\\'), result += Ch('r'); else if (*b == Ch('\t')) result += Ch('\\'), result += Ch('t'); else if (*b == Ch('/')) result += Ch('\\'), result += Ch('/'); else if (*b == Ch('"')) result += Ch('\\'), result += Ch('"'); else if (*b == Ch('\\')) result += Ch('\\'), result += Ch('\\'); else { const char *hexdigits = "0123456789ABCDEF"; unsigned long u = (std::min)(static_cast(static_cast(*b)), 0xFFFFul); int d1 = u / 4096; u -= d1 * 4096; int d2 = u / 256; u -= d2 * 256; int d3 = u / 16; u -= d3 * 16; int d4 = u; result += Ch('\\'); result += Ch('u'); result += Ch(hexdigits[d1]); result += Ch(hexdigits[d2]); result += Ch(hexdigits[d3]); result += Ch(hexdigits[d4]); } ++b; } return result; } using CondensedTimeKeyType = std::pair; using CondensedTimePointsType = std::map; using CondensedSliceKeyType = std::pair; using CondensedSlicesType = std::map; /** Helper function that checks if between an ID and a successing ID is no gap.*/ template bool isGap(const TValue& value, const TValue& successor) { return value successor + 1; } template void CheckAndCondenseElement(const TNewKey& newKeyMinID, const TNewValue& newValue, TMasterKey& masterKey, TMasterValue& masterValue, TCondensedContainer& condensedContainer) { if (newValue != masterValue || isGap(newKeyMinID, masterKey.second)) { condensedContainer[masterKey] = masterValue; masterValue = newValue; masterKey.first = newKeyMinID; } masterKey.second = newKeyMinID; } /** Helper function that tries to condense the values of time points for a slice as much as possible and returns all slices with condensed timepoint values.*/ CondensedSlicesType CondenseTimePointValuesOfProperty(const mitk::TemporoSpatialStringProperty* tsProp) { CondensedSlicesType uncondensedSlices; auto zs = tsProp->GetAvailableSlices(); for (const auto z : zs) { CondensedTimePointsType condensedTimePoints; auto timePointIDs = tsProp->GetAvailableTimeSteps(z); CondensedTimeKeyType condensedKey = { timePointIDs.front(),timePointIDs.front() }; auto refValue = tsProp->GetValue(timePointIDs.front(), z); for (const auto timePointID : timePointIDs) { const auto& newVal = tsProp->GetValue(timePointID, z); CheckAndCondenseElement(timePointID, newVal, condensedKey, refValue, condensedTimePoints); } condensedTimePoints[condensedKey] = refValue; uncondensedSlices[{ z, z }] = condensedTimePoints; } return uncondensedSlices; } ::std::string mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON( const mitk::BaseProperty *prop) { // REMARK: Implemented own serialization instead of using boost::ptree::json_write because // currently (<= boost 1.60) everything (even numbers) are converted into string representations // by the writer, so e.g. it becomes "t":"2" instaed of "t":2 // If this problem is fixed with boost, we shoud switch back to json_writer (and remove the custom // implementation of CreateJSONEscapes (see above)). const auto *tsProp = dynamic_cast(prop); if (!tsProp) { mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty."; } std::ostringstream stream; stream.imbue(std::locale("C")); stream << "{\"values\":["; //we condense the content of the property to have a compact serialization. //we start with condensing time points and then slices (in difference to the //internal layout). Reason: There is more entropy in slices (looking at DICOM) //than across time points for one slice, so we can "compress" to a higher rate. //We don't wanted to change the internal structure of the property as it would //introduce API inconvinience and subtle changes in behavior. CondensedSlicesType uncondensedSlices = CondenseTimePointValuesOfProperty(tsProp); //now condense the slices CondensedSlicesType condensedSlices; if(!uncondensedSlices.empty()) { CondensedTimePointsType& masterSlice = uncondensedSlices.begin()->second; CondensedSliceKeyType masterSliceKey = uncondensedSlices.begin()->first; for (const auto& uncondensedSlice : uncondensedSlices) { const auto& uncondensedSliceID = uncondensedSlice.first.first; CheckAndCondenseElement(uncondensedSliceID, uncondensedSlice.second, masterSliceKey, masterSlice, condensedSlices); } condensedSlices[masterSliceKey] = masterSlice; } bool first = true; for (const auto& z : condensedSlices) { for (const auto& t : z.second) { if (first) { first = false; } else { stream << ", "; } const auto& minSliceID = z.first.first; const auto& maxSliceID = z.first.second; const auto& minTimePointID = t.first.first; const auto& maxTimePointID = t.first.second; stream << "{\"z\":" << minSliceID << ", "; if (minSliceID != maxSliceID) { stream << "\"zmax\":" << maxSliceID << ", "; } stream << "\"t\":" << minTimePointID << ", "; if (minTimePointID != maxTimePointID) { stream << "\"tmax\":" << maxTimePointID << ", "; } const auto& value = t.second; stream << "\"value\":\"" << CreateJSONEscapes(value) << "\"}"; } } stream << "]}"; return stream.str(); } mitk::BaseProperty::Pointer mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty( const std::string &value) { if (value.empty()) return nullptr; mitk::TemporoSpatialStringProperty::Pointer prop = mitk::TemporoSpatialStringProperty::New(); - boost::property_tree::ptree root; - - std::istringstream stream(value); - stream.imbue(std::locale("C")); - - boost::property_tree::read_json(stream, root); + auto root = json::parse(value); - for (boost::property_tree::ptree::value_type &element : root.get_child("values")) + for (const auto& element : root["values"]) { - std::string value = element.second.get("value", ""); - mitk::TemporoSpatialStringProperty::IndexValueType z = - element.second.get("z", 0); - mitk::TemporoSpatialStringProperty::IndexValueType zmax = - element.second.get("zmax", z); - TimeStepType t = element.second.get("t", 0); - TimeStepType tmax = element.second.get("tmax", t); + auto value = element.value("value", ""); + auto z = element.value("z", 0); + auto zmax = element.value("zmax", z); + auto t = element.value("t", 0); + auto tmax = element.value("tmax", t); for (auto currentT = t; currentT <= tmax; ++currentT) { for (auto currentZ = z; currentZ <= zmax; ++currentZ) { prop->SetValue(currentT, currentZ, value); } } } return prop.GetPointer(); } diff --git a/Modules/CppMicroServices/cmake/usMacroCreateModule.cmake b/Modules/CppMicroServices/cmake/usMacroCreateModule.cmake index 31e6d48176..d27622bde8 100644 --- a/Modules/CppMicroServices/cmake/usMacroCreateModule.cmake +++ b/Modules/CppMicroServices/cmake/usMacroCreateModule.cmake @@ -1,243 +1,247 @@ # For internal use only macro(usMacroCreateModule _project_name) project(${_project_name}) cmake_parse_arguments(${PROJECT_NAME} "SKIP_EXAMPLES;SKIP_INIT" "VERSION;TARGET" - "DEPENDS;INTERNAL_INCLUDE_DIRS;LINK_LIBRARIES;SOURCES;PRIVATE_HEADERS;PUBLIC_HEADERS;RESOURCES;BINARY_RESOURCES" + "DEPENDS;INTERNAL_INCLUDE_DIRS;LINK_LIBRARIES;PRIVATE_LINK_LIBRARIES;SOURCES;PRIVATE_HEADERS;PUBLIC_HEADERS;RESOURCES;BINARY_RESOURCES" ${ARGN} ) if(NOT ${PROJECT_NAME}_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$") message(SEND_ERROR "VERSION argument invalid: ${${PROJECT_NAME}_VERSION}") endif() string(REPLACE "." ";" _version_numbers ${${PROJECT_NAME}_VERSION}) list(GET _version_numbers 0 ${PROJECT_NAME}_MAJOR_VERSION) list(GET _version_numbers 1 ${PROJECT_NAME}_MINOR_VERSION) list(GET _version_numbers 2 ${PROJECT_NAME}_PATCH_VERSION) if(NOT ${PROJECT_NAME}_TARGET) set(${PROJECT_NAME}_TARGET us${PROJECT_NAME}) endif() set(PROJECT_TARGET ${${PROJECT_NAME}_TARGET}) if(${PROJECT_NAME}_DEPENDS) find_package(CppMicroServices REQUIRED ${${PROJECT_NAME}_DEPENDS} QUIET HINTS ${CppMicroServices_BINARY_DIR} NO_DEFAULT_PATH ) endif() #----------------------------------------------------------------------------- # Include dirs and libraries #----------------------------------------------------------------------------- set(${PROJECT_NAME}_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include ) configure_file(${CppMicroServices_SOURCE_DIR}/cmake/usExport.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/us${PROJECT_NAME}Export.h) list(APPEND ${PROJECT_NAME}_PUBLIC_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/include/us${PROJECT_NAME}Export.h) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/include/us${PROJECT_NAME}Config.h.in) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/us${PROJECT_NAME}Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/us${PROJECT_NAME}Config.h) list(APPEND ${PROJECT_NAME}_PUBLIC_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/include/us${PROJECT_NAME}Config.h) endif() set(_internal_include_dirs ${${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS}) set(${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS ) if(_internal_include_dirs) foreach(_internal_include_dir ${_internal_include_dirs}) if(IS_ABSOLUTE "${_internal_include_dir}") list(APPEND ${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS ${_internal_include_dir}) else() list(APPEND ${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/${_internal_include_dir}) endif() endforeach() endif() if(CMAKE_VERSION VERSION_LESS 2.8.12) include_directories( ${US_INCLUDE_DIRS} ${${PROJECT_NAME}_INCLUDE_DIRS} ${${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS} ) endif() #----------------------------------------------------------------------------- # Create library #----------------------------------------------------------------------------- # Generate the module init file if(NOT ${PROJECT_NAME}_SKIP_INIT) usFunctionGenerateModuleInit(${PROJECT_NAME}_SOURCES) endif() if(${PROJECT_NAME}_RESOURCES OR ${PROJECT_NAME}_BINARY_RESOURCES) usFunctionGetResourceSource(TARGET ${${PROJECT_NAME}_TARGET} OUT ${PROJECT_NAME}_SOURCES) endif() # Create the module library add_library(${${PROJECT_NAME}_TARGET} ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_PRIVATE_HEADERS} ${${PROJECT_NAME}_PUBLIC_HEADERS}) # Compile definitions set_property(TARGET ${${PROJECT_NAME}_TARGET} APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=${${PROJECT_NAME}_TARGET}) set_property(TARGET ${${PROJECT_NAME}_TARGET} PROPERTY US_MODULE_NAME ${${PROJECT_NAME}_TARGET}) set_property(TARGET ${${PROJECT_NAME}_TARGET} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CppMicroServices") if(NOT US_BUILD_SHARED_LIBS) set_property(TARGET ${${PROJECT_NAME}_TARGET} APPEND PROPERTY COMPILE_DEFINITIONS US_STATIC_MODULE) endif() # Link flags if(${PROJECT_NAME}_LINK_FLAGS OR US_LINK_FLAGS) set_target_properties(${${PROJECT_NAME}_TARGET} PROPERTIES LINK_FLAGS "${US_LINK_FLAGS} ${${PROJECT_NAME}_LINK_FLAGS}" ) endif() if(CMAKE_VERSION VERSION_GREATER 2.8.11.99) # Currently, public headers include private header files, # so the internal include dirs need to be in the public # include dir section. This needs to be fixed. target_include_directories(${${PROJECT_NAME}_TARGET} PUBLIC ${US_INCLUDE_DIRS} ${${PROJECT_NAME}_INCLUDE_DIRS} PUBLIC ${${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS} ) endif() set_target_properties(${${PROJECT_NAME}_TARGET} PROPERTIES SOVERSION ${${PROJECT_NAME}_VERSION} PUBLIC_HEADER "${${PROJECT_NAME}_PUBLIC_HEADERS}" PRIVATE_HEADER "${${PROJECT_NAME}_PRIVATE_HEADERS}" ) # Link additional libraries if(${PROJECT_NAME}_LINK_LIBRARIES OR US_LIBRARIES) - target_link_libraries(${${PROJECT_NAME}_TARGET} ${US_LIBRARIES} ${${PROJECT_NAME}_LINK_LIBRARIES}) + target_link_libraries(${${PROJECT_NAME}_TARGET} PUBLIC ${US_LIBRARIES} ${${PROJECT_NAME}_LINK_LIBRARIES}) +endif() + +if(${PROJECT_NAME}_PRIVATE_LINK_LIBRARIES) + target_link_libraries(${${PROJECT_NAME}_TARGET} PRIVATE ${${PROJECT_NAME}_PRIVATE_LINK_LIBRARIES}) endif() # Embed module resources if(${PROJECT_NAME}_RESOURCES OR US_LIBRARIES) usFunctionAddResources(TARGET ${${PROJECT_NAME}_TARGET} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/resources FILES ${${PROJECT_NAME}_RESOURCES} ZIP_ARCHIVES ${US_LIBRARIES} ) endif() if(${PROJECT_NAME}_BINARY_RESOURCES) usFunctionAddResources(TARGET ${${PROJECT_NAME}_TARGET} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/resources FILES ${${PROJECT_NAME}_BINARY_RESOURCES} ) endif() usFunctionEmbedResources(TARGET ${${PROJECT_NAME}_TARGET}) #----------------------------------------------------------------------------- # Install support #----------------------------------------------------------------------------- if(NOT US_NO_INSTALL) install(TARGETS ${${PROJECT_NAME}_TARGET} EXPORT us${PROJECT_NAME}Targets RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT} LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT} ARCHIVE DESTINATION ${ARCHIVE_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT} PUBLIC_HEADER DESTINATION ${HEADER_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT} PRIVATE_HEADER DESTINATION ${HEADER_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT}) endif() #----------------------------------------------------------------------------- # US testing #----------------------------------------------------------------------------- if(US_BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") add_subdirectory(test) endif() #----------------------------------------------------------------------------- # Documentation #----------------------------------------------------------------------------- if(US_BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doc/snippets/CMakeLists.txt") # Compile source code snippets add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/doc/snippets) endif() #----------------------------------------------------------------------------- # Last configuration and install steps #----------------------------------------------------------------------------- # Version information configure_file( ${US_CMAKE_DIR}/usModuleConfigVersion.cmake.in ${CppMicroServices_BINARY_DIR}/us${PROJECT_NAME}ConfigVersion.cmake @ONLY ) export(TARGETS ${${PROJECT_NAME}_TARGET} ${US_LIBRARIES} FILE ${CppMicroServices_BINARY_DIR}/us${PROJECT_NAME}Targets.cmake) if(NOT US_NO_INSTALL) install(EXPORT us${PROJECT_NAME}Targets FILE us${PROJECT_NAME}Targets.cmake DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR}) endif() # Configure config file for the build tree set(PACKAGE_CONFIG_INCLUDE_DIR ${${PROJECT_NAME}_INCLUDE_DIRS} ${${PROJECT_NAME}_INTERNAL_INCLUDE_DIRS}) set(PACKAGE_CONFIG_RUNTIME_LIBRARY_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) configure_file( ${US_CMAKE_DIR}/usModuleConfig.cmake.in ${CppMicroServices_BINARY_DIR}/us${PROJECT_NAME}Config.cmake @ONLY ) # Configure config file for the install tree if(NOT US_NO_INSTALL) set(CONFIG_INCLUDE_DIR ${HEADER_INSTALL_DIR}) set(CONFIG_RUNTIME_LIBRARY_DIR ${RUNTIME_INSTALL_DIR}) configure_package_config_file( ${US_CMAKE_DIR}/usModuleConfig.cmake.in ${CppMicroServices_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/us${PROJECT_NAME}Config.cmake INSTALL_DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR} PATH_VARS CONFIG_INCLUDE_DIR CONFIG_RUNTIME_LIBRARY_DIR NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) install(FILES ${CppMicroServices_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/us${PROJECT_NAME}Config.cmake ${CppMicroServices_BINARY_DIR}/us${PROJECT_NAME}ConfigVersion.cmake DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT}) endif() #----------------------------------------------------------------------------- # Build the examples #----------------------------------------------------------------------------- if(US_BUILD_EXAMPLES AND NOT ${PROJECT_NAME}_SKIP_EXAMPLES AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt) set(CppMicroServices_DIR ${CppMicroServices_BINARY_DIR}) add_subdirectory(examples) endif() endmacro() diff --git a/Modules/CppMicroServices/core/CMakeLists.txt b/Modules/CppMicroServices/core/CMakeLists.txt index 244fa9a635..e70785c45a 100644 --- a/Modules/CppMicroServices/core/CMakeLists.txt +++ b/Modules/CppMicroServices/core/CMakeLists.txt @@ -1,45 +1,46 @@ # sources and headers include(${CMAKE_CURRENT_SOURCE_DIR}/src/CMakeLists.txt) include(${CMAKE_CURRENT_SOURCE_DIR}/include/CMakeLists.txt) set(_core_srcs ) foreach(_src ${_srcs}) list(APPEND _core_srcs ${CMAKE_CURRENT_SOURCE_DIR}/src/${_src}) endforeach() set(_core_private_headers) foreach(_header ${_private_headers}) list(APPEND _core_private_headers ${CMAKE_CURRENT_SOURCE_DIR}/src/${_header}) endforeach() set(_core_public_headers ) foreach(_header ${_public_headers}) list(APPEND _core_public_headers ${CMAKE_CURRENT_SOURCE_DIR}/include/${_header}) endforeach() # link libraries for the CppMicroServices lib set(_link_libraries ) if(UNIX) list(APPEND _link_libraries dl) endif() # Configure the modules manifest.json file configure_file(${CMAKE_CURRENT_SOURCE_DIR}/resources/manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/resources/manifest.json) usMacroCreateModule(Core SKIP_INIT # we initialize the module in usModuleRegistry.cpp ourselves VERSION "2.99.0" TARGET CppMicroServices INTERNAL_INCLUDE_DIRS src/util src/service src/module LINK_LIBRARIES ${_link_libraries} + PRIVATE_LINK_LIBRARIES nlohmann_json::nlohmann_json SOURCES ${_core_srcs} PUBLIC_HEADERS ${_core_public_headers} PRIVATE_HEADERS ${_core_private_headers} BINARY_RESOURCES manifest.json ) set_property( TARGET CppMicroServices APPEND PROPERTY COMPILE_DEFINITIONS "MINIZ_NO_ARCHIVE_WRITING_API;MINIZ_NO_ZLIB_COMPATIBLE_NAMES" ) diff --git a/Modules/CppMicroServices/core/src/CMakeLists.txt b/Modules/CppMicroServices/core/src/CMakeLists.txt index aeb9cf14bf..7d767f7b1b 100644 --- a/Modules/CppMicroServices/core/src/CMakeLists.txt +++ b/Modules/CppMicroServices/core/src/CMakeLists.txt @@ -1,84 +1,83 @@ #----------------------------------------------------------------------------- # Source files #----------------------------------------------------------------------------- set(_srcs util/usAny.cpp util/usLDAPProp.cpp util/usSharedLibrary.cpp util/usUtils.cpp service/usLDAPExpr.cpp service/usLDAPFilter.cpp service/usServiceException.cpp service/usServiceEvent.cpp service/usServiceEventListenerHook.cpp service/usServiceFindHook.cpp service/usServiceHooks.cpp service/usServiceListenerEntry.cpp service/usServiceListenerEntry_p.h service/usServiceListenerHook.cpp service/usServiceListeners.cpp service/usServiceListeners_p.h service/usServiceObjects.cpp service/usServiceProperties.cpp service/usServicePropertiesImpl.cpp service/usServiceReferenceBase.cpp service/usServiceReferenceBasePrivate.cpp service/usServiceRegistrationBase.cpp service/usServiceRegistrationBasePrivate.cpp service/usServiceRegistry.cpp service/usServiceRegistry_p.h module/usCoreModuleActivator.cpp module/usCoreModuleContext_p.h module/usCoreModuleContext.cpp module/usModuleContext.cpp module/usModule.cpp module/usModuleEvent.cpp module/usModuleEventHook.cpp module/usModuleFindHook.cpp module/usModuleHooks.cpp module/usModuleInfo.cpp module/usModuleManifest.cpp module/usModulePrivate.cpp module/usModuleRegistry.cpp module/usModuleResource.cpp module/usModuleResourceBuffer.cpp module/usModuleResourceContainer.cpp module/usModuleResourceStream.cpp module/usModuleSettings.cpp module/usModuleUtils.cpp module/usModuleVersion.cpp - ../../third_party/jsoncpp.cpp ../../third_party/miniz.c ) set(_private_headers util/usAtomicInt_p.h util/usListenerFunctors_p.h util/usLog_p.h util/usStaticInit_p.h util/usThreads_p.h util/usUtils_p.h util/usWaitCondition_p.h service/usServiceHooks_p.h service/usServiceListenerHook_p.h service/usServicePropertiesImpl_p.h service/usServiceTracker.tpp service/usServiceTrackerPrivate.h service/usServiceTrackerPrivate.tpp service/usTrackedService_p.h service/usTrackedServiceListener_p.h service/usTrackedService.tpp module/usModuleAbstractTracked_p.h module/usModuleAbstractTracked.tpp module/usModuleHooks_p.h module/usModuleResourceBuffer_p.h module/usModuleResourceContainer_p.h module/usModuleUtils_p.h ) diff --git a/Modules/CppMicroServices/core/src/module/usModuleManifest.cpp b/Modules/CppMicroServices/core/src/module/usModuleManifest.cpp index 904d538cb7..c897cfc168 100644 --- a/Modules/CppMicroServices/core/src/module/usModuleManifest.cpp +++ b/Modules/CppMicroServices/core/src/module/usModuleManifest.cpp @@ -1,154 +1,156 @@ /*============================================================================ Library: CppMicroServices Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ============================================================================*/ #include "usModuleManifest_p.h" -#include "jsoncpp.h" +#include #include +using namespace nlohmann; + US_BEGIN_NAMESPACE namespace { typedef std::map AnyMap; typedef std::vector AnyVector; - void ParseJsonObject(const Json::Value& jsonObject, AnyMap& anyMap); - void ParseJsonArray(const Json::Value& jsonArray, AnyVector& anyVector); + void ParseJsonObject(const json& jsonObject, AnyMap& anyMap); + void ParseJsonArray(const json& jsonArray, AnyVector& anyVector); - Any ParseJsonValue(const Json::Value& jsonValue) + Any ParseJsonValue(const json& jsonValue) { - if (jsonValue.isObject()) + if (jsonValue.is_object()) { Any any = AnyMap(); ParseJsonObject(jsonValue, ref_any_cast(any)); return any; } - else if (jsonValue.isArray()) + else if (jsonValue.is_array()) { Any any = AnyVector(); ParseJsonArray(jsonValue, ref_any_cast(any)); return any; } - else if (jsonValue.isString()) + else if (jsonValue.is_string()) { - return Any(jsonValue.asString()); + return Any(jsonValue.get()); } - else if (jsonValue.isBool()) + else if (jsonValue.is_boolean()) { - return Any(jsonValue.asBool()); + return Any(jsonValue.get()); } - else if (jsonValue.isIntegral()) + else if (jsonValue.is_number_integer()) { - return Any(jsonValue.asInt()); + return Any(jsonValue.get()); } - else if (jsonValue.isDouble()) + else if (jsonValue.is_number_float()) { - return Any(jsonValue.asDouble()); + return Any(jsonValue.get()); } return Any(); } - void ParseJsonObject(const Json::Value& jsonObject, AnyMap& anyMap) + void ParseJsonObject(const json& jsonObject, AnyMap& anyMap) { - for (Json::Value::const_iterator it = jsonObject.begin(); - it != jsonObject.end(); ++it) + for (const auto& [key, jsonValue] : jsonObject.items()) { - const Json::Value& jsonValue = *it; Any anyValue = ParseJsonValue(jsonValue); if (!anyValue.Empty()) { - anyMap.insert(std::make_pair(it.name(), anyValue)); + anyMap.insert(std::make_pair(key, anyValue)); } } } - void ParseJsonArray(const Json::Value& jsonArray, AnyVector& anyVector) + void ParseJsonArray(const json& jsonArray, AnyVector& anyVector) { - for (Json::Value::const_iterator it = jsonArray.begin(); - it != jsonArray.end(); ++it) + for (const auto& jsonValue : jsonArray) { - const Json::Value& jsonValue = *it; Any anyValue = ParseJsonValue(jsonValue); if (!anyValue.Empty()) { anyVector.push_back(anyValue); } } } } ModuleManifest::ModuleManifest() { } void ModuleManifest::Parse(std::istream& is) { - Json::Value root; - Json::Reader jsonReader(Json::Features::strictMode()); - if (!jsonReader.parse(is, root, false)) + json root; + + try + { + root = json::parse(is); + } + catch (const json::exception& e) { - throw std::runtime_error(jsonReader.getFormattedErrorMessages()); + throw std::runtime_error(e.what()); } - if (!root.isObject()) + if (!root.is_object()) { throw std::runtime_error("The Json root element must be an object."); } ParseJsonObject(root, m_Properties); } bool ModuleManifest::Contains(const std::string& key) const { return m_Properties.count(key) > 0; } Any ModuleManifest::GetValue(const std::string& key) const { AnyMap::const_iterator iter = m_Properties.find(key); if (iter != m_Properties.end()) { return iter->second; } return Any(); } std::vector ModuleManifest::GetKeys() const { std::vector keys; for (AnyMap::const_iterator iter = m_Properties.begin(); iter != m_Properties.end(); ++iter) { keys.push_back(iter->first); } return keys; } void ModuleManifest::SetValue(const std::string& key, const Any& value) { m_Properties[key] = value; } US_END_NAMESPACE diff --git a/Modules/CppMicroServices/third_party/jsoncpp.cpp b/Modules/CppMicroServices/third_party/jsoncpp.cpp deleted file mode 100644 index 52ccdf35fa..0000000000 --- a/Modules/CppMicroServices/third_party/jsoncpp.cpp +++ /dev/null @@ -1,5337 +0,0 @@ -/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/). -/// It is intended to be used with #include "json/json.h" - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: LICENSE -// ////////////////////////////////////////////////////////////////////// - -/* -The JsonCpp library's source code, including accompanying documentation, -tests and demonstration applications, are licensed under the following -conditions... - -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, -this software is released into the Public Domain. - -In jurisdictions which do not recognize Public Domain property (e.g. Germany as of -2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and -The JsonCpp Authors, and is released under the terms of the MIT License (see below). - -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual -Public Domain/MIT License conditions described here, as they choose. - -The MIT License is about as close to Public Domain as a license can get, and is -described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - -======================================================================== -Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, copy, -modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -======================================================================== -(END LICENSE TEXT) - -The MIT license is compatible with both the GPL and commercial -software, affording one all of the rights of Public Domain with the -minor nuisance of being required to keep the above copyright notice -and license text in the source code. Note also that by accepting the -Public Domain "license" you can re-license your copy using whatever -license you like. - -*/ - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: LICENSE -// ////////////////////////////////////////////////////////////////////// - - - - - - -#include "jsoncpp.h" - -#ifndef JSON_IS_AMALGAMATION -#error "Compile with -I PATH_TO_JSON_DIRECTORY" -#endif - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: src/lib_json/json_tool.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED -#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include -#endif - -// Also support old flag NO_LOCALE_SUPPORT -#ifdef NO_LOCALE_SUPPORT -#define JSONCPP_NO_LOCALE_SUPPORT -#endif - -#ifndef JSONCPP_NO_LOCALE_SUPPORT -#include -#endif - -/* This header provides common string manipulation support, such as UTF-8, - * portable conversion from/to string... - * - * It is an internal header that must not be exposed. - */ - -namespace Json { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunneeded-internal-declaration" -#endif -static inline char getDecimalPoint() { -#ifdef JSONCPP_NO_LOCALE_SUPPORT - return '\0'; -#else - struct lconv* lc = localeconv(); - return lc ? *(lc->decimal_point) : '\0'; -#endif -} -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -/// Converts a unicode code-point to UTF-8. -static inline String codePointToUTF8(unsigned int cp) { - String result; - - // based on description from http://en.wikipedia.org/wiki/UTF-8 - - if (cp <= 0x7f) { - result.resize(1); - result[0] = static_cast(cp); - } else if (cp <= 0x7FF) { - result.resize(2); - result[1] = static_cast(0x80 | (0x3f & cp)); - result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } else if (cp <= 0xFFFF) { - result.resize(3); - result[2] = static_cast(0x80 | (0x3f & cp)); - result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); - } else if (cp <= 0x10FFFF) { - result.resize(4); - result[3] = static_cast(0x80 | (0x3f & cp)); - result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } - - return result; -} - -enum { - /// Constant that specify the size of the buffer that must be passed to - /// uintToString. - uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 -}; - -// Defines a char buffer for use with uintToString(). -using UIntToStringBuffer = char[uintToStringBufferSize]; - -/** Converts an unsigned integer to string. - * @param value Unsigned integer to convert to string - * @param current Input/Output string buffer. - * Must have at least uintToStringBufferSize chars free. - */ -static inline void uintToString(LargestUInt value, char*& current) { - *--current = 0; - do { - *--current = static_cast(value % 10U + static_cast('0')); - value /= 10; - } while (value != 0); -} - -/** Change ',' to '.' everywhere in buffer. - * - * We had a sophisticated way, but it did not work in WinCE. - * @see https://github.com/open-source-parsers/jsoncpp/pull/9 - */ -template Iter fixNumericLocale(Iter begin, Iter end) { - for (; begin != end; ++begin) { - if (*begin == ',') { - *begin = '.'; - } - } - return begin; -} - -template void fixNumericLocaleInput(Iter begin, Iter end) { - char decimalPoint = getDecimalPoint(); - if (decimalPoint == '\0' || decimalPoint == '.') { - return; - } - for (; begin != end; ++begin) { - if (*begin == '.') { - *begin = decimalPoint; - } - } -} - -/** - * Return iterator that would be the new end of the range [begin,end), if we - * were to delete zeros in the end of string, but not the last zero before '.'. - */ -template -Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { - for (; begin != end; --end) { - if (*(end - 1) != '0') { - return end; - } - // Don't delete the last zero before the decimal point. - if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') { - if (precision) { - return end; - } - return end - 2; - } - } - return end; -} - -} // namespace Json - -#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: src/lib_json/json_tool.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: src/lib_json/json_reader.cpp -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors -// Copyright (C) 2016 InfoTeCS JSC. All rights reserved. -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include "json_tool.h" -#include -#include -#include -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#if __cplusplus >= 201103L - -#if !defined(sscanf) -#define sscanf std::sscanf -#endif - -#endif //__cplusplus - -#if defined(_MSC_VER) -#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) -#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES -#endif //_MSC_VER - -#if defined(_MSC_VER) -// Disable warning about strdup being deprecated. -#pragma warning(disable : 4996) -#endif - -// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile -// time to change the stack limit -#if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) -#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 -#endif - -static size_t const stackLimit_g = - JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() - -namespace Json { - -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -using CharReaderPtr = std::unique_ptr; -#else -using CharReaderPtr = std::auto_ptr; -#endif - -// Implementation of class Features -// //////////////////////////////// - -Features::Features() = default; - -Features Features::all() { return {}; } - -Features Features::strictMode() { - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - features.allowDroppedNullPlaceholders_ = false; - features.allowNumericKeys_ = false; - return features; -} - -// Implementation of class Reader -// //////////////////////////////// - -bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { - return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); -} - -// Class Reader -// ////////////////////////////////////////////////////////////////// - -Reader::Reader() : features_(Features::all()) {} - -Reader::Reader(const Features& features) : features_(features) {} - -bool Reader::parse(const std::string& document, Value& root, - bool collectComments) { - document_.assign(document.begin(), document.end()); - const char* begin = document_.c_str(); - const char* end = begin + document_.length(); - return parse(begin, end, root, collectComments); -} - -bool Reader::parse(std::istream& is, Value& root, bool collectComments) { - // std::istream_iterator begin(is); - // std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since String is reference-counted, this at least does not - // create an extra copy. - String doc(std::istreambuf_iterator(is), {}); - return parse(doc.data(), doc.data() + doc.size(), root, collectComments); -} - -bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = nullptr; - lastValue_ = nullptr; - commentsBefore_.clear(); - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - bool successful = readValue(); - Token token; - skipCommentTokens(token); - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; - } - } - return successful; -} - -bool Reader::readValue() { - // readValue() may call itself only if it calls readObject() or ReadArray(). - // These methods execute nodes_.push() just before and nodes_.pop)() just - // after calling readValue(). parse() executes one nodes_.push(), so > instead - // of >=. - if (nodes_.size() > stackLimit_g) - throwRuntimeError("Exceeded stackLimit in readValue()."); - - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); - commentsBefore_.clear(); - } - - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenFalse: { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNull: { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // Else, fall through... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - return successful; -} - -void Reader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } -} - -bool Reader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return ok; -} - -void Reader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; - else - break; - } -} - -bool Reader::match(const Char* pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool Reader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') - successful = readCppStyleComment(); - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; - } - - addComment(commentBegin, current_, placement); - } - return true; -} - -String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { - String normalized; - normalized.reserve(static_cast(end - begin)); - Reader::Location current = begin; - while (current != end) { - char c = *current++; - if (c == '\r') { - if (current != end && *current == '\n') - // convert dos EOL - ++current; - // convert Mac EOL - normalized += '\n'; - } else { - normalized += c; - } - } - return normalized; -} - -void Reader::addComment(Location begin, Location end, - CommentPlacement placement) { - assert(collectComments_); - const String& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != nullptr); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool Reader::readCStyleComment() { - while ((current_ + 1) < end_) { - Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - } - return getNextChar() == '/'; -} - -bool Reader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; - } - } - return true; -} - -void Reader::readNumber() { - Location p = current_; - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : '\0'; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } -} - -bool Reader::readString() { - Char c = '\0'; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - -bool Reader::readObject(Token& token) { - Token tokenName; - String name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object - return true; - name.clear(); - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; - } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover("Missing ':' after object member name", colon, - tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover("Missing ',' or '}' in object declaration", - comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover("Missing '}' or object member name", tokenName, - tokenObjectEnd); -} - -bool Reader::readArray(Token& token) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } - int index = 0; - for (;;) { - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); - - Token currentToken; - // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } - bool badTokenType = (currentToken.type_ != tokenArraySeparator && - currentToken.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover("Missing ',' or ']' in array declaration", - currentToken, tokenArrayEnd); - } - if (currentToken.type_ == tokenArrayEnd) - break; - } - return true; -} - -bool Reader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) - ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of - // them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - auto digit(static_cast(c - '0')); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, b) this is the last digit, and - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > maxIntegerValue % 10) { - return decodeDouble(token, decoded); - } - } - value = value * 10 + digit; - } - if (isNegative && value == maxIntegerValue) - decoded = Value::minLargestInt; - else if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) - decoded = Value::LargestInt(value); - else - decoded = value; - return true; -} - -bool Reader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); - decoded = value; - return true; -} - -bool Reader::decodeString(Token& token) { - String decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeString(Token& token, String& decoded) { - decoded.reserve(static_cast(token.end_ - token.start_ - 2)); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, - Location end, unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, current); - if (*(current++) == '\\' && *(current++) == 'u') { - unsigned int surrogatePair; - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else - return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, current); - } - return true; -} - -bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, - unsigned int& ret_unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", token, - current); - int unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; - else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, current); - } - ret_unicode = static_cast(unicode); - return true; -} - -bool Reader::addError(const String& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; -} - -bool Reader::recoverFromError(TokenType skipUntilToken) { - size_t const errorCount = errors_.size(); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; -} - -bool Reader::addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); -} - -Value& Reader::currentValue() { return *(nodes_.top()); } - -Reader::Char Reader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; -} - -void Reader::getLocationLineAndColumn(Location location, int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - -String Reader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; - jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); - return buffer; -} - -// Deprecated. Preserved for backward compatibility -String Reader::getFormatedErrorMessages() const { - return getFormattedErrorMessages(); -} - -String Reader::getFormattedErrorMessages() const { - String formattedMessage; - for (const auto& error : errors_) { - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; -} - -std::vector Reader::getStructuredErrors() const { - std::vector allErrors; - for (const auto& error : errors_) { - Reader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; -} - -bool Reader::pushError(const Value& value, const String& message) { - ptrdiff_t const length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = nullptr; - errors_.push_back(info); - return true; -} - -bool Reader::pushError(const Value& value, const String& message, - const Value& extra) { - ptrdiff_t const length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length || - extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; -} - -bool Reader::good() const { return errors_.empty(); } - -// Originally copied from the Features class (now deprecated), used internally -// for features implementation. -class OurFeatures { -public: - static OurFeatures all(); - bool allowComments_; - bool allowTrailingCommas_; - bool strictRoot_; - bool allowDroppedNullPlaceholders_; - bool allowNumericKeys_; - bool allowSingleQuotes_; - bool failIfExtra_; - bool rejectDupKeys_; - bool allowSpecialFloats_; - bool skipBom_; - size_t stackLimit_; -}; // OurFeatures - -OurFeatures OurFeatures::all() { return {}; } - -// Implementation of class Reader -// //////////////////////////////// - -// Originally copied from the Reader class (now deprecated), used internally -// for implementing JSON reading. -class OurReader { -public: - using Char = char; - using Location = const Char*; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; - - explicit OurReader(OurFeatures const& features); - bool parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments = true); - String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; - -private: - OurReader(OurReader const&); // no impl - void operator=(OurReader const&); // no impl - - enum TokenType { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenNaN, - tokenPosInf, - tokenNegInf, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo { - public: - Token token_; - String message_; - Location extra_; - }; - - using Errors = std::deque; - - bool readToken(Token& token); - void skipSpaces(); - void skipBom(bool skipBom); - bool match(const Char* pattern, int patternLength); - bool readComment(); - bool readCStyleComment(bool* containsNewLineResult); - bool readCppStyleComment(); - bool readString(); - bool readStringSingleQuote(); - bool readNumber(bool checkInf); - bool readValue(); - bool readObject(Token& token); - bool readArray(Token& token); - bool decodeNumber(Token& token); - bool decodeNumber(Token& token, Value& decoded); - bool decodeString(Token& token); - bool decodeString(Token& token, String& decoded); - bool decodeDouble(Token& token); - bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, - unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, unsigned int& unicode); - bool addError(const String& message, Token& token, Location extra = nullptr); - bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken); - void skipUntilSpace(); - Value& currentValue(); - Char getNextChar(); - void getLocationLineAndColumn(Location location, int& line, - int& column) const; - String getLocationLineAndColumn(Location location) const; - void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); - - static String normalizeEOL(Location begin, Location end); - static bool containsNewLine(Location begin, Location end); - - using Nodes = std::stack; - - Nodes nodes_{}; - Errors errors_{}; - String document_{}; - Location begin_ = nullptr; - Location end_ = nullptr; - Location current_ = nullptr; - Location lastValueEnd_ = nullptr; - Value* lastValue_ = nullptr; - bool lastValueHasAComment_ = false; - String commentsBefore_{}; - - OurFeatures const features_; - bool collectComments_ = false; -}; // OurReader - -// complete copy of Read impl, for OurReader - -bool OurReader::containsNewLine(OurReader::Location begin, - OurReader::Location end) { - return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); -} - -OurReader::OurReader(OurFeatures const& features) : features_(features) {} - -bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = nullptr; - lastValue_ = nullptr; - commentsBefore_.clear(); - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - // skip byte order mark if it exists at the beginning of the UTF-8 text. - skipBom(features_.skipBom_); - bool successful = readValue(); - nodes_.pop(); - Token token; - skipCommentTokens(token); - if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { - addError("Extra non-whitespace after JSON value.", token); - return false; - } - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; - } - } - return successful; -} - -bool OurReader::readValue() { - // To preserve the old behaviour we cast size_t to int. - if (nodes_.size() > features_.stackLimit_) - throwRuntimeError("Exceeded stackLimit in readValue()."); - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); - commentsBefore_.clear(); - } - - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenFalse: { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNull: { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNaN: { - Value v(std::numeric_limits::quiet_NaN()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenPosInf: { - Value v(std::numeric_limits::infinity()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNegInf: { - Value v(-std::numeric_limits::infinity()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // else, fall through ... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValueHasAComment_ = false; - lastValue_ = ¤tValue(); - } - - return successful; -} - -void OurReader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } -} - -bool OurReader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '\'': - if (features_.allowSingleQuotes_) { - token.type_ = tokenString; - ok = readStringSingleQuote(); - } else { - // If we don't allow single quotes, this is a failure case. - ok = false; - } - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - token.type_ = tokenNumber; - readNumber(false); - break; - case '-': - if (readNumber(true)) { - token.type_ = tokenNumber; - } else { - token.type_ = tokenNegInf; - ok = features_.allowSpecialFloats_ && match("nfinity", 7); - } - break; - case '+': - if (readNumber(true)) { - token.type_ = tokenNumber; - } else { - token.type_ = tokenPosInf; - ok = features_.allowSpecialFloats_ && match("nfinity", 7); - } - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case 'N': - if (features_.allowSpecialFloats_) { - token.type_ = tokenNaN; - ok = match("aN", 2); - } else { - ok = false; - } - break; - case 'I': - if (features_.allowSpecialFloats_) { - token.type_ = tokenPosInf; - ok = match("nfinity", 7); - } else { - ok = false; - } - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return ok; -} - -void OurReader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; - else - break; - } -} - -void OurReader::skipBom(bool skipBom) { - // The default behavior is to skip BOM. - if (skipBom) { - if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { - begin_ += 3; - current_ = begin_; - } - } -} - -bool OurReader::match(const Char* pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool OurReader::readComment() { - const Location commentBegin = current_ - 1; - const Char c = getNextChar(); - bool successful = false; - bool cStyleWithEmbeddedNewline = false; - - const bool isCStyleComment = (c == '*'); - const bool isCppStyleComment = (c == '/'); - if (isCStyleComment) { - successful = readCStyleComment(&cStyleWithEmbeddedNewline); - } else if (isCppStyleComment) { - successful = readCppStyleComment(); - } - - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - - if (!lastValueHasAComment_) { - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (isCppStyleComment || !cStyleWithEmbeddedNewline) { - placement = commentAfterOnSameLine; - lastValueHasAComment_ = true; - } - } - } - - addComment(commentBegin, current_, placement); - } - return true; -} - -String OurReader::normalizeEOL(OurReader::Location begin, - OurReader::Location end) { - String normalized; - normalized.reserve(static_cast(end - begin)); - OurReader::Location current = begin; - while (current != end) { - char c = *current++; - if (c == '\r') { - if (current != end && *current == '\n') - // convert dos EOL - ++current; - // convert Mac EOL - normalized += '\n'; - } else { - normalized += c; - } - } - return normalized; -} - -void OurReader::addComment(Location begin, Location end, - CommentPlacement placement) { - assert(collectComments_); - const String& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != nullptr); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool OurReader::readCStyleComment(bool* containsNewLineResult) { - *containsNewLineResult = false; - - while ((current_ + 1) < end_) { - Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - if (c == '\n') - *containsNewLineResult = true; - } - - return getNextChar() == '/'; -} - -bool OurReader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; - } - } - return true; -} - -bool OurReader::readNumber(bool checkInf) { - Location p = current_; - if (checkInf && p != end_ && *p == 'I') { - current_ = ++p; - return false; - } - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : '\0'; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - return true; -} -bool OurReader::readString() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - -bool OurReader::readStringSingleQuote() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '\'') - break; - } - return c == '\''; -} - -bool OurReader::readObject(Token& token) { - Token tokenName; - String name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && - (name.empty() || - features_.allowTrailingCommas_)) // empty object or trailing comma - return true; - name.clear(); - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; - } - if (name.length() >= (1U << 30)) - throwRuntimeError("keylength >= 2^30"); - if (features_.rejectDupKeys_ && currentValue().isMember(name)) { - String msg = "Duplicate key: '" + name + "'"; - return addErrorAndRecover(msg, tokenName, tokenObjectEnd); - } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover("Missing ':' after object member name", colon, - tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover("Missing ',' or '}' in object declaration", - comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover("Missing '}' or object member name", tokenName, - tokenObjectEnd); -} - -bool OurReader::readArray(Token& token) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - int index = 0; - for (;;) { - skipSpaces(); - if (current_ != end_ && *current_ == ']' && - (index == 0 || - (features_.allowTrailingCommas_ && - !features_.allowDroppedNullPlaceholders_))) // empty array or trailing - // comma - { - Token endArray; - readToken(endArray); - return true; - } - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); - - Token currentToken; - // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } - bool badTokenType = (currentToken.type_ != tokenArraySeparator && - currentToken.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover("Missing ',' or ']' in array declaration", - currentToken, tokenArrayEnd); - } - if (currentToken.type_ == tokenArrayEnd) - break; - } - return true; -} - -bool OurReader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - const bool isNegative = *current == '-'; - if (isNegative) { - ++current; - } - - // We assume we can represent the largest and smallest integer types as - // unsigned integers with separate sign. This is only true if they can fit - // into an unsigned integer. - static_assert(Value::maxLargestInt <= Value::maxLargestUInt, - "Int must be smaller than UInt"); - - // We need to convert minLargestInt into a positive number. The easiest way - // to do this conversion is to assume our "threshold" value of minLargestInt - // divided by 10 can fit in maxLargestInt when absolute valued. This should - // be a safe assumption. - static_assert(Value::minLargestInt <= -Value::maxLargestInt, - "The absolute value of minLargestInt must be greater than or " - "equal to maxLargestInt"); - static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, - "The absolute value of minLargestInt must be only 1 magnitude " - "larger than maxLargest Int"); - - static constexpr Value::LargestUInt positive_threshold = - Value::maxLargestUInt / 10; - static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; - - // For the negative values, we have to be more careful. Since typically - // -Value::minLargestInt will cause an overflow, we first divide by 10 and - // then take the inverse. This assumes that minLargestInt is only a single - // power of 10 different in magnitude, which we check above. For the last - // digit, we take the modulus before negating for the same reason. - static constexpr auto negative_threshold = - Value::LargestUInt(-(Value::minLargestInt / 10)); - static constexpr auto negative_last_digit = - Value::UInt(-(Value::minLargestInt % 10)); - - const Value::LargestUInt threshold = - isNegative ? negative_threshold : positive_threshold; - const Value::UInt max_last_digit = - isNegative ? negative_last_digit : positive_last_digit; - - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - - const auto digit(static_cast(c - '0')); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, meaing value == threshold, - // b) this is the last digit, or - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > max_last_digit) { - return decodeDouble(token, decoded); - } - } - value = value * 10 + digit; - } - - if (isNegative) { - // We use the same magnitude assumption here, just in case. - const auto last_digit = static_cast(value % 10); - decoded = -Value::LargestInt(value / 10) * 10 - last_digit; - } else if (value <= Value::LargestUInt(Value::maxLargestInt)) { - decoded = Value::LargestInt(value); - } else { - decoded = value; - } - - return true; -} - -bool OurReader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - const String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) { - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); - } - decoded = value; - return true; -} - -bool OurReader::decodeString(Token& token) { - String decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeString(Token& token, String& decoded) { - decoded.reserve(static_cast(token.end_ - token.start_ - 2)); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, - Location end, unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, current); - if (*(current++) == '\\' && *(current++) == 'u') { - unsigned int surrogatePair; - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else - return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, current); - } - return true; -} - -bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, - unsigned int& ret_unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", token, - current); - int unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; - else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, current); - } - ret_unicode = static_cast(unicode); - return true; -} - -bool OurReader::addError(const String& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; -} - -bool OurReader::recoverFromError(TokenType skipUntilToken) { - size_t errorCount = errors_.size(); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; -} - -bool OurReader::addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); -} - -Value& OurReader::currentValue() { return *(nodes_.top()); } - -OurReader::Char OurReader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; -} - -void OurReader::getLocationLineAndColumn(Location location, int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - -String OurReader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; - jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); - return buffer; -} - -String OurReader::getFormattedErrorMessages() const { - String formattedMessage; - for (const auto& error : errors_) { - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; -} - -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; - for (const auto& error : errors_) { - OurReader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; -} - -class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; - -public: - OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); - } - return ok; - } -}; - -CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } -CharReaderBuilder::~CharReaderBuilder() = default; -CharReader* CharReaderBuilder::newCharReader() const { - bool collectComments = settings_["collectComments"].asBool(); - OurFeatures features = OurFeatures::all(); - features.allowComments_ = settings_["allowComments"].asBool(); - features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); - features.strictRoot_ = settings_["strictRoot"].asBool(); - features.allowDroppedNullPlaceholders_ = - settings_["allowDroppedNullPlaceholders"].asBool(); - features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); - features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); - - // Stack limit is always a size_t, so we get this as an unsigned int - // regardless of it we have 64-bit integer support enabled. - features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); - features.failIfExtra_ = settings_["failIfExtra"].asBool(); - features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); - features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); - features.skipBom_ = settings_["skipBom"].asBool(); - return new OurCharReader(collectComments, features); -} - -bool CharReaderBuilder::validate(Json::Value* invalid) const { - static const auto& valid_keys = *new std::set{ - "collectComments", - "allowComments", - "allowTrailingCommas", - "strictRoot", - "allowDroppedNullPlaceholders", - "allowNumericKeys", - "allowSingleQuotes", - "stackLimit", - "failIfExtra", - "rejectDupKeys", - "allowSpecialFloats", - "skipBom", - }; - for (auto si = settings_.begin(); si != settings_.end(); ++si) { - auto key = si.name(); - if (valid_keys.count(key)) - continue; - if (invalid) - (*invalid)[key] = *si; - else - return false; - } - return invalid ? invalid->empty() : true; -} - -Value& CharReaderBuilder::operator[](const String& key) { - return settings_[key]; -} -// static -void CharReaderBuilder::strictMode(Json::Value* settings) { - //! [CharReaderBuilderStrictMode] - (*settings)["allowComments"] = false; - (*settings)["allowTrailingCommas"] = false; - (*settings)["strictRoot"] = true; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; - (*settings)["failIfExtra"] = true; - (*settings)["rejectDupKeys"] = true; - (*settings)["allowSpecialFloats"] = false; - (*settings)["skipBom"] = true; - //! [CharReaderBuilderStrictMode] -} -// static -void CharReaderBuilder::setDefaults(Json::Value* settings) { - //! [CharReaderBuilderDefaults] - (*settings)["collectComments"] = true; - (*settings)["allowComments"] = true; - (*settings)["allowTrailingCommas"] = true; - (*settings)["strictRoot"] = false; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; - (*settings)["failIfExtra"] = false; - (*settings)["rejectDupKeys"] = false; - (*settings)["allowSpecialFloats"] = false; - (*settings)["skipBom"] = true; - //! [CharReaderBuilderDefaults] -} - -////////////////////////////////// -// global functions - -bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, - String* errs) { - OStringStream ssin; - ssin << sin.rdbuf(); - String doc = ssin.str(); - char const* begin = doc.data(); - char const* end = begin + doc.size(); - // Note that we do not actually need a null-terminator. - CharReaderPtr const reader(fact.newCharReader()); - return reader->parse(begin, end, root, errs); -} - -IStream& operator>>(IStream& sin, Value& root) { - CharReaderBuilder b; - String errs; - bool ok = parseFromStream(b, sin, &root, &errs); - if (!ok) { - throwRuntimeError(errs); - } - return sin; -} - -} // namespace Json - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: src/lib_json/json_reader.cpp -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: src/lib_json/json_valueiterator.inl -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -// included by json_value.cpp - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIteratorBase -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIteratorBase::ValueIteratorBase() : current_() {} - -ValueIteratorBase::ValueIteratorBase( - const Value::ObjectValues::iterator& current) - : current_(current), isNull_(false) {} - -Value& ValueIteratorBase::deref() { return current_->second; } -const Value& ValueIteratorBase::deref() const { return current_->second; } - -void ValueIteratorBase::increment() { ++current_; } - -void ValueIteratorBase::decrement() { --current_; } - -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance(const SelfType& other) const { - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if (isNull_ && other.isNull_) { - return 0; - } - - // Usage of std::distance is not portable (does not compile with Sun Studio 12 - // RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for (Value::ObjectValues::iterator it = current_; it != other.current_; - ++it) { - ++myDistance; - } - return myDistance; -} - -bool ValueIteratorBase::isEqual(const SelfType& other) const { - if (isNull_) { - return other.isNull_; - } - return current_ == other.current_; -} - -void ValueIteratorBase::copy(const SelfType& other) { - current_ = other.current_; - isNull_ = other.isNull_; -} - -Value ValueIteratorBase::key() const { - const Value::CZString czstring = (*current_).first; - if (czstring.data()) { - if (czstring.isStaticString()) - return Value(StaticString(czstring.data())); - return Value(czstring.data(), czstring.data() + czstring.length()); - } - return Value(czstring.index()); -} - -UInt ValueIteratorBase::index() const { - const Value::CZString czstring = (*current_).first; - if (!czstring.data()) - return czstring.index(); - return Value::UInt(-1); -} - -String ValueIteratorBase::name() const { - char const* keey; - char const* end; - keey = memberName(&end); - if (!keey) - return String(); - return String(keey, end); -} - -char const* ValueIteratorBase::memberName() const { - const char* cname = (*current_).first.data(); - return cname ? cname : ""; -} - -char const* ValueIteratorBase::memberName(char const** end) const { - const char* cname = (*current_).first.data(); - if (!cname) { - *end = nullptr; - return nullptr; - } - *end = cname + (*current_).first.length(); - return cname; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueConstIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueConstIterator::ValueConstIterator() = default; - -ValueConstIterator::ValueConstIterator( - const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} - -ValueConstIterator::ValueConstIterator(ValueIterator const& other) - : ValueIteratorBase(other) {} - -ValueConstIterator& ValueConstIterator:: -operator=(const ValueIteratorBase& other) { - copy(other); - return *this; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIterator::ValueIterator() = default; - -ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} - -ValueIterator::ValueIterator(const ValueConstIterator& other) - : ValueIteratorBase(other) { - throwRuntimeError("ConstIterator to Iterator should never be allowed."); -} - -ValueIterator::ValueIterator(const ValueIterator& other) = default; - -ValueIterator& ValueIterator::operator=(const SelfType& other) { - copy(other); - return *this; -} - -} // namespace Json - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: src/lib_json/json_valueiterator.inl -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: src/lib_json/json_value.cpp -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#include -#include -#include - -// Provide implementation equivalent of std::snprintf for older _MSC compilers -#if defined(_MSC_VER) && _MSC_VER < 1900 -#include -static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, - const char* format, va_list ap) { - int count = -1; - if (size != 0) - count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); - if (count == -1) - count = _vscprintf(format, ap); - return count; -} - -int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, - const char* format, ...) { - va_list ap; - va_start(ap, format); - const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); - va_end(ap); - return count; -} -#endif - -// Disable warning C4702 : unreachable code -#if defined(_MSC_VER) -#pragma warning(disable : 4702) -#endif - -#define JSON_ASSERT_UNREACHABLE assert(false) - -namespace Json { -template -static std::unique_ptr cloneUnique(const std::unique_ptr& p) { - std::unique_ptr r; - if (p) { - r = std::unique_ptr(new T(*p)); - } - return r; -} - -// This is a walkaround to avoid the static initialization of Value::null. -// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of -// 8 (instead of 4) as a bit of future-proofing. -#if defined(__ARMEL__) -#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) -#else -#define ALIGNAS(byte_alignment) -#endif - -// static -Value const& Value::nullSingleton() { - static Value const nullStatic; - return nullStatic; -} - -#if JSON_USE_NULLREF -// for backwards compatibility, we'll leave these global references around, but -// DO NOT use them in JSONCPP library code any more! -// static -Value const& Value::null = Value::nullSingleton(); - -// static -Value const& Value::nullRef = Value::nullSingleton(); -#endif - -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) -template -static inline bool InRange(double d, T min, U max) { - // The casts can lose precision, but we are looking only for - // an approximate range. Might fail on edge cases though. ~cdunn - return d >= static_cast(min) && d <= static_cast(max); -} -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) -static inline double integerToDouble(Json::UInt64 value) { - return static_cast(Int64(value / 2)) * 2.0 + - static_cast(Int64(value & 1)); -} - -template static inline double integerToDouble(T value) { - return static_cast(value); -} - -template -static inline bool InRange(double d, T min, U max) { - return d >= integerToDouble(min) && d <= integerToDouble(max); -} -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - -/** Duplicates the specified string value. - * @param value Pointer to the string to duplicate. Must be zero-terminated if - * length is "unknown". - * @param length Length of the value. if equals to unknown, then it will be - * computed using strlen(value). - * @return Pointer on the duplicate instance of string. - */ -static inline char* duplicateStringValue(const char* value, size_t length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - if (length >= static_cast(Value::maxInt)) - length = Value::maxInt - 1; - - auto newString = static_cast(malloc(length + 1)); - if (newString == nullptr) { - throwRuntimeError("in Json::Value::duplicateStringValue(): " - "Failed to allocate string value buffer"); - } - memcpy(newString, value, length); - newString[length] = 0; - return newString; -} - -/* Record the length as a prefix. - */ -static inline char* duplicateAndPrefixStringValue(const char* value, - unsigned int length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - - sizeof(unsigned) - 1U, - "in Json::Value::duplicateAndPrefixStringValue(): " - "length too big for prefixing"); - size_t actualLength = sizeof(length) + length + 1; - auto newString = static_cast(malloc(actualLength)); - if (newString == nullptr) { - throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " - "Failed to allocate string value buffer"); - } - *reinterpret_cast(newString) = length; - memcpy(newString + sizeof(unsigned), value, length); - newString[actualLength - 1U] = - 0; // to avoid buffer over-run accidents by users later - return newString; -} -inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, - unsigned* length, char const** value) { - if (!isPrefixed) { - *length = static_cast(strlen(prefixed)); - *value = prefixed; - } else { - *length = *reinterpret_cast(prefixed); - *value = prefixed + sizeof(unsigned); - } -} -/** Free the string duplicated by - * duplicateStringValue()/duplicateAndPrefixStringValue(). - */ -#if JSONCPP_USING_SECURE_MEMORY -static inline void releasePrefixedStringValue(char* value) { - unsigned length = 0; - char const* valueDecoded; - decodePrefixedString(true, value, &length, &valueDecoded); - size_t const size = sizeof(unsigned) + length + 1U; - memset(value, 0, size); - free(value); -} -static inline void releaseStringValue(char* value, unsigned length) { - // length==0 => we allocated the strings memory - size_t size = (length == 0) ? strlen(value) : length; - memset(value, 0, size); - free(value); -} -#else // !JSONCPP_USING_SECURE_MEMORY -static inline void releasePrefixedStringValue(char* value) { free(value); } -static inline void releaseStringValue(char* value, unsigned) { free(value); } -#endif // JSONCPP_USING_SECURE_MEMORY - -} // namespace Json - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ValueInternals... -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -#if !defined(JSON_IS_AMALGAMATION) - -#include "json_valueiterator.inl" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -#if JSON_USE_EXCEPTION -Exception::Exception(String msg) : msg_(std::move(msg)) {} -Exception::~Exception() noexcept = default; -char const* Exception::what() const noexcept { return msg_.c_str(); } -RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} -LogicError::LogicError(String const& msg) : Exception(msg) {} -JSONCPP_NORETURN void throwRuntimeError(String const& msg) { - throw RuntimeError(msg); -} -JSONCPP_NORETURN void throwLogicError(String const& msg) { - throw LogicError(msg); -} -#else // !JSON_USE_EXCEPTION -JSONCPP_NORETURN void throwRuntimeError(String const& msg) { - std::cerr << msg << std::endl; - abort(); -} -JSONCPP_NORETURN void throwLogicError(String const& msg) { - std::cerr << msg << std::endl; - abort(); -} -#endif - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CZString -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -// Notes: policy_ indicates if the string was allocated when -// a string is stored. - -Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} - -Value::CZString::CZString(char const* str, unsigned length, - DuplicationPolicy allocate) - : cstr_(str) { - // allocate != duplicate - storage_.policy_ = allocate & 0x3; - storage_.length_ = length & 0x3FFFFFFF; -} - -Value::CZString::CZString(const CZString& other) { - cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr - ? duplicateStringValue(other.cstr_, other.storage_.length_) - : other.cstr_); - storage_.policy_ = - static_cast( - other.cstr_ - ? (static_cast(other.storage_.policy_) == - noDuplication - ? noDuplication - : duplicate) - : static_cast(other.storage_.policy_)) & - 3U; - storage_.length_ = other.storage_.length_; -} - -Value::CZString::CZString(CZString&& other) noexcept - : cstr_(other.cstr_), index_(other.index_) { - other.cstr_ = nullptr; -} - -Value::CZString::~CZString() { - if (cstr_ && storage_.policy_ == duplicate) { - releaseStringValue(const_cast(cstr_), - storage_.length_ + 1U); // +1 for null terminating - // character for sake of - // completeness but not actually - // necessary - } -} - -void Value::CZString::swap(CZString& other) { - std::swap(cstr_, other.cstr_); - std::swap(index_, other.index_); -} - -Value::CZString& Value::CZString::operator=(const CZString& other) { - cstr_ = other.cstr_; - index_ = other.index_; - return *this; -} - -Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { - cstr_ = other.cstr_; - index_ = other.index_; - other.cstr_ = nullptr; - return *this; -} - -bool Value::CZString::operator<(const CZString& other) const { - if (!cstr_) - return index_ < other.index_; - // return strcmp(cstr_, other.cstr_) < 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - unsigned min_len = std::min(this_len, other_len); - JSON_ASSERT(this->cstr_ && other.cstr_); - int comp = memcmp(this->cstr_, other.cstr_, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); -} - -bool Value::CZString::operator==(const CZString& other) const { - if (!cstr_) - return index_ == other.index_; - // return strcmp(cstr_, other.cstr_) == 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - if (this_len != other_len) - return false; - JSON_ASSERT(this->cstr_ && other.cstr_); - int comp = memcmp(this->cstr_, other.cstr_, this_len); - return comp == 0; -} - -ArrayIndex Value::CZString::index() const { return index_; } - -// const char* Value::CZString::c_str() const { return cstr_; } -const char* Value::CZString::data() const { return cstr_; } -unsigned Value::CZString::length() const { return storage_.length_; } -bool Value::CZString::isStaticString() const { - return storage_.policy_ == noDuplication; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::Value -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/*! \internal Default constructor initialization must be equivalent to: - * memset( this, 0, sizeof(Value) ) - * This optimization is used in ValueInternalMap fast allocator. - */ -Value::Value(ValueType type) { - static char const emptyString[] = ""; - initBasic(type); - switch (type) { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - // allocated_ == false, so this is safe. - value_.string_ = const_cast(static_cast(emptyString)); - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - -Value::Value(Int value) { - initBasic(intValue); - value_.int_ = value; -} - -Value::Value(UInt value) { - initBasic(uintValue); - value_.uint_ = value; -} -#if defined(JSON_HAS_INT64) -Value::Value(Int64 value) { - initBasic(intValue); - value_.int_ = value; -} -Value::Value(UInt64 value) { - initBasic(uintValue); - value_.uint_ = value; -} -#endif // defined(JSON_HAS_INT64) - -Value::Value(double value) { - initBasic(realValue); - value_.real_ = value; -} - -Value::Value(const char* value) { - initBasic(stringValue, true); - JSON_ASSERT_MESSAGE(value != nullptr, - "Null Value Passed to Value Constructor"); - value_.string_ = duplicateAndPrefixStringValue( - value, static_cast(strlen(value))); -} - -Value::Value(const char* begin, const char* end) { - initBasic(stringValue, true); - value_.string_ = - duplicateAndPrefixStringValue(begin, static_cast(end - begin)); -} - -Value::Value(const String& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue( - value.data(), static_cast(value.length())); -} - -Value::Value(const StaticString& value) { - initBasic(stringValue); - value_.string_ = const_cast(value.c_str()); -} - -Value::Value(bool value) { - initBasic(booleanValue); - value_.bool_ = value; -} - -Value::Value(const Value& other) { - dupPayload(other); - dupMeta(other); -} - -Value::Value(Value&& other) noexcept { - initBasic(nullValue); - swap(other); -} - -Value::~Value() { - releasePayload(); - value_.uint_ = 0; -} - -Value& Value::operator=(const Value& other) { - Value(other).swap(*this); - return *this; -} - -Value& Value::operator=(Value&& other) noexcept { - other.swap(*this); - return *this; -} - -void Value::swapPayload(Value& other) { - std::swap(bits_, other.bits_); - std::swap(value_, other.value_); -} - -void Value::copyPayload(const Value& other) { - releasePayload(); - dupPayload(other); -} - -void Value::swap(Value& other) { - swapPayload(other); - std::swap(comments_, other.comments_); - std::swap(start_, other.start_); - std::swap(limit_, other.limit_); -} - -void Value::copy(const Value& other) { - copyPayload(other); - dupMeta(other); -} - -ValueType Value::type() const { - return static_cast(bits_.value_type_); -} - -int Value::compare(const Value& other) const { - if (*this < other) - return -1; - if (*this > other) - return 1; - return 0; -} - -bool Value::operator<(const Value& other) const { - int typeDelta = type() - other.type(); - if (typeDelta) - return typeDelta < 0; - switch (type()) { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: { - if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { - return other.value_.string_ != nullptr; - } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, - &other_str); - unsigned min_len = std::min(this_len, other_len); - JSON_ASSERT(this_str && other_str); - int comp = memcmp(this_str, other_str, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); - } - case arrayValue: - case objectValue: { - auto thisSize = value_.map_->size(); - auto otherSize = other.value_.map_->size(); - if (thisSize != otherSize) - return thisSize < otherSize; - return (*value_.map_) < (*other.value_.map_); - } - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool Value::operator<=(const Value& other) const { return !(other < *this); } - -bool Value::operator>=(const Value& other) const { return !(*this < other); } - -bool Value::operator>(const Value& other) const { return other < *this; } - -bool Value::operator==(const Value& other) const { - if (type() != other.type()) - return false; - switch (type()) { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: { - if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { - return (value_.string_ == other.value_.string_); - } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, - &other_str); - if (this_len != other_len) - return false; - JSON_ASSERT(this_str && other_str); - int comp = memcmp(this_str, other_str, this_len); - return comp == 0; - } - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() && - (*value_.map_) == (*other.value_.map_); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool Value::operator!=(const Value& other) const { return !(*this == other); } - -const char* Value::asCString() const { - JSON_ASSERT_MESSAGE(type() == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == nullptr) - return nullptr; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return this_str; -} - -#if JSONCPP_USING_SECURE_MEMORY -unsigned Value::getCStringLength() const { - JSON_ASSERT_MESSAGE(type() == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) - return 0; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return this_len; -} -#endif - -bool Value::getString(char const** begin, char const** end) const { - if (type() != stringValue) - return false; - if (value_.string_ == nullptr) - return false; - unsigned length; - decodePrefixedString(this->isAllocated(), this->value_.string_, &length, - begin); - *end = *begin + length; - return true; -} - -String Value::asString() const { - switch (type()) { - case nullValue: - return ""; - case stringValue: { - if (value_.string_ == nullptr) - return ""; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return String(this_str, this_len); - } - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - return valueToString(value_.int_); - case uintValue: - return valueToString(value_.uint_); - case realValue: - return valueToString(value_.real_); - default: - JSON_FAIL_MESSAGE("Type is not convertible to string"); - } -} - -Value::Int Value::asInt() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); - return Int(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); - return Int(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), - "double out of Int range"); - return Int(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int."); -} - -Value::UInt Value::asUInt() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); - return UInt(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); - return UInt(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), - "double out of UInt range"); - return UInt(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt."); -} - -#if defined(JSON_HAS_INT64) - -Value::Int64 Value::asInt64() const { - switch (type()) { - case intValue: - return Int64(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); - return Int64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), - "double out of Int64 range"); - return Int64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int64."); -} - -Value::UInt64 Value::asUInt64() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); - return UInt64(value_.int_); - case uintValue: - return UInt64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), - "double out of UInt64 range"); - return UInt64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); -} -#endif // if defined(JSON_HAS_INT64) - -LargestInt Value::asLargestInt() const { -#if defined(JSON_NO_INT64) - return asInt(); -#else - return asInt64(); -#endif -} - -LargestUInt Value::asLargestUInt() const { -#if defined(JSON_NO_INT64) - return asUInt(); -#else - return asUInt64(); -#endif -} - -double Value::asDouble() const { - switch (type()) { - case intValue: - return static_cast(value_.int_); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return integerToDouble(value_.uint_); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return value_.real_; - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to double."); -} - -float Value::asFloat() const { - switch (type()) { - case intValue: - return static_cast(value_.int_); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - // This can fail (silently?) if the value is bigger than MAX_FLOAT. - return static_cast(integerToDouble(value_.uint_)); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return static_cast(value_.real_); - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0F : 0.0F; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to float."); -} - -bool Value::asBool() const { - switch (type()) { - case booleanValue: - return value_.bool_; - case nullValue: - return false; - case intValue: - return value_.int_ != 0; - case uintValue: - return value_.uint_ != 0; - case realValue: { - // According to JavaScript language zero or NaN is regarded as false - const auto value_classification = std::fpclassify(value_.real_); - return value_classification != FP_ZERO && value_classification != FP_NAN; - } - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to bool."); -} - -bool Value::isConvertibleTo(ValueType other) const { - switch (other) { - case nullValue: - return (isNumeric() && asDouble() == 0.0) || - (type() == booleanValue && !value_.bool_) || - (type() == stringValue && asString().empty()) || - (type() == arrayValue && value_.map_->empty()) || - (type() == objectValue && value_.map_->empty()) || - type() == nullValue; - case intValue: - return isInt() || - (type() == realValue && InRange(value_.real_, minInt, maxInt)) || - type() == booleanValue || type() == nullValue; - case uintValue: - return isUInt() || - (type() == realValue && InRange(value_.real_, 0, maxUInt)) || - type() == booleanValue || type() == nullValue; - case realValue: - return isNumeric() || type() == booleanValue || type() == nullValue; - case booleanValue: - return isNumeric() || type() == booleanValue || type() == nullValue; - case stringValue: - return isNumeric() || type() == booleanValue || type() == stringValue || - type() == nullValue; - case arrayValue: - return type() == arrayValue || type() == nullValue; - case objectValue: - return type() == objectValue || type() == nullValue; - } - JSON_ASSERT_UNREACHABLE; - return false; -} - -/// Number of values in array or object -ArrayIndex Value::size() const { - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; - case arrayValue: // size of the array is highest index + 1 - if (!value_.map_->empty()) { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index() + 1; - } - return 0; - case objectValue: - return ArrayIndex(value_.map_->size()); - } - JSON_ASSERT_UNREACHABLE; - return 0; // unreachable; -} - -bool Value::empty() const { - if (isNull() || isArray() || isObject()) - return size() == 0U; - return false; -} - -Value::operator bool() const { return !isNull(); } - -void Value::clear() { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || - type() == objectValue, - "in Json::Value::clear(): requires complex value"); - start_ = 0; - limit_ = 0; - switch (type()) { - case arrayValue: - case objectValue: - value_.map_->clear(); - break; - default: - break; - } -} - -void Value::resize(ArrayIndex newSize) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::resize(): requires arrayValue"); - if (type() == nullValue) - *this = Value(arrayValue); - ArrayIndex oldSize = size(); - if (newSize == 0) - clear(); - else if (newSize > oldSize) - for (ArrayIndex i = oldSize; i < newSize; ++i) - (*this)[i]; - else { - for (ArrayIndex index = newSize; index < oldSize; ++index) { - value_.map_->erase(index); - } - JSON_ASSERT(size() == newSize); - } -} - -Value& Value::operator[](ArrayIndex index) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == arrayValue, - "in Json::Value::operator[](ArrayIndex): requires arrayValue"); - if (type() == nullValue) - *this = Value(arrayValue); - CZString key(index); - auto it = value_.map_->lower_bound(key); - if (it != value_.map_->end() && (*it).first == key) - return (*it).second; - - ObjectValues::value_type defaultValue(key, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - return (*it).second; -} - -Value& Value::operator[](int index) { - JSON_ASSERT_MESSAGE( - index >= 0, - "in Json::Value::operator[](int index): index cannot be negative"); - return (*this)[ArrayIndex(index)]; -} - -const Value& Value::operator[](ArrayIndex index) const { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == arrayValue, - "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); - if (type() == nullValue) - return nullSingleton(); - CZString key(index); - ObjectValues::const_iterator it = value_.map_->find(key); - if (it == value_.map_->end()) - return nullSingleton(); - return (*it).second; -} - -const Value& Value::operator[](int index) const { - JSON_ASSERT_MESSAGE( - index >= 0, - "in Json::Value::operator[](int index) const: index cannot be negative"); - return (*this)[ArrayIndex(index)]; -} - -void Value::initBasic(ValueType type, bool allocated) { - setType(type); - setIsAllocated(allocated); - comments_ = Comments{}; - start_ = 0; - limit_ = 0; -} - -void Value::dupPayload(const Value& other) { - setType(other.type()); - setIsAllocated(false); - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if (other.value_.string_ && other.isAllocated()) { - unsigned len; - char const* str; - decodePrefixedString(other.isAllocated(), other.value_.string_, &len, - &str); - value_.string_ = duplicateAndPrefixStringValue(str, len); - setIsAllocated(true); - } else { - value_.string_ = other.value_.string_; - } - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(*other.value_.map_); - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - -void Value::releasePayload() { - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if (isAllocated()) - releasePrefixedStringValue(value_.string_); - break; - case arrayValue: - case objectValue: - delete value_.map_; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - -void Value::dupMeta(const Value& other) { - comments_ = other.comments_; - start_ = other.start_; - limit_ = other.limit_; -} - -// Access an object value by name, create a null member if it does not exist. -// @pre Type of '*this' is object or null. -// @param key is null-terminated. -Value& Value::resolveReference(const char* key) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::resolveReference(): requires objectValue"); - if (type() == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(strlen(key)), - CZString::noDuplication); // NOTE! - auto it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; - - ObjectValues::value_type defaultValue(actualKey, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; -} - -// @param key is not null-terminated. -Value& Value::resolveReference(char const* key, char const* end) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::resolveReference(key, end): requires objectValue"); - if (type() == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(end - key), - CZString::duplicateOnCopy); - auto it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; - - ObjectValues::value_type defaultValue(actualKey, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; -} - -Value Value::get(ArrayIndex index, const Value& defaultValue) const { - const Value* value = &((*this)[index]); - return value == &nullSingleton() ? defaultValue : *value; -} - -bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } - -Value const* Value::find(char const* begin, char const* end) const { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::find(begin, end): requires " - "objectValue or nullValue"); - if (type() == nullValue) - return nullptr; - CZString actualKey(begin, static_cast(end - begin), - CZString::noDuplication); - ObjectValues::const_iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return nullptr; - return &(*it).second; -} -Value* Value::demand(char const* begin, char const* end) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::demand(begin, end): requires " - "objectValue or nullValue"); - return &resolveReference(begin, end); -} -const Value& Value::operator[](const char* key) const { - Value const* found = find(key, key + strlen(key)); - if (!found) - return nullSingleton(); - return *found; -} -Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); - if (!found) - return nullSingleton(); - return *found; -} - -Value& Value::operator[](const char* key) { - return resolveReference(key, key + strlen(key)); -} - -Value& Value::operator[](const String& key) { - return resolveReference(key.data(), key.data() + key.length()); -} - -Value& Value::operator[](const StaticString& key) { - return resolveReference(key.c_str()); -} - -Value& Value::append(const Value& value) { return append(Value(value)); } - -Value& Value::append(Value&& value) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::append: requires arrayValue"); - if (type() == nullValue) { - *this = Value(arrayValue); - } - return this->value_.map_->emplace(size(), std::move(value)).first->second; -} - -bool Value::insert(ArrayIndex index, const Value& newValue) { - return insert(index, Value(newValue)); -} - -bool Value::insert(ArrayIndex index, Value&& newValue) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::insert: requires arrayValue"); - ArrayIndex length = size(); - if (index > length) { - return false; - } - for (ArrayIndex i = length; i > index; i--) { - (*this)[i] = std::move((*this)[i - 1]); - } - (*this)[index] = std::move(newValue); - return true; -} - -Value Value::get(char const* begin, char const* end, - Value const& defaultValue) const { - Value const* found = find(begin, end); - return !found ? defaultValue : *found; -} -Value Value::get(char const* key, Value const& defaultValue) const { - return get(key, key + strlen(key), defaultValue); -} -Value Value::get(String const& key, Value const& defaultValue) const { - return get(key.data(), key.data() + key.length(), defaultValue); -} - -bool Value::removeMember(const char* begin, const char* end, Value* removed) { - if (type() != objectValue) { - return false; - } - CZString actualKey(begin, static_cast(end - begin), - CZString::noDuplication); - auto it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return false; - if (removed) - *removed = std::move(it->second); - value_.map_->erase(it); - return true; -} -bool Value::removeMember(const char* key, Value* removed) { - return removeMember(key, key + strlen(key), removed); -} -bool Value::removeMember(String const& key, Value* removed) { - return removeMember(key.data(), key.data() + key.length(), removed); -} -void Value::removeMember(const char* key) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::removeMember(): requires objectValue"); - if (type() == nullValue) - return; - - CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); - value_.map_->erase(actualKey); -} -void Value::removeMember(const String& key) { removeMember(key.c_str()); } - -bool Value::removeIndex(ArrayIndex index, Value* removed) { - if (type() != arrayValue) { - return false; - } - CZString key(index); - auto it = value_.map_->find(key); - if (it == value_.map_->end()) { - return false; - } - if (removed) - *removed = it->second; - ArrayIndex oldSize = size(); - // shift left all items left, into the place of the "removed" - for (ArrayIndex i = index; i < (oldSize - 1); ++i) { - CZString keey(i); - (*value_.map_)[keey] = (*this)[i + 1]; - } - // erase the last one ("leftover") - CZString keyLast(oldSize - 1); - auto itLast = value_.map_->find(keyLast); - value_.map_->erase(itLast); - return true; -} - -bool Value::isMember(char const* begin, char const* end) const { - Value const* value = find(begin, end); - return nullptr != value; -} -bool Value::isMember(char const* key) const { - return isMember(key, key + strlen(key)); -} -bool Value::isMember(String const& key) const { - return isMember(key.data(), key.data() + key.length()); -} - -Value::Members Value::getMemberNames() const { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::getMemberNames(), value must be objectValue"); - if (type() == nullValue) - return Value::Members(); - Members members; - members.reserve(value_.map_->size()); - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for (; it != itEnd; ++it) { - members.push_back(String((*it).first.data(), (*it).first.length())); - } - return members; -} - -static bool IsIntegral(double d) { - double integral_part; - return modf(d, &integral_part) == 0.0; -} - -bool Value::isNull() const { return type() == nullValue; } - -bool Value::isBool() const { return type() == booleanValue; } - -bool Value::isInt() const { - switch (type()) { - case intValue: -#if defined(JSON_HAS_INT64) - return value_.int_ >= minInt && value_.int_ <= maxInt; -#else - return true; -#endif - case uintValue: - return value_.uint_ <= UInt(maxInt); - case realValue: - return value_.real_ >= minInt && value_.real_ <= maxInt && - IsIntegral(value_.real_); - default: - break; - } - return false; -} - -bool Value::isUInt() const { - switch (type()) { - case intValue: -#if defined(JSON_HAS_INT64) - return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); -#else - return value_.int_ >= 0; -#endif - case uintValue: -#if defined(JSON_HAS_INT64) - return value_.uint_ <= maxUInt; -#else - return true; -#endif - case realValue: - return value_.real_ >= 0 && value_.real_ <= maxUInt && - IsIntegral(value_.real_); - default: - break; - } - return false; -} - -bool Value::isInt64() const { -#if defined(JSON_HAS_INT64) - switch (type()) { - case intValue: - return true; - case uintValue: - return value_.uint_ <= UInt64(maxInt64); - case realValue: - // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a - // double, so double(maxInt64) will be rounded up to 2^63. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < double(maxInt64) && IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; -} - -bool Value::isUInt64() const { -#if defined(JSON_HAS_INT64) - switch (type()) { - case intValue: - return value_.int_ >= 0; - case uintValue: - return true; - case realValue: - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && - IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; -} - -bool Value::isIntegral() const { - switch (type()) { - case intValue: - case uintValue: - return true; - case realValue: -#if defined(JSON_HAS_INT64) - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); -#else - return value_.real_ >= minInt && value_.real_ <= maxUInt && - IsIntegral(value_.real_); -#endif // JSON_HAS_INT64 - default: - break; - } - return false; -} - -bool Value::isDouble() const { - return type() == intValue || type() == uintValue || type() == realValue; -} - -bool Value::isNumeric() const { return isDouble(); } - -bool Value::isString() const { return type() == stringValue; } - -bool Value::isArray() const { return type() == arrayValue; } - -bool Value::isObject() const { return type() == objectValue; } - -Value::Comments::Comments(const Comments& that) - : ptr_{cloneUnique(that.ptr_)} {} - -Value::Comments::Comments(Comments&& that) noexcept - : ptr_{std::move(that.ptr_)} {} - -Value::Comments& Value::Comments::operator=(const Comments& that) { - ptr_ = cloneUnique(that.ptr_); - return *this; -} - -Value::Comments& Value::Comments::operator=(Comments&& that) noexcept { - ptr_ = std::move(that.ptr_); - return *this; -} - -bool Value::Comments::has(CommentPlacement slot) const { - return ptr_ && !(*ptr_)[slot].empty(); -} - -String Value::Comments::get(CommentPlacement slot) const { - if (!ptr_) - return {}; - return (*ptr_)[slot]; -} - -void Value::Comments::set(CommentPlacement slot, String comment) { - if (slot >= CommentPlacement::numberOfCommentPlacement) - return; - if (!ptr_) - ptr_ = std::unique_ptr(new Array()); - (*ptr_)[slot] = std::move(comment); -} - -void Value::setComment(String comment, CommentPlacement placement) { - if (!comment.empty() && (comment.back() == '\n')) { - // Always discard trailing newline, to aid indentation. - comment.pop_back(); - } - JSON_ASSERT(!comment.empty()); - JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', - "in Json::Value::setComment(): Comments must start with /"); - comments_.set(placement, std::move(comment)); -} - -bool Value::hasComment(CommentPlacement placement) const { - return comments_.has(placement); -} - -String Value::getComment(CommentPlacement placement) const { - return comments_.get(placement); -} - -void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } - -void Value::setOffsetLimit(ptrdiff_t limit) { limit_ = limit; } - -ptrdiff_t Value::getOffsetStart() const { return start_; } - -ptrdiff_t Value::getOffsetLimit() const { return limit_; } - -String Value::toStyledString() const { - StreamWriterBuilder builder; - - String out = this->hasComment(commentBefore) ? "\n" : ""; - out += Json::writeString(builder, *this); - out += '\n'; - - return out; -} - -Value::const_iterator Value::begin() const { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->begin()); - break; - default: - break; - } - return {}; -} - -Value::const_iterator Value::end() const { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->end()); - break; - default: - break; - } - return {}; -} - -Value::iterator Value::begin() { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->begin()); - break; - default: - break; - } - return iterator(); -} - -Value::iterator Value::end() { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->end()); - break; - default: - break; - } - return iterator(); -} - -// class PathArgument -// ////////////////////////////////////////////////////////////////// - -PathArgument::PathArgument() = default; - -PathArgument::PathArgument(ArrayIndex index) - : index_(index), kind_(kindIndex) {} - -PathArgument::PathArgument(const char* key) : key_(key), kind_(kindKey) {} - -PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} - -// class Path -// ////////////////////////////////////////////////////////////////// - -Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, - const PathArgument& a3, const PathArgument& a4, - const PathArgument& a5) { - InArgs in; - in.reserve(5); - in.push_back(&a1); - in.push_back(&a2); - in.push_back(&a3); - in.push_back(&a4); - in.push_back(&a5); - makePath(path, in); -} - -void Path::makePath(const String& path, const InArgs& in) { - const char* current = path.c_str(); - const char* end = current + path.length(); - auto itInArg = in.begin(); - while (current != end) { - if (*current == '[') { - ++current; - if (*current == '%') - addPathInArg(path, in, itInArg, PathArgument::kindIndex); - else { - ArrayIndex index = 0; - for (; current != end && *current >= '0' && *current <= '9'; ++current) - index = index * 10 + ArrayIndex(*current - '0'); - args_.push_back(index); - } - if (current == end || *++current != ']') - invalidPath(path, int(current - path.c_str())); - } else if (*current == '%') { - addPathInArg(path, in, itInArg, PathArgument::kindKey); - ++current; - } else if (*current == '.' || *current == ']') { - ++current; - } else { - const char* beginName = current; - while (current != end && !strchr("[.", *current)) - ++current; - args_.push_back(String(beginName, current)); - } - } -} - -void Path::addPathInArg(const String& /*path*/, const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind) { - if (itInArg == in.end()) { - // Error: missing argument %d - } else if ((*itInArg)->kind_ != kind) { - // Error: bad argument type - } else { - args_.push_back(**itInArg++); - } -} - -void Path::invalidPath(const String& /*path*/, int /*location*/) { - // Error: invalid path. -} - -const Value& Path::resolve(const Value& root) const { - const Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) { - // Error: unable to resolve path (array value expected at position... ) - return Value::nullSingleton(); - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: unable to resolve path (object value expected at position...) - return Value::nullSingleton(); - } - node = &((*node)[arg.key_]); - if (node == &Value::nullSingleton()) { - // Error: unable to resolve path (object has no member named '' at - // position...) - return Value::nullSingleton(); - } - } - } - return *node; -} - -Value Path::resolve(const Value& root, const Value& defaultValue) const { - const Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) - return defaultValue; - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) - return defaultValue; - node = &((*node)[arg.key_]); - if (node == &Value::nullSingleton()) - return defaultValue; - } - } - return *node; -} - -Value& Path::make(Value& root) const { - Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray()) { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); - } - } - return *node; -} - -} // namespace Json - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: src/lib_json/json_value.cpp -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: src/lib_json/json_writer.cpp -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include "json_tool.h" -#include -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if __cplusplus >= 201103L -#include -#include - -#if !defined(isnan) -#define isnan std::isnan -#endif - -#if !defined(isfinite) -#define isfinite std::isfinite -#endif - -#else -#include -#include - -#if defined(_MSC_VER) -#if !defined(isnan) -#include -#define isnan _isnan -#endif - -#if !defined(isfinite) -#include -#define isfinite _finite -#endif - -#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) -#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES - -#endif //_MSC_VER - -#if defined(__sun) && defined(__SVR4) // Solaris -#if !defined(isfinite) -#include -#define isfinite finite -#endif -#endif - -#if defined(__hpux) -#if !defined(isfinite) -#if defined(__ia64) && !defined(finite) -#define isfinite(x) \ - ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) -#endif -#endif -#endif - -#if !defined(isnan) -// IEEE standard states that NaN values will not compare to themselves -#define isnan(x) ((x) != (x)) -#endif - -#if !defined(__APPLE__) -#if !defined(isfinite) -#define isfinite finite -#endif -#endif -#endif - -#if defined(_MSC_VER) -// Disable warning about strdup being deprecated. -#pragma warning(disable : 4996) -#endif - -namespace Json { - -#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -using StreamWriterPtr = std::unique_ptr; -#else -using StreamWriterPtr = std::auto_ptr; -#endif - -String valueToString(LargestInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - if (value == Value::minLargestInt) { - uintToString(LargestUInt(Value::maxLargestInt) + 1, current); - *--current = '-'; - } else if (value < 0) { - uintToString(LargestUInt(-value), current); - *--current = '-'; - } else { - uintToString(LargestUInt(value), current); - } - assert(current >= buffer); - return current; -} - -String valueToString(LargestUInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - uintToString(value, current); - assert(current >= buffer); - return current; -} - -#if defined(JSON_HAS_INT64) - -String valueToString(Int value) { return valueToString(LargestInt(value)); } - -String valueToString(UInt value) { return valueToString(LargestUInt(value)); } - -#endif // # if defined(JSON_HAS_INT64) - -namespace { -String valueToString(double value, bool useSpecialFloats, - unsigned int precision, PrecisionType precisionType) { - // Print into the buffer. We need not request the alternative representation - // that always has a decimal point because JSON doesn't distinguish the - // concepts of reals and integers. - if (!isfinite(value)) { - static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, - {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; - } - - String buffer(size_t(36), '\0'); - while (true) { - int len = jsoncpp_snprintf( - &*buffer.begin(), buffer.size(), - (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", - precision, value); - assert(len >= 0); - auto wouldPrint = static_cast(len); - if (wouldPrint >= buffer.size()) { - buffer.resize(wouldPrint + 1); - continue; - } - buffer.resize(wouldPrint); - break; - } - - buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); - - // try to ensure we preserve the fact that this was given to us as a double on - // input - if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { - buffer += ".0"; - } - - // strip the zero padding from the right - if (precisionType == PrecisionType::decimalPlaces) { - buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), - buffer.end()); - } - - return buffer; -} -} // namespace - -String valueToString(double value, unsigned int precision, - PrecisionType precisionType) { - return valueToString(value, false, precision, precisionType); -} - -String valueToString(bool value) { return value ? "true" : "false"; } - -static bool doesAnyCharRequireEscaping(char const* s, size_t n) { - assert(s || !n); - - return std::any_of(s, s + n, [](unsigned char c) { - return c == '\\' || c == '"' || c < 0x20 || c > 0x7F; - }); -} - -static unsigned int utf8ToCodepoint(const char*& s, const char* e) { - const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; - - unsigned int firstByte = static_cast(*s); - - if (firstByte < 0x80) - return firstByte; - - if (firstByte < 0xE0) { - if (e - s < 2) - return REPLACEMENT_CHARACTER; - - unsigned int calculated = - ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); - s += 1; - // oversized encoded characters are invalid - return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; - } - - if (firstByte < 0xF0) { - if (e - s < 3) - return REPLACEMENT_CHARACTER; - - unsigned int calculated = ((firstByte & 0x0F) << 12) | - ((static_cast(s[1]) & 0x3F) << 6) | - (static_cast(s[2]) & 0x3F); - s += 2; - // surrogates aren't valid codepoints itself - // shouldn't be UTF-8 encoded - if (calculated >= 0xD800 && calculated <= 0xDFFF) - return REPLACEMENT_CHARACTER; - // oversized encoded characters are invalid - return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; - } - - if (firstByte < 0xF8) { - if (e - s < 4) - return REPLACEMENT_CHARACTER; - - unsigned int calculated = ((firstByte & 0x07) << 18) | - ((static_cast(s[1]) & 0x3F) << 12) | - ((static_cast(s[2]) & 0x3F) << 6) | - (static_cast(s[3]) & 0x3F); - s += 3; - // oversized encoded characters are invalid - return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; - } - - return REPLACEMENT_CHARACTER; -} - -static const char hex2[] = "000102030405060708090a0b0c0d0e0f" - "101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f" - "303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f" - "505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f" - "707172737475767778797a7b7c7d7e7f" - "808182838485868788898a8b8c8d8e8f" - "909192939495969798999a9b9c9d9e9f" - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; - -static String toHex16Bit(unsigned int x) { - const unsigned int hi = (x >> 8) & 0xff; - const unsigned int lo = x & 0xff; - String result(4, ' '); - result[0] = hex2[2 * hi]; - result[1] = hex2[2 * hi + 1]; - result[2] = hex2[2 * lo]; - result[3] = hex2[2 * lo + 1]; - return result; -} - -static void appendRaw(String& result, unsigned ch) { - result += static_cast(ch); -} - -static void appendHex(String& result, unsigned ch) { - result.append("\\u").append(toHex16Bit(ch)); -} - -static String valueToQuotedStringN(const char* value, size_t length, - bool emitUTF8 = false) { - if (value == nullptr) - return ""; - - if (!doesAnyCharRequireEscaping(value, length)) - return String("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to String is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL - String result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - char const* end = value + length; - for (const char* c = value; c != end; ++c) { - switch (*c) { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - // case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something.) - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - if (codepoint < 0x20) { - appendHex(result, codepoint); - } else { - appendRaw(result, codepoint); - } - } else { - unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` - if (codepoint < 0x20) { - appendHex(result, codepoint); - } else if (codepoint < 0x80) { - appendRaw(result, codepoint); - } else if (codepoint < 0x10000) { - // Basic Multilingual Plane - appendHex(result, codepoint); - } else { - // Extended Unicode. Encode 20 bits as a surrogate pair. - codepoint -= 0x10000; - appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); - appendHex(result, 0xdc00 + (codepoint & 0x3ff)); - } - } - } break; - } - } - result += "\""; - return result; -} - -String valueToQuotedString(const char* value) { - return valueToQuotedStringN(value, strlen(value)); -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -Writer::~Writer() = default; - -// Class FastWriter -// ////////////////////////////////////////////////////////////////// - -FastWriter::FastWriter() - - = default; - -void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } - -void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } - -void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } - -String FastWriter::write(const Value& root) { - document_.clear(); - writeValue(root); - if (!omitEndingLineFeed_) - document_ += '\n'; - return document_; -} - -void FastWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - if (!dropNullPlaceholders_) - document_ += "null"; - break; - case intValue: - document_ += valueToString(value.asLargestInt()); - break; - case uintValue: - document_ += valueToString(value.asLargestUInt()); - break; - case realValue: - document_ += valueToString(value.asDouble()); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - document_ += valueToQuotedStringN(str, static_cast(end - str)); - break; - } - case booleanValue: - document_ += valueToString(value.asBool()); - break; - case arrayValue: { - document_ += '['; - ArrayIndex size = value.size(); - for (ArrayIndex index = 0; index < size; ++index) { - if (index > 0) - document_ += ','; - writeValue(value[index]); - } - document_ += ']'; - } break; - case objectValue: { - Value::Members members(value.getMemberNames()); - document_ += '{'; - for (auto it = members.begin(); it != members.end(); ++it) { - const String& name = *it; - if (it != members.begin()) - document_ += ','; - document_ += valueToQuotedStringN(name.data(), name.length()); - document_ += yamlCompatibilityEnabled_ ? ": " : ":"; - writeValue(value[name]); - } - document_ += '}'; - } break; - } -} - -// Class StyledWriter -// ////////////////////////////////////////////////////////////////// - -StyledWriter::StyledWriter() = default; - -String StyledWriter::write(const Value& root) { - document_.clear(); - addChildValues_ = false; - indentString_.clear(); - writeCommentBeforeValue(root); - writeValue(root); - writeCommentAfterValueOnSameLine(root); - document_ += '\n'; - return document_; -} - -void StyledWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - const String& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - document_ += " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledWriter::writeArrayValue(const Value& value) { - size_t size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultilineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - ArrayIndex index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - writeIndent(); - writeValue(childValue); - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - document_ += "[ "; - for (size_t index = 0; index < size; ++index) { - if (index > 0) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - -bool StyledWriter::isMultilineArray(const Value& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void StyledWriter::pushValue(const String& value) { - if (addChildValues_) - childValues_.push_back(value); - else - document_ += value; -} - -void StyledWriter::writeIndent() { - if (!document_.empty()) { - char last = document_[document_.length() - 1]; - if (last == ' ') // already indented - return; - if (last != '\n') // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - -void StyledWriter::writeWithIndent(const String& value) { - writeIndent(); - document_ += value; -} - -void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } - -void StyledWriter::unindent() { - assert(indentString_.size() >= indentSize_); - indentString_.resize(indentString_.size() - indentSize_); -} - -void StyledWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; - - document_ += '\n'; - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - document_ += *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - writeIndent(); - ++iter; - } - - // Comments are stripped of trailing newlines, so add one here - document_ += '\n'; -} - -void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - document_ += " " + root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - document_ += '\n'; - document_ += root.getComment(commentAfter); - document_ += '\n'; - } -} - -bool StyledWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -// Class StyledStreamWriter -// ////////////////////////////////////////////////////////////////// - -StyledStreamWriter::StyledStreamWriter(String indentation) - : document_(nullptr), indentation_(std::move(indentation)), - addChildValues_(), indented_(false) {} - -void StyledStreamWriter::write(OStream& out, const Value& root) { - document_ = &out; - addChildValues_ = false; - indentString_.clear(); - indented_ = true; - writeCommentBeforeValue(root); - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *document_ << "\n"; - document_ = nullptr; // Forget the stream, for safety. -} - -void StyledStreamWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - const String& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - *document_ << " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledStreamWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultilineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - *document_ << "[ "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - -bool StyledStreamWriter::isMultilineArray(const Value& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void StyledStreamWriter::pushValue(const String& value) { - if (addChildValues_) - childValues_.push_back(value); - else - *document_ << value; -} - -void StyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - *document_ << '\n' << indentString_; -} - -void StyledStreamWriter::writeWithIndent(const String& value) { - if (!indented_) - writeIndent(); - *document_ << value; - indented_ = false; -} - -void StyledStreamWriter::indent() { indentString_ += indentation_; } - -void StyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); -} - -void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; - - if (!indented_) - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *document_ << *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would include newline - *document_ << indentString_; - ++iter; - } - indented_ = false; -} - -void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - *document_ << ' ' << root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - writeIndent(); - *document_ << root.getComment(commentAfter); - } - indented_ = false; -} - -bool StyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -////////////////////////// -// BuiltStyledStreamWriter - -/// Scoped enums are not available until C++11. -struct CommentStyle { - /// Decide whether to write comments. - enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. - }; -}; - -struct BuiltStyledStreamWriter : public StreamWriter { - BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, - String colonSymbol, String nullSymbol, - String endingLineFeedSymbol, bool useSpecialFloats, - bool emitUTF8, unsigned int precision, - PrecisionType precisionType); - int write(Value const& root, OStream* sout) override; - -private: - void writeValue(Value const& value); - void writeArrayValue(Value const& value); - bool isMultilineArray(Value const& value); - void pushValue(String const& value); - void writeIndent(); - void writeWithIndent(String const& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(Value const& root); - void writeCommentAfterValueOnSameLine(Value const& root); - static bool hasCommentForValue(const Value& value); - - using ChildValues = std::vector; - - ChildValues childValues_; - String indentString_; - unsigned int rightMargin_; - String indentation_; - CommentStyle::Enum cs_; - String colonSymbol_; - String nullSymbol_; - String endingLineFeedSymbol_; - bool addChildValues_ : 1; - bool indented_ : 1; - bool useSpecialFloats_ : 1; - bool emitUTF8_ : 1; - unsigned int precision_; - PrecisionType precisionType_; -}; -BuiltStyledStreamWriter::BuiltStyledStreamWriter( - String indentation, CommentStyle::Enum cs, String colonSymbol, - String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, - bool emitUTF8, unsigned int precision, PrecisionType precisionType) - : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), - colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), - endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), - addChildValues_(false), indented_(false), - useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8), - precision_(precision), precisionType_(precisionType) {} -int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { - sout_ = sout; - addChildValues_ = false; - indented_ = true; - indentString_.clear(); - writeCommentBeforeValue(root); - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *sout_ << endingLineFeedSymbol_; - sout_ = nullptr; - return 0; -} -void BuiltStyledStreamWriter::writeValue(Value const& value) { - switch (value.type()) { - case nullValue: - pushValue(nullSymbol_); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, - precisionType_)); - break; - case stringValue: { - // Is NULL is possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue( - valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - String const& name = *it; - Value const& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent( - valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); - *sout_ << colonSymbol_; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); - if (isMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - Value const& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - *sout_ << "["; - if (!indentation_.empty()) - *sout_ << " "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *sout_ << ((!indentation_.empty()) ? ", " : ","); - *sout_ << childValues_[index]; - } - if (!indentation_.empty()) - *sout_ << " "; - *sout_ << "]"; - } - } -} - -bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - Value const& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void BuiltStyledStreamWriter::pushValue(String const& value) { - if (addChildValues_) - childValues_.push_back(value); - else - *sout_ << value; -} - -void BuiltStyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - - if (!indentation_.empty()) { - // In this case, drop newlines too. - *sout_ << '\n' << indentString_; - } -} - -void BuiltStyledStreamWriter::writeWithIndent(String const& value) { - if (!indented_) - writeIndent(); - *sout_ << value; - indented_ = false; -} - -void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } - -void BuiltStyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); -} - -void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { - if (cs_ == CommentStyle::None) - return; - if (!root.hasComment(commentBefore)) - return; - - if (!indented_) - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *sout_ << *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would write extra newline - *sout_ << indentString_; - ++iter; - } - indented_ = false; -} - -void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( - Value const& root) { - if (cs_ == CommentStyle::None) - return; - if (root.hasComment(commentAfterOnSameLine)) - *sout_ << " " + root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - writeIndent(); - *sout_ << root.getComment(commentAfter); - } -} - -// static -bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -/////////////// -// StreamWriter - -StreamWriter::StreamWriter() : sout_(nullptr) {} -StreamWriter::~StreamWriter() = default; -StreamWriter::Factory::~Factory() = default; -StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } -StreamWriterBuilder::~StreamWriterBuilder() = default; -StreamWriter* StreamWriterBuilder::newStreamWriter() const { - const String indentation = settings_["indentation"].asString(); - const String cs_str = settings_["commentStyle"].asString(); - const String pt_str = settings_["precisionType"].asString(); - const bool eyc = settings_["enableYAMLCompatibility"].asBool(); - const bool dnp = settings_["dropNullPlaceholders"].asBool(); - const bool usf = settings_["useSpecialFloats"].asBool(); - const bool emitUTF8 = settings_["emitUTF8"].asBool(); - unsigned int pre = settings_["precision"].asUInt(); - CommentStyle::Enum cs = CommentStyle::All; - if (cs_str == "All") { - cs = CommentStyle::All; - } else if (cs_str == "None") { - cs = CommentStyle::None; - } else { - throwRuntimeError("commentStyle must be 'All' or 'None'"); - } - PrecisionType precisionType(significantDigits); - if (pt_str == "significant") { - precisionType = PrecisionType::significantDigits; - } else if (pt_str == "decimal") { - precisionType = PrecisionType::decimalPlaces; - } else { - throwRuntimeError("precisionType must be 'significant' or 'decimal'"); - } - String colonSymbol = " : "; - if (eyc) { - colonSymbol = ": "; - } else if (indentation.empty()) { - colonSymbol = ":"; - } - String nullSymbol = "null"; - if (dnp) { - nullSymbol.clear(); - } - if (pre > 17) - pre = 17; - String endingLineFeedSymbol; - return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, - endingLineFeedSymbol, usf, emitUTF8, pre, - precisionType); -} - -bool StreamWriterBuilder::validate(Json::Value* invalid) const { - static const auto& valid_keys = *new std::set{ - "indentation", - "commentStyle", - "enableYAMLCompatibility", - "dropNullPlaceholders", - "useSpecialFloats", - "emitUTF8", - "precision", - "precisionType", - }; - for (auto si = settings_.begin(); si != settings_.end(); ++si) { - auto key = si.name(); - if (valid_keys.count(key)) - continue; - if (invalid) - (*invalid)[key] = *si; - else - return false; - } - return invalid ? invalid->empty() : true; -} - -Value& StreamWriterBuilder::operator[](const String& key) { - return settings_[key]; -} -// static -void StreamWriterBuilder::setDefaults(Json::Value* settings) { - //! [StreamWriterBuilderDefaults] - (*settings)["commentStyle"] = "All"; - (*settings)["indentation"] = "\t"; - (*settings)["enableYAMLCompatibility"] = false; - (*settings)["dropNullPlaceholders"] = false; - (*settings)["useSpecialFloats"] = false; - (*settings)["emitUTF8"] = false; - (*settings)["precision"] = 17; - (*settings)["precisionType"] = "significant"; - //! [StreamWriterBuilderDefaults] -} - -String writeString(StreamWriter::Factory const& factory, Value const& root) { - OStringStream sout; - StreamWriterPtr const writer(factory.newStreamWriter()); - writer->write(root, &sout); - return sout.str(); -} - -OStream& operator<<(OStream& sout, Value const& root) { - StreamWriterBuilder builder; - StreamWriterPtr const writer(builder.newStreamWriter()); - writer->write(root, &sout); - return sout; -} - -} // namespace Json - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: src/lib_json/json_writer.cpp -// ////////////////////////////////////////////////////////////////////// - - - - - diff --git a/Modules/CppMicroServices/third_party/jsoncpp.h b/Modules/CppMicroServices/third_party/jsoncpp.h deleted file mode 100644 index b280790a4e..0000000000 --- a/Modules/CppMicroServices/third_party/jsoncpp.h +++ /dev/null @@ -1,2346 +0,0 @@ -/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/). -/// It is intended to be used with #include "json/json.h" - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: LICENSE -// ////////////////////////////////////////////////////////////////////// - -/* -The JsonCpp library's source code, including accompanying documentation, -tests and demonstration applications, are licensed under the following -conditions... - -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, -this software is released into the Public Domain. - -In jurisdictions which do not recognize Public Domain property (e.g. Germany as of -2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and -The JsonCpp Authors, and is released under the terms of the MIT License (see below). - -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual -Public Domain/MIT License conditions described here, as they choose. - -The MIT License is about as close to Public Domain as a license can get, and is -described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - -======================================================================== -Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, copy, -modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -======================================================================== -(END LICENSE TEXT) - -The MIT license is compatible with both the GPL and commercial -software, affording one all of the rights of Public Domain with the -minor nuisance of being required to keep the above copyright notice -and license text in the source code. Note also that by accepting the -Public Domain "license" you can re-license your copy using whatever -license you like. - -*/ - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: LICENSE -// ////////////////////////////////////////////////////////////////////// - - - - - -#ifndef JSON_AMALGAMATED_H_INCLUDED -# define JSON_AMALGAMATED_H_INCLUDED -/// If defined, indicates that the source file is amalgamated -/// to prevent private header inclusion. -#define JSON_IS_AMALGAMATION - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/version.h -// ////////////////////////////////////////////////////////////////////// - -#ifndef JSON_VERSION_H_INCLUDED -#define JSON_VERSION_H_INCLUDED - -// Note: version must be updated in three places when doing a release. This -// annoying process ensures that amalgamate, CMake, and meson all report the -// correct version. -// 1. /meson.build -// 2. /include/json/version.h -// 3. /CMakeLists.txt -// IMPORTANT: also update the SOVERSION!! - -#define JSONCPP_VERSION_STRING "1.9.5" -#define JSONCPP_VERSION_MAJOR 1 -#define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 5 -#define JSONCPP_VERSION_QUALIFIER -#define JSONCPP_VERSION_HEXA \ - ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ - (JSONCPP_VERSION_PATCH << 8)) - -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY -#endif -#define JSONCPP_USING_SECURE_MEMORY 0 -// If non-zero, the library zeroes any memory that it has allocated before -// it frees its memory. - -#endif // JSON_VERSION_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/version.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/allocator.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_ALLOCATOR_H_INCLUDED -#define JSON_ALLOCATOR_H_INCLUDED - -#include -#include - -#pragma pack(push, 8) - -namespace Json { -template class SecureAllocator { -public: - // Type definitions - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - /** - * Allocate memory for N items using the standard allocator. - */ - pointer allocate(size_type n) { - // allocate using "global operator new" - return static_cast(::operator new(n * sizeof(T))); - } - - /** - * Release memory which was allocated for N items at pointer P. - * - * The memory block is filled with zeroes before being released. - */ - void deallocate(pointer p, size_type n) { - // memset_s is used because memset may be optimized away by the compiler - memset_s(p, n * sizeof(T), 0, n * sizeof(T)); - // free using "global operator delete" - ::operator delete(p); - } - - /** - * Construct an item in-place at pointer P. - */ - template void construct(pointer p, Args&&... args) { - // construct using "placement new" and "perfect forwarding" - ::new (static_cast(p)) T(std::forward(args)...); - } - - size_type max_size() const { return size_t(-1) / sizeof(T); } - - pointer address(reference x) const { return std::addressof(x); } - - const_pointer address(const_reference x) const { return std::addressof(x); } - - /** - * Destroy an item in-place at pointer P. - */ - void destroy(pointer p) { - // destroy using "explicit destructor" - p->~T(); - } - - // Boilerplate - SecureAllocator() {} - template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; -}; - -template -bool operator==(const SecureAllocator&, const SecureAllocator&) { - return true; -} - -template -bool operator!=(const SecureAllocator&, const SecureAllocator&) { - return false; -} - -} // namespace Json - -#pragma pack(pop) - -#endif // JSON_ALLOCATOR_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/allocator.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/config.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_CONFIG_H_INCLUDED -#define JSON_CONFIG_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include - -// If non-zero, the library uses exceptions to report bad input instead of C -// assertion macros. The default is to use exceptions. -#ifndef JSON_USE_EXCEPTION -#define JSON_USE_EXCEPTION 1 -#endif - -// Temporary, tracked for removal with issue #982. -#ifndef JSON_USE_NULLREF -#define JSON_USE_NULLREF 1 -#endif - -/// If defined, indicates that the source file is amalgamated -/// to prevent private header inclusion. -/// Remarks: it is automatically defined in the generated amalgamated header. -// #define JSON_IS_AMALGAMATION - -// Export macros for DLL visibility -#if defined(JSON_DLL_BUILD) -#if defined(_MSC_VER) || defined(__MINGW32__) -#define JSON_API __declspec(dllexport) -#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING -#elif defined(__GNUC__) || defined(__clang__) -#define JSON_API __attribute__((visibility("default"))) -#endif // if defined(_MSC_VER) - -#elif defined(JSON_DLL) -#if defined(_MSC_VER) || defined(__MINGW32__) -#define JSON_API __declspec(dllimport) -#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING -#endif // if defined(_MSC_VER) -#endif // ifdef JSON_DLL_BUILD - -#if !defined(JSON_API) -#define JSON_API -#endif - -#if defined(_MSC_VER) && _MSC_VER < 1800 -#error \ - "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" -#endif - -#if defined(_MSC_VER) && _MSC_VER < 1900 -// As recommended at -// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 -extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, - const char* format, ...); -#define jsoncpp_snprintf msvc_pre1900_c99_snprintf -#else -#define jsoncpp_snprintf std::snprintf -#endif - -// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for -// integer -// Storages, and 64 bits integer support is disabled. -// #define JSON_NO_INT64 1 - -// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. -// C++11 should be used directly in JSONCPP. -#define JSONCPP_OVERRIDE override - -#ifdef __clang__ -#if __has_extension(attribute_deprecated_with_message) -#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) -#endif -#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) -#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) -#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) -#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) -#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) -#endif // GNUC version -#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates - // MSVC) -#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -#endif // __clang__ || __GNUC__ || _MSC_VER - -#if !defined(JSONCPP_DEPRECATED) -#define JSONCPP_DEPRECATED(message) -#endif // if !defined(JSONCPP_DEPRECATED) - -#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) -#define JSON_USE_INT64_DOUBLE_CONVERSION 1 -#endif - -#if !defined(JSON_IS_AMALGAMATION) - -#include "allocator.h" -#include "version.h" - -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { -using Int = int; -using UInt = unsigned int; -#if defined(JSON_NO_INT64) -using LargestInt = int; -using LargestUInt = unsigned int; -#undef JSON_HAS_INT64 -#else // if defined(JSON_NO_INT64) -// For Microsoft Visual use specific types as long long is not supported -#if defined(_MSC_VER) // Microsoft Visual Studio -using Int64 = __int64; -using UInt64 = unsigned __int64; -#else // if defined(_MSC_VER) // Other platforms, use long long -using Int64 = int64_t; -using UInt64 = uint64_t; -#endif // if defined(_MSC_VER) -using LargestInt = Int64; -using LargestUInt = UInt64; -#define JSON_HAS_INT64 -#endif // if defined(JSON_NO_INT64) - -template -using Allocator = - typename std::conditional, - std::allocator>::type; -using String = std::basic_string, Allocator>; -using IStringStream = - std::basic_istringstream; -using OStringStream = - std::basic_ostringstream; -using IStream = std::istream; -using OStream = std::ostream; -} // namespace Json - -// Legacy names (formerly macros). -using JSONCPP_STRING = Json::String; -using JSONCPP_ISTRINGSTREAM = Json::IStringStream; -using JSONCPP_OSTRINGSTREAM = Json::OStringStream; -using JSONCPP_ISTREAM = Json::IStream; -using JSONCPP_OSTREAM = Json::OStream; - -#endif // JSON_CONFIG_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/config.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/forwards.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_FORWARDS_H_INCLUDED -#define JSON_FORWARDS_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "config.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -// writer.h -class StreamWriter; -class StreamWriterBuilder; -class Writer; -class FastWriter; -class StyledWriter; -class StyledStreamWriter; - -// reader.h -class Reader; -class CharReader; -class CharReaderBuilder; - -// json_features.h -class Features; - -// value.h -using ArrayIndex = unsigned int; -class StaticString; -class Path; -class PathArgument; -class Value; -class ValueIteratorBase; -class ValueIterator; -class ValueConstIterator; - -} // namespace Json - -#endif // JSON_FORWARDS_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/forwards.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/json_features.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_FEATURES_H_INCLUDED -#define JSON_FEATURES_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -#pragma pack(push, 8) - -namespace Json { - -/** \brief Configuration passed to reader and writer. - * This configuration object can be used to force the Reader or Writer - * to behave in a standard conforming way. - */ -class JSON_API Features { -public: - /** \brief A configuration that allows all features and assumes all strings - * are UTF-8. - * - C & C++ comments are allowed - * - Root object can be any JSON value - * - Assumes Value strings are encoded in UTF-8 - */ - static Features all(); - - /** \brief A configuration that is strictly compatible with the JSON - * specification. - * - Comments are forbidden. - * - Root object must be either an array or an object value. - * - Assumes Value strings are encoded in UTF-8 - */ - static Features strictMode(); - - /** \brief Initialize the configuration like JsonConfig::allFeatures; - */ - Features(); - - /// \c true if comments are allowed. Default: \c true. - bool allowComments_{true}; - - /// \c true if root must be either an array or an object value. Default: \c - /// false. - bool strictRoot_{false}; - - /// \c true if dropped null placeholders are allowed. Default: \c false. - bool allowDroppedNullPlaceholders_{false}; - - /// \c true if numeric object key are allowed. Default: \c false. - bool allowNumericKeys_{false}; -}; - -} // namespace Json - -#pragma pack(pop) - -#endif // JSON_FEATURES_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/json_features.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/value.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_H_INCLUDED -#define JSON_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -// Conditional NORETURN attribute on the throw functions would: -// a) suppress false positives from static code analysis -// b) possibly improve optimization opportunities. -#if !defined(JSONCPP_NORETURN) -#if defined(_MSC_VER) && _MSC_VER == 1800 -#define JSONCPP_NORETURN __declspec(noreturn) -#else -#define JSONCPP_NORETURN [[noreturn]] -#endif -#endif - -// Support for '= delete' with template declarations was a late addition -// to the c++11 standard and is rejected by clang 3.8 and Apple clang 8.2 -// even though these declare themselves to be c++11 compilers. -#if !defined(JSONCPP_TEMPLATE_DELETE) -#if defined(__clang__) && defined(__apple_build_version__) -#if __apple_build_version__ <= 8000042 -#define JSONCPP_TEMPLATE_DELETE -#endif -#elif defined(__clang__) -#if __clang_major__ == 3 && __clang_minor__ <= 8 -#define JSONCPP_TEMPLATE_DELETE -#endif -#endif -#if !defined(JSONCPP_TEMPLATE_DELETE) -#define JSONCPP_TEMPLATE_DELETE = delete -#endif -#endif - -#include -#include -#include -#include -#include -#include - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(push) -#pragma warning(disable : 4251 4275) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#pragma pack(push, 8) - -/** \brief JSON (JavaScript Object Notation). - */ -namespace Json { - -#if JSON_USE_EXCEPTION -/** Base class for all exceptions we throw. - * - * We use nothing but these internally. Of course, STL can throw others. - */ -class JSON_API Exception : public std::exception { -public: - Exception(String msg); - ~Exception() noexcept override; - char const* what() const noexcept override; - -protected: - String msg_; -}; - -/** Exceptions which the user cannot easily avoid. - * - * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input - * - * \remark derived from Json::Exception - */ -class JSON_API RuntimeError : public Exception { -public: - RuntimeError(String const& msg); -}; - -/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. - * - * These are precondition-violations (user bugs) and internal errors (our bugs). - * - * \remark derived from Json::Exception - */ -class JSON_API LogicError : public Exception { -public: - LogicError(String const& msg); -}; -#endif - -/// used internally -JSONCPP_NORETURN void throwRuntimeError(String const& msg); -/// used internally -JSONCPP_NORETURN void throwLogicError(String const& msg); - -/** \brief Type of the value held by a Value object. - */ -enum ValueType { - nullValue = 0, ///< 'null' value - intValue, ///< signed integer value - uintValue, ///< unsigned integer value - realValue, ///< double value - stringValue, ///< UTF-8 string value - booleanValue, ///< bool value - arrayValue, ///< array value (ordered list) - objectValue ///< object value (collection of name/value pairs). -}; - -enum CommentPlacement { - commentBefore = 0, ///< a comment placed on the line before a value - commentAfterOnSameLine, ///< a comment just after a value on the same line - commentAfter, ///< a comment on the line after a value (only make sense for - /// root value) - numberOfCommentPlacement -}; - -/** \brief Type of precision for formatting of real values. - */ -enum PrecisionType { - significantDigits = 0, ///< we set max number of significant digits in string - decimalPlaces ///< we set max number of digits after "." in string -}; - -/** \brief Lightweight wrapper to tag static string. - * - * Value constructor and objectValue member assignment takes advantage of the - * StaticString and avoid the cost of string duplication when storing the - * string or the member name. - * - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ -class JSON_API StaticString { -public: - explicit StaticString(const char* czstring) : c_str_(czstring) {} - - operator const char*() const { return c_str_; } - - const char* c_str() const { return c_str_; } - -private: - const char* c_str_; -}; - -/** \brief Represents a JSON value. - * - * This class is a discriminated union wrapper that can represents a: - * - signed integer [range: Value::minInt - Value::maxInt] - * - unsigned integer (range: 0 - Value::maxUInt) - * - double - * - UTF-8 string - * - boolean - * - 'null' - * - an ordered list of Value - * - collection of name/value pairs (javascript object) - * - * The type of the held value is represented by a #ValueType and - * can be obtained using type(). - * - * Values of an #objectValue or #arrayValue can be accessed using operator[]() - * methods. - * Non-const methods will automatically create the a #nullValue element - * if it does not exist. - * The sequence of an #arrayValue will be automatically resized and initialized - * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. - * - * The get() methods can be used to obtain default value in the case the - * required element does not exist. - * - * It is possible to iterate over the list of member keys of an object using - * the getMemberNames() method. - * - * \note #Value string-length fit in size_t, but keys must be < 2^30. - * (The reason is an implementation detail.) A #CharReader will raise an - * exception if a bound is exceeded to avoid security holes in your app, - * but the Value API does *not* check bounds. That is the responsibility - * of the caller. - */ -class JSON_API Value { - friend class ValueIteratorBase; - -public: - using Members = std::vector; - using iterator = ValueIterator; - using const_iterator = ValueConstIterator; - using UInt = Json::UInt; - using Int = Json::Int; -#if defined(JSON_HAS_INT64) - using UInt64 = Json::UInt64; - using Int64 = Json::Int64; -#endif // defined(JSON_HAS_INT64) - using LargestInt = Json::LargestInt; - using LargestUInt = Json::LargestUInt; - using ArrayIndex = Json::ArrayIndex; - - // Required for boost integration, e. g. BOOST_TEST - using value_type = std::string; - -#if JSON_USE_NULLREF - // Binary compatibility kludges, do not use. - static const Value& null; - static const Value& nullRef; -#endif - - // null and nullRef are deprecated, use this instead. - static Value const& nullSingleton(); - - /// Minimum signed integer value that can be stored in a Json::Value. - static constexpr LargestInt minLargestInt = - LargestInt(~(LargestUInt(-1) / 2)); - /// Maximum signed integer value that can be stored in a Json::Value. - static constexpr LargestInt maxLargestInt = LargestInt(LargestUInt(-1) / 2); - /// Maximum unsigned integer value that can be stored in a Json::Value. - static constexpr LargestUInt maxLargestUInt = LargestUInt(-1); - - /// Minimum signed int value that can be stored in a Json::Value. - static constexpr Int minInt = Int(~(UInt(-1) / 2)); - /// Maximum signed int value that can be stored in a Json::Value. - static constexpr Int maxInt = Int(UInt(-1) / 2); - /// Maximum unsigned int value that can be stored in a Json::Value. - static constexpr UInt maxUInt = UInt(-1); - -#if defined(JSON_HAS_INT64) - /// Minimum signed 64 bits int value that can be stored in a Json::Value. - static constexpr Int64 minInt64 = Int64(~(UInt64(-1) / 2)); - /// Maximum signed 64 bits int value that can be stored in a Json::Value. - static constexpr Int64 maxInt64 = Int64(UInt64(-1) / 2); - /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. - static constexpr UInt64 maxUInt64 = UInt64(-1); -#endif // defined(JSON_HAS_INT64) - /// Default precision for real value for string representation. - static constexpr UInt defaultRealPrecision = 17; - // The constant is hard-coded because some compiler have trouble - // converting Value::maxUInt64 to a double correctly (AIX/xlC). - // Assumes that UInt64 is a 64 bits integer. - static constexpr double maxUInt64AsDouble = 18446744073709551615.0; -// Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler -// when using gcc and clang backend compilers. CZString -// cannot be defined as private. See issue #486 -#ifdef __NVCC__ -public: -#else -private: -#endif -#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - class CZString { - public: - enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; - CZString(ArrayIndex index); - CZString(char const* str, unsigned length, DuplicationPolicy allocate); - CZString(CZString const& other); - CZString(CZString&& other) noexcept; - ~CZString(); - CZString& operator=(const CZString& other); - CZString& operator=(CZString&& other) noexcept; - - bool operator<(CZString const& other) const; - bool operator==(CZString const& other) const; - ArrayIndex index() const; - // const char* c_str() const; ///< \deprecated - char const* data() const; - unsigned length() const; - bool isStaticString() const; - - private: - void swap(CZString& other); - - struct StringStorage { - unsigned policy_ : 2; - unsigned length_ : 30; // 1GB max - }; - - char const* cstr_; // actually, a prefixed string, unless policy is noDup - union { - ArrayIndex index_; - StringStorage storage_; - }; - }; - -public: - typedef std::map ObjectValues; -#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - -public: - /** - * \brief Create a default Value of the given type. - * - * This is a very useful constructor. - * To create an empty array, pass arrayValue. - * To create an empty object, pass objectValue. - * Another Value can then be set to this one by assignment. - * This is useful since clear() and resize() will not alter types. - * - * Examples: - * \code - * Json::Value null_value; // null - * Json::Value arr_value(Json::arrayValue); // [] - * Json::Value obj_value(Json::objectValue); // {} - * \endcode - */ - Value(ValueType type = nullValue); - Value(Int value); - Value(UInt value); -#if defined(JSON_HAS_INT64) - Value(Int64 value); - Value(UInt64 value); -#endif // if defined(JSON_HAS_INT64) - Value(double value); - Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) - Value(const char* begin, const char* end); ///< Copy all, incl zeroes. - /** - * \brief Constructs a value from a static string. - * - * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to - * this constructor. - * - * \note This works only for null-terminated strings. (We cannot change the - * size of this class, so we have nowhere to store the length, which might be - * computed later for various operations.) - * - * Example of usage: - * \code - * static StaticString foo("some text"); - * Json::Value aValue(foo); - * \endcode - */ - Value(const StaticString& value); - Value(const String& value); - Value(bool value); - Value(std::nullptr_t ptr) = delete; - Value(const Value& other); - Value(Value&& other) noexcept; - ~Value(); - - /// \note Overwrite existing comments. To preserve comments, use - /// #swapPayload(). - Value& operator=(const Value& other); - Value& operator=(Value&& other) noexcept; - - /// Swap everything. - void swap(Value& other); - /// Swap values but leave comments and source offsets in place. - void swapPayload(Value& other); - - /// copy everything. - void copy(const Value& other); - /// copy values but leave comments and source offsets in place. - void copyPayload(const Value& other); - - ValueType type() const; - - /// Compare payload only, not comments etc. - bool operator<(const Value& other) const; - bool operator<=(const Value& other) const; - bool operator>=(const Value& other) const; - bool operator>(const Value& other) const; - bool operator==(const Value& other) const; - bool operator!=(const Value& other) const; - int compare(const Value& other) const; - - const char* asCString() const; ///< Embedded zeroes could cause you trouble! -#if JSONCPP_USING_SECURE_MEMORY - unsigned getCStringLength() const; // Allows you to understand the length of - // the CString -#endif - String asString() const; ///< Embedded zeroes are possible. - /** Get raw char* of string-value. - * \return false if !string. (Seg-fault if str or end are NULL.) - */ - bool getString(char const** begin, char const** end) const; - Int asInt() const; - UInt asUInt() const; -#if defined(JSON_HAS_INT64) - Int64 asInt64() const; - UInt64 asUInt64() const; -#endif // if defined(JSON_HAS_INT64) - LargestInt asLargestInt() const; - LargestUInt asLargestUInt() const; - float asFloat() const; - double asDouble() const; - bool asBool() const; - - bool isNull() const; - bool isBool() const; - bool isInt() const; - bool isInt64() const; - bool isUInt() const; - bool isUInt64() const; - bool isIntegral() const; - bool isDouble() const; - bool isNumeric() const; - bool isString() const; - bool isArray() const; - bool isObject() const; - - /// The `as` and `is` member function templates and specializations. - template T as() const JSONCPP_TEMPLATE_DELETE; - template bool is() const JSONCPP_TEMPLATE_DELETE; - - bool isConvertibleTo(ValueType other) const; - - /// Number of values in array or object - ArrayIndex size() const; - - /// \brief Return true if empty array, empty object, or null; - /// otherwise, false. - bool empty() const; - - /// Return !isNull() - explicit operator bool() const; - - /// Remove all object members and array elements. - /// \pre type() is arrayValue, objectValue, or nullValue - /// \post type() is unchanged - void clear(); - - /// Resize the array to newSize elements. - /// New elements are initialized to null. - /// May only be called on nullValue or arrayValue. - /// \pre type() is arrayValue or nullValue - /// \post type() is arrayValue - void resize(ArrayIndex newSize); - - //@{ - /// Access an array element (zero based index). If the array contains less - /// than index element, then null value are inserted in the array so that - /// its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value& operator[](ArrayIndex index); - Value& operator[](int index); - //@} - - //@{ - /// Access an array element (zero based index). - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value& operator[](ArrayIndex index) const; - const Value& operator[](int index) const; - //@} - - /// If the array contains at least index+1 elements, returns the element - /// value, otherwise returns defaultValue. - Value get(ArrayIndex index, const Value& defaultValue) const; - /// Return true if index < size(). - bool isValidIndex(ArrayIndex index) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; - Value& append(const Value& value); - Value& append(Value&& value); - - /// \brief Insert value in array at specific index - bool insert(ArrayIndex index, const Value& newValue); - bool insert(ArrayIndex index, Value&& newValue); - - /// Access an object value by name, create a null member if it does not exist. - /// \note Because of our implementation, keys are limited to 2^30 -1 chars. - /// Exceeding that will cause an exception. - Value& operator[](const char* key); - /// Access an object value by name, returns null if there is no member with - /// that name. - const Value& operator[](const char* key) const; - /// Access an object value by name, create a null member if it does not exist. - /// \param key may contain embedded nulls. - Value& operator[](const String& key); - /// Access an object value by name, returns null if there is no member with - /// that name. - /// \param key may contain embedded nulls. - const Value& operator[](const String& key) const; - /** \brief Access an object value by name, create a null member if it does not - * exist. - * - * If the object has no entry for that name, then the member name used to - * store the new entry is not duplicated. - * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - Value& operator[](const StaticString& key); - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - Value get(const char* key, const Value& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - /// \note key may contain embedded nulls. - Value get(const char* begin, const char* end, - const Value& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - /// \param key may contain embedded nulls. - Value get(const String& key, const Value& defaultValue) const; - /// Most general and efficient version of isMember()const, get()const, - /// and operator[]const - /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 - Value const* find(char const* begin, char const* end) const; - /// Most general and efficient version of object-mutators. - /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 - /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. - Value* demand(char const* begin, char const* end); - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \pre type() is objectValue or nullValue - /// \post type() is unchanged - void removeMember(const char* key); - /// Same as removeMember(const char*) - /// \param key may contain embedded nulls. - void removeMember(const String& key); - /// Same as removeMember(const char* begin, const char* end, Value* removed), - /// but 'key' is null-terminated. - bool removeMember(const char* key, Value* removed); - /** \brief Remove the named map member. - * - * Update 'removed' iff removed. - * \param key may contain embedded nulls. - * \return true iff removed (no exceptions) - */ - bool removeMember(String const& key, Value* removed); - /// Same as removeMember(String const& key, Value* removed) - bool removeMember(const char* begin, const char* end, Value* removed); - /** \brief Remove the indexed array element. - * - * O(n) expensive operations. - * Update 'removed' iff removed. - * \return true if removed (no exceptions) - */ - bool removeIndex(ArrayIndex index, Value* removed); - - /// Return true if the object has a member named key. - /// \note 'key' must be null-terminated. - bool isMember(const char* key) const; - /// Return true if the object has a member named key. - /// \param key may contain embedded nulls. - bool isMember(const String& key) const; - /// Same as isMember(String const& key)const - bool isMember(const char* begin, const char* end) const; - - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is objectValue or nullValue - /// \post if type() was nullValue, it remains nullValue - Members getMemberNames() const; - - /// \deprecated Always pass len. - JSONCPP_DEPRECATED("Use setComment(String const&) instead.") - void setComment(const char* comment, CommentPlacement placement) { - setComment(String(comment, strlen(comment)), placement); - } - /// Comments must be //... or /* ... */ - void setComment(const char* comment, size_t len, CommentPlacement placement) { - setComment(String(comment, len), placement); - } - /// Comments must be //... or /* ... */ - void setComment(String comment, CommentPlacement placement); - bool hasComment(CommentPlacement placement) const; - /// Include delimiters and embedded newlines. - String getComment(CommentPlacement placement) const; - - String toStyledString() const; - - const_iterator begin() const; - const_iterator end() const; - - iterator begin(); - iterator end(); - - // Accessors for the [start, limit) range of bytes within the JSON text from - // which this value was parsed, if any. - void setOffsetStart(ptrdiff_t start); - void setOffsetLimit(ptrdiff_t limit); - ptrdiff_t getOffsetStart() const; - ptrdiff_t getOffsetLimit() const; - -private: - void setType(ValueType v) { - bits_.value_type_ = static_cast(v); - } - bool isAllocated() const { return bits_.allocated_; } - void setIsAllocated(bool v) { bits_.allocated_ = v; } - - void initBasic(ValueType type, bool allocated = false); - void dupPayload(const Value& other); - void releasePayload(); - void dupMeta(const Value& other); - - Value& resolveReference(const char* key); - Value& resolveReference(const char* key, const char* end); - - // struct MemberNamesTransform - //{ - // typedef const char *result_type; - // const char *operator()( const CZString &name ) const - // { - // return name.c_str(); - // } - //}; - - union ValueHolder { - LargestInt int_; - LargestUInt uint_; - double real_; - bool bool_; - char* string_; // if allocated_, ptr to { unsigned, char[] }. - ObjectValues* map_; - } value_; - - struct { - // Really a ValueType, but types should agree for bitfield packing. - unsigned int value_type_ : 8; - // Unless allocated_, string_ must be null-terminated. - unsigned int allocated_ : 1; - } bits_; - - class Comments { - public: - Comments() = default; - Comments(const Comments& that); - Comments(Comments&& that) noexcept; - Comments& operator=(const Comments& that); - Comments& operator=(Comments&& that) noexcept; - bool has(CommentPlacement slot) const; - String get(CommentPlacement slot) const; - void set(CommentPlacement slot, String comment); - - private: - using Array = std::array; - std::unique_ptr ptr_; - }; - Comments comments_; - - // [start, limit) byte offsets in the source JSON text from which this Value - // was extracted. - ptrdiff_t start_; - ptrdiff_t limit_; -}; - -template <> inline bool Value::as() const { return asBool(); } -template <> inline bool Value::is() const { return isBool(); } - -template <> inline Int Value::as() const { return asInt(); } -template <> inline bool Value::is() const { return isInt(); } - -template <> inline UInt Value::as() const { return asUInt(); } -template <> inline bool Value::is() const { return isUInt(); } - -#if defined(JSON_HAS_INT64) -template <> inline Int64 Value::as() const { return asInt64(); } -template <> inline bool Value::is() const { return isInt64(); } - -template <> inline UInt64 Value::as() const { return asUInt64(); } -template <> inline bool Value::is() const { return isUInt64(); } -#endif - -template <> inline double Value::as() const { return asDouble(); } -template <> inline bool Value::is() const { return isDouble(); } - -template <> inline String Value::as() const { return asString(); } -template <> inline bool Value::is() const { return isString(); } - -/// These `as` specializations are type conversions, and do not have a -/// corresponding `is`. -template <> inline float Value::as() const { return asFloat(); } -template <> inline const char* Value::as() const { - return asCString(); -} - -/** \brief Experimental and untested: represents an element of the "path" to - * access a node. - */ -class JSON_API PathArgument { -public: - friend class Path; - - PathArgument(); - PathArgument(ArrayIndex index); - PathArgument(const char* key); - PathArgument(String key); - -private: - enum Kind { kindNone = 0, kindIndex, kindKey }; - String key_; - ArrayIndex index_{}; - Kind kind_{kindNone}; -}; - -/** \brief Experimental and untested: represents a "path" to access a node. - * - * Syntax: - * - "." => root node - * - ".[n]" => elements at index 'n' of root node (an array value) - * - ".name" => member named 'name' of root node (an object value) - * - ".name1.name2.name3" - * - ".[0][1][2].name1[3]" - * - ".%" => member name is provided as parameter - * - ".[%]" => index is provided as parameter - */ -class JSON_API Path { -public: - Path(const String& path, const PathArgument& a1 = PathArgument(), - const PathArgument& a2 = PathArgument(), - const PathArgument& a3 = PathArgument(), - const PathArgument& a4 = PathArgument(), - const PathArgument& a5 = PathArgument()); - - const Value& resolve(const Value& root) const; - Value resolve(const Value& root, const Value& defaultValue) const; - /// Creates the "path" to access the specified node and returns a reference on - /// the node. - Value& make(Value& root) const; - -private: - using InArgs = std::vector; - using Args = std::vector; - - void makePath(const String& path, const InArgs& in); - void addPathInArg(const String& path, const InArgs& in, - InArgs::const_iterator& itInArg, PathArgument::Kind kind); - static void invalidPath(const String& path, int location); - - Args args_; -}; - -/** \brief base class for Value iterators. - * - */ -class JSON_API ValueIteratorBase { -public: - using iterator_category = std::bidirectional_iterator_tag; - using size_t = unsigned int; - using difference_type = int; - using SelfType = ValueIteratorBase; - - bool operator==(const SelfType& other) const { return isEqual(other); } - - bool operator!=(const SelfType& other) const { return !isEqual(other); } - - difference_type operator-(const SelfType& other) const { - return other.computeDistance(*this); - } - - /// Return either the index or the member name of the referenced value as a - /// Value. - Value key() const; - - /// Return the index of the referenced Value, or -1 if it is not an - /// arrayValue. - UInt index() const; - - /// Return the member name of the referenced Value, or "" if it is not an - /// objectValue. - /// \note Avoid `c_str()` on result, as embedded zeroes are possible. - String name() const; - - /// Return the member name of the referenced Value. "" if it is not an - /// objectValue. - /// \deprecated This cannot be used for UTF-8 strings, since there can be - /// embedded nulls. - JSONCPP_DEPRECATED("Use `key = name();` instead.") - char const* memberName() const; - /// Return the member name of the referenced Value, or NULL if it is not an - /// objectValue. - /// \note Better version than memberName(). Allows embedded nulls. - char const* memberName(char const** end) const; - -protected: - /*! Internal utility functions to assist with implementing - * other iterator functions. The const and non-const versions - * of the "deref" protected methods expose the protected - * current_ member variable in a way that can often be - * optimized away by the compiler. - */ - const Value& deref() const; - Value& deref(); - - void increment(); - - void decrement(); - - difference_type computeDistance(const SelfType& other) const; - - bool isEqual(const SelfType& other) const; - - void copy(const SelfType& other); - -private: - Value::ObjectValues::iterator current_; - // Indicates that iterator is for a null value. - bool isNull_{true}; - -public: - // For some reason, BORLAND needs these at the end, rather - // than earlier. No idea why. - ValueIteratorBase(); - explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); -}; - -/** \brief const iterator for object and array value. - * - */ -class JSON_API ValueConstIterator : public ValueIteratorBase { - friend class Value; - -public: - using value_type = const Value; - // typedef unsigned int size_t; - // typedef int difference_type; - using reference = const Value&; - using pointer = const Value*; - using SelfType = ValueConstIterator; - - ValueConstIterator(); - ValueConstIterator(ValueIterator const& other); - -private: - /*! \internal Use by Value to create an iterator. - */ - explicit ValueConstIterator(const Value::ObjectValues::iterator& current); - -public: - SelfType& operator=(const ValueIteratorBase& other); - - SelfType operator++(int) { - SelfType temp(*this); - ++*this; - return temp; - } - - SelfType operator--(int) { - SelfType temp(*this); - --*this; - return temp; - } - - SelfType& operator--() { - decrement(); - return *this; - } - - SelfType& operator++() { - increment(); - return *this; - } - - reference operator*() const { return deref(); } - - pointer operator->() const { return &deref(); } -}; - -/** \brief Iterator for object and array value. - */ -class JSON_API ValueIterator : public ValueIteratorBase { - friend class Value; - -public: - using value_type = Value; - using size_t = unsigned int; - using difference_type = int; - using reference = Value&; - using pointer = Value*; - using SelfType = ValueIterator; - - ValueIterator(); - explicit ValueIterator(const ValueConstIterator& other); - ValueIterator(const ValueIterator& other); - -private: - /*! \internal Use by Value to create an iterator. - */ - explicit ValueIterator(const Value::ObjectValues::iterator& current); - -public: - SelfType& operator=(const SelfType& other); - - SelfType operator++(int) { - SelfType temp(*this); - ++*this; - return temp; - } - - SelfType operator--(int) { - SelfType temp(*this); - --*this; - return temp; - } - - SelfType& operator--() { - decrement(); - return *this; - } - - SelfType& operator++() { - increment(); - return *this; - } - - /*! The return value of non-const iterators can be - * changed, so the these functions are not const - * because the returned references/pointers can be used - * to change state of the base class. - */ - reference operator*() const { return const_cast(deref()); } - pointer operator->() const { return const_cast(&deref()); } -}; - -inline void swap(Value& a, Value& b) { a.swap(b); } - -} // namespace Json - -#pragma pack(pop) - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // JSON_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/value.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/reader.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_READER_H_INCLUDED -#define JSON_READER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "json_features.h" -#include "value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(push) -#pragma warning(disable : 4251) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#pragma pack(push, 8) - -namespace Json { - -/** \brief Unserialize a JSON document into a - * Value. - * - * \deprecated Use CharReader and CharReaderBuilder. - */ - -class JSON_API Reader { -public: - using Char = char; - using Location = const Char*; - - /** \brief An error tagged with where in the JSON text it was encountered. - * - * The offsets give the [start, limit) range of bytes within the text. Note - * that this is bytes, not codepoints. - */ - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; - - /** \brief Constructs a Reader allowing all features for parsing. - * \deprecated Use CharReader and CharReaderBuilder. - */ - Reader(); - - /** \brief Constructs a Reader allowing the specified feature set for parsing. - * \deprecated Use CharReader and CharReaderBuilder. - */ - Reader(const Features& features); - - /** \brief Read a Value from a JSON - * document. - * - * \param document UTF-8 encoded string containing the document - * to read. - * \param[out] root Contains the root value of the document if it - * was successfully parsed. - * \param collectComments \c true to collect comment and allow writing - * them back during serialization, \c false to - * discard comments. This parameter is ignored - * if Features::allowComments_ is \c false. - * \return \c true if the document was successfully parsed, \c false if an - * error occurred. - */ - bool parse(const std::string& document, Value& root, - bool collectComments = true); - - /** \brief Read a Value from a JSON - * document. - * - * \param beginDoc Pointer on the beginning of the UTF-8 encoded - * string of the document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string - * of the document to read. Must be >= beginDoc. - * \param[out] root Contains the root value of the document if it - * was successfully parsed. - * \param collectComments \c true to collect comment and allow writing - * them back during serialization, \c false to - * discard comments. This parameter is ignored - * if Features::allowComments_ is \c false. - * \return \c true if the document was successfully parsed, \c false if an - * error occurred. - */ - bool parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments = true); - - /// \brief Parse from input stream. - /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse(IStream& is, Value& root, bool collectComments = true); - - /** \brief Returns a user friendly string that list errors in the parsed - * document. - * - * \return Formatted error message with the list of errors with their - * location in the parsed document. An empty string is returned if no error - * occurred during parsing. - * \deprecated Use getFormattedErrorMessages() instead (typo fix). - */ - JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") - String getFormatedErrorMessages() const; - - /** \brief Returns a user friendly string that list errors in the parsed - * document. - * - * \return Formatted error message with the list of errors with their - * location in the parsed document. An empty string is returned if no error - * occurred during parsing. - */ - String getFormattedErrorMessages() const; - - /** \brief Returns a vector of structured errors encountered while parsing. - * - * \return A (possibly empty) vector of StructuredError objects. Currently - * only one error can be returned, but the caller should tolerate multiple - * errors. This can occur if the parser recovers from a non-fatal parse - * error and then encounters additional errors. - */ - std::vector getStructuredErrors() const; - - /** \brief Add a semantic error message. - * - * \param value JSON Value location associated with the error - * \param message The error message. - * \return \c true if the error was successfully added, \c false if the Value - * offset exceeds the document size. - */ - bool pushError(const Value& value, const String& message); - - /** \brief Add a semantic error message with extra context. - * - * \param value JSON Value location associated with the error - * \param message The error message. - * \param extra Additional JSON Value location to contextualize the error - * \return \c true if the error was successfully added, \c false if either - * Value offset exceeds the document size. - */ - bool pushError(const Value& value, const String& message, const Value& extra); - - /** \brief Return whether there are any errors. - * - * \return \c true if there are no errors to report \c false if errors have - * occurred. - */ - bool good() const; - -private: - enum TokenType { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo { - public: - Token token_; - String message_; - Location extra_; - }; - - using Errors = std::deque; - - bool readToken(Token& token); - void skipSpaces(); - bool match(const Char* pattern, int patternLength); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - void readNumber(); - bool readValue(); - bool readObject(Token& token); - bool readArray(Token& token); - bool decodeNumber(Token& token); - bool decodeNumber(Token& token, Value& decoded); - bool decodeString(Token& token); - bool decodeString(Token& token, String& decoded); - bool decodeDouble(Token& token); - bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, - unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, unsigned int& unicode); - bool addError(const String& message, Token& token, Location extra = nullptr); - bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken); - void skipUntilSpace(); - Value& currentValue(); - Char getNextChar(); - void getLocationLineAndColumn(Location location, int& line, - int& column) const; - String getLocationLineAndColumn(Location location) const; - void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); - - static bool containsNewLine(Location begin, Location end); - static String normalizeEOL(Location begin, Location end); - - using Nodes = std::stack; - Nodes nodes_; - Errors errors_; - String document_; - Location begin_{}; - Location end_{}; - Location current_{}; - Location lastValueEnd_{}; - Value* lastValue_{}; - String commentsBefore_; - Features features_; - bool collectComments_{}; -}; // Reader - -/** Interface for reading JSON from a char array. - */ -class JSON_API CharReader { -public: - virtual ~CharReader() = default; - /** \brief Read a Value from a JSON - * document. The document must be a UTF-8 encoded string containing the - * document to read. - * - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string - * of the document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - * document to read. Must be >= beginDoc. - * \param[out] root Contains the root value of the document if it was - * successfully parsed. - * \param[out] errs Formatted error messages (if not NULL) a user - * friendly string that lists errors in the parsed - * document. - * \return \c true if the document was successfully parsed, \c false if an - * error occurred. - */ - virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) = 0; - - class JSON_API Factory { - public: - virtual ~Factory() = default; - /** \brief Allocate a CharReader via operator new(). - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - virtual CharReader* newCharReader() const = 0; - }; // Factory -}; // CharReader - -/** \brief Build a CharReader implementation. - * - * Usage: - * \code - * using namespace Json; - * CharReaderBuilder builder; - * builder["collectComments"] = false; - * Value value; - * String errs; - * bool ok = parseFromStream(builder, std::cin, &value, &errs); - * \endcode - */ -class JSON_API CharReaderBuilder : public CharReader::Factory { -public: - // Note: We use a Json::Value so that we can add data-members to this class - // without a major version bump. - /** Configuration of this builder. - * These are case-sensitive. - * Available settings (case-sensitive): - * - `"collectComments": false or true` - * - true to collect comment and allow writing them back during - * serialization, false to discard comments. This parameter is ignored - * if allowComments is false. - * - `"allowComments": false or true` - * - true if comments are allowed. - * - `"allowTrailingCommas": false or true` - * - true if trailing commas in objects and arrays are allowed. - * - `"strictRoot": false or true` - * - true if root must be either an array or an object value - * - `"allowDroppedNullPlaceholders": false or true` - * - true if dropped null placeholders are allowed. (See - * StreamWriterBuilder.) - * - `"allowNumericKeys": false or true` - * - true if numeric object keys are allowed. - * - `"allowSingleQuotes": false or true` - * - true if '' are allowed for strings (both keys and values) - * - `"stackLimit": integer` - * - Exceeding stackLimit (recursive depth of `readValue()`) will cause an - * exception. - * - This is a security issue (seg-faults caused by deeply nested JSON), so - * the default is low. - * - `"failIfExtra": false or true` - * - If true, `parse()` returns false when extra non-whitespace trails the - * JSON value in the input string. - * - `"rejectDupKeys": false or true` - * - If true, `parse()` returns false when a key is duplicated within an - * object. - * - `"allowSpecialFloats": false or true` - * - If true, special float values (NaNs and infinities) are allowed and - * their values are lossfree restorable. - * - `"skipBom": false or true` - * - If true, if the input starts with the Unicode byte order mark (BOM), - * it is skipped. - * - * You can examine 'settings_` yourself to see the defaults. You can also - * write and read them just like any JSON Value. - * \sa setDefaults() - */ - Json::Value settings_; - - CharReaderBuilder(); - ~CharReaderBuilder() override; - - CharReader* newCharReader() const override; - - /** \return true if 'settings' are legal and consistent; - * otherwise, indicate bad settings via 'invalid'. - */ - bool validate(Json::Value* invalid) const; - - /** A simple way to update a specific setting. - */ - Value& operator[](const String& key); - - /** Called by ctor, but you can use this to reset settings_. - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults - */ - static void setDefaults(Json::Value* settings); - /** Same as old Features::strictMode(). - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode - */ - static void strictMode(Json::Value* settings); -}; - -/** Consume entire stream and use its begin/end. - * Someday we might have a real StreamReader, but for now this - * is convenient. - */ -bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, - String* errs); - -/** \brief Read from 'sin' into 'root'. - * - * Always keep comments from the input JSON. - * - * This can be used to read a file into a particular sub-object. - * For example: - * \code - * Json::Value root; - * cin >> root["dir"]["file"]; - * cout << root; - * \endcode - * Result: - * \verbatim - * { - * "dir": { - * "file": { - * // The input stream JSON would be nested here. - * } - * } - * } - * \endverbatim - * \throw std::exception on parse error. - * \see Json::operator<<() - */ -JSON_API IStream& operator>>(IStream&, Value&); - -} // namespace Json - -#pragma pack(pop) - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // JSON_READER_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/reader.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/writer.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_WRITER_H_INCLUDED -#define JSON_WRITER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) && defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4251) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#pragma pack(push, 8) - -namespace Json { - -class Value; - -/** - * - * Usage: - * \code - * using namespace Json; - * void writeToStdout(StreamWriter::Factory const& factory, Value const& value) - * { std::unique_ptr const writer( factory.newStreamWriter()); - * writer->write(value, &std::cout); - * std::cout << std::endl; // add lf and flush - * } - * \endcode - */ -class JSON_API StreamWriter { -protected: - OStream* sout_; // not owned; will not delete -public: - StreamWriter(); - virtual ~StreamWriter(); - /** Write Value into document as configured in sub-class. - * Do not take ownership of sout, but maintain a reference during function. - * \pre sout != NULL - * \return zero on success (For now, we always return zero, so check the - * stream instead.) \throw std::exception possibly, depending on - * configuration - */ - virtual int write(Value const& root, OStream* sout) = 0; - - /** \brief A simple abstract factory. - */ - class JSON_API Factory { - public: - virtual ~Factory(); - /** \brief Allocate a CharReader via operator new(). - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - virtual StreamWriter* newStreamWriter() const = 0; - }; // Factory -}; // StreamWriter - -/** \brief Write into stringstream, then return string, for convenience. - * A StreamWriter will be created from the factory, used, and then deleted. - */ -String JSON_API writeString(StreamWriter::Factory const& factory, - Value const& root); - -/** \brief Build a StreamWriter implementation. - -* Usage: -* \code -* using namespace Json; -* Value value = ...; -* StreamWriterBuilder builder; -* builder["commentStyle"] = "None"; -* builder["indentation"] = " "; // or whatever you like -* std::unique_ptr writer( -* builder.newStreamWriter()); -* writer->write(value, &std::cout); -* std::cout << std::endl; // add lf and flush -* \endcode -*/ -class JSON_API StreamWriterBuilder : public StreamWriter::Factory { -public: - // Note: We use a Json::Value so that we can add data-members to this class - // without a major version bump. - /** Configuration of this builder. - * Available settings (case-sensitive): - * - "commentStyle": "None" or "All" - * - "indentation": "". - * - Setting this to an empty string also omits newline characters. - * - "enableYAMLCompatibility": false or true - * - slightly change the whitespace around colons - * - "dropNullPlaceholders": false or true - * - Drop the "null" string from the writer's output for nullValues. - * Strictly speaking, this is not valid JSON. But when the output is being - * fed to a browser's JavaScript, it makes for smaller output and the - * browser can handle the output just fine. - * - "useSpecialFloats": false or true - * - If true, outputs non-finite floating point values in the following way: - * NaN values as "NaN", positive infinity as "Infinity", and negative - * infinity as "-Infinity". - * - "precision": int - * - Number of precision digits for formatting of real values. - * - "precisionType": "significant"(default) or "decimal" - * - Type of precision for formatting of real values. - * - "emitUTF8": false or true - * - If true, outputs raw UTF8 strings instead of escaping them. - - * You can examine 'settings_` yourself - * to see the defaults. You can also write and read them just like any - * JSON Value. - * \sa setDefaults() - */ - Json::Value settings_; - - StreamWriterBuilder(); - ~StreamWriterBuilder() override; - - /** - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - StreamWriter* newStreamWriter() const override; - - /** \return true if 'settings' are legal and consistent; - * otherwise, indicate bad settings via 'invalid'. - */ - bool validate(Json::Value* invalid) const; - /** A simple way to update a specific setting. - */ - Value& operator[](const String& key); - - /** Called by ctor, but you can use this to reset settings_. - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults - */ - static void setDefaults(Json::Value* settings); -}; - -/** \brief Abstract class for writers. - * \deprecated Use StreamWriter. (And really, this is an implementation detail.) - */ -class JSON_API Writer { -public: - virtual ~Writer(); - - virtual String write(const Value& root) = 0; -}; - -/** \brief Outputs a Value in JSON format - *without formatting (not human friendly). - * - * The JSON document is written in a single line. It is not intended for 'human' - *consumption, - * but may be useful to support feature such as RPC where bandwidth is limited. - * \sa Reader, Value - * \deprecated Use StreamWriterBuilder. - */ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4996) // Deriving from deprecated class -#endif -class JSON_API FastWriter - : public Writer { -public: - FastWriter(); - ~FastWriter() override = default; - - void enableYAMLCompatibility(); - - /** \brief Drop the "null" string from the writer's output for nullValues. - * Strictly speaking, this is not valid JSON. But when the output is being - * fed to a browser's JavaScript, it makes for smaller output and the - * browser can handle the output just fine. - */ - void dropNullPlaceholders(); - - void omitEndingLineFeed(); - -public: // overridden from Writer - String write(const Value& root) override; - -private: - void writeValue(const Value& value); - - String document_; - bool yamlCompatibilityEnabled_{false}; - bool dropNullPlaceholders_{false}; - bool omitEndingLineFeed_{false}; -}; -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -/** \brief Writes a Value in JSON format in a - *human friendly way. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per - *line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value - *types, - * and all the values fit on one lines, then print the array on a single - *line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their - *#CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - * \deprecated Use StreamWriterBuilder. - */ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4996) // Deriving from deprecated class -#endif -class JSON_API - StyledWriter : public Writer { -public: - StyledWriter(); - ~StyledWriter() override = default; - -public: // overridden from Writer - /** \brief Serialize a Value in JSON format. - * \param root Value to serialize. - * \return String containing the JSON document that represents the root value. - */ - String write(const Value& root) override; - -private: - void writeValue(const Value& value); - void writeArrayValue(const Value& value); - bool isMultilineArray(const Value& value); - void pushValue(const String& value); - void writeIndent(); - void writeWithIndent(const String& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(const Value& root); - void writeCommentAfterValueOnSameLine(const Value& root); - static bool hasCommentForValue(const Value& value); - static String normalizeEOL(const String& text); - - using ChildValues = std::vector; - - ChildValues childValues_; - String document_; - String indentString_; - unsigned int rightMargin_{74}; - unsigned int indentSize_{3}; - bool addChildValues_{false}; -}; -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -/** \brief Writes a Value in JSON format in a - human friendly way, - to a stream rather than to a string. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per - line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value - types, - * and all the values fit on one lines, then print the array on a single - line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their - #CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - * \deprecated Use StreamWriterBuilder. - */ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4996) // Deriving from deprecated class -#endif -class JSON_API - StyledStreamWriter { -public: - /** - * \param indentation Each level will be indented by this amount extra. - */ - StyledStreamWriter(String indentation = "\t"); - ~StyledStreamWriter() = default; - -public: - /** \brief Serialize a Value in JSON format. - * \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not - * return a value. - */ - void write(OStream& out, const Value& root); - -private: - void writeValue(const Value& value); - void writeArrayValue(const Value& value); - bool isMultilineArray(const Value& value); - void pushValue(const String& value); - void writeIndent(); - void writeWithIndent(const String& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(const Value& root); - void writeCommentAfterValueOnSameLine(const Value& root); - static bool hasCommentForValue(const Value& value); - static String normalizeEOL(const String& text); - - using ChildValues = std::vector; - - ChildValues childValues_; - OStream* document_; - String indentString_; - unsigned int rightMargin_{74}; - String indentation_; - bool addChildValues_ : 1; - bool indented_ : 1; -}; -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -#if defined(JSON_HAS_INT64) -String JSON_API valueToString(Int value); -String JSON_API valueToString(UInt value); -#endif // if defined(JSON_HAS_INT64) -String JSON_API valueToString(LargestInt value); -String JSON_API valueToString(LargestUInt value); -String JSON_API valueToString( - double value, unsigned int precision = Value::defaultRealPrecision, - PrecisionType precisionType = PrecisionType::significantDigits); -String JSON_API valueToString(bool value); -String JSON_API valueToQuotedString(const char* value); - -/// \brief Output using the StyledStreamWriter. -/// \see Json::operator>>() -JSON_API OStream& operator<<(OStream&, const Value& root); - -} // namespace Json - -#pragma pack(pop) - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // JSON_WRITER_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/writer.h -// ////////////////////////////////////////////////////////////////////// - - - - - - -// ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/assertions.h -// ////////////////////////////////////////////////////////////////////// - -// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_ASSERTIONS_H_INCLUDED -#define JSON_ASSERTIONS_H_INCLUDED - -#include -#include - -#if !defined(JSON_IS_AMALGAMATION) -#include "config.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -/** It should not be possible for a maliciously designed file to - * cause an abort() or seg-fault, so these macros are used only - * for pre-condition violations and internal logic errors. - */ -#if JSON_USE_EXCEPTION - -// @todo <= add detail about condition in exception -#define JSON_ASSERT(condition) \ - do { \ - if (!(condition)) { \ - Json::throwLogicError("assert json failed"); \ - } \ - } while (0) - -#define JSON_FAIL_MESSAGE(message) \ - do { \ - OStringStream oss; \ - oss << message; \ - Json::throwLogicError(oss.str()); \ - abort(); \ - } while (0) - -#else // JSON_USE_EXCEPTION - -#define JSON_ASSERT(condition) assert(condition) - -// The call to assert() will show the failure message in debug builds. In -// release builds we abort, for a core-dump or debugger. -#define JSON_FAIL_MESSAGE(message) \ - { \ - OStringStream oss; \ - oss << message; \ - assert(false && oss.str().c_str()); \ - abort(); \ - } - -#endif - -#define JSON_ASSERT_MESSAGE(condition, message) \ - do { \ - if (!(condition)) { \ - JSON_FAIL_MESSAGE(message); \ - } \ - } while (0) - -#endif // JSON_ASSERTIONS_H_INCLUDED - -// ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/assertions.h -// ////////////////////////////////////////////////////////////////////// - - - - - -#endif //ifndef JSON_AMALGAMATED_H_INCLUDED diff --git a/Modules/IGTBase/autoload/IO/mitkNavigationDataSetWriterXML.cpp b/Modules/IGTBase/autoload/IO/mitkNavigationDataSetWriterXML.cpp index 1256f4f8f0..c39e49d5c1 100644 --- a/Modules/IGTBase/autoload/IO/mitkNavigationDataSetWriterXML.cpp +++ b/Modules/IGTBase/autoload/IO/mitkNavigationDataSetWriterXML.cpp @@ -1,141 +1,143 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkNavigationDataSetWriterXML.h" #include #include // Third Party #include #include #include #include mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML() : AbstractFileWriter(NavigationDataSet::GetStaticNameOfClass(), mitk::IGTMimeTypes::NAVIGATIONDATASETXML_MIMETYPE(), "MITK NavigationDataSet Writer (XML)") { RegisterService(); } mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML(const mitk::NavigationDataSetWriterXML& other) : AbstractFileWriter(other) { } mitk::NavigationDataSetWriterXML::~NavigationDataSetWriterXML() { } mitk::NavigationDataSetWriterXML* mitk::NavigationDataSetWriterXML::Clone() const { return new NavigationDataSetWriterXML(*this); } void mitk::NavigationDataSetWriterXML::Write() { std::ostream* out = GetOutputStream(); if (out == nullptr) { out = new std::ofstream( GetOutputLocation().c_str() ); } mitk::NavigationDataSet::ConstPointer data = dynamic_cast (this->GetInput()); mitk::LocaleSwitch localeSwitch("C"); StreamHeader(out, data); StreamData(out, data); StreamFooter(out); // Cleanup out->flush(); delete out; } void mitk::NavigationDataSetWriterXML::StreamHeader (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data) { stream->precision(10); //TODO store date and GMT time //checking if the stream is good if (stream->good()) { *stream << "" << std::endl; /**m_Stream << "" << std::endl;*/ // should be a generic version, meaning a member variable, which has the actual version *stream << " " << "GetNumberOfTools() << "\" version=\"1.0\">" << std::endl; } } void mitk::NavigationDataSetWriterXML::StreamData (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data) { // For each time step in the Dataset for (auto it = data->Begin(); it != data->End(); it++) { for (std::size_t toolIndex = 0; toolIndex < it->size(); toolIndex++) { mitk::NavigationData::Pointer nd = it->at(toolIndex); tinyxml2::XMLDocument doc; auto *elem = doc.NewElement("ND"); elem->SetAttribute("Time", nd->GetIGTTimeStamp()); // elem->SetAttribute("SystemTime", sysTimeStr); // tag for system time elem->SetAttribute("Tool", static_cast(toolIndex)); elem->SetAttribute("X", nd->GetPosition()[0]); elem->SetAttribute("Y", nd->GetPosition()[1]); elem->SetAttribute("Z", nd->GetPosition()[2]); elem->SetAttribute("QX", nd->GetOrientation()[0]); elem->SetAttribute("QY", nd->GetOrientation()[1]); elem->SetAttribute("QZ", nd->GetOrientation()[2]); elem->SetAttribute("QR", nd->GetOrientation()[3]); elem->SetAttribute("C00", nd->GetCovErrorMatrix()[0][0]); elem->SetAttribute("C01", nd->GetCovErrorMatrix()[0][1]); elem->SetAttribute("C02", nd->GetCovErrorMatrix()[0][2]); elem->SetAttribute("C03", nd->GetCovErrorMatrix()[0][3]); elem->SetAttribute("C04", nd->GetCovErrorMatrix()[0][4]); elem->SetAttribute("C05", nd->GetCovErrorMatrix()[0][5]); elem->SetAttribute("C10", nd->GetCovErrorMatrix()[1][0]); elem->SetAttribute("C11", nd->GetCovErrorMatrix()[1][1]); elem->SetAttribute("C12", nd->GetCovErrorMatrix()[1][2]); elem->SetAttribute("C13", nd->GetCovErrorMatrix()[1][3]); elem->SetAttribute("C14", nd->GetCovErrorMatrix()[1][4]); elem->SetAttribute("C15", nd->GetCovErrorMatrix()[1][5]); if (nd->IsDataValid()) elem->SetAttribute("Valid",1); else elem->SetAttribute("Valid",0); if (nd->GetHasOrientation()) elem->SetAttribute("hO",1); else elem->SetAttribute("hO",0); if (nd->GetHasPosition()) elem->SetAttribute("hP",1); else elem->SetAttribute("hP",0); + doc.InsertFirstChild(elem); + tinyxml2::XMLPrinter printer; doc.Print(&printer); *stream << " " << printer.CStr() << std::endl; } } } void mitk::NavigationDataSetWriterXML::StreamFooter (std::ostream* stream) { *stream << "" << std::endl; } diff --git a/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.cpp b/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.cpp index ee1b33b256..7858243380 100644 --- a/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.cpp +++ b/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.cpp @@ -1,421 +1,421 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkFiducialRegistrationWidget.h" #include #include #include #include #include #include #include #define FRW_LOG MITK_INFO("Fiducial Registration Widget") #define FRW_WARN MITK_WARN("Fiducial Registration Widget") #define FRW_DEBUG MITK_DEBUG("Fiducial Registration Widget") /* VIEW MANAGEMENT */ QmitkFiducialRegistrationWidget::QmitkFiducialRegistrationWidget(QWidget* parent) : QWidget(parent), m_Controls(nullptr),m_MultiWidget(nullptr), m_ImageFiducialsNode(nullptr), m_TrackerFiducialsNode(nullptr) { CreateQtPartControl(this); } QmitkFiducialRegistrationWidget::~QmitkFiducialRegistrationWidget() { m_Controls = nullptr; //clean up data nodes if (m_DataStorage.IsNotNull()) { if (m_ImageFiducialsNode.IsNotNull()) m_DataStorage->Remove(m_ImageFiducialsNode); if (m_TrackerFiducialsNode.IsNotNull()) m_DataStorage->Remove(m_TrackerFiducialsNode); } } void QmitkFiducialRegistrationWidget::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { // create GUI widgets m_Controls = new Ui::QmitkFiducialRegistrationWidget; m_Controls->setupUi(parent); // hide additional image fiducial button m_Controls->m_AddImageFiducialBtn->setHidden(true); m_Controls->m_spaceHolderGroupBox->setStyleSheet("QGroupBox {border: 0px transparent;}"); m_Controls->m_spaceHolderGroupBox2->setStyleSheet("QGroupBox {border: 0px transparent;}"); this->CreateConnections(); } } void QmitkFiducialRegistrationWidget::CreateConnections() { connect( (QObject*)(m_Controls->m_AddTrackingFiducialBtn), SIGNAL(clicked()), this, SIGNAL(AddedTrackingFiducial()) ); connect((QObject*)(m_Controls->m_AddTrackingFiducialBtn), SIGNAL(clicked()), this, SLOT(AddTrackerPoint())); connect( (QObject*)(m_Controls->m_AddImageFiducialBtn), SIGNAL(clicked()), this, SIGNAL(AddedImageFiducial()) ); connect( (QObject*)(m_Controls->m_RegisterFiducialsBtn), SIGNAL(clicked()), this, SIGNAL(PerformFiducialRegistration()) ); connect((QObject*)(m_Controls->m_RegisterFiducialsBtn), SIGNAL(clicked()), this, SLOT(Register())); connect( (QObject*)(m_Controls->m_UseICPRegistration), SIGNAL(toggled(bool)), this, SIGNAL(FindFiducialCorrespondences(bool)) ); //unselects the edit button of the other widget if one is selected connect( (QObject*)(m_Controls->m_RegistrationImagePoints), SIGNAL(EditPointSets(bool)), this, SLOT(DisableEditButtonRegistrationTrackingPoints(bool))); connect( (QObject*)(m_Controls->m_RegistrationTrackingPoints), SIGNAL(EditPointSets(bool)), this, SLOT(DisableEditButtonRegistrationImagePoints(bool))); } void QmitkFiducialRegistrationWidget::DisableEditButtonRegistrationImagePoints(bool activated) { if (activated) m_Controls->m_RegistrationImagePoints->UnselectEditButton(); } void QmitkFiducialRegistrationWidget::DisableEditButtonRegistrationTrackingPoints(bool activated) { if (activated) m_Controls->m_RegistrationTrackingPoints->UnselectEditButton(); } void QmitkFiducialRegistrationWidget::SetWidgetAppearanceMode(WidgetAppearanceMode widgetMode) { if (widgetMode==LANDMARKMODE) { this->HideContinousRegistrationRadioButton(true); this->HideStaticRegistrationRadioButton(true); this->HideFiducialRegistrationGroupBox(); this->HideUseICPRegistrationCheckbox(true); this->HideImageFiducialButton(false); this->m_Controls->sourceLandmarksGroupBox->setTitle("Target/Reference landmarks"); this->m_Controls->targetLandmarksGroupBox->setTitle("Source Landmarks"); this->m_Controls->m_AddImageFiducialBtn->setText("Add target landmark"); this->m_Controls->m_AddTrackingFiducialBtn->setText("Add source landmark"); } else if (widgetMode==FIDUCIALMODE) { this->HideContinousRegistrationRadioButton(false); this->HideStaticRegistrationRadioButton(false); this->HideFiducialRegistrationGroupBox(); this->HideUseICPRegistrationCheckbox(false); this->HideImageFiducialButton(true); this->m_Controls->sourceLandmarksGroupBox->setTitle("Image fiducials"); this->m_Controls->targetLandmarksGroupBox->setTitle("OR fiducials"); this->m_Controls->m_AddImageFiducialBtn->setText("Add image fiducial"); this->m_Controls->m_AddTrackingFiducialBtn->setText("Add current instrument position"); } } void QmitkFiducialRegistrationWidget::SetQualityDisplayText( QString text ) { if (text == nullptr) return; m_Controls->m_RegistrationQualityDisplay->setText(text); // set text on the QLabel } bool QmitkFiducialRegistrationWidget::UseICPIsChecked() { if(m_Controls->m_UseICPRegistration->isChecked()) return true; else return false; } void QmitkFiducialRegistrationWidget::SetImageFiducialsNode( mitk::DataNode::Pointer imageFiducialsNode ) { if(imageFiducialsNode.IsNull()) { FRW_WARN<< "tracker fiducial node is nullptr"; return; } m_Controls->m_RegistrationImagePoints->SetPointSetNode(imageFiducialsNode); // pass node to pointListWidget if(m_MultiWidget == nullptr) { - MITK_DEBUG<< "stdMultiWidget is nullptr"; + MITK_DEBUG<< "MultiWidget is nullptr"; return; } m_Controls->m_RegistrationImagePoints->SetMultiWidget(m_MultiWidget); // pass multiWidget to pointListWidget } void QmitkFiducialRegistrationWidget::SetTrackerFiducialsNode( mitk::DataNode::Pointer trackerFiducialsNode ) { if(trackerFiducialsNode.IsNull()) { FRW_WARN<< "tracker fiducial node is nullptr"; return; } m_Controls->m_RegistrationTrackingPoints->SetPointSetNode(trackerFiducialsNode); // pass node to pointListWidget if(m_MultiWidget == nullptr) { - MITK_DEBUG<< "stdMultiWidget is nullptr"; + MITK_DEBUG<< "MultiWidget is nullptr"; return; } m_Controls->m_RegistrationTrackingPoints->SetMultiWidget(m_MultiWidget); // pass multiWidget to pointListWidget } -void QmitkFiducialRegistrationWidget::SetMultiWidget( QmitkStdMultiWidget* multiWidget ) +void QmitkFiducialRegistrationWidget::SetMultiWidget( QmitkAbstractMultiWidget* multiWidget ) { m_MultiWidget=multiWidget; } void QmitkFiducialRegistrationWidget::AddSliceNavigationController(mitk::SliceNavigationController* snc) { m_Controls->m_RegistrationTrackingPoints->AddSliceNavigationController(snc); m_Controls->m_RegistrationImagePoints->AddSliceNavigationController(snc); } mitk::DataNode::Pointer QmitkFiducialRegistrationWidget::GetImageFiducialsNode() { return m_ImageFiducialsNode; } mitk::DataNode::Pointer QmitkFiducialRegistrationWidget::GetTrackerFiducialsNode() { return m_TrackerFiducialsNode; } void QmitkFiducialRegistrationWidget::HideStaticRegistrationRadioButton( bool on ) { m_Controls->m_rbStaticRegistration->setHidden(on); HideFiducialRegistrationGroupBox(); } void QmitkFiducialRegistrationWidget::HideContinousRegistrationRadioButton( bool on ) { m_Controls->m_rbContinousRegistration->setHidden(on); HideFiducialRegistrationGroupBox(); } void QmitkFiducialRegistrationWidget::HideFiducialRegistrationGroupBox() { if (m_Controls->m_rbStaticRegistration->isHidden() && m_Controls->m_rbContinousRegistration->isHidden()) { m_Controls->m_gbFiducialRegistration->setHidden(true); } else { m_Controls->m_gbFiducialRegistration->setHidden(false); } } void QmitkFiducialRegistrationWidget::HideUseICPRegistrationCheckbox( bool on ) { m_Controls->m_UseICPRegistration->setHidden(on); } void QmitkFiducialRegistrationWidget::HideImageFiducialButton( bool on ) { m_Controls->m_AddImageFiducialBtn->setHidden(on); AdjustButtonSpacing(); } void QmitkFiducialRegistrationWidget::HideTrackingFiducialButton( bool on ) { m_Controls->m_AddTrackingFiducialBtn->setHidden(on); AdjustButtonSpacing(); } void QmitkFiducialRegistrationWidget::AdjustButtonSpacing() { if (m_Controls->m_AddImageFiducialBtn->isHidden() && m_Controls->m_AddTrackingFiducialBtn->isHidden()) { m_Controls->m_spaceHolderGroupBox->setHidden(true); m_Controls->m_spaceHolderGroupBox2->setHidden(true); } else { m_Controls->m_spaceHolderGroupBox->setHidden(false); m_Controls->m_spaceHolderGroupBox2->setHidden(false); } } void QmitkFiducialRegistrationWidget::SetSourceLandmarkName(QString sourceLandmarkName) { QString groupBoxTitle = sourceLandmarkName; groupBoxTitle.append(" Landmarks"); m_Controls->sourceLandmarksGroupBox->setTitle(groupBoxTitle); QString buttonText = "Add "; buttonText.append(sourceLandmarkName); buttonText.append(" Landmark"); m_Controls->m_AddImageFiducialBtn->setText(buttonText); } void QmitkFiducialRegistrationWidget::SetTargetLandmarkName(QString targetLandmarkName) { QString groupBoxTitle = targetLandmarkName; groupBoxTitle.append(" Landmarks"); m_Controls->targetLandmarksGroupBox->setTitle(groupBoxTitle); QString buttonText = "Add "; buttonText.append(targetLandmarkName); buttonText.append(" Landmark"); m_Controls->m_AddTrackingFiducialBtn->setText(buttonText); } void QmitkFiducialRegistrationWidget::setImageNode(mitk::DataNode::Pointer i) { m_ImageNode = i; } void QmitkFiducialRegistrationWidget::setTrackerNavigationData(mitk::NavigationData::Pointer t) { m_TrackerNavigationData = t; } void QmitkFiducialRegistrationWidget::setDataStorage(mitk::DataStorage::Pointer d) { m_DataStorage = d; mitk::DataNode::Pointer ImageFiducialsNode = mitk::DataNode::New(); mitk::PointSet::Pointer imagePointSet = mitk::PointSet::New(); ImageFiducialsNode->SetData(imagePointSet); ImageFiducialsNode->SetName("Image Point Set"); m_DataStorage->Add(ImageFiducialsNode); this->SetImageFiducialsNode(ImageFiducialsNode); m_ImageFiducialsNode = ImageFiducialsNode; mitk::DataNode::Pointer TrackerFiducialsNode = mitk::DataNode::New(); mitk::PointSet::Pointer trackerPointSet = mitk::PointSet::New(); TrackerFiducialsNode->SetData(trackerPointSet); TrackerFiducialsNode->SetName("Tracker Point Set"); m_DataStorage->Add(TrackerFiducialsNode); this->SetTrackerFiducialsNode(TrackerFiducialsNode); m_TrackerFiducialsNode = TrackerFiducialsNode; } void QmitkFiducialRegistrationWidget::AddTrackerPoint() { if (m_DataStorage.IsNull()) { return ; } //here the widget should simply do nothing (for backward compatibility) else if (m_TrackerNavigationData.IsNull() || m_TrackerFiducialsNode.IsNull()) { MITK_WARN << "Tracker node not correctly initialized"; return; } mitk::PointSet::Pointer ps = dynamic_cast(m_TrackerFiducialsNode->GetData()); ps->InsertPoint(ps->GetSize(), m_TrackerNavigationData->GetPosition()); } bool QmitkFiducialRegistrationWidget::CheckRegistrationInitialization() { if (m_DataStorage.IsNull()) { return false; } //here the widget should simply do nothing (for backward compatibility) else if ( m_ImageFiducialsNode.IsNull() || m_TrackerFiducialsNode.IsNull() ) {MITK_WARN << "Registration not correctly initialized"; return false;} else {return true;} } void QmitkFiducialRegistrationWidget::Register() { //Check for initialization if (!CheckRegistrationInitialization()) return; /* retrieve fiducials */ mitk::PointSet::Pointer imageFiducials = dynamic_cast(m_ImageFiducialsNode->GetData()); mitk::PointSet::Pointer trackerFiducials = dynamic_cast(m_TrackerFiducialsNode->GetData()); if (trackerFiducials->GetSize() != imageFiducials->GetSize()) { MITK_WARN << "Not the same number of fiducials, cannot register"; return; } else if (trackerFiducials->GetSize() < 3) { MITK_WARN << "Need at least 3 fiducials, cannot register"; return; } //############### conversion to vtk data types (we will use the vtk landmark based transform) ########################## //convert point sets to vtk poly data vtkSmartPointer sourcePoints = vtkSmartPointer::New(); vtkSmartPointer targetPoints = vtkSmartPointer::New(); for (int i = 0; iGetSize(); i++) { double point[3] = { imageFiducials->GetPoint(i)[0], imageFiducials->GetPoint(i)[1], imageFiducials->GetPoint(i)[2] }; sourcePoints->InsertNextPoint(point); double point_targets[3] = { trackerFiducials->GetPoint(i)[0], trackerFiducials->GetPoint(i)[1], trackerFiducials->GetPoint(i)[2] }; targetPoints->InsertNextPoint(point_targets); } //########################### here, the actual transform is computed ########################## //compute transform vtkSmartPointer transform = vtkSmartPointer::New(); transform->SetSourceLandmarks(sourcePoints); transform->SetTargetLandmarks(targetPoints); transform->SetModeToRigidBody(); transform->Modified(); transform->Update(); //compute FRE of transform double FRE = mitk::StaticIGTHelperFunctions::ComputeFRE(imageFiducials, trackerFiducials, transform); this->SetQualityDisplayText("FRE: " + QString::number(FRE) + " mm"); //############################################################################################# //############### conversion back to itk/mitk data types ########################## //convert from vtk to itk data types itk::Matrix rotationFloat = itk::Matrix(); itk::Vector translationFloat = itk::Vector(); itk::Matrix rotationDouble = itk::Matrix(); itk::Vector translationDouble = itk::Vector(); vtkSmartPointer m = transform->GetMatrix(); for (int k = 0; k<3; k++) for (int l = 0; l<3; l++) { rotationFloat[k][l] = m->GetElement(k, l); rotationDouble[k][l] = m->GetElement(k, l); } for (int k = 0; k<3; k++) { translationFloat[k] = m->GetElement(k, 3); translationDouble[k] = m->GetElement(k, 3); } //create affine transform 3D surface mitk::AffineTransform3D::Pointer mitkTransform = mitk::AffineTransform3D::New(); mitkTransform->SetMatrix(rotationDouble); mitkTransform->SetOffset(translationDouble); //############################################################################################# //############### object is transformed ########################## //save transform m_T_ObjectReg = mitk::NavigationData::New(mitkTransform); // this is stored in a member because it is needed for permanent registration later on //transform surface/image //only move image if we have one. Sometimes, this widget is used just to register point sets without images. if (m_ImageNode.IsNotNull()) { //first we have to store the original ct image transform to compose it with the new transform later mitk::AffineTransform3D::Pointer imageTransform = m_ImageNode->GetData()->GetGeometry()->GetIndexToWorldTransform(); imageTransform->Compose(mitkTransform); mitk::AffineTransform3D::Pointer newImageTransform = mitk::AffineTransform3D::New(); //create new image transform... setting the composed directly leads to an error itk::Matrix rotationFloatNew = imageTransform->GetMatrix(); itk::Vector translationFloatNew = imageTransform->GetOffset(); newImageTransform->SetMatrix(rotationFloatNew); newImageTransform->SetOffset(translationFloatNew); m_ImageNode->GetData()->GetGeometry()->SetIndexToWorldTransform(newImageTransform); } //If this option is set, each point will be transformed and the acutal coordinates of the points change. if (this->m_Controls->m_MoveImagePoints->isChecked()) { mitk::PointSet* pointSet_orig = dynamic_cast(m_ImageFiducialsNode->GetData()); mitk::PointSet::Pointer pointSet_moved = mitk::PointSet::New(); for (int i = 0; i < pointSet_orig->GetSize(); i++) { pointSet_moved->InsertPoint(mitkTransform->TransformPoint(pointSet_orig->GetPoint(i))); } pointSet_orig->Clear(); for (int i = 0; i < pointSet_moved->GetSize(); i++) pointSet_orig->InsertPoint(pointSet_moved->GetPoint(i)); } //Do a global reinit mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); } diff --git a/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.h b/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.h index 2c6e5a2ddf..cf8aaf9ee3 100644 --- a/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.h +++ b/Modules/IGTUI/Qmitk/QmitkFiducialRegistrationWidget.h @@ -1,132 +1,132 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 _QmitkFiducialRegistrationWidget_H_INCLUDED #define _QmitkFiducialRegistrationWidget_H_INCLUDED #include "ui_QmitkFiducialRegistrationWidget.h" -#include "QmitkStdMultiWidget.h" +#include "QmitkAbstractMultiWidget.h" #include "MitkIGTUIExports.h" #include "mitkNavigationData.h" /*! * \brief IGT Fiducial Registration Widget * * Widget used to set fiducial landmarks in the image and to confirm the corresponding landmarks on the world object (patient/phantom). * * The widget can add tracker fiducials and perform the registration internally. To enable this functionaltity the * methods SetDataStorage(), setTrackerNavigationData() and setImageNode() needs to be called before. * * If Registration should be handled from outside the class the methods SetImageFiducialsNode() and SetTrackerFiducialsNode() * must be called, otherwise the QmitkPointListWidget can not work. If SetDataStorage() is not called the widget does nothing * internally. * * \sa IGT */ class MITKIGTUI_EXPORT QmitkFiducialRegistrationWidget : public QWidget { Q_OBJECT // this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) public: QmitkFiducialRegistrationWidget(QWidget* parent); ~QmitkFiducialRegistrationWidget() override; /** Adds the image node which is transformed after "register" is clicked. */ void setImageNode(mitk::DataNode::Pointer i); /** Adds the tracker navigation data which is used when "add current instrument position" is clicked. */ void setTrackerNavigationData(mitk::NavigationData::Pointer t); /** Sets the data storage. This is required is the widget shoul add tracker points and perform * registrations internally. When not setting the data storage the widget can still be used * by reacting on the signals and do custom actions outside.*/ void setDataStorage(mitk::DataStorage::Pointer d); /*! \brief enumeration to specify the appearance of the widget. 'FIDUCIALMODE' is likely to be used for (tracking) fiducial based registration purposes 'LANDMARKMODE' can be used for any kind of landmark based registration (source landmarks -> target/reference landmarks) */ enum WidgetAppearanceMode { FIDUCIALMODE, LANDMARKMODE }; /*! \brief set the appearance mode of this widget 'FIDUCIALMODE' adapts the widget for (tracking) fiducial based registration purposes 'LANDMARKMODE' adapts the widget for landmark based registration (source landmarks -> target/reference landmarks) */ void SetWidgetAppearanceMode(WidgetAppearanceMode widgetMode); - void SetMultiWidget(QmitkStdMultiWidget* multiWidget); ///< (Deprecated method. Multiwidget is not required any more.) Set the default stdMultiWidget (needed for the PointListwidget) + void SetMultiWidget(QmitkAbstractMultiWidget* multiWidget); ///< (Deprecated method. Multiwidget is not required any more.) Set the default MultiWidget (needed for the PointListwidget) void AddSliceNavigationController(mitk::SliceNavigationController* snc); ///< add the slice navigation controller to be used to move the crosshair to the actual point position void SetImageFiducialsNode(mitk::DataNode::Pointer imageFiducialsNode); ///< specify data tree node for the image fiducials void SetTrackerFiducialsNode(mitk::DataNode::Pointer trackerFiducialsNode); ///< specify data tree node for the tracker fiducials mitk::DataNode::Pointer GetImageFiducialsNode(); ///< returns data tree node for the image fiducials mitk::DataNode::Pointer GetTrackerFiducialsNode(); ///< returns data tree node for the tracker fiducials void SetQualityDisplayText(QString text); ///< sets specific text on the UI (useful to display FRE/TRE...) /*! \brief Specify the name of the source landmarks. Will be used for label and button. Example: sourceLandmarkName="CT" will result in group box title "CT landmarks" and button text "Add CT landmark". */ void SetSourceLandmarkName(QString sourceLandmarkName); /*! \brief Specify the name of the source landmarks. Will be used for label and button. Example: targetLandmarkName="CT" will result in group box title "CT landmarks" and button text "Add CT landmark". */ void SetTargetLandmarkName(QString targetLandmarkName); bool UseICPIsChecked(); ///< returns true if automatic correspondences search is activated else false void HideStaticRegistrationRadioButton(bool on); ///< show or hide "static Fiducial Registration" radio button in the UI void HideContinousRegistrationRadioButton(bool on); ///< show or hide "hybrid continuous Fiducial Registration" radio button in the UI void HideFiducialRegistrationGroupBox(); ///< show or hide "Fiducial Registration method" groupbox in the UI, depending on the visibility of the radio buttons void HideUseICPRegistrationCheckbox(bool on); ///< show or hide "Find fiducial correspondences (needs 6+ fiducial pairs)" check box in the UI void HideImageFiducialButton(bool on); ///< show or hide "Add image fiducial" button in the UI void HideTrackingFiducialButton(bool on); ///< show or hide "Add tracking fiducial" button in the UI void AdjustButtonSpacing(); ///< Rearrange spacing when buttons are turned on or off signals: void AddedTrackingFiducial(); ///< signal if a world instrument position was added to a tracking space fiducial void AddedImageFiducial(); ///< signal if an image position was added to a image space fiducial void PerformFiducialRegistration(); ///< signal if all fiducial were added and registration can be performed void FindFiducialCorrespondences(bool on); ///< signal if automatic correspondences search is toggled protected slots: void DisableEditButtonRegistrationImagePoints(bool);///< Disables the edit button of the widget RegistrationImagePoints if the activated variable is true. void DisableEditButtonRegistrationTrackingPoints(bool);///< Disables the edit button of the widget RegistrationTrackingPoints if the activated variable is true. void AddTrackerPoint(); void Register(); protected: void CreateQtPartControl(QWidget *parent); void CreateConnections(); bool CheckRegistrationInitialization(); Ui::QmitkFiducialRegistrationWidget* m_Controls; ///< gui widget - QmitkStdMultiWidget* m_MultiWidget; + QmitkAbstractMultiWidget* m_MultiWidget; mitk::DataNode::Pointer m_ImageFiducialsNode; mitk::DataNode::Pointer m_TrackerFiducialsNode; mitk::DataStorage::Pointer m_DataStorage; mitk::NavigationData::Pointer m_TrackerNavigationData; mitk::DataNode::Pointer m_ImageNode; mitk::NavigationData::Pointer m_T_ObjectReg; }; #endif // _QmitkFiducialRegistrationWidget_H_INCLUDED diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 3e2514a6b3..6612d48157 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1125 +1,1160 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack - AddLayer(); + if (this->GetNumberOfLayers() == 0) + { + AddLayer(); + } } mitk::LabelSetImage::~LabelSetImage() { m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } unsigned int newLabelSetId = this->AddLayer(newImage, labelSet); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabel(PixelType pixelValue, unsigned int layer) { this->GetLabelSet(layer)->RemoveLabel(pixelValue); this->EraseLabel(pixelValue); } void mitk::LabelSetImage::RemoveLabels(std::vector& VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(this, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } Modified(); } void mitk::LabelSetImage::EraseLabels(std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { // for now, we just retrieve the voxel in the middle typedef itk::ImageRegionConstIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); std::vector indexVector; while (!iter.IsAtEnd()) { // TODO fix comparison warning more effective if (iter.Get() == pixelValue) { indexVector.push_back(iter.GetIndex()); } ++iter; } mitk::Point3D pos; pos.Fill(0.0); if (!indexVector.empty()) { typename itk::ImageRegionConstIteratorWithIndex::IndexType centerIndex; centerIndex = indexVector.at(indexVector.size() / 2); if (centerIndex.GetIndexDimension() == 3) { pos[0] = centerIndex[0]; pos[1] = centerIndex[1]; pos[2] = centerIndex[2]; } else return; } GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { - if (existingSourceValue == this->m_SourceLabel - && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle - || !this->m_DestinationLabelSet->GetLabel(existingDestinationValue)->GetLocked())) + if (existingSourceValue == this->m_SourceLabel) { - return this->m_NewDestinationLabel; + if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) + { + return this->m_NewDestinationLabel; + } + else + { + auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); + if (nullptr == label || !label->GetLocked()) + { + return this->m_NewDestinationLabel; + } + } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContent to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template -void TransferLabelContentHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) +void TransferLabelContentHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, + const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, + bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->Update(); } void mitk::TransferLabelContent( - const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, + const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, + mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } - - const auto sourceBackground = sourceImage->GetExteriorLabel()->GetValue(); - const auto destinationBackground = destinationImage->GetExteriorLabel()->GetValue(); - const auto destinationBackgroundLocked = destinationImage->GetExteriorLabel()->GetLocked(); - const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); + if (nullptr == destinationLabelSet) + { + mitkThrow() << "Invalid call of TransferLabelContent; destinationLabelSet must not be null"; + } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { - if (!sourceImage->ExistLabel(sourceLabel, sourceImage->GetActiveLayer())) - { - mitkThrow() << "Invalid call of TransferLabelContent. Defined source label does not exist in sourceImage. SourceLabel: " << sourceLabel; - } - if (!destinationImage->ExistLabel(newDestinationLabel, destinationImage->GetActiveLayer())) + if (nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContent. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } - AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } + +void mitk::TransferLabelContent( + const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, + MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) +{ + if (nullptr == sourceImage) + { + mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; + } + + const auto sourceBackground = sourceImage->GetExteriorLabel()->GetValue(); + const auto destinationBackground = destinationImage->GetExteriorLabel()->GetValue(); + const auto destinationBackgroundLocked = destinationImage->GetExteriorLabel()->GetLocked(); + const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); + + for (const auto& mappingElement : labelMapping) + { + if (!sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) + { + mitkThrow() << "Invalid call of TransferLabelContent. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; + } + } + + TransferLabelContent(sourceImage, destinationImage, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, + labelMapping, mergeStyle, overwriteStlye, timeStep); +} diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index 5701e558b0..491b80ca67 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,387 +1,417 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __mitkLabelSetImage_H_ #define __mitkLabelSetImage_H_ #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes the label with the given value. * The label is removed from the labelset of the given layer and * the pixel values of the image below the label are reset. * @param pixelValue the pixel value of the label to be removed * @param layer the layer from which the label should be removed */ void RemoveLabel(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes a list of labels with th given value. * The labels are removed from the labelset of the given layer and * the pixel values of the image below the label are reset. * Calls mitk::LabelSetImage::EraseLabels(). * @param VectorOfLabelPixelValues a list of pixel values of labels to be removed * @param layer the layer from which the labels should be removed */ void RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ bool ExistLabel(PixelType pixelValue) const; /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ bool ExistLabel(PixelType pixelValue, unsigned int layer) const; /** * \brief Returns true if the labelset exists*/ bool ExistLabelSet(unsigned int layer) const; /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ mitk::Label *GetActiveLabel(unsigned int layer = 0); const mitk::Label* GetActiveLabel(unsigned int layer = 0) const; /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer = 0) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ mitk::LabelSet *GetActiveLabelSet(); const mitk::LabelSet* GetActiveLabelSet() const; /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a LabelSet to an existing layer * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); /** * @brief Sets the label which is used as default exterior label when creating a new layer * @param label the label which will be used as new exterior label */ void SetExteriorLabel(mitk::Label *label); /** * @brief Gets the mitk::Label which is used as default exterior label * @return the exterior mitk::Label */ mitk::Label *GetExteriorLabel(); const mitk::Label *GetExteriorLabel() const; protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; mitk::Label::Pointer m_ExteriorLabel; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a lable value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. - @param destionationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. + @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks, const TimeStepType timeStep = 0); + /**Helper function that transfers pixels of the specified source label from source image to the destination image by using + a specified destination label. Function processes the whole image volume of the specified time step. + @remark the function assumes that it is only called with source and destination image of same geometry. + @param sourceImage Pointer to the image that should be used as source for the transfer. + @param destinationImage Pointer to the image that should be used as destination for the transfer. + @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel + values in the destinationImage will be assumed to be unlocked. + @param sourceBackground Value indicating the background in the source image. + @param destinationBackground Value indicating the background in the destination image. + @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. + @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the + label in the source image. The second element is the label that transferred pixels should become in the destination image. + The order in which the labels will be transfered is the same order of elements in the labelMapping. + If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that + for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be + altered with MergeStyle Replace). + @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of + MultiLabelSegmentation::MergeStyle. + @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see + documentation of MultiLabelSegmentation::OverwriteStyle. + @param timeStep indicate the time step that should be transferred. + @pre sourceImage, destinationImage and destinationLabelSet must be valid + @pre sourceImage and destinationImage must contain the indicated timeStep + @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ + MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, + mitk::Label::PixelType sourceBackground = 0, mitk::Label::PixelType destinationBackground = 0, bool destinationBackgroundLocked = false, + std::vector > labelMapping = { {1,1} }, + MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, + MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks, + const TimeStepType timeStep = 0); } // namespace mitk #endif // __mitkLabelSetImage_H_ diff --git a/Modules/Pharmacokinetics/src/Models/mitkDescriptivePharmacokineticBrixModel.cpp b/Modules/Pharmacokinetics/src/Models/mitkDescriptivePharmacokineticBrixModel.cpp index 6e43f11d5b..438f0353c3 100644 --- a/Modules/Pharmacokinetics/src/Models/mitkDescriptivePharmacokineticBrixModel.cpp +++ b/Modules/Pharmacokinetics/src/Models/mitkDescriptivePharmacokineticBrixModel.cpp @@ -1,257 +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 "mitkDescriptivePharmacokineticBrixModel.h" const std::string mitk::DescriptivePharmacokineticBrixModel::MODEL_DISPLAY_NAME = "Descriptive Pharmacokinetic Brix Model"; const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_PARAMETER_A = "A"; const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_PARAMETER_kep = "kep"; const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_PARAMETER_kel = "kel"; //tlag in minutes const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_PARAMETER_tlag = "tlag"; const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_PARAMETER_A = "a.u."; const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_PARAMETER_kep = "1/min"; const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_PARAMETER_kel = "1/min"; //tlag in minutes const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_PARAMETER_tlag = "min"; const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_STATIC_PARAMETER_Tau = "Tau"; const std::string mitk::DescriptivePharmacokineticBrixModel::NAME_STATIC_PARAMETER_S0 = "S0"; const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_STATIC_PARAMETER_Tau = "min"; const std::string mitk::DescriptivePharmacokineticBrixModel::UNIT_STATIC_PARAMETER_S0 = "I"; const unsigned int mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_A = 0; const unsigned int mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kep = 1; const unsigned int mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kel = 2; const unsigned int mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_tlag = 3; const unsigned int mitk::DescriptivePharmacokineticBrixModel::NUMBER_OF_PARAMETERS = 4; std::string mitk::DescriptivePharmacokineticBrixModel::GetModelDisplayName() const { return MODEL_DISPLAY_NAME; }; std::string mitk::DescriptivePharmacokineticBrixModel::GetModelType() const { return "Perfusion.MR"; }; std::string mitk::DescriptivePharmacokineticBrixModel::GetXAxisName() const { return "Time"; }; std::string mitk::DescriptivePharmacokineticBrixModel::GetXAxisUnit() const { return "s"; } std::string mitk::DescriptivePharmacokineticBrixModel::GetYAxisName() const { return ""; }; std::string mitk::DescriptivePharmacokineticBrixModel::GetYAxisUnit() const { return ""; } mitk::DescriptivePharmacokineticBrixModel::DescriptivePharmacokineticBrixModel(): m_Tau(0), m_S0(1) { } mitk::DescriptivePharmacokineticBrixModel::~DescriptivePharmacokineticBrixModel() { } mitk::DescriptivePharmacokineticBrixModel::ParameterNamesType mitk::DescriptivePharmacokineticBrixModel::GetParameterNames() const { ParameterNamesType result; result.push_back(NAME_PARAMETER_A); result.push_back(NAME_PARAMETER_kep); result.push_back(NAME_PARAMETER_kel); result.push_back(NAME_PARAMETER_tlag); return result; } mitk::DescriptivePharmacokineticBrixModel::ParametersSizeType mitk::DescriptivePharmacokineticBrixModel::GetNumberOfParameters() const { return NUMBER_OF_PARAMETERS; } mitk::DescriptivePharmacokineticBrixModel::ParamterUnitMapType mitk::DescriptivePharmacokineticBrixModel::GetParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair(NAME_PARAMETER_A, UNIT_PARAMETER_A)); result.insert(std::make_pair(NAME_PARAMETER_kep, UNIT_PARAMETER_kep)); result.insert(std::make_pair(NAME_PARAMETER_kel, UNIT_PARAMETER_kel)); result.insert(std::make_pair(NAME_PARAMETER_tlag, UNIT_PARAMETER_tlag)); return result; }; mitk::DescriptivePharmacokineticBrixModel::ParameterNamesType mitk::DescriptivePharmacokineticBrixModel::GetStaticParameterNames() const { ParameterNamesType result; result.push_back(NAME_STATIC_PARAMETER_Tau); result.push_back(NAME_STATIC_PARAMETER_S0); return result; } mitk::DescriptivePharmacokineticBrixModel::ParamterUnitMapType mitk::DescriptivePharmacokineticBrixModel::GetStaticParameterUnits() const { ParamterUnitMapType result; result.insert(std::make_pair(NAME_STATIC_PARAMETER_Tau, UNIT_STATIC_PARAMETER_Tau)); result.insert(std::make_pair(NAME_STATIC_PARAMETER_S0, UNIT_STATIC_PARAMETER_S0)); return result; }; mitk::DescriptivePharmacokineticBrixModel::ParametersSizeType mitk::DescriptivePharmacokineticBrixModel::GetNumberOfStaticParameters() const { - return 1; + return 2; } mitk::DescriptivePharmacokineticBrixModel::ModelResultType mitk::DescriptivePharmacokineticBrixModel::ComputeModelfunction(const ParametersType& parameters) const { if (m_TimeGrid.GetSize() == 0) { itkExceptionMacro("No Time Grid Set! Cannot Calculate Signal"); } if (m_Tau == 0) { itkExceptionMacro("Injection time is 0! Cannot Calculate Signal"); } + ModelResultType signal(m_TimeGrid.GetSize()); double tx = 0; double amplitude = parameters[POSITION_PARAMETER_A]; double kel = parameters[POSITION_PARAMETER_kel]; double kep = parameters[POSITION_PARAMETER_kep]; double tlag = parameters[POSITION_PARAMETER_tlag]; + if (kep == kel) + { + itkExceptionMacro("(kep-kel) is 0! Cannot Calculate Signal"); + } + TimeGridType::const_iterator timeGridEnd = m_TimeGrid.end(); ModelResultType::iterator signalPos = signal.begin(); for (TimeGridType::const_iterator gridPos = m_TimeGrid.begin(); gridPos != timeGridEnd; ++gridPos, ++signalPos) { double t = (*gridPos) / 60.0; //convert from [sec] to [min] if (t <= tlag) { tx = 0; } else if ((t > tlag) && (t < (m_Tau + tlag))) { tx = t - tlag; } else if (t >= (m_Tau + tlag)) { tx = m_Tau; } double kDiff = kep - kel; double tDiff = t - tlag; double expkel = (kep * exp(-kel * tDiff)); double expkeltx = exp(kel * tx); double expkep = exp(-kep * tDiff); double expkeptx = exp(kep * tx); double value = 1 + (amplitude / m_Tau) * (((expkel / (kel * kDiff)) * (expkeltx - 1)) - (( expkep / kDiff) * (expkeptx - 1))); *signalPos = value * m_S0; } return signal; } void mitk::DescriptivePharmacokineticBrixModel::SetStaticParameter(const ParameterNameType& name, const StaticParameterValuesType& values) { if (name == NAME_STATIC_PARAMETER_Tau) { SetTau(values[0]); } if (name == NAME_STATIC_PARAMETER_S0) { SetS0(values[0]); } }; mitk::DescriptivePharmacokineticBrixModel::StaticParameterValuesType mitk::DescriptivePharmacokineticBrixModel::GetStaticParameterValue(const ParameterNameType& name) const { StaticParameterValuesType result; if (name == NAME_STATIC_PARAMETER_Tau) { result.push_back(GetTau()); } if (name == NAME_STATIC_PARAMETER_S0) { result.push_back(GetS0()); } return result; }; itk::LightObject::Pointer mitk::DescriptivePharmacokineticBrixModel::InternalClone() const { DescriptivePharmacokineticBrixModel::Pointer newClone = DescriptivePharmacokineticBrixModel::New(); newClone->SetTimeGrid(this->m_TimeGrid); newClone->SetTau(this->m_Tau); newClone->SetS0(this->m_S0); return newClone.GetPointer(); }; void mitk::DescriptivePharmacokineticBrixModel::PrintSelf(std::ostream& os, ::itk::Indent indent) const { Superclass::PrintSelf(os, indent); os << indent << "Tau (injection time): " << m_Tau; os << indent << "S0 (base value): " << m_S0; }; diff --git a/Modules/Pharmacokinetics/test/mitkDescriptivePharmacokineticBrixModelTest.cpp b/Modules/Pharmacokinetics/test/mitkDescriptivePharmacokineticBrixModelTest.cpp index e6035ab22b..acb5b64ca1 100644 --- a/Modules/Pharmacokinetics/test/mitkDescriptivePharmacokineticBrixModelTest.cpp +++ b/Modules/Pharmacokinetics/test/mitkDescriptivePharmacokineticBrixModelTest.cpp @@ -1,60 +1,165 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkVector.h" +#include "mitkTestFixture.h" +//MITK includes +#include "mitkVector.h" #include "mitkDescriptivePharmacokineticBrixModel.h" +class mitkDescriptivePharmacokineticBrixModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkDescriptivePharmacokineticBrixModelTestSuite); + MITK_TEST(GetModelDisplayNameTest); + MITK_TEST(GetModelTypeTest); + MITK_TEST(GetXAxisNameTest); + MITK_TEST(GetXAxisUnitTest); + MITK_TEST(GetYAxisNameTest); + MITK_TEST(GetYAxisUnitTest); + MITK_TEST(GetParameterNamesTest); + MITK_TEST(GetNumberOfParametersTest); + MITK_TEST(GetParameterUnitsTest); + MITK_TEST(GetStaticParameterNamesTest); + MITK_TEST(GetNumberOfStaticParametersTest); + MITK_TEST(GetStaticParameterUnitsTest); + MITK_TEST(ComputeModelfunctionTest); + CPPUNIT_TEST_SUITE_END(); + + private: + mitk::DescriptivePharmacokineticBrixModel::Pointer m_testmodel; + std::string NAME_PARAMETER_A, POSITION_PARAMETER_kep, POSITION_PARAMETER_kel, POSITION_PARAMETER_tlag; + mitk::ModelBase::ModelResultType m_output; + + public: + void setUp() override + { + mitk::ModelBase::TimeGridType m_grid(22); + mitk::ModelBase::ParametersType m_testparameters(4); + + m_testparameters[mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_A] = 1.25; + m_testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kep) = 3.89; + m_testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kel) = 0.12; + m_testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_tlag) = 1.14; + + for (int i = 0; i < 22; ++i) + { + // time grid in seconds, 14s between frames + m_grid[i] = (double)14 * i; + } + //injection time in minutes --> 30s + double m_injectiontime = 0.5; + m_testmodel = mitk::DescriptivePharmacokineticBrixModel::New(); + m_testmodel->SetTimeGrid(m_grid); + m_testmodel->SetTau(m_injectiontime); + + //ComputeModelfunction is called within GetSignal(), therefore no explicit testing of ComputeModelFunction() + m_output = m_testmodel->GetSignal(m_testparameters); + } + void tearDown() override + { + m_testmodel = nullptr; + NAME_PARAMETER_A = ""; + POSITION_PARAMETER_kep = ""; + POSITION_PARAMETER_kel = ""; + POSITION_PARAMETER_tlag = ""; + m_output.clear(); + } + + void GetModelDisplayNameTest() + { + m_testmodel->GetModelDisplayName(); + CPPUNIT_ASSERT_MESSAGE("Checking model display name.", m_testmodel->GetModelDisplayName() == "Descriptive Pharmacokinetic Brix Model"); + } + + void GetModelTypeTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking model type.", m_testmodel->GetModelType() == "Perfusion.MR"); + } + + void GetXAxisNameTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking x-axis name.", m_testmodel->GetXAxisName() == "Time"); + } + + void GetXAxisUnitTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking x-axis unit", m_testmodel->GetXAxisUnit() == "s"); + } -int mitkDescriptivePharmacokineticBrixModelTest(int /*argc*/ , char*[] /*argv[]*/){ + void GetYAxisNameTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking y-axis name.", m_testmodel->GetYAxisName() == ""); + } - MITK_TEST_BEGIN("DescriptivePharmacokineticBrixModel") + void GetYAxisUnitTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking y-axis unit", m_testmodel->GetYAxisUnit() == ""); + } - mitk::ModelBase::TimeGridType grid(22); - mitk::ModelBase::ModelResultType output; - mitk::ModelBase::ParametersType testparameters(4); + void GetParameterNamesTest() + { + mitk::DescriptivePharmacokineticBrixModel::ParameterNamesType parameterNames; + parameterNames.push_back("A"); + parameterNames.push_back("kep"); + parameterNames.push_back("kel"); + parameterNames.push_back("tlag"); + CPPUNIT_ASSERT_MESSAGE("Checking parameter names.", m_testmodel->GetParameterNames() == parameterNames); + } - testparameters[mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_A] = 1.25; - testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kep) = 3.89; - testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_kel) = 0.12; - testparameters(mitk::DescriptivePharmacokineticBrixModel::POSITION_PARAMETER_tlag) = 1.14; + void GetNumberOfParametersTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking number of parameters in model.", m_testmodel->GetNumberOfParameters() == 4); + } + void GetParameterUnitsTest() + { + mitk::DescriptivePharmacokineticBrixModel::ParamterUnitMapType parameterUnitMap; + parameterUnitMap.insert(std::make_pair("A", "a.u.")); + parameterUnitMap.insert(std::make_pair("kep", "1/min")); + parameterUnitMap.insert(std::make_pair("kel", "1/min")); + parameterUnitMap.insert(std::make_pair("tlag", "min")); + CPPUNIT_ASSERT_MESSAGE("Checking parameter unit map.", m_testmodel->GetParameterUnits() == parameterUnitMap); + } + void GetStaticParameterNamesTest() + { + mitk::DescriptivePharmacokineticBrixModel::ParameterNamesType staticParameterNames; + staticParameterNames.push_back("Tau"); + staticParameterNames.push_back("S0"); + CPPUNIT_ASSERT_MESSAGE("Checking static parameter names.", m_testmodel->GetStaticParameterNames() == staticParameterNames); + } - for (int i = 0; i<22; ++i) + void GetNumberOfStaticParametersTest() { - // time grid in seconds, 14s between frames - grid[i]=(double) 14*i; + CPPUNIT_ASSERT_MESSAGE("Checking number of static parameters.", m_testmodel->GetNumberOfStaticParameters() == 2); } - //injection time in minutes --> 60s - double injectiontime = 0.5; - mitk::DescriptivePharmacokineticBrixModel::Pointer testmodel = mitk::DescriptivePharmacokineticBrixModel::New(); - testmodel->SetTimeGrid(grid); - testmodel->SetTau(injectiontime); - CPPUNIT_ASSERT_MESSAGE("Check number of parameters in Model.", 4 == testmodel->GetNumberOfParameters()); - output = testmodel->GetSignal(testparameters); - //precision of output ? - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1,output[0], 1e-6, true)==true,"Check Output signal values - 0"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1,output[4], 1e-6, true)==true,"Check Output signal values - 4"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1.003338,output[5], 1e-6, true)==true,"Check Output signal values - 5"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1.238392,output[6], 1e-6, true)==true,"Check Output signal values - 6"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1.669835,output[7], 1e-6, true)==true,"Check Output signal values - 7"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(1.982948,output[8], 1e-6, true)==true,"Check Output signal values - 8"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(2.089685,output[9], 1e-6, true)==true,"Check Output signal values - 9"); - MITK_TEST_CONDITION_REQUIRED(mitk::Equal(2.113611,output[10], 1e-6, true)==true,"Check Output signal values - 10"); + void GetStaticParameterUnitsTest() + { + mitk::DescriptivePharmacokineticBrixModel::ParamterUnitMapType staticParameterUnitMap; + staticParameterUnitMap.insert(std::make_pair("Tau", "min")); + staticParameterUnitMap.insert(std::make_pair("S0", "I")); + CPPUNIT_ASSERT_MESSAGE("Checking static parameters units.", m_testmodel->GetStaticParameterUnits() == staticParameterUnitMap); + } - MITK_TEST_END() + void ComputeModelfunctionTest() + { + CPPUNIT_ASSERT_MESSAGE("Checking signal of parameter set 1 at time frame 0.", mitk::Equal(1, m_output[0], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal of parameter set 1 at time frame 10.",mitk::Equal(2.113611, m_output[10], 1e-6, true) == true); + CPPUNIT_ASSERT_MESSAGE("Checking signal of parameter set 1 at time frame 20.", mitk::Equal(1.870596, m_output[20], 1e-6, true) == true); + } +}; -} +MITK_TEST_SUITE_REGISTRATION(mitkDescriptivePharmacokineticBrixModel) diff --git a/Modules/QtWidgetsExt/files.cmake b/Modules/QtWidgetsExt/files.cmake index e8bd9dd1f6..d5b2255af4 100644 --- a/Modules/QtWidgetsExt/files.cmake +++ b/Modules/QtWidgetsExt/files.cmake @@ -1,106 +1,104 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES qclickablelabel.cpp QmitkAboutDialog.cpp QmitkBasePropertyView.cpp QmitkBoolPropertyWidget.cpp QmitkBoundingObjectWidget.cpp QmitkCallbackFromGUIThread.cpp QmitkColorPropertyEditor.cpp QmitkColorPropertyView.cpp QmitkColorTransferFunctionCanvas.cpp QmitkCrossWidget.cpp QmitkEditPointDialog.cpp QmitkEnumerationPropertyWidget.cpp QmitkFFmpegWriter.cpp QmitkFileChooser.cpp QmitkGnuplotWidget.cpp QmitkHistogram.cpp QmitkHotkeyLineEdit.cpp QmitkModulesDialog.cpp QmitkModuleTableModel.cpp QmitkNumberPropertyEditor.cpp QmitkNumberPropertySlider.cpp QmitkNumberPropertyView.cpp QmitkPiecewiseFunctionCanvas.cpp QmitkPlotDialog.cpp QmitkPlotWidget.cpp QmitkPointListModel.cpp QmitkPointListView.cpp - QmitkPointListViewWidget.cpp QmitkPointListWidget.cpp QmitkPrimitiveMovieNavigatorWidget.cpp QmitkPropertyViewFactory.cpp QmitkSelectableGLWidget.cpp QmitkSliceWidget.cpp QmitkSliderNavigatorWidget.cpp QmitkStandardViews.cpp QmitkStepperAdapter.cpp QmitkStringPropertyEditor.cpp QmitkStringPropertyOnDemandEdit.cpp QmitkStringPropertyView.cpp QmitkTransferFunctionCanvas.cpp QmitkTransferFunctionGeneratorWidget.cpp QmitkTransferFunctionWidget.cpp QmitkUGCombinedRepresentationPropertyWidget.cpp QmitkVideoBackground.cpp QtWidgetsExtRegisterClasses.cpp ) set(MOC_H_FILES include/qclickablelabel.h include/QmitkAboutDialog.h include/QmitkBasePropertyView.h include/QmitkBoolPropertyWidget.h include/QmitkBoundingObjectWidget.h include/QmitkCallbackFromGUIThread.h include/QmitkColorPropertyEditor.h include/QmitkColorPropertyView.h include/QmitkColorTransferFunctionCanvas.h include/QmitkCrossWidget.h include/QmitkEditPointDialog.h include/QmitkEnumerationPropertyWidget.h include/QmitkFFmpegWriter.h include/QmitkFileChooser.h include/QmitkGnuplotWidget.h include/QmitkHotkeyLineEdit.h include/QmitkNumberPropertyEditor.h include/QmitkNumberPropertySlider.h include/QmitkNumberPropertyView.h include/QmitkPiecewiseFunctionCanvas.h include/QmitkPlotWidget.h include/QmitkPointListModel.h include/QmitkPointListView.h - include/QmitkPointListViewWidget.h include/QmitkPointListWidget.h include/QmitkPrimitiveMovieNavigatorWidget.h include/QmitkSelectableGLWidget.h include/QmitkSliceWidget.h include/QmitkSliderNavigatorWidget.h include/QmitkStandardViews.h include/QmitkStepperAdapter.h include/QmitkStringPropertyEditor.h include/QmitkStringPropertyOnDemandEdit.h include/QmitkStringPropertyView.h include/QmitkTransferFunctionCanvas.h include/QmitkTransferFunctionGeneratorWidget.h include/QmitkTransferFunctionWidget.h include/QmitkUGCombinedRepresentationPropertyWidget.h include/QmitkVideoBackground.h ) set(UI_FILES src/QmitkAboutDialogGUI.ui src/QmitkGnuplotWidget.ui src/QmitkPrimitiveMovieNavigatorWidget.ui src/QmitkSelectableGLWidget.ui src/QmitkSliceWidget.ui src/QmitkSliderNavigator.ui src/QmitkTransferFunctionGeneratorWidget.ui src/QmitkTransferFunctionWidget.ui ) set(QRC_FILES resource/QtWidgetsExt.qrc ) diff --git a/Modules/QtWidgetsExt/include/QmitkPointListView.h b/Modules/QtWidgetsExt/include/QmitkPointListView.h index fccb80b0b4..7dcc2b309e 100644 --- a/Modules/QtWidgetsExt/include/QmitkPointListView.h +++ b/Modules/QtWidgetsExt/include/QmitkPointListView.h @@ -1,120 +1,124 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITK_POINTLIST_VIEW_H_INCLUDED #define QMITK_POINTLIST_VIEW_H_INCLUDED #include "MitkQtWidgetsExtExports.h" -#include "QmitkPointListModel.h" + +#include + #include #include #include -class QmitkStdMultiWidget; +class QmitkAbstractMultiWidget; /*! * \brief GUI widget for handling mitk::PointSet * * Displays all the points in a mitk::PointSet graphically. * Reacts automatically to changes in the PointSet's selection status. * Updates PointSet's selection status when this list's selection changes. * -* If a QmitkStdMultiWidget is assigned via SetMultiWidget(), the -* crosshair of the QmitkStdMultiWidget is moved to the currently selected +* If a QmitkAbstractMultiWidget is assigned via SetMultiWidget(), the +* crosshair of the QmitkAbstractMultiWidget is moved to the currently selected * point. * */ class MITKQTWIDGETSEXT_EXPORT QmitkPointListView : public QListView { Q_OBJECT public: QmitkPointListView(QWidget *parent = nullptr); ~QmitkPointListView() override; /// assign a point set for observation void SetPointSetNode(mitk::DataNode *pointSetNode); /// which point set to work on const mitk::PointSet *GetPointSet() const; /** * \brief If Multiwidget is set, the crosshair is automatically centering to the selected point * As an alternative, if you dont have a multiwidget, you can call SetSnc1, SetSnc2, SetSnc3 to set the * SliceNavigationControllers directly to enable the focussing feature. */ - void SetMultiWidget(QmitkStdMultiWidget *multiWidget); + void SetMultiWidget(QmitkAbstractMultiWidget* multiWidget); - QmitkStdMultiWidget *GetMultiWidget() - const; ///< return the QmitkStdMultiWidget that is used for updating render window crosshair + /** + * \brief Return the QmitkAbstractMultiWidget that is used for updating the render window crosshair. + */ + QmitkAbstractMultiWidget* GetMultiWidget() const; /** * @brief Add a mitk::SliceNavigationController instance. * @param snc The mitk::SliceNavigationController instance. * * This method adds \c snc to the set of slice navigation controllers which are * used to navigate to the selected point. */ void AddSliceNavigationController(mitk::SliceNavigationController *snc); /** * @brief Remove a mitk::SliceNavigationController instance. * @param snc The mitk::SliceNavigationController instance. * * This method removes \c snc from the set of slice navigation controllers which are * used to navigate to the selected point. */ void RemoveSliceNavigationController(mitk::SliceNavigationController *snc); signals: void SignalPointSelectionChanged(); ///< this signal is emmitted, if the selection of a point in the pointset is changed void SignalTimeStepChanged(int); protected slots: /// Filtering double click event for editing point coordinates via a dialog void OnPointDoubleClicked(const QModelIndex &index); /// called when the point set data structure changes void OnPointSetSelectionChanged(); /// called when the selection of the view widget changes void OnListViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); /// open ContextMenu void ctxMenu(const QPoint &pos); /// Turn TimeStep Fading On/Off void SetFading(bool onOff); /// Delete all points in the list void ClearPointList(); /// delete all points in the list in the current timestep void ClearPointListTS(); protected: void keyPressEvent(QKeyEvent *e) override; ///< react to F2, F3 and DEL keys void wheelEvent(QWheelEvent *event) override; ///< change timestep of the current pointset by mouse wheel std::set m_Sncs; QmitkPointListModel *m_PointListModel; bool m_SelfCall; bool m_showFading; /// used to position the planes on a selected point - QmitkStdMultiWidget *m_MultiWidget; + QmitkAbstractMultiWidget* m_MultiWidget; }; #endif diff --git a/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h b/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h deleted file mode 100644 index 51420445c1..0000000000 --- a/Modules/QtWidgetsExt/include/QmitkPointListViewWidget.h +++ /dev/null @@ -1,100 +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 QmitkPointListViewWidget_h -#define QmitkPointListViewWidget_h - -#include "MitkQtWidgetsExtExports.h" -#include - -#include -#include - -class QmitkStdMultiWidget; - -/*! - * \brief GUI widget for handling mitk::PointSet - * - * Displays all the points in a mitk::PointSet graphically. - * Reacts automatically to changes in the PointSet's selection status. - * Updates PointSet's selection status when this list's selection changes. - * - * If a QmitkStdMultiWidget is assigned via SetMultiWidget(), the - * crosshair of the QmitkStdMultiWidget is moved to the currently selected - * point. - * - */ -class MITKQTWIDGETSEXT_EXPORT QmitkPointListViewWidget : public QListWidget -{ - Q_OBJECT - -signals: - void PointSelectionChanged(); ///< this signal is emmitted, if the selection of a point in the pointset is changed -public: - QmitkPointListViewWidget(QWidget *parent = nullptr); - ~QmitkPointListViewWidget() override; - - /// assign a point set for observation - void SetPointSet(mitk::PointSet *pointSet); - - /// which point set to work on - const mitk::PointSet *GetPointSet() const; - - void SetMultiWidget( - QmitkStdMultiWidget *multiWidget); ///< assign a QmitkStdMultiWidget for updating render window crosshair - - QmitkStdMultiWidget *GetMultiWidget() - const; ///< return the QmitkStdMultiWidget that is used for updating render window crosshair - - /// which time step to display/model - void SetTimeStep(int t); - - /// which time step to display/model - int GetTimeStep() const; - - /// observer for point set "modified" events - void OnPointSetChanged(); - - /// observer for point set "delete" events - void OnPointSetDeleted(); - -protected slots: - /// - /// Filtering double click event for editing point coordinates via a dialog - /// - void OnItemDoubleClicked(QListWidgetItem *item); - - /// called when the selection of the view widget changes - void OnCurrentRowChanged(int /*currentRow*/); - -protected: - void keyPressEvent(QKeyEvent *e) override; ///< react to F2, F3 and DEL keys - void MoveSelectedPointUp(); - void MoveSelectedPointDown(); - void RemoveSelectedPoint(); - void Update(bool currentRowChanged = false); - -protected: - mitk::WeakPointer m_PointSet; - - unsigned long m_PointSetDeletedTag; - unsigned long m_PointSetModifiedTag; - - int m_TimeStep; - - bool m_SelfCall; - - /// used to position the planes on a selected point - QmitkStdMultiWidget *m_MultiWidget; -}; - -#endif diff --git a/Modules/QtWidgetsExt/include/QmitkPointListWidget.h b/Modules/QtWidgetsExt/include/QmitkPointListWidget.h index b886dea24a..20ffd84ce7 100644 --- a/Modules/QtWidgetsExt/include/QmitkPointListWidget.h +++ b/Modules/QtWidgetsExt/include/QmitkPointListWidget.h @@ -1,152 +1,154 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkPointListWidget_H #define QmitkPointListWidget_H #include "MitkQtWidgetsExtExports.h" + #include #include -#include #include #include #include #include #include +class QmitkAbstractMultiWidget; + /*! * \brief Widget for regular operations on point sets * * Displays a list of point coordinates and a couple of * buttons which * * \li enable point set interaction * \li clear all points from a set * \li load points from file * \li save points to file * * The user/application module of this widget needs to * assign a mitk::PointSet object to this widget. The user * also has to decide whether it wants to put the point set * into (a) DataStorage. This widget will not add/remove * point sets to DataStorage. * * If the render window crosshair should be moved to the * currently selected point, the widget user has to provide - * a QmitkStdMultiWidget object. + * a QmitkAbstractMultiWidget object. */ class MITKQTWIDGETSEXT_EXPORT QmitkPointListWidget : public QWidget { Q_OBJECT public: QmitkPointListWidget(QWidget *parent = nullptr, int orientation = 0); ~QmitkPointListWidget() override; void SetupConnections(); /** * @brief Add a mitk::SliceNavigationController instance. * @param snc The mitk::SliceNavigationController instance. * * This method adds \c snc to the set of slice navigation controllers which are * used to navigate to the selected point. */ void AddSliceNavigationController(mitk::SliceNavigationController *snc); /** * @brief Remove a mitk::SliceNavigationController instance. * @param snc The mitk::SliceNavigationController instance. * * This method removes \c snc from the set of slice navigation controllers which are * used to navigate to the selected point. */ void RemoveSliceNavigationController(mitk::SliceNavigationController *snc); /** @brief assign a point set (contained in a node of DataStorage) for observation */ void SetPointSet(mitk::PointSet *newPs); mitk::PointSet *GetPointSet(); /** @brief assign a point set (contained in a node of DataStorage) for observation */ void SetPointSetNode(mitk::DataNode *newNode); mitk::DataNode *GetPointSetNode(); - /** @brief assign a QmitkStdMultiWidget for updating render window crosshair */ - void SetMultiWidget(QmitkStdMultiWidget *multiWidget); + /** @brief assign a QmitkAbstractMultiWidget for updating render window crosshair */ + void SetMultiWidget(QmitkAbstractMultiWidget*multiWidget); /** @brief itk observer for node "delete" events */ void OnNodeDeleted(const itk::EventObject &e); /** @brief Unselects the edit button if it is selected. */ void UnselectEditButton(); public slots: void DeactivateInteractor(bool deactivate); void EnableEditButton(bool enabled); signals: /** @brief signal to inform about the state of the EditPointSetButton, whether an interactor for setting points is * active or not */ void EditPointSets(bool active); /// signal to inform that the selection of a point in the pointset has changed void PointSelectionChanged(); /// signal to inform about cleared or loaded point sets void PointListChanged(); protected slots: void OnBtnSavePoints(); void OnBtnLoadPoints(); void RemoveSelectedPoint(); void MoveSelectedPointDown(); void MoveSelectedPointUp(); void OnBtnAddPoint(bool checked); void OnBtnAddPointManually(); void OnTimeStepChanged(int timeStep); /*! \brief pass through signal from PointListView that point selection has changed */ void OnPointSelectionChanged(); void OnListDoubleClick(); protected: void SetupUi(); void ObserveNewNode(mitk::DataNode *node); QmitkPointListView *m_PointListView; mitk::DataNode::Pointer m_PointSetNode; int m_Orientation; QPushButton *m_MovePointUpBtn; QPushButton *m_MovePointDownBtn; QPushButton *m_RemovePointBtn; QPushButton *m_SavePointsBtn; QPushButton *m_LoadPointsBtn; QPushButton *m_ToggleAddPoint; QPushButton *m_AddPoint; QLabel *m_TimeStepDisplay; QLabel *m_TimeStepLabel; mitk::DataInteractor::Pointer m_DataInteractor; int m_TimeStep; bool m_EditAllowed; unsigned long m_NodeObserverTag; QmitkPointListModel *m_PointListModel; }; #endif diff --git a/Modules/QtWidgetsExt/src/QmitkPointListView.cpp b/Modules/QtWidgetsExt/src/QmitkPointListView.cpp index 9ecf11704a..cf25972d78 100644 --- a/Modules/QtWidgetsExt/src/QmitkPointListView.cpp +++ b/Modules/QtWidgetsExt/src/QmitkPointListView.cpp @@ -1,335 +1,336 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkPointListView.h" -#include "QmitkEditPointDialog.h" -#include "QmitkPointListModel.h" -#include "QmitkRenderWindow.h" -#include "QmitkStdMultiWidget.h" +#include +#include +#include +#include -#include "mitkRenderingManager.h" +#include #include #include #include #include #include QmitkPointListView::QmitkPointListView(QWidget *parent) : QListView(parent), m_PointListModel(new QmitkPointListModel()), m_SelfCall(false), m_showFading(false), m_MultiWidget(nullptr) { QListView::setAlternatingRowColors(true); QListView::setSelectionBehavior(QAbstractItemView::SelectItems); QListView::setSelectionMode(QAbstractItemView::SingleSelection); QListView::setModel(m_PointListModel); QString tooltip = QString("Use the F2/F3 keys to move a point up/down, the Del key to remove a point\nand the mouse " "wheel to change the timestep.\n\nTimeStep:\t%1") .arg(0); QListView::setToolTip(tooltip); this->setContextMenuPolicy(Qt::CustomContextMenu); this->setMinimumHeight(40); this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); connect(m_PointListModel, SIGNAL(SignalUpdateSelection()), this, SLOT(OnPointSetSelectionChanged())); connect(this, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(OnPointDoubleClicked(const QModelIndex &))); connect(QListView::selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(OnListViewSelectionChanged(const QItemSelection &, const QItemSelection &))); // T28213: Every single action in the context menu is either not implemented or working. Implement/fix or remove in future. // connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(ctxMenu(const QPoint &))); } QmitkPointListView::~QmitkPointListView() { delete m_PointListModel; } void QmitkPointListView::SetPointSetNode(mitk::DataNode *pointSetNode) { m_PointListModel->SetPointSetNode(pointSetNode); } const mitk::PointSet *QmitkPointListView::GetPointSet() const { return m_PointListModel->GetPointSet(); } -void QmitkPointListView::SetMultiWidget(QmitkStdMultiWidget *multiWidget) +void QmitkPointListView::SetMultiWidget(QmitkAbstractMultiWidget* multiWidget) { m_MultiWidget = multiWidget; if (nullptr != m_MultiWidget) { - AddSliceNavigationController(m_MultiWidget->GetRenderWindow1()->GetSliceNavigationController()); - AddSliceNavigationController(m_MultiWidget->GetRenderWindow2()->GetSliceNavigationController()); - AddSliceNavigationController(m_MultiWidget->GetRenderWindow3()->GetSliceNavigationController()); + for (const auto& renderWindow : m_MultiWidget->GetRenderWindows().values()) + { + AddSliceNavigationController(renderWindow->GetSliceNavigationController()); + } } } -QmitkStdMultiWidget *QmitkPointListView::GetMultiWidget() const +QmitkAbstractMultiWidget* QmitkPointListView::GetMultiWidget() const { return m_MultiWidget; } void QmitkPointListView::OnPointDoubleClicked(const QModelIndex &index) { mitk::PointSet::PointType p; mitk::PointSet::PointIdentifier id; m_PointListModel->GetPointForModelIndex(index, p, id); QmitkEditPointDialog _EditPointDialog(this); _EditPointDialog.SetPoint(m_PointListModel->GetPointSet(), id, m_PointListModel->GetTimeStep()); _EditPointDialog.exec(); } void QmitkPointListView::OnPointSetSelectionChanged() { const mitk::PointSet *pointSet = m_PointListModel->GetPointSet(); if (pointSet == nullptr) return; // update this view's selection status as a result to changes in the point set data structure m_SelfCall = true; int timeStep = m_PointListModel->GetTimeStep(); if (pointSet->GetNumberOfSelected(timeStep) > 1) { MITK_ERROR << "Point set has multiple selected points. This view is not designed for more than one selected point."; } int selectedIndex = pointSet->SearchSelectedPoint(timeStep); if (selectedIndex == -1) // no selected point is found { m_SelfCall = false; return; } QModelIndex index; bool modelIndexOkay = m_PointListModel->GetModelIndexForPointID(selectedIndex, index); if (modelIndexOkay == true) QListView::selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); emit SignalPointSelectionChanged(); m_SelfCall = false; } void QmitkPointListView::OnListViewSelectionChanged(const QItemSelection &selected, const QItemSelection & /*deselected*/) { if (m_SelfCall) return; mitk::PointSet *pointSet = const_cast(m_PointListModel->GetPointSet()); if (pointSet == nullptr) return; // (take care that this widget doesn't react to self-induced changes by setting m_SelfCall) m_SelfCall = true; // update selection of all points in pointset: select the one(s) that are selected in the view, deselect all others QModelIndexList selectedIndexes = selected.indexes(); // only call setSelectInfo on a point set with 'selected = true' if you deselcted the other entries int indexToSelect = -1; for (mitk::PointSet::PointsContainer::Iterator it = pointSet->GetPointSet(m_PointListModel->GetTimeStep())->GetPoints()->Begin(); it != pointSet->GetPointSet(m_PointListModel->GetTimeStep())->GetPoints()->End(); ++it) { QModelIndex index; if (m_PointListModel->GetModelIndexForPointID(it->Index(), index)) { if (selectedIndexes.indexOf(index) != -1) // index is found in the selected indices list { indexToSelect = it->Index(); } else { pointSet->SetSelectInfo(it->Index(), false, m_PointListModel->GetTimeStep()); } } } // force selection of only one index after deselecting the others if (indexToSelect > -1) { pointSet->SetSelectInfo(indexToSelect, true, m_PointListModel->GetTimeStep()); mitk::Point3D p = pointSet->GetPoint(indexToSelect, m_PointListModel->GetTimeStep()); for (auto snc : m_Sncs) snc->SelectSliceByPoint(p); } m_SelfCall = false; emit SignalPointSelectionChanged(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkPointListView::keyPressEvent(QKeyEvent *e) { if (m_PointListModel == nullptr) return; int key = e->key(); switch (key) { case Qt::Key_F2: m_PointListModel->MoveSelectedPointUp(); break; case Qt::Key_F3: m_PointListModel->MoveSelectedPointDown(); break; case Qt::Key_Delete: m_PointListModel->RemoveSelectedPoint(); break; default: break; } } void QmitkPointListView::wheelEvent(QWheelEvent *event) { if (!m_PointListModel || !m_PointListModel->GetPointSet() || (int)(m_PointListModel->GetPointSet()->GetTimeSteps()) == 1) return; int whe = event->delta(); mitk::PointSet::Pointer ps = dynamic_cast(m_PointListModel->GetPointSet()); unsigned int numberOfTS = ps->GetTimeSteps(); if (numberOfTS == 1) return; int currentTS = this->m_PointListModel->GetTimeStep(); if (whe > 0) { if ((currentTS + 1 >= (int)numberOfTS)) return; this->m_PointListModel->SetTimeStep(++currentTS); } else { if ((currentTS <= 0)) return; this->m_PointListModel->SetTimeStep(--currentTS); } QString tooltip = QString("Use the F2/F3 keys to move a point up/down, the Del key to remove a point\nand the mouse " "wheel to change the timestep.\n\nTimeStep:\t%1") .arg(currentTS); this->setToolTip(tooltip); emit SignalTimeStepChanged(currentTS); } void QmitkPointListView::ctxMenu(const QPoint &pos) { QMenu *menu = new QMenu; // add Fading check QAction *showFading = new QAction(this); showFading->setCheckable(false); // TODO: reset when fading is working showFading->setEnabled(false); // TODO: reset when fading is working showFading->setText("Fade TimeStep"); connect(showFading, SIGNAL(triggered(bool)), this, SLOT(SetFading(bool))); menu->addAction(showFading); // add Clear action QAction *clearList = new QAction(this); clearList->setText("Clear List"); connect(clearList, SIGNAL(triggered()), this, SLOT(ClearPointList())); menu->addAction(clearList); // add Clear TimeStep action QAction *clearTS = new QAction(this); clearTS->setText("Clear current time step"); connect(clearTS, SIGNAL(triggered()), this, SLOT(ClearPointListTS())); menu->addAction(clearTS); menu->exec(this->mapToGlobal(pos)); } void QmitkPointListView::SetFading(bool onOff) { m_showFading = onOff; } void QmitkPointListView::ClearPointList() { if (!m_PointListModel->GetPointSet()) return; mitk::PointSet::Pointer curPS = m_PointListModel->GetPointSet(); if (curPS->GetSize() == 0) return; switch (QMessageBox::question(this, tr("Clear Points"), tr("Remove all points from the displayed list?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { case QMessageBox::Yes: { mitk::PointSet::PointsIterator it; mitk::PointSet::PointsContainer *curPsPoints; while (!curPS->IsEmptyTimeStep(0)) { curPsPoints = curPS->GetPointSet()->GetPoints(); it = curPsPoints->Begin(); curPS->SetSelectInfo(it->Index(), true); m_PointListModel->RemoveSelectedPoint(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); break; } case QMessageBox::No: default: break; } } void QmitkPointListView::ClearPointListTS() { } void QmitkPointListView::AddSliceNavigationController(mitk::SliceNavigationController *snc) { if (snc == nullptr) return; m_Sncs.insert(snc); } void QmitkPointListView::RemoveSliceNavigationController(mitk::SliceNavigationController *snc) { if (snc == nullptr) return; m_Sncs.erase(snc); } diff --git a/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp b/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp deleted file mode 100644 index 13a7321abe..0000000000 --- a/Modules/QtWidgetsExt/src/QmitkPointListViewWidget.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkPointListViewWidget.h" - -#include "QmitkEditPointDialog.h" -#include "QmitkPointListModel.h" -#include "QmitkStdMultiWidget.h" - -#include "mitkInteractionConst.h" -#include "mitkPointOperation.h" -#include "mitkRenderingManager.h" - -#include - -QmitkPointListViewWidget::QmitkPointListViewWidget(QWidget *parent) - : QListWidget(parent), m_TimeStep(0), m_SelfCall(false), m_MultiWidget(nullptr) -{ - QListWidget::setAlternatingRowColors(true); - // logic - - QListWidget::setSelectionBehavior(QAbstractItemView::SelectRows); - QListWidget::setSelectionMode(QAbstractItemView::SingleSelection); - - connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(OnItemDoubleClicked(QListWidgetItem *))); - - connect(this, SIGNAL(currentRowChanged(int)), this, SLOT(OnCurrentRowChanged(int))); -} - -QmitkPointListViewWidget::~QmitkPointListViewWidget() -{ - this->SetPointSet(nullptr); // remove listener -} - -void QmitkPointListViewWidget::SetPointSet(mitk::PointSet *pointSet) -{ - auto lockedPointSet = m_PointSet.Lock(); - - if (lockedPointSet.IsNotNull()) - { - lockedPointSet->RemoveObserver(m_PointSetModifiedTag); - lockedPointSet->RemoveObserver(m_PointSetDeletedTag); - } - - m_PointSet = pointSet; - lockedPointSet = m_PointSet.Lock(); - - if (lockedPointSet.IsNotNull()) - { - auto onPointSetDeleted = itk::SimpleMemberCommand::New(); - onPointSetDeleted->SetCallbackFunction(this, &QmitkPointListViewWidget::OnPointSetDeleted); - m_PointSetDeletedTag = lockedPointSet->AddObserver(itk::DeleteEvent(), onPointSetDeleted); - - auto onPointSetModified = itk::SimpleMemberCommand::New(); - onPointSetModified->SetCallbackFunction(this, &QmitkPointListViewWidget::OnPointSetChanged); - m_PointSetModifiedTag = lockedPointSet->AddObserver(itk::DeleteEvent(), onPointSetModified); - } - - this->Update(); -} - -const mitk::PointSet *QmitkPointListViewWidget::GetPointSet() const -{ - return m_PointSet.Lock(); -} - -void QmitkPointListViewWidget::SetTimeStep(int t) -{ - m_TimeStep = t; - this->Update(); -} - -int QmitkPointListViewWidget::GetTimeStep() const -{ - return m_TimeStep; -} - -void QmitkPointListViewWidget::SetMultiWidget(QmitkStdMultiWidget *multiWidget) -{ - m_MultiWidget = multiWidget; -} - -QmitkStdMultiWidget *QmitkPointListViewWidget::GetMultiWidget() const -{ - return m_MultiWidget; -} - -void QmitkPointListViewWidget::OnPointSetChanged() -{ - if (!m_SelfCall) - this->Update(); -} - -void QmitkPointListViewWidget::OnPointSetDeleted() -{ - this->SetPointSet(nullptr); - this->Update(); -} - -void QmitkPointListViewWidget::OnItemDoubleClicked(QListWidgetItem *item) -{ - QmitkEditPointDialog _EditPointDialog(this); - _EditPointDialog.SetPoint(m_PointSet.Lock(), this->row(item), m_TimeStep); - _EditPointDialog.exec(); -} - -void QmitkPointListViewWidget::OnCurrentRowChanged(int) -{ - this->Update(true); -} - -void QmitkPointListViewWidget::keyPressEvent(QKeyEvent *e) -{ - if (m_PointSet.IsExpired()) - return; - - int key = e->key(); - switch (key) - { - case Qt::Key_F2: - this->MoveSelectedPointUp(); - break; - case Qt::Key_F3: - this->MoveSelectedPointDown(); - break; - case Qt::Key_Delete: - this->RemoveSelectedPoint(); - break; - default: - break; - } -} - -void QmitkPointListViewWidget::MoveSelectedPointUp() -{ - auto pointSet = m_PointSet.Lock(); - - if (pointSet.IsNull()) - return; - - mitk::PointSet::PointIdentifier selectedID; - selectedID = pointSet->SearchSelectedPoint(m_TimeStep); - mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpMOVEPOINTUP, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - pointSet->ExecuteOperation(doOp); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper -} - -void QmitkPointListViewWidget::MoveSelectedPointDown() -{ - auto pointSet = m_PointSet.Lock(); - - if (pointSet.IsNull()) - return; - - mitk::PointSet::PointIdentifier selectedID; - selectedID = pointSet->SearchSelectedPoint(m_TimeStep); - mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpMOVEPOINTDOWN, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - pointSet->ExecuteOperation(doOp); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper -} - -void QmitkPointListViewWidget::RemoveSelectedPoint() -{ - auto pointSet = m_PointSet.Lock(); - - if (pointSet.IsNull()) - return; - - mitk::PointSet::PointIdentifier selectedID; - selectedID = pointSet->SearchSelectedPoint(m_TimeStep); - mitk::PointOperation *doOp = - new mitk::PointOperation(mitk::OpREMOVE, pointSet->GetPoint(selectedID, m_TimeStep), selectedID, true); - pointSet->ExecuteOperation(doOp); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // Workaround for update problem in Pointset/Mapper -} - -void QmitkPointListViewWidget::Update(bool currentRowChanged) -{ - if (m_SelfCall) - return; - - auto pointSet = m_PointSet.Lock(); - - if (pointSet.IsNull()) - { - this->clear(); - return; - } - - m_SelfCall = true; - QString text; - int i = 0; - - mitk::PointSet::DataType::Pointer pointset = pointSet->GetPointSet(m_TimeStep); - for (mitk::PointSet::PointsContainer::Iterator it = pointset->GetPoints()->Begin(); - it != pointset->GetPoints()->End(); - ++it) - { - text = QString("%0: (%1, %2, %3)") - .arg(i, 3) - .arg(it.Value().GetElement(0), 0, 'f', 3) - .arg(it.Value().GetElement(1), 0, 'f', 3) - .arg(it.Value().GetElement(2), 0, 'f', 3); - - if (i == this->count()) - this->addItem(text); // insert text - else - this->item(i)->setText(text); // update text - - if (currentRowChanged) - { - if (i == this->currentRow()) - pointSet->SetSelectInfo(this->currentRow(), true, m_TimeStep); - else - pointSet->SetSelectInfo(it->Index(), false, m_TimeStep); // select nothing now - } - ++i; - } - - // remove unnecessary listwidgetitems - while (pointSet->GetPointSet(m_TimeStep)->GetPoints()->Size() < (unsigned int)this->count()) - { - QListWidgetItem *item = this->takeItem(this->count() - 1); - delete item; - } - - // update selection in pointset or in the list widget - if (!currentRowChanged) - { - if (pointSet->GetNumberOfSelected(m_TimeStep) > 1) - { - /// @TODO use logging as soon as available - std::cerr << "Point set has multiple selected points. This view is not designed for more than one selected point." - << std::endl; - } - - int selectedIndex = pointSet->SearchSelectedPoint(m_TimeStep); - if (selectedIndex != -1) // no selected point is found - { - this->setCurrentRow(selectedIndex); - } - } - m_SelfCall = false; -} diff --git a/Modules/QtWidgetsExt/src/QmitkPointListWidget.cpp b/Modules/QtWidgetsExt/src/QmitkPointListWidget.cpp index b2c9dcd2a8..04c09b9610 100644 --- a/Modules/QtWidgetsExt/src/QmitkPointListWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkPointListWidget.cpp @@ -1,484 +1,481 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkPointListWidget.h" -#include "QmitkPointListView.h" -#include "QmitkPointListModel.h" #include #include #include #include #include - +#include #include #include #include -#include - QmitkPointListWidget::QmitkPointListWidget(QWidget *parent, int orientation) : QWidget(parent), m_PointListView(nullptr), m_PointSetNode(nullptr), m_Orientation(0), m_MovePointUpBtn(nullptr), m_MovePointDownBtn(nullptr), m_RemovePointBtn(nullptr), m_SavePointsBtn(nullptr), m_LoadPointsBtn(nullptr), m_ToggleAddPoint(nullptr), m_AddPoint(nullptr), m_TimeStepDisplay(nullptr), m_DataInteractor(nullptr), m_TimeStep(0), m_EditAllowed(true), m_NodeObserverTag(0) { m_PointListView = new QmitkPointListView(); if (orientation != 0) m_Orientation = orientation; SetupUi(); SetupConnections(); ObserveNewNode(nullptr); } QmitkPointListWidget::~QmitkPointListWidget() { m_DataInteractor = nullptr; if (m_PointSetNode && m_NodeObserverTag) { m_PointSetNode->RemoveObserver(m_NodeObserverTag); m_NodeObserverTag = 0; } delete m_PointListView; } void QmitkPointListWidget::SetupConnections() { connect(this->m_LoadPointsBtn, SIGNAL(clicked()), this, SLOT(OnBtnLoadPoints())); connect(this->m_SavePointsBtn, SIGNAL(clicked()), this, SLOT(OnBtnSavePoints())); connect(this->m_MovePointUpBtn, SIGNAL(clicked()), this, SLOT(MoveSelectedPointUp())); connect(this->m_MovePointDownBtn, SIGNAL(clicked()), this, SLOT(MoveSelectedPointDown())); connect(this->m_RemovePointBtn, SIGNAL(clicked()), this, SLOT(RemoveSelectedPoint())); connect(this->m_ToggleAddPoint, SIGNAL(toggled(bool)), this, SLOT(OnBtnAddPoint(bool))); connect(this->m_AddPoint, SIGNAL(clicked()), this, SLOT(OnBtnAddPointManually())); connect(this->m_PointListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnListDoubleClick())); connect(this->m_PointListView, SIGNAL(SignalPointSelectionChanged()), this, SLOT(OnPointSelectionChanged())); connect(this->m_PointListView, SIGNAL(SignalTimeStepChanged(int)), this, SLOT(OnTimeStepChanged(int))); } void QmitkPointListWidget::OnTimeStepChanged(int timeStep) { m_TimeStepLabel->setText(QString("%1").arg(timeStep)); } void QmitkPointListWidget::SetupUi() { // Setup the buttons m_ToggleAddPoint = new QPushButton(); m_ToggleAddPoint->setMaximumSize(25, 25); m_ToggleAddPoint->setCheckable(true); m_ToggleAddPoint->setToolTip("Toggle point editing (use SHIFT + Left Mouse Button to add Points)"); m_ToggleAddPoint->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/plus.svg"))); m_AddPoint = new QPushButton(); m_AddPoint->setMaximumSize(25, 25); m_AddPoint->setToolTip("Manually add point"); m_AddPoint->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/plus-xyz.svg"))); m_RemovePointBtn = new QPushButton(); m_RemovePointBtn->setMaximumSize(25, 25); m_RemovePointBtn->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/eraser.svg"))); m_RemovePointBtn->setToolTip("Erase one point from list (Hotkey: DEL)"); m_MovePointUpBtn = new QPushButton(); m_MovePointUpBtn->setMaximumSize(25, 25); m_MovePointUpBtn->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/arrow-up.svg"))); m_MovePointUpBtn->setToolTip("Swap selected point upwards (Hotkey: F2)"); m_MovePointDownBtn = new QPushButton(); m_MovePointDownBtn->setMaximumSize(25, 25); m_MovePointDownBtn->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/arrow-down.svg"))); m_MovePointDownBtn->setToolTip("Swap selected point downwards (Hotkey: F3)"); m_SavePointsBtn = new QPushButton(); m_SavePointsBtn->setMaximumSize(25, 25); m_SavePointsBtn->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/save.svg"))); m_SavePointsBtn->setToolTip("Save points to file"); m_LoadPointsBtn = new QPushButton(); m_LoadPointsBtn->setMaximumSize(25, 25); m_LoadPointsBtn->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/QtWidgetsExt/folder-open.svg"))); m_LoadPointsBtn->setToolTip("Load list of points from file (REPLACES current content)"); int i; QBoxLayout *lay1; QBoxLayout *lay2; QBoxLayout *lay3; switch (m_Orientation) { case 0: lay1 = new QVBoxLayout(this); lay2 = new QHBoxLayout(); i = 0; break; case 1: lay1 = new QHBoxLayout(this); lay2 = new QVBoxLayout(); i = -1; break; case 2: lay1 = new QHBoxLayout(this); lay2 = new QVBoxLayout(); i = 0; break; default: lay1 = new QVBoxLayout(this); lay2 = new QHBoxLayout(); i = -1; break; } // setup Layouts this->setLayout(lay1); lay2->stretch(true); lay2->addWidget(m_ToggleAddPoint); lay2->addWidget(m_AddPoint); lay2->addWidget(m_RemovePointBtn); lay2->addWidget(m_MovePointUpBtn); lay2->addWidget(m_MovePointDownBtn); lay2->addWidget(m_SavePointsBtn); lay2->addWidget(m_LoadPointsBtn); // setup Labels m_TimeStepDisplay = new QLabel; m_TimeStepLabel = new QLabel; lay3 = new QHBoxLayout; m_TimeStepDisplay->setMaximumSize(200, 15); lay3->stretch(true); lay3->setAlignment(Qt::AlignLeft); lay3->addWidget(m_TimeStepDisplay); lay3->addWidget(m_TimeStepLabel); m_TimeStepDisplay->setText("Time Step: "); m_TimeStepLabel->setMaximumSize(10, 15); this->OnTimeStepChanged(0); //Add Layouts lay1->insertWidget(i, m_PointListView); this->setLayout(lay1); lay1->addLayout(lay2); lay1->addLayout(lay3); } void QmitkPointListWidget::SetPointSet(mitk::PointSet *newPs) { if (newPs == nullptr) return; this->m_PointSetNode->SetData(newPs); dynamic_cast(this->m_PointListView->model())->SetPointSetNode(m_PointSetNode); ObserveNewNode(m_PointSetNode); } void QmitkPointListWidget::SetPointSetNode(mitk::DataNode *newNode) { if (m_DataInteractor.IsNotNull()) m_DataInteractor->SetDataNode(newNode); ObserveNewNode(newNode); dynamic_cast(this->m_PointListView->model())->SetPointSetNode(newNode); } void QmitkPointListWidget::OnBtnSavePoints() { if ((dynamic_cast(m_PointSetNode->GetData())) == nullptr) return; // don't write empty point sets. If application logic requires something else then do something else. if ((dynamic_cast(m_PointSetNode->GetData()))->GetSize() == 0) return; // take the previously defined name of node as proposal for filename std::string nodeName = m_PointSetNode->GetName(); nodeName = "/" + nodeName + ".mps"; QString fileNameProposal = QString(); fileNameProposal.append(nodeName.c_str()); QString aFilename = QFileDialog::getSaveFileName( nullptr, "Save point set", QDir::currentPath() + fileNameProposal, "MITK Pointset (*.mps)"); if (aFilename.isEmpty()) return; try { mitk::IOUtil::Save(m_PointSetNode->GetData(), aFilename.toStdString()); } catch (...) { QMessageBox::warning(this, "Save point set", QString("File writer reported problems writing %1\n\n" "PLEASE CHECK output file!") .arg(aFilename)); } } void QmitkPointListWidget::OnBtnLoadPoints() { // get the name of the file to load QString filename = QFileDialog::getOpenFileName(nullptr, "Open MITK Pointset", "", "MITK Point Sets (*.mps)"); if (filename.isEmpty()) return; // attempt to load file try { mitk::PointSet::Pointer pointSet = mitk::IOUtil::Load(filename.toStdString()); if (pointSet.IsNull()) { QMessageBox::warning(this, "Load point set", QString("File reader could not read %1").arg(filename)); return; } // loading successful this->SetPointSet(pointSet); } catch (...) { QMessageBox::warning(this, "Load point set", QString("File reader collapsed while reading %1").arg(filename)); } emit PointListChanged(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::PointSet *QmitkPointListWidget::GetPointSet() { return dynamic_cast(m_PointSetNode->GetData()); } mitk::DataNode *QmitkPointListWidget::GetPointSetNode() { return m_PointSetNode; } -void QmitkPointListWidget::SetMultiWidget(QmitkStdMultiWidget *multiWidget) +void QmitkPointListWidget::SetMultiWidget(QmitkAbstractMultiWidget* multiWidget) { m_PointListView->SetMultiWidget(multiWidget); } void QmitkPointListWidget::RemoveSelectedPoint() { if (!m_PointSetNode) return; mitk::PointSet *pointSet = dynamic_cast(m_PointSetNode->GetData()); if (!pointSet) return; if (pointSet->GetSize() == 0) return; QmitkPointListModel *pointListModel = dynamic_cast(m_PointListView->model()); pointListModel->RemoveSelectedPoint(); emit PointListChanged(); } void QmitkPointListWidget::MoveSelectedPointDown() { if (!m_PointSetNode) return; mitk::PointSet *pointSet = dynamic_cast(m_PointSetNode->GetData()); if (!pointSet) return; if (pointSet->GetSize() == 0) return; QmitkPointListModel *pointListModel = dynamic_cast(m_PointListView->model()); pointListModel->MoveSelectedPointDown(); emit PointListChanged(); } void QmitkPointListWidget::MoveSelectedPointUp() { if (!m_PointSetNode) return; mitk::PointSet *pointSet = dynamic_cast(m_PointSetNode->GetData()); if (!pointSet) return; if (pointSet->GetSize() == 0) return; QmitkPointListModel *pointListModel = dynamic_cast(m_PointListView->model()); pointListModel->MoveSelectedPointUp(); emit PointListChanged(); } void QmitkPointListWidget::OnBtnAddPoint(bool checked) { if (m_PointSetNode.IsNotNull()) { if (checked) { m_DataInteractor = m_PointSetNode->GetDataInteractor(); // If no data Interactor is present create a new one if (m_DataInteractor.IsNull()) { // Create PointSetData Interactor m_DataInteractor = mitk::PointSetDataInteractor::New(); // Load the according state machine for regular point set interaction m_DataInteractor->LoadStateMachine("PointSet.xml"); // Set the configuration file that defines the triggers for the transitions m_DataInteractor->SetEventConfig("PointSetConfig.xml"); // set the DataNode (which already is added to the DataStorage m_DataInteractor->SetDataNode(m_PointSetNode); } } else { m_PointSetNode->SetDataInteractor(nullptr); m_DataInteractor = nullptr; } emit EditPointSets(checked); } } void QmitkPointListWidget::OnBtnAddPointManually() { mitk::PointSet *pointSet = this->GetPointSet(); QmitkEditPointDialog editPointDialog(this); if (this->GetPointSet()->IsEmpty()) { editPointDialog.SetPoint(pointSet, 0, m_TimeStep); } else { mitk::PointSet::PointsIterator maxIt = pointSet->GetMaxId(); mitk::PointSet::PointIdentifier maxId = maxIt->Index(); editPointDialog.SetPoint(pointSet, maxId + 1, m_TimeStep); } editPointDialog.exec(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkPointListWidget::OnListDoubleClick() { } void QmitkPointListWidget::OnPointSelectionChanged() { emit this->PointSelectionChanged(); } void QmitkPointListWidget::DeactivateInteractor(bool) { } void QmitkPointListWidget::EnableEditButton(bool enabled) { m_EditAllowed = enabled; if (enabled == false) m_ToggleAddPoint->setEnabled(false); else m_ToggleAddPoint->setEnabled(true); OnBtnAddPoint(enabled); } void QmitkPointListWidget::ObserveNewNode(mitk::DataNode *node) { if (m_DataInteractor.IsNotNull()) m_DataInteractor->SetDataNode(node); // remove old observer if (m_PointSetNode) { if (m_DataInteractor) { m_DataInteractor = nullptr; m_ToggleAddPoint->setChecked(false); } m_PointSetNode->RemoveObserver(m_NodeObserverTag); m_NodeObserverTag = 0; } m_PointSetNode = node; // add new observer if necessary if (m_PointSetNode) { itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkPointListWidget::OnNodeDeleted); m_NodeObserverTag = m_PointSetNode->AddObserver(itk::DeleteEvent(), command); } else { m_NodeObserverTag = 0; } if (m_EditAllowed == true) m_ToggleAddPoint->setEnabled(m_PointSetNode); else m_ToggleAddPoint->setEnabled(false); m_RemovePointBtn->setEnabled(m_PointSetNode); m_LoadPointsBtn->setEnabled(m_PointSetNode); m_SavePointsBtn->setEnabled(m_PointSetNode); m_AddPoint->setEnabled(m_PointSetNode); } void QmitkPointListWidget::OnNodeDeleted(const itk::EventObject &) { if (m_PointSetNode.IsNotNull() && !m_NodeObserverTag) m_PointSetNode->RemoveObserver(m_NodeObserverTag); m_NodeObserverTag = 0; m_PointSetNode = nullptr; m_PointListView->SetPointSetNode(nullptr); m_ToggleAddPoint->setEnabled(false); m_RemovePointBtn->setEnabled(false); m_LoadPointsBtn->setEnabled(false); m_SavePointsBtn->setEnabled(false); m_AddPoint->setEnabled(false); } void QmitkPointListWidget::AddSliceNavigationController(mitk::SliceNavigationController *snc) { m_PointListView->AddSliceNavigationController(snc); } void QmitkPointListWidget::RemoveSliceNavigationController(mitk::SliceNavigationController *snc) { m_PointListView->RemoveSliceNavigationController(snc); } void QmitkPointListWidget::UnselectEditButton() { m_ToggleAddPoint->setChecked(false); } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp deleted file mode 100644 index 579561cfd0..0000000000 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkAdaptiveRegionGrowingTool.h" -#include "mitkImage.h" -#include "mitkProperties.h" -#include "mitkToolManager.h" -// us -#include -#include -#include -#include - -namespace mitk -{ - MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AdaptiveRegionGrowingTool, "AdaptiveRegionGrowingTool"); -} - -mitk::AdaptiveRegionGrowingTool::AdaptiveRegionGrowingTool() -{ - m_PointSetNode = mitk::DataNode::New(); - m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("3D_Regiongrowing_Seedpoint")); - m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_PointSet = mitk::PointSet::New(); - m_PointSetNode->SetData(m_PointSet); -} - -mitk::AdaptiveRegionGrowingTool::~AdaptiveRegionGrowingTool() -{ -} - -bool mitk::AdaptiveRegionGrowingTool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const -{ - if (referenceData == nullptr) - return false; - - auto *image = dynamic_cast(referenceData); - - if (image == nullptr) - return false; - - if (image->GetDimension() < 3) - return false; - - if (image->GetTimeSteps() > 1) //release quickfix for T28275 - return false; - - return true; -} - -const char **mitk::AdaptiveRegionGrowingTool::GetXPM() const -{ - return nullptr; -} - -const char *mitk::AdaptiveRegionGrowingTool::GetName() const -{ - return "Region Growing 3D"; -} - -us::ModuleResource mitk::AdaptiveRegionGrowingTool::GetIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); - return resource; -} - -void mitk::AdaptiveRegionGrowingTool::Activated() -{ - Superclass::Activated(); - - if (!GetDataStorage()->Exists(m_PointSetNode)) - GetDataStorage()->Add(m_PointSetNode, GetWorkingData()); - m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); - m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); - m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); - m_SeedPointInteractor->SetDataNode(m_PointSetNode); -} - -void mitk::AdaptiveRegionGrowingTool::Deactivated() -{ - m_PointSet->Clear(); - GetDataStorage()->Remove(m_PointSetNode); - - Superclass::Deactivated(); -} - -void mitk::AdaptiveRegionGrowingTool::ConfirmSegmentation() -{ - this->GetToolManager()->ActivateTool(-1); -} - -mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetReferenceData() -{ - return this->GetToolManager()->GetReferenceData(0); -} - -mitk::DataStorage *mitk::AdaptiveRegionGrowingTool::GetDataStorage() -{ - return this->GetToolManager()->GetDataStorage(); -} - -mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetWorkingData() -{ - return this->GetToolManager()->GetWorkingData(0); -} - -mitk::DataNode::Pointer mitk::AdaptiveRegionGrowingTool::GetPointSetNode() -{ - return m_PointSetNode; -} diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h deleted file mode 100644 index 7924978442..0000000000 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h +++ /dev/null @@ -1,128 +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 mitkAdaptiveRegionGrowingTool_h_Included -#define mitkAdaptiveRegionGrowingTool_h_Included - -#include "mitkAutoSegmentationTool.h" -#include "mitkCommon.h" -#include "mitkDataStorage.h" -#include "mitkPointSet.h" -#include "mitkSinglePointDataInteractor.h" -#include - -namespace us -{ - class ModuleResource; -} - -namespace mitk -{ - /** - \brief Dummy Tool for AdaptiveRegionGrowingToolGUI to get Tool functionality for AdaptiveRegionGrowing. - The actual logic is implemented in QmitkAdaptiveRegionGrowingToolGUI. - - \ingroup ToolManagerEtAl - \sa mitk::Tool - \sa QmitkInteractiveSegmentation - - */ - class MITKSEGMENTATION_EXPORT AdaptiveRegionGrowingTool : public AutoSegmentationTool - { - public: - /** - * @brief mitkClassMacro - */ - mitkClassMacro(AdaptiveRegionGrowingTool, AutoSegmentationTool); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; - - /** - * @brief Get XPM - * @return nullptr - */ - const char **GetXPM() const override; - - /** - * @brief Get name - * @return name of the Tool - */ - const char *GetName() const override; - - /** - * @brief Get icon resource - * @return the resource Object of the Icon - */ - us::ModuleResource GetIconResource() const override; - - /** - * @brief Adds interactor for the seedpoint and creates a seedpoint if neccessary. - * - * - */ - void Activated() override; - - /** - * @brief Removes all set points and interactors. - * - * - */ - void Deactivated() override; - - /** - * @brief get pointset node - * @return the point set node - */ - virtual DataNode::Pointer GetPointSetNode(); - - /** - * @brief get reference data - * @return the current reference data. - */ - mitk::DataNode *GetReferenceData(); - - /** - * @brief Get working data - * @return a list of all working data. - */ - mitk::DataNode *GetWorkingData(); - - /** - * @brief Get datastorage - * @return the current data storage. - */ - mitk::DataStorage *GetDataStorage(); - - void ConfirmSegmentation(); - - protected: - /** - * @brief constructor - */ - AdaptiveRegionGrowingTool(); // purposely hidden - - /** - * @brief destructor - */ - ~AdaptiveRegionGrowingTool() override; - - private: - PointSet::Pointer m_PointSet; - SinglePointDataInteractor::Pointer m_SeedPointInteractor; - DataNode::Pointer m_PointSetNode; - }; - -} // namespace - -#endif diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp deleted file mode 100644 index 903be794ef..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ /dev/null @@ -1,209 +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. - -============================================================================*/ - -// 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->ResetPreviewContent(); - } -} - -void mitk::AutoMLSegmentationWithPreviewTool::SetNodeProperties(LabelSetImage::Pointer newMLPreview) -{ - 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(this->GetUserDefinedActiveLabel()); - 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(this->GetUserDefinedActiveLabel()); - 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 deleted file mode 100644 index 60921765c6..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ /dev/null @@ -1,82 +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 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/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp deleted file mode 100644 index ef35be95dc..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkAutoSegmentationTool.h" -#include "mitkImage.h" -#include "mitkToolManager.h" -#include -#include - -mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) -{ -} - -mitk::AutoSegmentationTool::AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_OverwriteExistingSegmentation(false) -{ -} - -mitk::AutoSegmentationTool::~AutoSegmentationTool() -{ -} - -void mitk::AutoSegmentationTool::Activated() -{ - Superclass::Activated(); - - m_NoneOverwriteTargetSegmentationNode = nullptr; -} - -void mitk::AutoSegmentationTool::Deactivated() -{ - m_NoneOverwriteTargetSegmentationNode = nullptr; - - Superclass::Deactivated(); -} - -const char *mitk::AutoSegmentationTool::GetGroup() const -{ - return "autoSegmentation"; -} - -mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) -{ - return SelectImageByTimeStep(image, timestep); -} - -mitk::Image::Pointer mitk::AutoSegmentationTool::GetImageByTimeStep(mitk::Image* image, unsigned int timestep) -{ - return SelectImageByTimeStep(image, timestep); -} - -mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) -{ - return SelectImageByTimeStep(image, timePoint); -} - -void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) -{ - if (m_OverwriteExistingSegmentation != overwrite) - { - m_OverwriteExistingSegmentation = overwrite; - m_NoneOverwriteTargetSegmentationNode = nullptr; - } -} - -std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() -{ - if (this->GetToolManager()->GetWorkingData(0)) - return this->GetToolManager()->GetWorkingData(0)->GetName(); - else - return ""; -} - -mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() const -{ - mitk::DataNode::Pointer segmentationNode = this->GetToolManager()->GetWorkingData(0); - if (!m_OverwriteExistingSegmentation) - { - if (m_NoneOverwriteTargetSegmentationNode.IsNull()) - { - mitk::DataNode::Pointer refNode = this->GetToolManager()->GetReferenceData(0); - if (refNode.IsNull()) - { - // TODO create and use segmentation exceptions instead!! - MITK_ERROR << "No valid reference data!"; - return nullptr; - } - std::string nodename = refNode->GetName() + "_" + this->GetName(); - - const auto labelSetImage = dynamic_cast(segmentationNode->GetData()); - if (nullptr == labelSetImage) - { - //TODO: this part of the if statement is old legacy code and should be removed. - //Keept because I didn't want to break/rework to many things before - //the release 2022.04. Should be removed when the seg tool classes are streamlined and the - //multi data structure is the only one used in seg APIs and code. - - mitk::Color color; - color.SetRed(1); - color.SetBlue(0); - color.SetGreen(0); - //create a new segmentation node based on the current segmentation as template - m_NoneOverwriteTargetSegmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); - } - else - { - auto clonedSegmentation = labelSetImage->Clone(); - m_NoneOverwriteTargetSegmentationNode = LabelSetImageHelper::CreateEmptySegmentationNode(nodename); - m_NoneOverwriteTargetSegmentationNode->SetData(clonedSegmentation); - } - } - segmentationNode = m_NoneOverwriteTargetSegmentationNode; - } - return segmentationNode; -} - -void mitk::AutoSegmentationTool::EnsureTargetSegmentationNodeInDataStorage() const -{ - auto targetNode = this->GetTargetSegmentationNode(); - if (!this->GetToolManager()->GetDataStorage()->Exists(targetNode)) - { - this->GetToolManager()->GetDataStorage()->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); - } -} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h deleted file mode 100644 index 8665208340..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ /dev/null @@ -1,86 +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 mitkAutoSegmentationTool_h_Included -#define mitkAutoSegmentationTool_h_Included - -#include "mitkCommon.h" -#include "mitkTool.h" -#include - -namespace mitk -{ - class Image; - - /** - \brief Superclass for tool that create a new segmentation without user interaction in render windows - - This class is undocumented. Ask the creator ($Author$) to supply useful comments. - */ - class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool - { - public: - mitkClassMacro(AutoSegmentationTool, Tool); - - void Activated() override; - void Deactivated() override; - - /** This function controls wether a confirmed segmentation should replace the old - * segmentation/working node (true) or if it should be stored as new and additional - * node (false). - */ - void SetOverwriteExistingSegmentation(bool overwrite); - - /** - * @brief Gets the name of the currently selected segmentation node - * @return the name of the segmentation node or an empty string if - * none is selected - */ - std::string GetCurrentSegmentationName(); - - /** - * @brief Depending on the selected mode either returns the currently selected segmentation node - * or (if overwrite mode is false) creates a new one from the selected reference data. - * @remark Please keep in mind that new created nodes are not automatically added to the data storage. - * Derived tools can call EnsureTargetSegmentationNodeInDataStorage to ensure it as soon as it is clear - * that the target segmentation node will be/is confirmed. - * @return a mitk::DataNode which contains a segmentation image - */ - virtual DataNode *GetTargetSegmentationNode() const; - - protected: - AutoSegmentationTool(); // purposely hidden - AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden - ~AutoSegmentationTool() override; - - const char *GetGroup() const override; - - /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ - static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); - /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ - static Image::Pointer GetImageByTimeStep(Image* image, unsigned int timestep); - /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ - static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); - - void EnsureTargetSegmentationNodeInDataStorage() const; - - bool m_OverwriteExistingSegmentation; - - private: - /**Contains the node returned by GetTargetSementationNode if m_OverwriteExistingSegmentation == false. Then - * GetTargetSegmentation generates a new target segmentation node.*/ - mutable DataNode::Pointer m_NoneOverwriteTargetSegmentationNode; - }; - -} // namespace - -#endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index cbc5a650cb..baad32e9c2 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,122 +1,122 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkBinaryThresholdBaseTool.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLabelSetImage.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThreshold(-100), m_SensibleMaximumThreshold(+100), m_LowerThreshold(1), m_UpperThreshold(1) { } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThreshold || lower > m_SensibleMaximumThreshold || upper < m_SensibleMinimumThreshold || upper > m_SensibleMaximumThreshold) { return; } m_LowerThreshold = lower; m_UpperThreshold = upper; if (nullptr != this->GetPreviewSegmentation()) { UpdatePreview(); } } void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { const auto referenceImage = this->GetReferenceData(); if (nullptr != referenceImage) { m_SensibleMinimumThreshold = std::numeric_limits::max(); m_SensibleMaximumThreshold = std::numeric_limits::lowest(); Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { m_SensibleMinimumThreshold = std::min(m_SensibleMinimumThreshold, static_cast(statistics->GetScalarValueMin())); m_SensibleMaximumThreshold = std::max(m_SensibleMaximumThreshold, static_cast(statistics->GetScalarValueMax())); } if (m_LockedUpperThreshold) { m_LowerThreshold = (m_SensibleMaximumThreshold + m_SensibleMinimumThreshold) / 2.0; m_UpperThreshold = m_SensibleMaximumThreshold; } else { double range = m_SensibleMaximumThreshold - m_SensibleMinimumThreshold; m_LowerThreshold = m_SensibleMinimumThreshold + range / 3.0; m_UpperThreshold = m_SensibleMinimumThreshold + 2 * range / 3.0; } bool isFloatImage = false; if ((referenceImage->GetPixelType().GetPixelType() == itk::IOPixelEnum::SCALAR) && (referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::FLOAT || referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::DOUBLE)) { isFloatImage = true; } IntervalBordersChanged.Send(m_SensibleMinimumThreshold, m_SensibleMaximumThreshold, isFloatImage); ThresholdingValuesChanged.Send(m_LowerThreshold, m_UpperThreshold); } } -void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) +void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage) { AccessByItk_n(inputAtTimeStep, ITKThresholding, (previewImage, timeStep)); } } template void mitk::BinaryThresholdBaseTool::ITKThresholding(const itk::Image* inputImage, Image* segmentation, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(inputImage); filter->SetLowerThreshold(m_LowerThreshold); filter->SetUpperThreshold(m_UpperThreshold); filter->SetInsideValue(this->GetUserDefinedActiveLabel()); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index e7aa8fbddf..c283490bfc 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,77 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBinaryThresholdBaseTool_h_Included #define mitkBinaryThresholdBaseTool_h_Included #include -#include +#include #include #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public SegWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; - mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationWithPreviewTool); + mitkClassMacro(BinaryThresholdBaseTool, SegWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThreshold, ScalarType); itkGetMacro(SensibleMaximumThreshold, ScalarType); void InitiateToolByInput() override; - void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; template void ITKThresholding(const itk::Image* inputImage, Image* segmentation, unsigned int timeStep); private: ScalarType m_SensibleMinimumThreshold; ScalarType m_SensibleMaximumThreshold; ScalarType m_LowerThreshold; ScalarType m_UpperThreshold; /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index ec02f6a346..a7ce47fcec 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,86 +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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; + this->SetLabelTransferMode(LabelTransferMode::AllLabels); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } const char* mitk::OtsuTool3D::GetName() const { return "Otsu"; } -mitk::LabelSetImage::Pointer mitk::OtsuTool3D::ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType /*timeStep*/) +void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { int numberOfThresholds = m_NumberOfRegions - 1; mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(m_UseValley); otsuFilter->SetNumberOfBins(m_NumberOfBins); otsuFilter->SetInput(inputAtTimeStep); otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } auto otsuResultImage = mitk::LabelSetImage::New(); otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); - return otsuResultImage; + TransferLabelSetImageContent(otsuResultImage, previewImage, timeStep); } unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index 3703b237a6..800d2f819f 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,64 +1,64 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKOTSUTOOL3D_H #define MITKOTSUTOOL3D_H -#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkSegWithPreviewTool.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; - class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoMLSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT OtsuTool3D : public SegWithPreviewTool { public: - mitkClassMacro(OtsuTool3D, AutoMLSegmentationWithPreviewTool); + mitkClassMacro(OtsuTool3D, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; itkSetMacro(NumberOfBins, unsigned int); itkGetConstMacro(NumberOfBins, unsigned int); itkSetMacro(NumberOfRegions, unsigned int); itkGetConstMacro(NumberOfRegions, unsigned int); itkSetMacro(UseValley, bool); itkGetConstMacro(UseValley, bool); itkBooleanMacro(UseValley); /**Returns the number of max bins based on the current input image.*/ unsigned int GetMaxNumberOfBins() const; protected: OtsuTool3D() = default; ~OtsuTool3D() = default; - LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; unsigned int m_NumberOfBins = 128; unsigned int m_NumberOfRegions = 2; bool m_UseValley = false; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index b45438f6e3..6f94213db6 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,276 +1,276 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } -mitk::PickingTool::PickingTool() : AutoSegmentationWithPreviewTool(false, "PressMoveReleaseAndPointSetting") +mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } bool mitk::PickingTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData,workingData)) return false; auto* image = dynamic_cast(referenceData); if (image == nullptr) return false; return true; } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Pick_48x48.png"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } -void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) +void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { Label::PixelType backgroundValue = 0; auto labelSetImage = dynamic_cast(oldSegAtTimeStep); if (nullptr != labelSetImage) { backgroundValue = labelSetImage->GetExteriorLabel()->GetValue(); } AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), backgroundValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.h b/Modules/Segmentation/Interactions/mitkPickingTool.h index dd092dcf28..c129eb6087 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.h +++ b/Modules/Segmentation/Interactions/mitkPickingTool.h @@ -1,85 +1,85 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPickingTool_h_Included #define mitkPickingTool_h_Included -#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkSegWithPreviewTool.h" #include "mitkPointSet.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Extracts a single region from a segmentation image and creates a new image with same geometry of the input image. The region is extracted in 3D space. This is done by performing region growing within the desired region. Use shift click to add the seed point. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT PickingTool : public AutoSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT PickingTool : public SegWithPreviewTool { public: - mitkClassMacro(PickingTool, AutoSegmentationWithPreviewTool); + mitkClassMacro(PickingTool, SegWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; void Activated() override; void Deactivated() override; /**Clears all picks and updates the preview.*/ void ClearPicks(); bool HasPicks() const; protected: PickingTool(); // purposely hidden ~PickingTool() override; void ConnectActionsAndFunctions() override; /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Delete action of StateMachine pattern virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Clear all seed points. void ClearSeeds(); - void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; // seed point PointSet::Pointer m_PointSet; DataNode::Pointer m_PointSetNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp similarity index 69% rename from Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp rename to Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 2d9d777380..0a5b9717bf 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,596 +1,732 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" -mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) +mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } -mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : AutoSegmentationTool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) +mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } -mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() +mitk::SegWithPreviewTool::~SegWithPreviewTool() { } -void mitk::AutoSegmentationWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) +void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; } -void mitk::AutoSegmentationWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) +void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; } +void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode LabelTransferMode) +{ + m_LabelTransferMode = LabelTransferMode; +} + +void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) +{ + m_SelectedLabels = labelsToTransfer; +} -bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const +bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } -void mitk::AutoSegmentationWithPreviewTool::Activated() +void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += - MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnRoiDataChanged); + MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += - MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnTimePointChanged); + MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } -void mitk::AutoSegmentationWithPreviewTool::Deactivated() +void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= - MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnRoiDataChanged); + MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= - MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnTimePointChanged); + MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } -void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() +void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } - CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } } -void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() +void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } -mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() +mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() +{ + if (m_PreviewSegmentationNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_PreviewSegmentationNode->GetData()); +} + +const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } - return dynamic_cast(m_PreviewSegmentationNode->GetData()); + return dynamic_cast(m_PreviewSegmentationNode->GetData()); } -mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() +mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } -const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const +const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } -const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const +const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } -void mitk::AutoSegmentationWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) +void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } -void mitk::AutoSegmentationWithPreviewTool::ResetPreviewContent() +void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } -void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() +void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... auto* activeLayer = newPreviewImage->GetActiveLabelSet(); auto* activeLabel = activeLayer->GetActiveLabel(); activeLabel->SetColor(previewColor); activeLayer->UpdateLookupTable(activeLabel->GetValue()); activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } -void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) +mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const +{ + LabelMappingType labelMapping = { { this->GetUserDefinedActiveLabel(),this->GetUserDefinedActiveLabel() } }; + if (LabelTransferMode::SelectedLabels == this->m_LabelTransferMode) + { + labelMapping.clear(); + for (auto label : this->m_SelectedLabels) + { + labelMapping.push_back({ label, label }); + } + } + else if (LabelTransferMode::AllLabels == this->m_LabelTransferMode) + { + labelMapping.clear(); + const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); + } + } + + return labelMapping; +} + +void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); - TransferLabelContent(sourceLSImage, destLSImage, { {this->GetUserDefinedActiveLabel(),this->GetUserDefinedActiveLabel()} }, m_MergeStyle, - m_OverwriteStyle, timeStep); + auto labelMapping = this->GetLabelMapping(); + TransferLabelContent(sourceLSImage, destLSImage, labelMapping, m_MergeStyle, m_OverwriteStyle, timeStep); } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } -void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() +void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { - mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." + mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } + this->TransferPrepare(); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } - if (m_OverwriteExistingSegmentation) - { //if we overwrite the segmentation (and not just store it as a new result - //in the data storage) we update also the tool manager state. - this->GetToolManager()->SetWorkingData(resultSegmentationNode); - this->GetToolManager()->GetWorkingData(0)->Modified(); - } this->EnsureTargetSegmentationNodeInDataStorage(); } } } -void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() +void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } -void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() +void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } -bool mitk::AutoSegmentationWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() +bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } -void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) +void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback image. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback image feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } -bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const +bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } -void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() +void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } -void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() +void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } -mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const +void mitk::SegWithPreviewTool::TransferLabelInformation(LabelMappingType& labelMapping, + const mitk::LabelSetImage* source, mitk::LabelSetImage* target) +{ + for (const auto& [sourceLabel, targetLabel] : labelMapping) + { + if (!target->ExistLabel(targetLabel, target->GetActiveLayer())) + { + if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) + { + mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; + } + + auto clonedLabel = source->GetLabel(sourceLabel, source->GetActiveLayer())->Clone(); + clonedLabel->SetValue(targetLabel); + target->GetActiveLabelSet()->AddLabel(clonedLabel); + } + } +} + +void mitk::SegWithPreviewTool::TransferPrepare() +{ + auto labelMapping = this->GetLabelMapping(); + + DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); + + if (resultSegmentationNode.IsNotNull()) + { + auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); + + if (nullptr == resultSegmentation) + { + mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; + } + + auto preview = this->GetPreviewSegmentation(); + TransferLabelInformation(labelMapping, preview, resultSegmentation); + } +} + +mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } + +const char* mitk::SegWithPreviewTool::GetGroup() const +{ + return "autoSegmentation"; +} + +mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) +{ + return SelectImageByTimeStep(image, timestep); +} + +mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) +{ + return SelectImageByTimeStep(image, timestep); +} + +mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) +{ + return SelectImageByTimeStep(image, timePoint); +} + +void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const +{ + auto targetNode = this->GetTargetSegmentationNode(); + auto dataStorage = this->GetToolManager()->GetDataStorage(); + if (!dataStorage->Exists(targetNode)) + { + dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); + } +} + +std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() +{ + auto workingData = this->GetToolManager()->GetWorkingData(0); + + return nullptr != workingData + ? workingData->GetName() + : ""; +} + + +mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const +{ + return this->GetToolManager()->GetWorkingData(0); +} + +void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) +{ + mitk::ImageReadAccessor newMitkImgAcc(source); + + LabelMappingType labelMapping; + const auto labelSet = source->GetActiveLabelSet(); + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); + } + TransferLabelInformation(labelMapping, source, target); + + target->SetVolume(newMitkImgAcc.GetData(), timeStep); +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h similarity index 70% rename from Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h rename to Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h index 09ec1d6634..b9462aa25e 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h @@ -1,225 +1,287 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef mitkAutoSegmentationWithPreviewTool_h_Included -#define mitkAutoSegmentationWithPreviewTool_h_Included +#ifndef mitkSegWithPreviewTool_h_Included +#define mitkSegWithPreviewTool_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT SegWithPreviewTool : public Tool { public: - mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); + mitkClassMacro(SegWithPreviewTool, Tool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); itkSetMacro(ResetsToEmptyPreview, bool); itkGetMacro(ResetsToEmptyPreview, bool); itkBooleanMacro(ResetsToEmptyPreview); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle); itkGetMacro(MergeStyle, MultiLabelSegmentation::MergeStyle); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle); itkGetMacro(OverwriteStyle, MultiLabelSegmentation::OverwriteStyle); + enum class LabelTransferMode + { + ActiveLabel, //Only the active label will be transfered from preview to segmentation. + SelectedLabels, //The labels defined as selected labels will be transfered. + AllLabels //Transfer all labels of the preview + }; + /*itk macro was not used on purpose, to aviod the change of mtime.*/ + void SetLabelTransferMode(LabelTransferMode LabelTransferMode); + itkGetMacro(LabelTransferMode, LabelTransferMode); + + using SelectedLabelVectorType = std::vector; + /** Specifies the labels that should be transfered form preview to the working image, + if the segmentation is confirmed. The setting will be used, if LabelTransferMode is set to "SelectedLabels".*/ + void SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer); + itkGetMacro(SelectedLabels, SelectedLabelVectorType); + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ bool IsUpdating() const; + /** + * @brief Gets the name of the currently selected segmentation node + * @return the name of the segmentation node or an empty string if + * none is selected + */ + std::string GetCurrentSegmentationName(); + + /** + * @brief Returns the currently selected segmentation node + * @return a mitk::DataNode which contains a segmentation image + */ + virtual DataNode* GetTargetSegmentationNode() const; + + /** Returns the image that contains the preview of the current segmentation. + * Returns null if the node is not set or does not contain an image.*/ + LabelSetImage* GetPreviewSegmentation(); + const LabelSetImage* GetPreviewSegmentation() const; + DataNode* GetPreviewSegmentationNode(); + protected: ToolCommand::Pointer m_ProgressCommand; + SegWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden + SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden + + ~SegWithPreviewTool() override; + + const char* GetGroup() const override; + + /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimeStep(const Image* image, TimeStepType timestep); + /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ + static Image::Pointer GetImageByTimeStep(Image* image, TimeStepType timestep); + /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); + + void EnsureTargetSegmentationNodeInDataStorage() const; + /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); /** This member function offers derived classes the possibility to alter what should happen directly before the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdatePrepare(); /** This member function offers derived classes the possibility to alter what should happen directly after the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdateCleanUp(); + /** This member function offers derived classes the possibility to alter what should + happen directly before the content of the preview is transfered to the segmentation, + when the segmentation is confirmed. It is called by CreateResultSegmentationFromPreview. + Default implementation ensure that all labels that will be transfered, exist in the + segmentation. If they are not existing before the transfer, the will be added by + cloning the label information of the preview.*/ + virtual void TransferPrepare(); + + using LabelMappingType = std::vector >; + static void TransferLabelInformation(LabelMappingType& labelMapping, + const mitk::LabelSetImage* source, mitk::LabelSetImage* target); + + /**Helper function that can be used to move the content of an LabelSetImage (the pixels of the active source layer and the labels). + This is e.g. helpfull if you generate an LabelSetImage content in DoUpdatePreview and you want to transfer it into the preview image.*/ + static void TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep); + /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. * It also provides the current/old segmentation at the time point, * which can be used, if the preview depends on the the segmenation so far. */ - virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; - - AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden - AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden - - ~AutoSegmentationWithPreviewTool() override; - - /** Returns the image that contains the preview of the current segmentation. - * Returns null if the node is not set or does not contain an image.*/ - Image* GetPreviewSegmentation(); - DataNode* GetPreviewSegmentationNode(); + virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) = 0; /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool @remark Calling this function will generate a new preview image, and the old might be invalidated. Therefore this function should not be used within the scope of UpdatePreview (m_IsUpdating == true).*/ void ResetPreviewNode(); /** Resets the complete content of the preview image. The instance of the preview image and its settings * stay the same.*/ void ResetPreviewContent(); /** Resets only the image content of the specified timeStep of the preview image. If the preview image or the specified time step does not exist, nothing happens.*/ void ResetPreviewContentAtTimeStep(unsigned int timeStep); TimePointType GetLastTimePointOfUpdate() const; itkGetConstMacro(UserDefinedActiveLabel, Label::PixelType); itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /**Internal helper that ensures that the stored active label is up to date. This is a fix for T28131 / T28986. It should be refactored if T28524 is being worked on. On the long run, the active label will be communicated/set by the user/toolmanager as a state of the tool and the tool should react accordingly (like it does for other external state changes). @return indicates if the label has changed (true) or not. */ bool EnsureUpToDateUserDefinedActiveLabel(); + LabelMappingType GetLabelMapping() const; + /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; /** Controls if ResetPreviewNode generates an empty content (true) or clones the current segmentation (false).*/ bool m_ResetsToEmptyPreview = false; TimePointType m_LastTimePointOfUpdate = 0.; bool m_IsUpdating = false; Label::PixelType m_UserDefinedActiveLabel = 1; /** This variable indicates if for the tool a working plane geometry is defined. * If a working plane is defined the tool will only work an the slice of the input * and the segmentation. Thus only the relevant input slice will be passed to * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when * ConfirmSegmentation() is called.*/ PlaneGeometry::Pointer m_WorkingPlaneGeometry; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::MergeStyle. */ MultiLabelSegmentation::MergeStyle m_MergeStyle = MultiLabelSegmentation::MergeStyle::Replace; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::OverwriteStyle. */ MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = MultiLabelSegmentation::OverwriteStyle::RegardLocks; + + LabelTransferMode m_LabelTransferMode = LabelTransferMode::ActiveLabel; + SelectedLabelVectorType m_SelectedLabels = {}; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index 99779403d0..1245ee2124 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,327 +1,320 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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(); + this->SetLabelTransferMode(LabelTransferMode::AllLabels); } void mitk::nnUNetTool::RenderOutputBuffer() { if (m_OutputBuffer != nullptr) { - Superclass::SetNodeProperties(m_OutputBuffer); try { if (nullptr != this->GetPreviewSegmentationNode()) { - this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); - } - if (this->GetSelectedLabels().empty()) - { - this->ResetPreviewNode(); + auto previewImage = this->GetPreviewSegmentation(); + previewImage->InitializeByLabeledImage(m_OutputBuffer); } } catch (const mitk::Exception &e) { MITK_INFO << e.GetDescription(); } } } void mitk::nnUNetTool::SetOutputBuffer(LabelSetImage::Pointer segmentation) { m_OutputBuffer = segmentation; } mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() { return m_OutputBuffer; } void mitk::nnUNetTool::ClearOutputBuffer() { m_OutputBuffer = nullptr; } us::ModuleResource mitk::nnUNetTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI_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*/) +void mitk::nnUNetTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType /*timeStep*/) { - if (m_InputBuffer == inputAtTimeStep) - { - return m_OutputBuffer; - } std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; 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, m_TEMPLATE_FILENAME, 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 { if (this->GetMultiModal()) { const std::string fileFormat(".nii.gz"); const std::string fileNamePart("_000_000"); std::string outModalFile; size_t len = inDir.length() + 1 + token.length() + fileNamePart.length() + 1 + fileFormat.length(); outModalFile.reserve(len); // The 1(s) indicates a directory separator char and an underscore. for (size_t i = 0; i < m_OtherModalPaths.size(); ++i) { mitk::Image::ConstPointer modalImage = m_OtherModalPaths[i]; outModalFile.append(inDir); outModalFile.push_back(IOUtil::GetDirectorySeparator()); outModalFile.append(token); outModalFile.append(fileNamePart); outModalFile.append(std::to_string(i)); outModalFile.append(fileFormat); IOUtil::Save(modalImage.GetPointer(), outModalFile); outModalFile.clear(); } } else { IOUtil::Save(inputAtTimeStep, inputImagePath); } } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); - return nullptr; + return; } // 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()) { 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; + return; } } 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); if (!this->GetPostProcessingJsonDirectory().empty()) { 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()); + previewImage->InitializeByLabeledImage(outputImage); + previewImage->SetGeometry(inputAtTimeStep->GetGeometry()); m_InputBuffer = inputAtTimeStep; - m_OutputBuffer = resultImage; - return resultImage; + m_OutputBuffer = mitk::LabelSetImage::New(); + m_OutputBuffer->InitializeByLabeledImage(outputImage); + m_OutputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); - return nullptr; + return; } } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h index 7e0fcffd7a..320bfca130 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.h +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -1,213 +1,213 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitknnUnetTool_h_Included #define mitknnUnetTool_h_Included -#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkSegWithPreviewTool.h" #include "mitkCommon.h" #include "mitkToolManager.h" #include #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; std::string inputName; std::string timeStamp; size_t generateHash() const { std::string toHash; std::string foldsConcatenated = std::accumulate(folds.begin(), folds.end(), std::string("")); toHash += this->task; toHash += this->model; toHash += this->inputName; toHash += foldsConcatenated; toHash += this->timeStamp; size_t hashVal = std::hash{}(toHash); return hashVal; } }; /** \brief nnUNet segmentation tool. \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoMLSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT nnUNetTool : public SegWithPreviewTool { public: - mitkClassMacro(nnUNetTool, AutoMLSegmentationWithPreviewTool); + mitkClassMacro(nnUNetTool, SegWithPreviewTool); 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; mitk::Image::ConstPointer m_InputBuffer; /** * @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(); void SetOutputBuffer(LabelSetImage::Pointer); 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 DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) 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; const std::string m_TEMPLATE_FILENAME = "XXXXXX_000_0000.nii.gz"; }; } // namespace mitk #endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index e8f1ecffb7..d931118bc0 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,110 +1,107 @@ 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/mitkSegWithPreviewTool.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/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/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 AI_48x48.png AI_Cursor_32x32.png Erase_48x48.png Erase_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 Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.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/CMakeLists.txt b/Modules/SegmentationUI/CMakeLists.txt index daf52bda9c..be064b6867 100644 --- a/Modules/SegmentationUI/CMakeLists.txt +++ b/Modules/SegmentationUI/CMakeLists.txt @@ -1,5 +1,5 @@ MITK_CREATE_MODULE ( INCLUDE_DIRS Qmitk DEPENDS MitkSegmentation MitkQtWidgetsExt -PACKAGE_DEPENDS CTK|CTKWidgets +PACKAGE_DEPENDS PRIVATE CTK|CTKWidgets nlohmann_json ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp deleted file mode 100644 index 2b6853bb74..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +++ /dev/null @@ -1,1006 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ -#include "QmitkAdaptiveRegionGrowingToolGUI.h" - -#include - -#include "mitkITKImageImport.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageTimeSelector.h" -#include "mitkNodePredicateDataType.h" -#include "mitkProperties.h" -#include "mitkTransferFunctionProperty.h" - -#include "mitkImageStatisticsHolder.h" - -#include "itkMaskImageFilter.h" -#include "itkNumericTraits.h" -#include -#include -#include -#include - -#include "QmitkConfirmSegmentationDialog.h" -#include "itkOrImageFilter.h" -#include "mitkImageCast.h" - -#include "mitkImagePixelReadAccessor.h" -#include "mitkPixelTypeMultiplex.h" - -#include "mitkImageCast.h" - -MITK_TOOL_GUI_MACRO(, QmitkAdaptiveRegionGrowingToolGUI, "") - -QmitkAdaptiveRegionGrowingToolGUI::QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent) - : QmitkToolGUI(), - m_DataStorage(nullptr), - m_UseVolumeRendering(false), - m_UpdateSuggestedThreshold(true), - m_SuggestedThValue(0.0) -{ - this->setParent(parent); - - m_Controls.setupUi(this); - - m_Controls.m_ThresholdSlider->setDecimals(1); - m_Controls.m_ThresholdSlider->setSpinBoxAlignment(Qt::AlignVCenter); - - m_Controls.m_PreviewSlider->setEnabled(false); - m_Controls.m_PreviewSlider->setSingleStep(0.5); - // Not yet available - // m_Controls.m_PreviewSlider->InvertedAppearance(true); - - //3D preview doesn't work: T24430. Postponed until reimplementation of segmentation - m_Controls.m_cbVolumeRendering->setVisible(false); - - this->CreateConnections(); - this->SetDataNodeNames("labeledRGSegmentation", "RGResult", "RGFeedbackSurface", "maskedSegmentation"); - - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); -} - -QmitkAdaptiveRegionGrowingToolGUI::~QmitkAdaptiveRegionGrowingToolGUI() -{ - // Removing the observer of the PointSet node - if (m_RegionGrow3DTool->GetPointSetNode().IsNotNull()) - { - m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetAddObserverTag); - m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetMoveObserverTag); - } - this->RemoveHelperNodes(); -} - -void QmitkAdaptiveRegionGrowingToolGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - m_RegionGrow3DTool = dynamic_cast(tool); - if (m_RegionGrow3DTool.IsNotNull()) - { - SetInputImageNode(this->m_RegionGrow3DTool->GetReferenceData()); - this->m_DataStorage = this->m_RegionGrow3DTool->GetDataStorage(); - this->EnableControls(true); - - // Watch for point added or modified - itk::SimpleMemberCommand::Pointer pointAddedCommand = - itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded); - m_PointSetAddObserverTag = - m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - m_PointSetMoveObserverTag = - m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetMoveEvent(), pointAddedCommand); - } - else - { - this->EnableControls(false); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::RemoveHelperNodes() -{ - mitk::DataNode::Pointer imageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - if (imageNode.IsNotNull()) - { - m_DataStorage->Remove(imageNode); - } - - mitk::DataNode::Pointer maskedSegmentationNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); - if (maskedSegmentationNode.IsNotNull()) - { - m_DataStorage->Remove(maskedSegmentationNode); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::CreateConnections() -{ - // Connecting GUI components - connect((QObject *)(m_Controls.m_pbRunSegmentation), SIGNAL(clicked()), this, SLOT(RunSegmentation())); - connect(m_Controls.m_PreviewSlider, SIGNAL(valueChanged(double)), this, SLOT(ChangeLevelWindow(double))); - connect((QObject *)(m_Controls.m_pbConfirmSegementation), SIGNAL(clicked()), this, SLOT(ConfirmSegmentation())); - connect( - m_Controls.m_ThresholdSlider, SIGNAL(maximumValueChanged(double)), this, SLOT(SetUpperThresholdValue(double))); - connect( - m_Controls.m_ThresholdSlider, SIGNAL(minimumValueChanged(double)), this, SLOT(SetLowerThresholdValue(double))); -} - -void QmitkAdaptiveRegionGrowingToolGUI::SetDataNodeNames(std::string labledSegmentation, - std::string binaryImage, - std::string surface, - std::string maskedSegmentation) -{ - m_NAMEFORLABLEDSEGMENTATIONIMAGE = labledSegmentation; - m_NAMEFORBINARYIMAGE = binaryImage; - m_NAMEFORSURFACE = surface; - m_NAMEFORMASKEDSEGMENTATION = maskedSegmentation; -} - -void QmitkAdaptiveRegionGrowingToolGUI::SetDataStorage(mitk::DataStorage *dataStorage) -{ - m_DataStorage = dataStorage; -} - -void QmitkAdaptiveRegionGrowingToolGUI::SetInputImageNode(mitk::DataNode *node) -{ - m_InputImageNode = node; - mitk::Image *inputImage = dynamic_cast(m_InputImageNode->GetData()); - if (inputImage) - { - mitk::ScalarType max = inputImage->GetStatistics()->GetScalarValueMax(); - mitk::ScalarType min = inputImage->GetStatistics()->GetScalarValueMin(); - m_Controls.m_ThresholdSlider->setMaximum(max); - m_Controls.m_ThresholdSlider->setMinimum(min); - // Just for initialization - m_Controls.m_ThresholdSlider->setMaximumValue(max); - m_Controls.m_ThresholdSlider->setMinimumValue(min); - } -} - -template -static void AccessPixel(mitk::PixelType /*ptype*/, mitk::Image* im, mitk::Point3D p, int& val) -{ - mitk::ImagePixelReadAccessor access(im); - val = access.GetPixelByWorldCoordinates(p); -} - -/**Overloaded const verison*/ -template -static void AccessPixel(mitk::PixelType /*ptype*/, const mitk::Image* im, mitk::Point3D p, int& val) -{ - mitk::ImagePixelReadAccessor access(im); - val = access.GetPixelByWorldCoordinates(p); -} - -void QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded() -{ - if (m_RegionGrow3DTool.IsNull()) - return; - - mitk::DataNode *node = m_RegionGrow3DTool->GetPointSetNode(); - - if (node != nullptr) - { - mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); - - if (pointSet.IsNull()) - { - QMessageBox::critical(nullptr, "QmitkAdaptiveRegionGrowingToolGUI", "PointSetNode does not contain a pointset"); - return; - } - - m_Controls.m_lblSetSeedpoint->setText(""); - - const mitk::Image *image = dynamic_cast(m_InputImageNode->GetData()); - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - auto image3D = GetImageByTimePoint(image, timePoint); - - if (nullptr == image3D) - { - MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected " - "reference image. Time point: " - << timePoint; - return; - } - - if (!pointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) - return; - - mitk::Point3D seedPoint = - pointSet - ->GetPointSet(static_cast(pointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint))) - ->GetPoints() - ->ElementAt(0); - - if (image3D->GetGeometry()->IsInside(seedPoint)) - mitkPixelTypeMultiplex3( - AccessPixel, image3D->GetChannelDescriptor().GetPixelType(), image3D, seedPoint, m_SeedpointValue) else return; - - /* In this case the seedpoint is placed e.g. in the lung or bronchialtree - * The lowerFactor sets the windowsize depending on the regiongrowing direction - */ - m_CurrentRGDirectionIsUpwards = true; - if (m_SeedpointValue < -500) - { - m_CurrentRGDirectionIsUpwards = false; - } - - // Initializing the region by the area around the seedpoint - m_SeedPointValueMean = 0; - - itk::Index<3> currentIndex, runningIndex; - mitk::ScalarType pixelValues[125]; - unsigned int pos(0); - - image3D->GetGeometry(0)->WorldToIndex(seedPoint, currentIndex); - runningIndex = currentIndex; - - for (int i = runningIndex[0] - 2; i <= runningIndex[0] + 2; i++) - { - for (int j = runningIndex[1] - 2; j <= runningIndex[1] + 2; j++) - { - for (int k = runningIndex[2] - 2; k <= runningIndex[2] + 2; k++) - { - currentIndex[0] = i; - currentIndex[1] = j; - currentIndex[2] = k; - - if (image3D->GetGeometry()->IsIndexInside(currentIndex)) - { - int component = 0; - m_InputImageNode->GetIntProperty("Image.Displayed Component", component); - mitkPixelTypeMultiplex4(mitk::FastSinglePixelAccess, - image3D->GetChannelDescriptor().GetPixelType(), - image3D, - nullptr, - currentIndex, - pixelValues[pos]); - - pos++; - } - else - { - pixelValues[pos] = std::numeric_limits::min(); - pos++; - } - } - } - } - - // Now calculation mean of the pixelValues - // Now calculation mean of the pixelValues - unsigned int numberOfValues(0); - for (auto &pixelValue : pixelValues) - { - if (pixelValue > std::numeric_limits::min()) - { - m_SeedPointValueMean += pixelValue; - numberOfValues++; - } - } - m_SeedPointValueMean = m_SeedPointValueMean / numberOfValues; - - mitk::ScalarType var = 0; - if (numberOfValues > 1) - { - for (auto &pixelValue : pixelValues) - { - if (pixelValue > std::numeric_limits::min()) - { - var += (pixelValue - m_SeedPointValueMean) * (pixelValue - m_SeedPointValueMean); - } - } - var /= numberOfValues - 1; - } - mitk::ScalarType stdDev = sqrt(var); - - /* - * Here the upper- and lower threshold is calculated: - * The windowSize is 20% of the maximum range of the intensity values existing in the current image - * If the RG direction is upwards the lower TH is meanSeedValue-0.15*windowSize and upper TH is - * meanSeedValue+0.85*windowsSize - * if the RG direction is downwards the lower TH is meanSeedValue-0.85*windowSize and upper TH is - * meanSeedValue+0.15*windowsSize - */ - const auto timeStepOfImage = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); - mitk::ScalarType min = image->GetStatistics()->GetScalarValueMin(timeStepOfImage); - mitk::ScalarType max = image->GetStatistics()->GetScalarValueMax(timeStepOfImage); - - mitk::ScalarType windowSize = max - min; - - windowSize = 0.15 * windowSize; - - if (m_CurrentRGDirectionIsUpwards) - { - m_LOWERTHRESHOLD = m_SeedPointValueMean - stdDev; - m_UPPERTHRESHOLD = m_SeedpointValue + windowSize; - if (m_UPPERTHRESHOLD > max) - m_UPPERTHRESHOLD = max; - m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); - m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); - } - else - { - m_UPPERTHRESHOLD = m_SeedPointValueMean; - if (m_SeedpointValue > m_SeedPointValueMean) - m_UPPERTHRESHOLD = m_SeedpointValue; - m_LOWERTHRESHOLD = m_SeedpointValue - windowSize; - if (m_LOWERTHRESHOLD < min) - m_LOWERTHRESHOLD = min; - m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); - m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); - } - } -} - -mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::GetImageByTimePoint(const mitk::Image *image, - mitk::TimePointType timePoint) const -{ - if (nullptr == image) - return image; - - if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) - return nullptr; - - if (image->GetDimension() != 4) - return image; - - auto imageTimeSelector = mitk::ImageTimeSelector::New(); - - imageTimeSelector->SetInput(image); - imageTimeSelector->SetTimeNr(static_cast(image->GetTimeGeometry()->TimePointToTimeStep(timePoint))); - - imageTimeSelector->UpdateLargestPossibleRegion(); - - return imageTimeSelector->GetOutput(); -} - -void QmitkAdaptiveRegionGrowingToolGUI::RunSegmentation() -{ - if (m_InputImageNode.IsNull()) - { - QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please specify the image in Datamanager!"); - return; - } - - mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); - - if (node.IsNull()) - { - QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please insert a seed point inside the " - "image.\n\nFirst press the \"Define Seed " - "Point\" button,\nthen click left mouse " - "button inside the image."); - return; - } - - // safety if no pointSet or pointSet empty - mitk::PointSet::Pointer seedPointSet = dynamic_cast(node->GetData()); - if (seedPointSet.IsNull()) - { - m_Controls.m_pbRunSegmentation->setEnabled(true); - QMessageBox::information( - nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); - return; - } - - mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); - - if (orgImage.IsNotNull()) - { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (!seedPointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) - mitkThrow() << "Point set is not defined for specified time point. Time point: " << timePoint; - - int timeStep = static_cast(seedPointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint)); - - if (!(seedPointSet->GetSize(timeStep))) - { - m_Controls.m_pbRunSegmentation->setEnabled(true); - QMessageBox::information( - nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); - return; - } - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - - mitk::PointSet::PointType seedPoint = seedPointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); - - auto image3D = GetImageByTimePoint(orgImage, timePoint); - - if (image3D.IsNotNull()) - { - // QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //set the cursor to waiting - AccessByItk_2(image3D, StartRegionGrowing, image3D->GetGeometry(), seedPoint); - // QApplication::restoreOverrideCursor();//reset cursor - } - else - { - QApplication::restoreOverrideCursor(); // reset cursor - QMessageBox::information( - nullptr, "Adaptive Region Growing functionality", "Only images of dimension 3 or 4 can be processed!"); - return; - } - } - EnableControls(true); // Segmentation ran successfully, so enable all controls. - node->SetVisibility(true); - QApplication::restoreOverrideCursor(); // reset cursor -} - -template -void QmitkAdaptiveRegionGrowingToolGUI::StartRegionGrowing(const itk::Image *itkImage, - const mitk::BaseGeometry *imageGeometry, - const mitk::PointSet::PointType seedPoint) -{ - typedef itk::Image InputImageType; - typedef typename InputImageType::IndexType IndexType; - typedef itk::ConnectedAdaptiveThresholdImageFilter RegionGrowingFilterType; - typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - typedef itk::MaskImageFilter MaskImageFilterType; - - if (!imageGeometry->IsInside(seedPoint)) - { - QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor - QMessageBox::information(nullptr, - "Segmentation functionality", - "The seed point is outside of the image! Please choose a position inside the image!"); - return; - } - - IndexType seedIndex; - imageGeometry->WorldToIndex(seedPoint, seedIndex); // convert world coordinates to image indices - - if (m_SeedpointValue > m_UPPERTHRESHOLD || m_SeedpointValue < m_LOWERTHRESHOLD) - { - QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor - QMessageBox::information( - nullptr, - "Segmentation functionality", - "The seed point is outside the defined thresholds! Please set a new seed point or adjust the thresholds."); - MITK_INFO << "Mean: " << m_SeedPointValueMean; - return; - } - - // Setting the direction of the regiongrowing. For dark structures e.g. the lung the regiongrowing - // is performed starting at the upper value going to the lower one - regionGrower->SetGrowingDirectionIsUpwards(m_CurrentRGDirectionIsUpwards); - regionGrower->SetInput(itkImage); - regionGrower->AddSeed(seedIndex); - // In some cases we have to subtract 1 for the lower threshold and add 1 to the upper. - // Otherwise no region growing is done. Maybe a bug in the ConnectiveAdaptiveThresholdFilter - mitk::ScalarType maxPixelValue = m_Controls.m_ThresholdSlider->maximum(); - mitk::ScalarType minPixelValue = m_Controls.m_ThresholdSlider->minimum(); - - if ((m_LOWERTHRESHOLD - minPixelValue) >= 1) - { - regionGrower->SetLower(m_LOWERTHRESHOLD - 1); - } - else - { - regionGrower->SetLower(m_LOWERTHRESHOLD); - } - - if ((maxPixelValue - m_UPPERTHRESHOLD) >= 1) - { - regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); - } - else - { - regionGrower->SetUpper(m_UPPERTHRESHOLD); - } - - - try - { - regionGrower->Update(); - } - catch (itk::ExceptionObject &exc) - { - QMessageBox errorInfo; - errorInfo.setWindowTitle("Adaptive RG Segmentation Functionality"); - errorInfo.setIcon(QMessageBox::Critical); - errorInfo.setText("An error occurred during region growing!"); - errorInfo.setDetailedText(exc.what()); - errorInfo.exec(); - return; // can't work - } - catch (...) - { - QMessageBox::critical(nullptr, "Adaptive RG Segmentation Functionality", "An error occurred during region growing!"); - return; - } - - mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput())->Clone(); - // initialize slider - m_Controls.m_PreviewSlider->setMinimum(m_LOWERTHRESHOLD); - - mitk::ScalarType max = m_SeedpointValue + resultImage->GetStatistics()->GetScalarValueMax(); - if (max < m_UPPERTHRESHOLD) - m_Controls.m_PreviewSlider->setMaximum(max); - else - m_Controls.m_PreviewSlider->setMaximum(m_UPPERTHRESHOLD); - - this->m_DetectedLeakagePoint = regionGrower->GetLeakagePoint(); - - if (m_CurrentRGDirectionIsUpwards) - { - m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean - 1); - } - else - { - m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean + 1); - } - this->m_SliderInitialized = true; - - // create new node and then delete the old one if there is one - mitk::DataNode::Pointer newNode = mitk::DataNode::New(); - newNode->SetData(resultImage); - - // set some properties - newNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORLABLEDSEGMENTATIONIMAGE)); - newNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - newNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); - newNode->SetProperty("layer", mitk::IntProperty::New(1)); - newNode->SetProperty("opacity", mitk::FloatProperty::New(0.7)); - - // delete the old image, if there was one: - mitk::DataNode::Pointer binaryNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - m_DataStorage->Remove(binaryNode); - - // now add result to data tree - m_DataStorage->Add(newNode, m_InputImageNode); - - typename InputImageType::Pointer inputImageItk; - mitk::CastToItkImage(resultImage, inputImageItk); - // volume rendering preview masking - typename ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); - thresholdFilter->SetInput(inputImageItk); - thresholdFilter->SetInsideValue(1); - thresholdFilter->SetOutsideValue(0); - - double sliderVal = this->m_Controls.m_PreviewSlider->value(); - if (m_CurrentRGDirectionIsUpwards) - { - thresholdFilter->SetLowerThreshold(sliderVal); - thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); - } - else - { - thresholdFilter->SetLowerThreshold(itk::NumericTraits::NonpositiveMin()); - thresholdFilter->SetUpperThreshold(sliderVal); - } - thresholdFilter->SetInPlace(false); - - typename MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); - maskFilter->SetInput(inputImageItk); - maskFilter->SetInPlace(false); - maskFilter->SetMaskImage(thresholdFilter->GetOutput()); - maskFilter->SetOutsideValue(0); - maskFilter->UpdateLargestPossibleRegion(); - - mitk::Image::Pointer mitkMask; - mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMask); - mitk::DataNode::Pointer maskedNode = mitk::DataNode::New(); - maskedNode->SetData(mitkMask); - - // set some properties - maskedNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORMASKEDSEGMENTATION)); - maskedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - maskedNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); - maskedNode->SetProperty("layer", mitk::IntProperty::New(1)); - maskedNode->SetProperty("opacity", mitk::FloatProperty::New(0.0)); - - // delete the old image, if there was one: - mitk::DataNode::Pointer deprecatedMask = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); - m_DataStorage->Remove(deprecatedMask); - - // now add result to data tree - m_DataStorage->Add(maskedNode, m_InputImageNode); - - this->InitializeLevelWindow(); - - if (m_UseVolumeRendering) - this->EnableVolumeRendering(true); - - m_UpdateSuggestedThreshold = true; // reset first stored threshold value - // Setting progress to finished - mitk::ProgressBar::GetInstance()->Progress(357); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkAdaptiveRegionGrowingToolGUI::InitializeLevelWindow() -{ - // get the preview from the datatree - mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - - mitk::LevelWindow tempLevelWindow; - newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); - - mitk::ScalarType *level = new mitk::ScalarType(0.0); - mitk::ScalarType *window = new mitk::ScalarType(1.0); - - int upper; - if (m_CurrentRGDirectionIsUpwards) - { - upper = m_UPPERTHRESHOLD - m_SeedpointValue; - } - else - { - upper = m_SeedpointValue - m_LOWERTHRESHOLD; - } - - tempLevelWindow.SetRangeMinMax(mitk::ScalarType(0), mitk::ScalarType(upper)); - - // get the suggested threshold from the detected leakage-point and adjust the slider - - if (m_CurrentRGDirectionIsUpwards) - { - this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); - *level = m_UPPERTHRESHOLD - (m_SeedpointValue) + 0.5; - } - else - { - this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); - *level = (m_SeedpointValue)-m_LOWERTHRESHOLD + 0.5; - } - - tempLevelWindow.SetLevelWindow(*level, *window); - newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); - // update the widgets - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_SliderInitialized = true; - - // inquiry need to fix bug#1828 - static int lastSliderPosition = 0; - if ((this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1) == lastSliderPosition) - { - this->ChangeLevelWindow(lastSliderPosition); - } - lastSliderPosition = this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1; - - if (m_UseVolumeRendering) - this->UpdateVolumeRenderingThreshold((int)(*level + 0.5)); // lower threshold for labeled image -} - -void QmitkAdaptiveRegionGrowingToolGUI::ChangeLevelWindow(double newValue) -{ - if (m_SliderInitialized) - { - // do nothing, if no preview exists - mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - if (newNode.IsNull()) - return; - - mitk::LevelWindow tempLevelWindow; - - newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // get the levelWindow associated with the preview - - mitk::ScalarType level; // = this->m_UPPERTHRESHOLD - newValue + 0.5; - mitk::ScalarType *window = new mitk::ScalarType(1); - - // adjust the levelwindow according to the position of the slider (newvalue) - if (m_CurrentRGDirectionIsUpwards) - { - level = m_UPPERTHRESHOLD - newValue + 0.5; - tempLevelWindow.SetLevelWindow(level, *window); - } - else - { - level = newValue - m_LOWERTHRESHOLD + 0.5; - tempLevelWindow.SetLevelWindow(level, *window); - } - - newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); - - if (m_UseVolumeRendering) - this->UpdateVolumeRenderingThreshold((int)(level - 0.5)); // lower threshold for labeled image - - newNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::DecreaseSlider() -{ - // moves the slider one step to the left, when the "-"-button is pressed - if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->minimum()) - { - int newValue = this->m_Controls.m_PreviewSlider->value() - 1; - this->ChangeLevelWindow(newValue); - this->m_Controls.m_PreviewSlider->setValue(newValue); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::IncreaseSlider() -{ - // moves the slider one step to the right, when the "+"-button is pressed - if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->maximum()) - { - int newValue = this->m_Controls.m_PreviewSlider->value() + 1; - this->ChangeLevelWindow(newValue); - this->m_Controls.m_PreviewSlider->setValue(newValue); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::ConfirmSegmentation() -{ - // get image node - if (m_InputImageNode.IsNull()) - { - QMessageBox::critical(nullptr, "Adaptive region growing functionality", "Please specify the image in Datamanager!"); - return; - } - // get image data - mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); - if (orgImage.IsNull()) - { - QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Image found!"); - return; - } - // get labeled segmentation - mitk::Image::Pointer labeledSeg = - (mitk::Image *)m_DataStorage->GetNamedObject(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - if (labeledSeg.IsNull()) - { - QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Segmentation Preview found!"); - return; - } - - mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - if (newNode.IsNull()) - return; - - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_RegionGrow3DTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) - { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: - m_RegionGrow3DTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: - m_RegionGrow3DTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } - - mitk::Image::Pointer img = dynamic_cast(newNode->GetData()); - AccessByItk(img, ITKThresholding); - - // disable volume rendering preview after the segmentation node was created - this->EnableVolumeRendering(false); - newNode->SetVisibility(false); - m_Controls.m_cbVolumeRendering->setChecked(false); - // TODO disable slider etc... - - if (m_RegionGrow3DTool.IsNotNull()) - { - m_RegionGrow3DTool->ConfirmSegmentation(); - } -} - -template -void QmitkAdaptiveRegionGrowingToolGUI::ITKThresholding(itk::Image *itkImage) -{ - mitk::Image::Pointer originalSegmentation = - dynamic_cast(this->m_RegionGrow3DTool->GetTargetSegmentationNode()->GetData()); - - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (!originalSegmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) - mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << timePoint; - - int timeStep = static_cast(originalSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint)); - - - if (originalSegmentation) - { - typedef itk::Image InputImageType; - typedef itk::Image SegmentationType; - - // select single 3D volume if we have more than one time step - typename SegmentationType::Pointer originalSegmentationInITK = SegmentationType::New(); - if (originalSegmentation->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(originalSegmentation); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - CastToItkImage(timeSelector->GetOutput(), originalSegmentationInITK); - } - else // use original - { - CastToItkImage(originalSegmentation, originalSegmentationInITK); - } - - // Fill current preiview image in segmentation image - originalSegmentationInITK->FillBuffer(0); - itk::ImageRegionIterator itOutput(originalSegmentationInITK, - originalSegmentationInITK->GetLargestPossibleRegion()); - itk::ImageRegionIterator itInput(itkImage, itkImage->GetLargestPossibleRegion()); - itOutput.GoToBegin(); - itInput.GoToBegin(); - - // calculate threhold from slider value - int currentTreshold = 0; - if (m_CurrentRGDirectionIsUpwards) - { - currentTreshold = m_UPPERTHRESHOLD - m_Controls.m_PreviewSlider->value() + 1; - } - else - { - currentTreshold = m_Controls.m_PreviewSlider->value() - m_LOWERTHRESHOLD; - } - - // iterate over image and set pixel in segmentation according to thresholded labeled image - while (!itOutput.IsAtEnd() && !itInput.IsAtEnd()) - { - // Use threshold slider to determine if pixel is set to 1 - if (itInput.Value() != 0 && itInput.Value() >= static_cast::PixelType>(currentTreshold)) - { - itOutput.Set(1); - } - - ++itOutput; - ++itInput; - } - - // combine current working segmentation image with our region growing result - originalSegmentation->SetVolume((void *)(originalSegmentationInITK->GetPixelContainer()->GetBufferPointer()), - timeStep); - - originalSegmentation->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkAdaptiveRegionGrowingToolGUI::EnableControls(bool enable) -{ - if (m_RegionGrow3DTool.IsNull()) - return; - - // Check if seed point is already set, if not leave RunSegmentation disabled - // if even m_DataStorage is nullptr leave node nullptr - mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); - if (node.IsNull()) - { - this->m_Controls.m_pbRunSegmentation->setEnabled(false); - } - else - { - this->m_Controls.m_pbRunSegmentation->setEnabled(enable); - } - - // Check if a segmentation exists, if not leave segmentation dependent disabled. - // if even m_DataStorage is nullptr leave node nullptr - node = m_DataStorage ? m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE) : nullptr; - if (node.IsNull()) - { - this->m_Controls.m_PreviewSlider->setEnabled(false); - this->m_Controls.m_pbConfirmSegementation->setEnabled(false); - } - else - { - this->m_Controls.m_PreviewSlider->setEnabled(enable); - this->m_Controls.m_pbConfirmSegementation->setEnabled(enable); - } - - this->m_Controls.m_cbVolumeRendering->setEnabled(enable); -} - -void QmitkAdaptiveRegionGrowingToolGUI::EnableVolumeRendering(bool enable) -{ - mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); - - if (node.IsNull()) - return; - - node->SetBoolProperty("volumerendering", enable); - - double val = this->m_Controls.m_PreviewSlider->value(); - this->ChangeLevelWindow(val); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkAdaptiveRegionGrowingToolGUI::UpdateVolumeRenderingThreshold(int) -{ - typedef short PixelType; - typedef itk::Image InputImageType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - typedef itk::MaskImageFilter MaskImageFilterType; - - mitk::DataNode::Pointer grownImageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - mitk::Image::Pointer grownImage = dynamic_cast(grownImageNode->GetData()); - - if (!grownImage) - { - MITK_ERROR << "Missing data node for labeled segmentation image."; - return; - } - - InputImageType::Pointer itkGrownImage; - mitk::CastToItkImage(grownImage, itkGrownImage); - - ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); - thresholdFilter->SetInput(itkGrownImage); - thresholdFilter->SetInPlace(false); - - double sliderVal = this->m_Controls.m_PreviewSlider->value(); - PixelType threshold = itk::NumericTraits::min(); - if (m_CurrentRGDirectionIsUpwards) - { - threshold = static_cast(m_UPPERTHRESHOLD - sliderVal + 0.5); - - thresholdFilter->SetLowerThreshold(threshold); - thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); - } - else - { - threshold = sliderVal - m_LOWERTHRESHOLD + 0.5; - - thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); - thresholdFilter->SetUpperThreshold(threshold); - } - thresholdFilter->UpdateLargestPossibleRegion(); - - MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); - maskFilter->SetInput(itkGrownImage); - maskFilter->SetInPlace(false); - maskFilter->SetMaskImage(thresholdFilter->GetOutput()); - maskFilter->SetOutsideValue(0); - maskFilter->UpdateLargestPossibleRegion(); - - mitk::Image::Pointer mitkMaskedImage; - mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMaskedImage); - mitk::DataNode::Pointer maskNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); - maskNode->SetData(mitkMaskedImage); -} - -void QmitkAdaptiveRegionGrowingToolGUI::UseVolumeRendering(bool on) -{ - m_UseVolumeRendering = on; - - this->EnableVolumeRendering(on); -} - -void QmitkAdaptiveRegionGrowingToolGUI::SetLowerThresholdValue(double lowerThreshold) -{ - m_LOWERTHRESHOLD = lowerThreshold; -} - -void QmitkAdaptiveRegionGrowingToolGUI::SetUpperThresholdValue(double upperThreshold) -{ - m_UPPERTHRESHOLD = upperThreshold; -} - -void QmitkAdaptiveRegionGrowingToolGUI::Deactivated() -{ - // make the segmentation preview node invisible - mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); - if (node.IsNotNull()) - { - node->SetVisibility(false); - } - - // disable volume rendering preview after the segmentation node was created - this->EnableVolumeRendering(false); - m_Controls.m_cbVolumeRendering->setChecked(false); -} - -void QmitkAdaptiveRegionGrowingToolGUI::Activated() -{ -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h deleted file mode 100644 index 79f848ea27..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h +++ /dev/null @@ -1,229 +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 QMITK_QmitkAdaptiveRegionGrowingToolGUI_H -#define QMITK_QmitkAdaptiveRegionGrowingToolGUI_H - -#include "itkImage.h" -#include "mitkDataStorage.h" -#include "mitkGeometry3D.h" -#include "mitkPointSet.h" - -#include "qwidget.h" -#include "ui_QmitkAdaptiveRegionGrowingToolGUIControls.h" - -#include - -#include "QmitkToolGUI.h" - -#include "mitkAdaptiveRegionGrowingTool.h" - -class DataNode; -class QmitkAdaptiveRegionGrowingToolGUIControls; - -/*! -* -* \brief QmitkAdaptiveRegionGrowingToolGUI -* -* Adaptive Region Growing View class of the segmentation. -* -*/ - -class MITKSEGMENTATIONUI_EXPORT QmitkAdaptiveRegionGrowingToolGUI : public QmitkToolGUI -{ - Q_OBJECT - -public: - /** - * @brief mitkClassMacro - */ - mitkClassMacro(QmitkAdaptiveRegionGrowingToolGUI, QmitkToolGUI); - - itkFactorylessNewMacro(Self); - - itkCloneMacro(Self); - - QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent = nullptr); - - /** \brief Method to create the connections for the component. This Method is obligatory even if no connections is - * needed*/ - virtual void CreateConnections(); - - ///** \brief Method to set the default data storage.*/ - virtual void SetDataStorage(mitk::DataStorage *dataStorage); - - /** - * @brief Method to set the name of a data node. - * @param labledSegmentation Name of the labeled segmentation - * @param binaryImage Name of the binary image - * @param surface Name of the surface - * @param maskedSegmentation - */ - void SetDataNodeNames(std::string labledSegmentation, - std::string binaryImage, - /*std::string vesselTree,*/ std::string surface, - std::string maskedSegmentation); - - /** - * @brief Method to enable/disable controls for region growing - * - * This method checks if a seed point is set and a segmentation exists. - * @param enable enable/disable controls - */ - void EnableControls(bool enable); - - /** - * @brief Method to set the input image node - * @param node data node - */ - void SetInputImageNode(mitk::DataNode *node); - - void Deactivated(); - void Activated(); - - /** - * @brief The created GUI from the .ui-File. This Attribute is obligatory - */ - Ui::QmitkAdaptiveRegionGrowingToolGUIControls m_Controls; - -protected slots: - - /** - * @brief Method to start the segmentation - * - * This method is called, when the "Start Segmentation" button is clicked. - */ - void RunSegmentation(); - - /** - * @brief Method to change the level window - * - * This method is called, when the level window slider is changed via the slider in the control widget - * @param newValue new value - */ - void ChangeLevelWindow(double newValue); - - /** - * @brief Method to increase the preview slider - * - * This method is called, when the + button is clicked and increases the value by 1 - */ - void IncreaseSlider(); - - /** - * @brief Method to decrease the preview slider - * - * This method is called, when the - button is clicked and decreases the value by 1 - */ - void DecreaseSlider(); - - /** - * @brief Method to confirm the preview segmentation - * - * This method is called, when the "Confirm Segmentation" button is clicked. - */ - void ConfirmSegmentation(); - - /** - * @brief Method to switch the volume rendering on/off - * @param on - */ - void UseVolumeRendering(bool on); - - /** - * @brief Method to set the lower threshold - * - * This method is called, when the minimum threshold slider has changed - * @param lowerThreshold lower threshold - */ - void SetLowerThresholdValue(double lowerThreshold); - - /** - * @brief Method to set upper threshold - * - * This Method is called, when the maximum threshold slider has changed - * @param upperThreshold upper threshold - */ - void SetUpperThresholdValue(double upperThreshold); - - /** - * @brief Method to determine which tool to activate - * - * This method listens to the tool manager and activates this tool if requested otherwise disables this view - */ - void OnNewToolAssociated(mitk::Tool *); - -protected: - mitk::AdaptiveRegionGrowingTool::Pointer m_RegionGrow3DTool; - - /** \brief Destructor. */ - ~QmitkAdaptiveRegionGrowingToolGUI() override; - - mitk::DataStorage *m_DataStorage; - - mitk::DataNode::Pointer m_InputImageNode; - - /** - * @brief Method to calculate parameter settings, when a seed point is set - */ - void OnPointAdded(); - - /** - * @brief Method to extract a 3D image based on a given time point that can be taken from the SliceNavigationController - * - * This ensures that the seed point is taken from the current selected 3D image - */ - mitk::Image::ConstPointer GetImageByTimePoint(const mitk::Image *image, - mitk::TimePointType timePoint) const; - -private: - std::string m_NAMEFORORGIMAGE; - std::string m_NAMEFORLABLEDSEGMENTATIONIMAGE; - std::string m_NAMEFORBINARYIMAGE; - std::string m_NAMEFORSURFACE; - std::string m_NAMEFORMASKEDSEGMENTATION; - - mitk::ScalarType m_LOWERTHRESHOLD; // Hounsfield value - mitk::ScalarType m_UPPERTHRESHOLD; // Hounsfield value - mitk::ScalarType m_SeedPointValueMean; - - void RemoveHelperNodes(); - - int m_DetectedLeakagePoint; - - bool m_CurrentRGDirectionIsUpwards; // defines fixed threshold (true = LOWERTHRESHOLD fixed, false = UPPERTHRESHOLD - // fixed) - - int m_SeedpointValue; - bool m_SliderInitialized; - bool m_UseVolumeRendering; - bool m_UpdateSuggestedThreshold; - float m_SuggestedThValue; - - long m_PointSetAddObserverTag; - long m_PointSetMoveObserverTag; - - template - void StartRegionGrowing(const itk::Image *itkImage, - const mitk::BaseGeometry *imageGeometry, - const mitk::PointSet::PointType seedPoint); - - template - void ITKThresholding(itk::Image *inputImage); - - void InitializeLevelWindow(); - - void EnableVolumeRendering(bool enable); - - void UpdateVolumeRenderingThreshold(int thValue); -}; - -#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui deleted file mode 100644 index 2b91deefe8..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui +++ /dev/null @@ -1,227 +0,0 @@ - - - QmitkAdaptiveRegionGrowingToolGUIControls - - - - 0 - 0 - 264 - 171 - - - - - 0 - 0 - - - - - 0 - 0 - - - - QmitkAdaptiveRegionGrowingWidget - - - Move to adjust the segmentation - - - - 0 - - - - - - 0 - 0 - - - - Shift+Click to place seedpoint - - - 0 - - - - - - - Define thresholds: - - - true - - - - 0 - - - 6 - - - 0 - - - 0 - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - - - Press to start the segmentation of the tubular structure. - - - Run Segmentation - - - Alt+R - - - - - - - false - - - - 0 - 0 - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Enable/disable GPU volume rendering for the segmentation preview.</span></p></body></html> - - - Qt::LeftToRight - - - 3D preview - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Adapt Region Growing: - - - true - - - - 0 - - - 6 - - - 0 - - - 0 - - - - - - - - - - - false - - - - - - Press to confirm the segmentation and calculate 3D representation. - - - &Confirm Segmentation - - - Alt+C - - - - - - - - - ctkRangeWidget - QWidget -
ctkRangeWidget.h
- 1 -
- - ctkSliderWidget - QWidget -
ctkSliderWidget.h
- 1 -
-
- - -
diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp deleted file mode 100644 index 863e753c08..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkAutoMLSegmentationToolGUIBase.h" -#include "mitkAutoMLSegmentationWithPreviewTool.h" - -#include - -QmitkAutoMLSegmentationToolGUIBase::QmitkAutoMLSegmentationToolGUIBase() : QmitkAutoSegmentationToolGUIBase(false) -{ - auto enableMLSelectedDelegate = [this](bool enabled) - { - bool result = enabled; - auto tool = this->GetConnectedToolAs(); - if (nullptr != tool) - { - result = !tool->GetSelectedLabels().empty() && enabled; - } - else - { - result = false; - } - - return result; - }; - - m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; -} - -void QmitkAutoMLSegmentationToolGUIBase::InitializeUI(QBoxLayout* mainLayout) -{ - m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); - m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); - QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - sizePolicy2.setHorizontalStretch(0); - sizePolicy2.setVerticalStretch(0); - sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); - m_LabelSelectionList->setSizePolicy(sizePolicy2); - m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); - - mainLayout->addWidget(m_LabelSelectionList); - - connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged); - - Superclass::InitializeUI(mainLayout); -} - -void QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) -{ - auto tool = this->GetConnectedToolAs(); - if (nullptr != tool) - { - mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; - for (const auto& label : selectedLabels) - { - labelIDs.push_back(label->GetValue()); - } - - tool->SetSelectedLabels(labelIDs); - tool->UpdatePreview(); - this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; - } -} - -void QmitkAutoMLSegmentationToolGUIBase::EnableWidgets(bool enabled) -{ - Superclass::EnableWidgets(enabled); - if (nullptr != m_LabelSelectionList) - { - m_LabelSelectionList->setEnabled(enabled); - } -} - -void QmitkAutoMLSegmentationToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) -{ - if (nullptr != m_LabelSelectionList) - { - m_LabelSelectionList->SetLabelSetImage(preview); - } -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp index 930eacadb7..f6231dea49 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp @@ -1,187 +1,187 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkBinaryThresholdToolGUIBase.h" #include "mitkBinaryThresholdBaseTool.h" #include "mitkBinaryThresholdTool.h" #include #include #include #include -QmitkBinaryThresholdToolGUIBase::QmitkBinaryThresholdToolGUIBase(bool ulMode) : QmitkAutoSegmentationToolGUIBase(false), m_ULMode(ulMode) +QmitkBinaryThresholdToolGUIBase::QmitkBinaryThresholdToolGUIBase(bool ulMode) : QmitkSegWithPreviewToolGUIBase(false), m_ULMode(ulMode) { } QmitkBinaryThresholdToolGUIBase::~QmitkBinaryThresholdToolGUIBase() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { tool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); tool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { m_InternalUpdate = true; if (m_ULMode) { if (!isFloat) { m_ThresholdRange->setRange(int(lower), int(upper)); m_ThresholdRange->setSingleStep(1); m_ThresholdRange->setDecimals(0); } else { m_ThresholdRange->setRange(lower, upper); } } else { if (!isFloat) { m_ThresholdSlider->setRange(int(lower), int(upper)); m_ThresholdSlider->setSingleStep(1); m_ThresholdSlider->setDecimals(0); } else { m_ThresholdSlider->setRange(lower, upper); } } m_InternalUpdate = false; } void QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) { if (m_ULMode) { m_ThresholdRange->setValues(lower, upper); } else { m_ThresholdSlider->setValue(lower); } } void QmitkBinaryThresholdToolGUIBase::OnThresholdRangeChanged(double min, double max) { auto tool = this->GetConnectedToolAs(); if (nullptr != tool && !m_InternalUpdate) { tool->SetThresholdValues(min, max); } } void QmitkBinaryThresholdToolGUIBase::OnThresholdSliderChanged(double value) { auto tool = this->GetConnectedToolAs(); if (nullptr != tool && !m_InternalUpdate) { tool->SetThresholdValue(value); } } -void QmitkBinaryThresholdToolGUIBase::DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) +void QmitkBinaryThresholdToolGUIBase::DisconnectOldTool(mitk::SegWithPreviewTool* oldTool) { Superclass::DisconnectOldTool(oldTool); auto tool = dynamic_cast(oldTool); if (nullptr != tool) { tool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); tool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); } } -void QmitkBinaryThresholdToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +void QmitkBinaryThresholdToolGUIBase::ConnectNewTool(mitk::SegWithPreviewTool* newTool) { Superclass::ConnectNewTool(newTool); auto tool = dynamic_cast(newTool); if (nullptr != tool) { tool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); tool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdToolGUIBase::InitializeUI(QBoxLayout* mainLayout) { QLabel* label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout* layout = new QHBoxLayout(); if (m_ULMode) { m_ThresholdRange = new ctkRangeWidget(); connect( m_ThresholdRange, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdRangeChanged(double, double))); layout->addWidget(m_ThresholdRange); m_ThresholdRange->setSingleStep(0.01); } else { m_ThresholdSlider = new ctkSliderWidget(); connect( m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnThresholdSliderChanged(double))); layout->addWidget(m_ThresholdSlider); m_ThresholdSlider->setSingleStep(0.01); } mainLayout->addLayout(layout); Superclass::InitializeUI(mainLayout); } void QmitkBinaryThresholdToolGUIBase::BusyStateChanged(bool value) { Superclass::BusyStateChanged(value); if (m_ThresholdRange) { m_ThresholdRange->setEnabled(!value); } if (m_ThresholdSlider) { m_ThresholdSlider->setEnabled(!value); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h index 47f25dd544..c1d57564e1 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h @@ -1,63 +1,63 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkBinaryThresholdToolGUIBase_h_Included #define QmitkBinaryThresholdToolGUIBase_h_Included -#include "QmitkAutoSegmentationToolGUIBase.h" +#include "QmitkSegWithPreviewToolGUIBase.h" #include "ctkRangeWidget.h" #include "ctkSliderWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief Base GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. */ -class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUIBase : public QmitkAutoSegmentationToolGUIBase +class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUIBase : public QmitkSegWithPreviewToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkBinaryThresholdToolGUIBase, QmitkAutoSegmentationToolGUIBase); + mitkClassMacro(QmitkBinaryThresholdToolGUIBase, QmitkSegWithPreviewToolGUIBase); void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); protected slots: void OnThresholdRangeChanged(double min, double max); void OnThresholdSliderChanged(double value); protected: QmitkBinaryThresholdToolGUIBase(bool ulMode); ~QmitkBinaryThresholdToolGUIBase() override; - void DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) override; - void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void DisconnectOldTool(mitk::SegWithPreviewTool* oldTool) override; + void ConnectNewTool(mitk::SegWithPreviewTool* newTool) override; void InitializeUI(QBoxLayout* mainLayout) override; void BusyStateChanged(bool) override; ctkRangeWidget* m_ThresholdRange = nullptr; ctkSliderWidget* m_ThresholdSlider = nullptr; /** Indicates if the tool UI is used for a tool with upper an lower threshold (true) ore only with one threshold (false)*/ bool m_ULMode; bool m_InternalUpdate = false; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp new file mode 100644 index 0000000000..2ba34c9581 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp @@ -0,0 +1,149 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" +#include "mitkSegWithPreviewTool.h" + +#include +#include + +QmitkMultiLabelSegWithPreviewToolGUIBase::QmitkMultiLabelSegWithPreviewToolGUIBase() : QmitkSegWithPreviewToolGUIBase(false) +{ + auto enableMLSelectedDelegate = [this](bool enabled) + { + auto tool = this->GetConnectedToolAs(); + return nullptr != tool + ? (tool->GetLabelTransferMode() == mitk::SegWithPreviewTool::LabelTransferMode::AllLabels || !tool->GetSelectedLabels().empty()) && enabled + : false; + }; + + m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::InitializeUI(QBoxLayout* mainLayout) +{ + auto radioTransferAll = new QRadioButton("Transfer all labels", this); + radioTransferAll->setToolTip("Transfer all preview label when confirmed."); + radioTransferAll->setChecked(true); + connect(radioTransferAll, &QAbstractButton::toggled, this, &QmitkMultiLabelSegWithPreviewToolGUIBase::OnRadioTransferAllClicked); + mainLayout->addWidget(radioTransferAll); + m_RadioTransferAll = radioTransferAll; + + auto radioTransferSelected = new QRadioButton("Transfer selected labels", this); + radioTransferSelected->setToolTip("Transfer the selected preview label when confirmed."); + radioTransferSelected->setChecked(false); + mainLayout->addWidget(radioTransferSelected); + m_RadioTransferSelected = radioTransferSelected; + + m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); + m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); + QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + sizePolicy2.setHorizontalStretch(0); + sizePolicy2.setVerticalStretch(0); + sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); + m_LabelSelectionList->setSizePolicy(sizePolicy2); + m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); + m_LabelSelectionList->setVisible(false); + + mainLayout->addWidget(m_LabelSelectionList); + connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkMultiLabelSegWithPreviewToolGUIBase::OnLabelSelectionChanged); + + this->OnRadioTransferAllClicked(true); + + Superclass::InitializeUI(mainLayout); +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + mitk::SegWithPreviewTool::SelectedLabelVectorType labelIDs; + for (const auto& label : selectedLabels) + { + labelIDs.push_back(label->GetValue()); + } + + tool->SetSelectedLabels(labelIDs); + this->ActualizePreviewLabelVisibility(); + this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; + } +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::ActualizePreviewLabelVisibility() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + auto preview = tool->GetPreviewSegmentation(); + if (nullptr != preview) + { + auto labelSet = preview->GetActiveLabelSet(); + auto selectedLabels = tool->GetSelectedLabels(); + + for (auto labelIter = labelSet->IteratorBegin(); labelIter != labelSet->IteratorEnd(); ++labelIter) + { + bool isVisible = tool->GetLabelTransferMode() == mitk::SegWithPreviewTool::LabelTransferMode::AllLabels + || (std::find(selectedLabels.begin(), selectedLabels.end(), labelIter->second->GetValue()) != selectedLabels.end()); + labelIter->second->SetVisible(isVisible); + labelSet->UpdateLookupTable(labelIter->second->GetValue()); + } + } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::OnRadioTransferAllClicked(bool checked) +{ + m_LabelSelectionList->setVisible(!checked); + + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + if (checked) + { + tool->SetLabelTransferMode(mitk::SegWithPreviewTool::LabelTransferMode::AllLabels); + } + else + { + tool->SetLabelTransferMode(mitk::SegWithPreviewTool::LabelTransferMode::SelectedLabels); + } + } + this->ActualizePreviewLabelVisibility(); +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); + if (nullptr != m_LabelSelectionList) + { + m_LabelSelectionList->setEnabled(enabled); + } + + if (nullptr != m_RadioTransferAll) + { + m_RadioTransferAll->setEnabled(enabled); + } + + if (nullptr != m_RadioTransferSelected) + { + m_RadioTransferSelected->setEnabled(enabled); + } +} + +void QmitkMultiLabelSegWithPreviewToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) +{ + if (nullptr != m_LabelSelectionList) + { + m_LabelSelectionList->SetLabelSetImage(preview); + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h similarity index 62% rename from Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h rename to Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h index e4e48dc867..d6d5caaeb9 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h @@ -1,52 +1,56 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkAutoMLSegmentationToolGUIBase_h_Included -#define QmitkAutoMLSegmentationToolGUIBase_h_Included +#ifndef QmitkMultiLabelSegWithPreviewToolGUIBase_h_Included +#define QmitkMultiLabelSegWithPreviewToolGUIBase_h_Included -#include "QmitkAutoSegmentationToolGUIBase.h" +#include "QmitkSegWithPreviewToolGUIBase.h" #include "QmitkSimpleLabelSetListWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for tools based on mitk::AutoMLSegmentationWithPreviewTool. This GUI offers an additional list to select the label that should be confirmed. */ -class MITKSEGMENTATIONUI_EXPORT QmitkAutoMLSegmentationToolGUIBase : public QmitkAutoSegmentationToolGUIBase +class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelSegWithPreviewToolGUIBase : public QmitkSegWithPreviewToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkAutoMLSegmentationToolGUIBase, QmitkAutoSegmentationToolGUIBase); + mitkClassMacro(QmitkMultiLabelSegWithPreviewToolGUIBase, QmitkSegWithPreviewToolGUIBase); protected slots : void OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); + void OnRadioTransferAllClicked(bool checked); protected: - QmitkAutoMLSegmentationToolGUIBase(); - ~QmitkAutoMLSegmentationToolGUIBase() = default; + QmitkMultiLabelSegWithPreviewToolGUIBase(); + ~QmitkMultiLabelSegWithPreviewToolGUIBase() = default; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; void SetLabelSetPreview(const mitk::LabelSetImage* preview); + void ActualizePreviewLabelVisibility(); private: QmitkSimpleLabelSetListWidget* m_LabelSelectionList = nullptr; + QWidget* m_RadioTransferAll = nullptr; + QWidget* m_RadioTransferSelected = nullptr; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.cpp b/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.cpp deleted file mode 100644 index a824e6d94a..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkNewSegmentationDialog.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -QmitkNewSegmentationDialog::QmitkNewSegmentationDialog(QWidget *parent) - : QDialog(parent) // true, modal - , selectedOrgan(tr("undefined")) - , newOrganEntry(false) -{ - QDialog::setFixedSize(250, 105); - - QBoxLayout *verticalLayout = new QVBoxLayout(this); - verticalLayout->setMargin(5); - verticalLayout->setSpacing(5); - - // to enter a name for the segmentation - lblPrompt = new QLabel(tr("Name and color of the segmentation"), this); - verticalLayout->addWidget(lblPrompt); - - // to choose a color - m_Color.setRed(255); - m_Color.setGreen(0); - m_Color.setBlue(0); - - btnColor = new QPushButton("", this); - btnColor->setFixedWidth(25); - btnColor->setAutoFillBackground(true); - btnColor->setStyleSheet(QString("background-color:rgb(%1, %2, %3)").arg(m_Color.red()).arg(m_Color.green()).arg(m_Color.blue())); - - connect(btnColor, SIGNAL(clicked()), this, SLOT(onColorBtnClicked())); - - lineEditName = new QLineEdit("", this); - QStringList completionList; - completionList << ""; - completer = new QCompleter(completionList); - completer->setCaseSensitivity(Qt::CaseInsensitive); - lineEditName->setCompleter(completer); - - connect(completer, SIGNAL(activated(const QString &)), this, SLOT(onColorChange(const QString &))); - - QBoxLayout *horizontalLayout2 = new QHBoxLayout(); - verticalLayout->addLayout(horizontalLayout2); - horizontalLayout2->addWidget(btnColor); - horizontalLayout2->addWidget(lineEditName); - - // buttons for closing the dialog - btnOk = new QPushButton(tr("Ok"), this); - btnOk->setDefault(true); - connect(btnOk, SIGNAL(clicked()), this, SLOT(onAcceptClicked())); - - QPushButton *btnCancel = new QPushButton(tr("Cancel"), this); - connect(btnCancel, SIGNAL(clicked()), this, SLOT(reject())); - - QBoxLayout *horizontalLayout = new QHBoxLayout(); - verticalLayout->addLayout(horizontalLayout); - horizontalLayout->setSpacing(5); - horizontalLayout->addStretch(); - horizontalLayout->addWidget(btnOk); - horizontalLayout->addWidget(btnCancel); - - lineEditName->setFocus(); -} - -QmitkNewSegmentationDialog::~QmitkNewSegmentationDialog() -{ -} - -const QString QmitkNewSegmentationDialog::GetSegmentationName() -{ - return m_SegmentationName; -} - -mitk::Color QmitkNewSegmentationDialog::GetColor() -{ - mitk::Color colorProperty; - if (m_Color.isValid()) - { - colorProperty.SetRed(m_Color.redF()); - colorProperty.SetGreen(m_Color.greenF()); - colorProperty.SetBlue(m_Color.blueF()); - } - else - { - colorProperty.SetRed(1); - colorProperty.SetGreen(0); - colorProperty.SetBlue(0); - } - return colorProperty; -} - -const char *QmitkNewSegmentationDialog::GetOrganType() -{ - return selectedOrgan.toLocal8Bit().constData(); -} - -void QmitkNewSegmentationDialog::SetSegmentationName(const QString &segmentationName) -{ - lineEditName->setText(segmentationName); -} - -void QmitkNewSegmentationDialog::SetColor(const mitk::Color &color) -{ - m_Color.setRedF(color.GetRed()); - m_Color.setGreenF(color.GetGreen()); - m_Color.setBlueF(color.GetBlue()); - - btnColor->setStyleSheet(QString("background-color:rgb(%1, %2, %3)").arg(m_Color.red()).arg(m_Color.green()).arg(m_Color.blue())); -} - -void QmitkNewSegmentationDialog::SetSuggestionList(QStringList organColorList) -{ - QStringList::iterator iter; - for (iter = organColorList.begin(); iter != organColorList.end(); ++iter) - { - QString &element = *iter; - QString colorName = element.right(7); - QColor color(colorName); - QString organName = element.left(element.size() - 7); - - organList.push_back(organName); - colorList.push_back(color); - } - - QStringListModel *completeModel = static_cast(completer->model()); - completeModel->setStringList(organList); -} - -void QmitkNewSegmentationDialog::setPrompt(const QString &prompt) -{ - lblPrompt->setText(prompt); -} - -void QmitkNewSegmentationDialog::onAcceptClicked() -{ - m_SegmentationName = lineEditName->text(); - accept(); -} - -void QmitkNewSegmentationDialog::onNewOrganNameChanged(const QString &newText) -{ - if (!newText.isEmpty()) - { - btnOk->setEnabled(true); - } - - selectedOrgan = newText; - SetSegmentationName(newText); -} - -void QmitkNewSegmentationDialog::onColorBtnClicked() -{ - auto selectedColor = QColorDialog::getColor(m_Color); - if (selectedColor.isValid()) - { - m_Color = selectedColor; - btnColor->setStyleSheet(QString("background-color:rgb(%1, %2, %3)").arg(m_Color.red()).arg(m_Color.green()).arg(m_Color.blue())); - } -} - -void QmitkNewSegmentationDialog::onColorChange(const QString &completedWord) -{ - if (organList.contains(completedWord)) - { - int j = organList.indexOf(completedWord); - m_Color = colorList.at(j); - btnColor->setStyleSheet(QString("background-color:rgb(%1, %2, %3)").arg(m_Color.red()).arg(m_Color.green()).arg(m_Color.blue())); - } -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.h b/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.h deleted file mode 100644 index e8559ebd77..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkNewSegmentationDialog.h +++ /dev/null @@ -1,93 +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 QmitkNewSegmentationDialog_h_Included -#define QmitkNewSegmentationDialog_h_Included - -#include "mitkColorProperty.h" -#include - -#include - -#include - -class QLabel; -class QLineEdit; -class Q3ListBox; -class QPushButton; - -#include - -/** - \brief Dialog for QmitkInteractiveSegmentation. - - \ingroup ToolManagerEtAl - \ingroup Widgets - - This dialog is used to ask a user about the type of a newly created segmentation and a name for it. -*/ -class MITKSEGMENTATIONUI_EXPORT QmitkNewSegmentationDialog : public QDialog -{ - Q_OBJECT - -public: - QmitkNewSegmentationDialog(QWidget *parent = nullptr); - - ~QmitkNewSegmentationDialog() override; - - const QString GetSegmentationName(); - mitk::Color GetColor(); - const char *GetOrganType(); - - void SetSegmentationName(const QString &segmentationName); - void SetColor(const mitk::Color &color); - void SetSuggestionList(QStringList organColorList); - -signals: - -public slots: - - void setPrompt(const QString &prompt); - -protected slots: - - void onAcceptClicked(); - void onNewOrganNameChanged(const QString &); - void onColorBtnClicked(); - void onColorChange(const QString &completedWord); - -protected: - - QLabel *lblPrompt; - Q3ListBox *lstOrgans; - QLineEdit *lineEditName; - - QPushButton *btnColor; - QPushButton *btnOk; - - QLineEdit *edtNewOrgan; - - QString selectedOrgan; - - bool newOrganEntry; - - QColor m_Color; - - QCompleter *completer; - - QString m_SegmentationName; - - QStringList organList; - QList colorList; -}; - -#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 6533d2e726..e199373c53 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,129 +1,143 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkOtsuTool3DGUI.h" #include "mitkOtsuTool3D.h" #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") -QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkAutoMLSegmentationToolGUIBase() +QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { + auto enableMLSelectedDelegate = [this](bool enabled) + { + if (this->m_FirstPreviewComputation) + { + return false; + } + else + { + return this->m_SuperclassEnableConfirmSegBtnFnc(enabled); + } + }; + + m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } -void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::SegWithPreviewTool* newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); m_FirstPreviewComputation = true; } void QmitkOtsuTool3DGUI::InitializeUI(QBoxLayout* mainLayout) { m_Controls.setupUi(this); mainLayout->addLayout(m_Controls.verticalLayout); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); Superclass::InitializeUI(mainLayout); } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); auto tool = this->GetConnectedToolAs(); if (toggled && nullptr != tool) { int max = tool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { if (!m_FirstPreviewComputation && (tool->GetNumberOfRegions() == static_cast(m_Controls.m_Spinbox->value()) && tool->GetUseValley() == m_Controls.m_ValleyCheckbox->isChecked() && tool->GetNumberOfBins() == static_cast(m_Controls.m_BinsSpinBox->value()))) return; m_FirstPreviewComputation = false; try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } tool->SetNumberOfRegions(static_cast(m_Controls.m_Spinbox->value())); tool->SetUseValley(m_Controls.m_ValleyCheckbox->isChecked()); tool->SetNumberOfBins(static_cast(m_Controls.m_BinsSpinBox->value())); tool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } - this->SetLabelSetPreview(tool->GetMLPreview()); + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); tool->IsTimePointChangeAwareOn(); + this->ActualizePreviewLabelVisibility(); } } void QmitkOtsuTool3DGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); m_Controls.m_ValleyCheckbox->setEnabled(enabled); m_Controls.binLabel->setEnabled(enabled); m_Controls.m_BinsSpinBox->setEnabled(enabled); m_Controls.previewButton->setEnabled(enabled); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index 55b7030ea5..44078f4d61 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,64 +1,65 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included -#include "QmitkAutoMLSegmentationToolGUIBase.h" +#include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::OtsuTool3D. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ -class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkAutoMLSegmentationToolGUIBase +class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkOtsuTool3DGUI, QmitkAutoMLSegmentationToolGUIBase); + mitkClassMacro(QmitkOtsuTool3DGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnPreviewBtnClicked(); void OnRegionSpinboxChanged(int); private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() = default; - void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void ConnectNewTool(mitk::SegWithPreviewTool* newTool) override; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; Ui_QmitkOtsuToolWidgetControls m_Controls; bool m_FirstPreviewComputation = true; + EnableConfirmSegBtnFunctionType m_SuperclassEnableConfirmSegBtnFnc; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp index 603a58877e..35d0b0176b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp @@ -1,111 +1,111 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkPickingToolGUI.h" #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkPickingToolGUI, "") -QmitkPickingToolGUI::QmitkPickingToolGUI() : QmitkAutoSegmentationToolGUIBase(false) +QmitkPickingToolGUI::QmitkPickingToolGUI() : QmitkSegWithPreviewToolGUIBase(false) { auto enablePickingDelegate = [this](bool enabled) { bool result = false; auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { result = enabled && tool->HasPicks(); } return result; }; m_EnableConfirmSegBtnFnc = enablePickingDelegate; } QmitkPickingToolGUI::~QmitkPickingToolGUI() { } void QmitkPickingToolGUI::OnResetPicksClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { tool->ClearPicks(); } } void QmitkPickingToolGUI::OnRadioPickClicked(bool checked) { if (checked) { this->SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle::Replace); this->SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); } else { this->SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle::Merge); this->SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } } void QmitkPickingToolGUI::InitializeUI(QBoxLayout* mainLayout) { auto radioPick = new QRadioButton("Picking mode", this); radioPick->setToolTip("Pick certain parts of the label and dismiss the rest of the label content"); radioPick->setChecked(true); connect(radioPick, &QAbstractButton::toggled, this, &QmitkPickingToolGUI::OnRadioPickClicked); mainLayout->addWidget(radioPick); m_RadioPick = radioPick; this->OnRadioPickClicked(true); auto radioRelabel = new QRadioButton("Relabel mode", this); radioRelabel->setToolTip("Relabel certain parts of the segmentation as active label."); radioRelabel->setChecked(false); mainLayout->addWidget(radioRelabel); m_RadioRelabel = radioRelabel; QLabel* label = new QLabel("Press SHIFT and click to pick region(s).\nPress DEL to remove last pick.", this); mainLayout->addWidget(label); auto clearButton = new QPushButton("Reset picks",this); connect(clearButton, &QPushButton::clicked, this, &QmitkPickingToolGUI::OnResetPicksClicked); mainLayout->addWidget(clearButton); m_ClearPicksBtn = clearButton; Superclass::InitializeUI(mainLayout); } void QmitkPickingToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); if (nullptr != m_ClearPicksBtn) { m_ClearPicksBtn->setEnabled(enabled); } if (nullptr != m_RadioPick) { m_RadioPick->setEnabled(enabled); } if (nullptr != m_RadioRelabel) { m_RadioRelabel->setEnabled(enabled); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h index 2d00cb3b78..46c99cc6ad 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h @@ -1,50 +1,50 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkPickingToolGUI_h_Included #define QmitkPickingToolGUI_h_Included -#include "QmitkAutoSegmentationToolGUIBase.h" +#include "QmitkSegWithPreviewToolGUIBase.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::PickingTool. \sa mitk::PickingTool */ -class MITKSEGMENTATIONUI_EXPORT QmitkPickingToolGUI : public QmitkAutoSegmentationToolGUIBase +class MITKSEGMENTATIONUI_EXPORT QmitkPickingToolGUI : public QmitkSegWithPreviewToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkPickingToolGUI, QmitkAutoSegmentationToolGUIBase); + mitkClassMacro(QmitkPickingToolGUI, QmitkSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnResetPicksClicked(); void OnRadioPickClicked(bool checked); protected: QmitkPickingToolGUI(); ~QmitkPickingToolGUI() override; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; private: QWidget* m_ClearPicksBtn = nullptr; QWidget* m_RadioPick = nullptr; QWidget* m_RadioRelabel = nullptr; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.cpp similarity index 64% rename from Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp rename to Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.cpp index f4a276d6ff..03b8b4851f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.cpp @@ -1,210 +1,186 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkAutoSegmentationToolGUIBase.h" +#include "QmitkSegWithPreviewToolGUIBase.h" #include #include #include #include #include bool DefaultEnableConfirmSegBtnFunction(bool enabled) { return enabled; } -QmitkAutoSegmentationToolGUIBase::QmitkAutoSegmentationToolGUIBase(bool mode2D) : QmitkToolGUI(), m_EnableConfirmSegBtnFnc(DefaultEnableConfirmSegBtnFunction), m_Mode2D(mode2D) +QmitkSegWithPreviewToolGUIBase::QmitkSegWithPreviewToolGUIBase(bool mode2D) : QmitkToolGUI(), m_EnableConfirmSegBtnFnc(DefaultEnableConfirmSegBtnFunction), m_Mode2D(mode2D) { connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } -QmitkAutoSegmentationToolGUIBase::~QmitkAutoSegmentationToolGUIBase() +QmitkSegWithPreviewToolGUIBase::~QmitkSegWithPreviewToolGUIBase() { if (m_Tool.IsNotNull()) { - m_Tool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); + m_Tool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkSegWithPreviewToolGUIBase::BusyStateChanged); } } -void QmitkAutoSegmentationToolGUIBase::OnNewToolAssociated(mitk::Tool *tool) +void QmitkSegWithPreviewToolGUIBase::OnNewToolAssociated(mitk::Tool *tool) { if (m_Tool.IsNotNull()) { this->DisconnectOldTool(m_Tool); } - m_Tool = dynamic_cast(tool); + m_Tool = dynamic_cast(tool); if (nullptr == m_MainLayout) { // create the visible widgets m_MainLayout = new QVBoxLayout(this); m_ConfirmSegBtn = new QPushButton("Confirm Segmentation", this); connect(m_ConfirmSegBtn, SIGNAL(clicked()), this, SLOT(OnAcceptPreview())); m_CheckIgnoreLocks = new QCheckBox("Ignore label locks", this); m_CheckIgnoreLocks->setChecked(m_Tool->GetOverwriteStyle() == mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); m_CheckIgnoreLocks->setToolTip("If checked, the lock state of labels will be ignored when the preview segmentation is confermed. Thus also locked label pixels can be changed by the operation."); m_CheckMerge = new QCheckBox("Merge with existing content", this); m_CheckMerge->setChecked(m_Tool->GetMergeStyle()==mitk::MultiLabelSegmentation::MergeStyle::Merge); m_CheckMerge->setToolTip("If checked, the preview segmantation will be merged with the existing segmantation into a union. If unchecked, the preview content will replace the old segmantation"); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); m_CheckProcessAll->setToolTip("Process all time steps of the dynamic segmentation and not just the currently visible time step."); m_CheckProcessAll->setVisible(!m_Mode2D); //remark: keept m_CheckProcessAll deactivated in 2D because in this refactoring //it should be kept to the status quo and it was not clear how interpolation //would behave. As soon as it is sorted out we can remove that "feature switch" //or the comment. - m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); - m_CheckCreateNew->setChecked(false); - m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); - m_CheckCreateNew->setVisible(!m_Mode2D); - //remark: keept m_CheckCreateNew deactivated in 2D because in this refactoring - //it should be kept to the status quo and it was not clear how interpolation - //would behave. As soon as it is sorted out we can remove that "feature switch" - //or the comment. - this->InitializeUI(m_MainLayout); m_MainLayout->addWidget(m_ConfirmSegBtn); m_MainLayout->addWidget(m_CheckIgnoreLocks); m_MainLayout->addWidget(m_CheckMerge); m_MainLayout->addWidget(m_CheckProcessAll); - m_MainLayout->addWidget(m_CheckCreateNew); } if (m_Tool.IsNotNull()) { this->ConnectNewTool(m_Tool); } } -void QmitkAutoSegmentationToolGUIBase::OnAcceptPreview() +void QmitkSegWithPreviewToolGUIBase::OnAcceptPreview() { if (m_Tool.IsNotNull()) { if (m_CheckIgnoreLocks->isChecked()) { m_Tool->SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } else { m_Tool->SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); } if (m_CheckMerge->isChecked()) { m_Tool->SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle::Merge); } else { m_Tool->SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle::Replace); } - if (m_CheckCreateNew->isChecked()) - { - m_Tool->SetOverwriteExistingSegmentation(false); - } - else - { - m_Tool->SetOverwriteExistingSegmentation(true); - } - m_Tool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); m_ConfirmSegBtn->setEnabled(false); m_Tool->ConfirmSegmentation(); } } -void QmitkAutoSegmentationToolGUIBase::DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) +void QmitkSegWithPreviewToolGUIBase::DisconnectOldTool(mitk::SegWithPreviewTool* oldTool) { - oldTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); + oldTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkSegWithPreviewToolGUIBase::BusyStateChanged); } -void QmitkAutoSegmentationToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +void QmitkSegWithPreviewToolGUIBase::ConnectNewTool(mitk::SegWithPreviewTool* newTool) { newTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); + mitk::MessageDelegate1(this, &QmitkSegWithPreviewToolGUIBase::BusyStateChanged); - newTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(newTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); this->EnableWidgets(true); } -void QmitkAutoSegmentationToolGUIBase::InitializeUI(QBoxLayout* /*mainLayout*/) +void QmitkSegWithPreviewToolGUIBase::InitializeUI(QBoxLayout* /*mainLayout*/) { //default implementation does nothing } -void QmitkAutoSegmentationToolGUIBase::BusyStateChanged(bool isBusy) +void QmitkSegWithPreviewToolGUIBase::BusyStateChanged(bool isBusy) { if (isBusy) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } else { QApplication::restoreOverrideCursor(); } this->EnableWidgets(!isBusy); } -void QmitkAutoSegmentationToolGUIBase::EnableWidgets(bool enabled) +void QmitkSegWithPreviewToolGUIBase::EnableWidgets(bool enabled) { if (nullptr != m_MainLayout) { if (nullptr != m_ConfirmSegBtn) { m_ConfirmSegBtn->setEnabled(m_EnableConfirmSegBtnFnc(enabled)); } if (nullptr != m_CheckIgnoreLocks) { m_CheckIgnoreLocks->setEnabled(enabled); } if (nullptr != m_CheckMerge) { m_CheckMerge->setEnabled(enabled); } if (nullptr != m_CheckProcessAll) { m_CheckProcessAll->setEnabled(enabled); } - if (nullptr != m_CheckCreateNew) - { - m_CheckCreateNew->setEnabled(enabled); - } } } -void QmitkAutoSegmentationToolGUIBase::SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle mergeStyle) +void QmitkSegWithPreviewToolGUIBase::SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle mergeStyle) { if (nullptr != m_CheckMerge) { m_CheckMerge->setChecked(mergeStyle == mitk::MultiLabelSegmentation::MergeStyle::Merge); } }; -void QmitkAutoSegmentationToolGUIBase::SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) +void QmitkSegWithPreviewToolGUIBase::SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { if (nullptr != m_CheckIgnoreLocks) { m_CheckIgnoreLocks->setChecked(overwriteStyle == mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } }; diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.h similarity index 78% rename from Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h rename to Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.h index 54701e27ca..6f5f0bedee 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSegWithPreviewToolGUIBase.h @@ -1,96 +1,95 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef QmitkAutoSegmentationToolGUIBase_h_Included -#define QmitkAutoSegmentationToolGUIBase_h_Included +#ifndef QmitkSegWithPreviewToolGUIBase_h_Included +#define QmitkSegWithPreviewToolGUIBase_h_Included #include "QmitkToolGUI.h" -#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkSegWithPreviewTool.h" #include class QCheckBox; class QPushButton; class QBoxLayout; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI base clase for tools derived from mitk::AutoSegmentationTool. */ -class MITKSEGMENTATIONUI_EXPORT QmitkAutoSegmentationToolGUIBase : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkSegWithPreviewToolGUIBase : public QmitkToolGUI { Q_OBJECT public: - mitkClassMacro(QmitkAutoSegmentationToolGUIBase, QmitkToolGUI); + mitkClassMacro(QmitkSegWithPreviewToolGUIBase, QmitkToolGUI); itkCloneMacro(Self); itkGetConstMacro(Mode2D, bool); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptPreview(); protected: - QmitkAutoSegmentationToolGUIBase(bool mode2D); - ~QmitkAutoSegmentationToolGUIBase() override; + QmitkSegWithPreviewToolGUIBase(bool mode2D); + ~QmitkSegWithPreviewToolGUIBase() override; - virtual void DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool); - virtual void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool); + virtual void DisconnectOldTool(mitk::SegWithPreviewTool* oldTool); + virtual void ConnectNewTool(mitk::SegWithPreviewTool* newTool); /**This method is called by OnNewToolAssociated if the UI is initialized the first time to allow derived classes to introduce own UI code. Overwrite to change. The implementation should ensure that alle widgets needed for the tool UI are properly allocated. If one needs to eecute time (e.g. to connect events between the tool and the UI) each time the tool changes, override the functions ConnectNewTool() and DisconnectOldTool().*/ virtual void InitializeUI(QBoxLayout* mainLayout); void BusyStateChanged(bool isBusy) override; using EnableConfirmSegBtnFunctionType = std::function; EnableConfirmSegBtnFunctionType m_EnableConfirmSegBtnFnc; /**This method is used to control/set the enabled state of the tool UI widgets. It is e.g. used if the busy state is changed (see BusyStateChanged). Override the default implmentation, e.g. if a tool adds his own UI elements (normally by overriding InitializeUI()) and wants to control how the widgets are enabled/disabled.*/ virtual void EnableWidgets(bool enabled); template TTool* GetConnectedToolAs() { return dynamic_cast(m_Tool.GetPointer()); }; void SetMergeStyle(mitk::MultiLabelSegmentation::MergeStyle mergeStyle); void SetOverwriteStyle(mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle); private: QCheckBox* m_CheckIgnoreLocks = nullptr; QCheckBox* m_CheckMerge = nullptr; QCheckBox* m_CheckProcessAll = nullptr; - QCheckBox* m_CheckCreateNew = nullptr; QPushButton* m_ConfirmSegBtn = nullptr; QBoxLayout* m_MainLayout = nullptr; /**Indicates if the tool is in 2D or 3D mode.*/ bool m_Mode2D; - mitk::AutoSegmentationWithPreviewTool::Pointer m_Tool; + mitk::SegWithPreviewTool::Pointer m_Tool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentationOrganNamesHandling.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentationOrganNamesHandling.cpp deleted file mode 100644 index c027481f2b..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentationOrganNamesHandling.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include -#include -#include - -namespace mitk -{ - struct OrganNamesHandling - { - static QStringList GetDefaultOrganColorString() - { - QStringList organColors; - - auto presets = vtkSmartPointer::New(); - presets->LoadPreset(); - - for (const auto& preset : presets->GetColorPresets()) - { - auto organName = preset.first.c_str(); - auto color = QColor(preset.second.GetRed(), preset.second.GetGreen(), preset.second.GetBlue()); - - AppendToOrganList(organColors, organName, color); - } - return organColors; - } - - static void UpdateOrganList(QStringList& organColors, const QString& organname, mitk::Color color) - { - QString listElement(organname + QColor(color.GetRed() * 255, color.GetGreen() * 255, color.GetBlue() * 255).name()); - - // remove previous definition if necessary - int oldIndex = organColors.indexOf(QRegExp(QRegExp::escape(organname) + "#......", Qt::CaseInsensitive)); - if (oldIndex < 0 || organColors.at(oldIndex) != listElement) - { - if (oldIndex >= 0) - { - organColors.removeAt(oldIndex); - } - - // add colored organ name AND sort list - organColors.append(listElement); - organColors.sort(); - } - } - - static void AppendToOrganList(QStringList& organColors, const QString& organname, const QColor& color) - { - organColors.append(organname + color.name()); - } - }; -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp index 8d6f3215f9..e0be0aa079 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp @@ -1,182 +1,199 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSimpleLabelSetListWidget.h" #include "mitkMessage.h" #include -QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr) +QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr), m_Emmiting(false) { QGridLayout* layout = new QGridLayout(this); this->setContentsMargins(0, 0, 0, 0); m_LabelList = new QListWidget(this); m_LabelList->setSelectionMode(QAbstractItemView::MultiSelection); m_LabelList->setResizeMode(QListView::Adjust); m_LabelList->setAutoScrollMargin(0); layout->addWidget(m_LabelList); connect(m_LabelList, SIGNAL(itemSelectionChanged()), this, SLOT(OnLabelSelectionChanged())); } QmitkSimpleLabelSetListWidget::~QmitkSimpleLabelSetListWidget() { if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection); OnLooseLabelSetConnection(); } } QmitkSimpleLabelSetListWidget::LabelVectorType QmitkSimpleLabelSetListWidget::SelectedLabels() const { auto selectedItems = m_LabelList->selectedItems(); LabelVectorType result; QList::Iterator it; for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { auto labelValue = (*it)->data(Qt::UserRole).toUInt(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); result.push_back(labelSet->GetLabel(labelValue)); } return result; } const mitk::LabelSetImage* QmitkSimpleLabelSetListWidget::GetLabelSetImage() const { return m_LabelSetImage; } void QmitkSimpleLabelSetListWidget::SetLabelSetImage(const mitk::LabelSetImage* image) { if (image != m_LabelSetImage) { if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); this->OnLooseLabelSetConnection(); } m_LabelSetImage = image; if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); this->OnLayerChanged(); } } } void QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection() { if (m_LabelSetImage.IsNull()) return; auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); // Reset LabelSetWidget Events labelSet->AddLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->RemoveLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->ModifyLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); } void QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection() { if (m_LabelSetImage.IsNull()) return; auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); // Reset LabelSetWidget Events labelSet->AddLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->RemoveLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->ModifyLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); } void QmitkSimpleLabelSetListWidget::OnLayerChanged() { this->OnEstablishLabelSetConnection(); - this->ResetList(); - emit ActiveLayerChanged(); - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_Emmiting) + { + this->ResetList(); + + this->m_Emmiting = true; + emit ActiveLayerChanged(); + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_Emmiting = false; + } } void QmitkSimpleLabelSetListWidget::OnLabelChanged() { - this->ResetList(); - emit ActiveLayerChanged(); - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_Emmiting) + { + this->ResetList(); + + this->m_Emmiting = true; + emit ActiveLayerChanged(); + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_Emmiting = false; + } } void QmitkSimpleLabelSetListWidget::OnLabelSelectionChanged() { - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_Emmiting) + { + this->m_Emmiting = true; + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_Emmiting = false; + } } void QmitkSimpleLabelSetListWidget::ResetList() { m_LabelList->clear(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); auto iter = labelSet->IteratorConstBegin(); for (; iter != labelSet->IteratorConstEnd(); ++iter) { auto color = iter->second->GetColor(); QPixmap pixmap(10, 10); pixmap.fill(QColor(color[0] * 255, color[1] * 255, color[2] * 255)); QIcon icon(pixmap); QListWidgetItem* item = new QListWidgetItem(icon, QString::fromStdString(iter->second->GetName())); item->setData(Qt::UserRole, QVariant(iter->second->GetValue())); m_LabelList->addItem(item); } } void QmitkSimpleLabelSetListWidget::SetSelectedLabels(const LabelVectorType& selectedLabels) { for (int i = 0; i < m_LabelList->count(); ++i) { QListWidgetItem* item = m_LabelList->item(i); auto labelValue = item->data(Qt::UserRole).toUInt(); auto finding = std::find_if(selectedLabels.begin(), selectedLabels.end(), [labelValue](const mitk::Label* label) {return label->GetValue() == labelValue; }); item->setSelected(finding != selectedLabels.end()); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h index 88c716ff0c..727be32c0e 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h @@ -1,63 +1,64 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSimpleLabelSetListWidget_h_Included #define QmitkSimpleLabelSetListWidget_h_Included #include "mitkLabel.h" #include "mitkLabelSetImage.h" #include #include /** \brief Widget that offers a simple list that displays all labels (color and name) in the active layer of a LabelSetImage. */ class MITKSEGMENTATIONUI_EXPORT QmitkSimpleLabelSetListWidget : public QWidget { Q_OBJECT public: QmitkSimpleLabelSetListWidget(QWidget* parent = nullptr); ~QmitkSimpleLabelSetListWidget() override; using LabelVectorType = std::vector; LabelVectorType SelectedLabels() const; const mitk::LabelSetImage* GetLabelSetImage() const; signals: void SelectedLabelsChanged(const LabelVectorType& selectedLabels); void ActiveLayerChanged(); public slots : void SetLabelSetImage(const mitk::LabelSetImage* image); void SetSelectedLabels(const LabelVectorType& selectedLabels); protected slots: void OnLabelSelectionChanged(); protected: void OnLayerChanged(); void OnLabelChanged(); void OnLooseLabelSetConnection(); void OnEstablishLabelSetConnection(); void ResetList(); mitk::LabelSetImage::ConstPointer m_LabelSetImage; QListWidget* m_LabelList; + bool m_Emmiting; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp index 7533b4382e..e0f5f84b4d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp @@ -1,710 +1,708 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSliceBasedInterpolatorWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "QmitkStdMultiWidget.h" - #include #include #include #include #include QmitkSliceBasedInterpolatorWidget::QmitkSliceBasedInterpolatorWidget(QWidget *parent, const char * /*name*/) : QWidget(parent), m_SliceInterpolatorController(mitk::SliceBasedInterpolationController::New()), m_ToolManager(nullptr), m_Activated(false), m_DataStorage(nullptr), m_LastSNC(nullptr), m_LastSliceIndex(0) { m_Controls.setupUi(this); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); connect(m_Controls.m_btStart, SIGNAL(toggled(bool)), this, SLOT(OnToggleWidgetActivation(bool))); connect(m_Controls.m_btApplyForCurrentSlice, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_Controls.m_btApplyForAllSlices, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged); m_InterpolationInfoChangedObserverTag = m_SliceInterpolatorController->AddObserver(itk::ModifiedEvent(), command); // feedback node and its visualization properties m_PreviewNode = mitk::DataNode::New(); m_PreviewNode->SetName("3D tool preview"); m_PreviewNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_PreviewNode->SetProperty("layer", mitk::IntProperty::New(100)); m_PreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary shadow", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PreviewNode->SetOpacity(1.0); m_PreviewNode->SetColor(0.0, 1.0, 0.0); m_Controls.m_btApplyForCurrentSlice->setEnabled(false); m_Controls.m_btApplyForAllSlices->setEnabled(false); this->setEnabled(false); } QmitkSliceBasedInterpolatorWidget::~QmitkSliceBasedInterpolatorWidget() { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSliceDimensionMap.clear(); // remove observer m_SliceInterpolatorController->RemoveObserver(m_InterpolationInfoChangedObserverTag); } const QmitkSliceBasedInterpolatorWidget::ActionToSliceDimensionMapType QmitkSliceBasedInterpolatorWidget::CreateActionToSliceDimension() { ActionToSliceDimensionMapType actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { std::string name = slicer->GetRenderer()->GetName(); if (name == "stdmulti.widget0") name = "Axial (red window)"; else if (name == "stdmulti.widget1") name = "Sagittal (green window)"; else if (name == "stdmulti.widget2") name = "Coronal (blue window)"; actionToSliceDimension[new QAction(QString::fromStdString(name), nullptr)] = slicer; } return actionToSliceDimension; } void QmitkSliceBasedInterpolatorWidget::SetDataStorage(mitk::DataStorage &storage) { m_DataStorage = &storage; } void QmitkSliceBasedInterpolatorWidget::SetSliceNavigationControllers( const QList &controllers) { Q_ASSERT(!controllers.empty()); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } m_ActionToSliceDimensionMap = this->CreateActionToSliceDimension(); } void QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified() { mitk::DataNode *workingNode = this->m_ToolManager->GetWorkingData(0); if (!workingNode) { this->setEnabled(false); return; } mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); // TODO adapt tool manager so that this check is done there, e.g. convenience function // Q_ASSERT(workingImage); if (!workingImage) { this->setEnabled(false); return; } if (workingImage->GetDimension() > 4 || workingImage->GetDimension() < 3) { this->setEnabled(false); return; } m_WorkingImage = workingImage; this->setEnabled(true); } void QmitkSliceBasedInterpolatorWidget::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); m_TimePoints[slicer] = slicer->GetSelectedTimePoint(); // TODO Macht das hier wirklich Sinn???? if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSliceBasedInterpolatorWidget::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (m_Activated && m_WorkingImage.IsNotNull()) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (slicer) { this->TranslateAndInterpolateChangedSlice(e, slicer); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // slicer->GetRenderer()->RequestUpdate(); } } } void QmitkSliceBasedInterpolatorWidget::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (m_Activated && m_WorkingImage.IsNotNull()) { const mitk::SliceNavigationController::GeometrySliceEvent &geometrySliceEvent = dynamic_cast(e); mitk::TimeGeometry *timeGeometry = geometrySliceEvent.GetTimeGeometry(); if (timeGeometry && m_TimePoints.contains(slicer) && timeGeometry->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { mitk::PlaneGeometry *plane = slicedGeometry->GetPlaneGeometry(geometrySliceEvent.GetPos()); if (plane) { m_LastSNC = slicer; this->Interpolate(plane, m_TimePoints[slicer], slicer); } } } } } void QmitkSliceBasedInterpolatorWidget::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { int clickedSliceDimension(-1); int clickedSliceIndex(-1); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); // calculate real slice position, i.e. slice of the image // see if timestep is needed here mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_PreviewNode->SetData(interpolation); const mitk::Color &color = m_WorkingImage->GetActiveLabel()->GetColor(); m_PreviewNode->SetColor(color); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } mitk::Image::Pointer QmitkSliceBasedInterpolatorWidget::GetWorkingSlice(const mitk::PlaneGeometry *planeGeometry) { const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot get slice of WorkingImage. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_WorkingImage); const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return nullptr; } mitk::Image::Pointer slice = extractor->GetOutput(); // specify the undo operation with the non edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(slice->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_undoOperation = new mitk::DiffSliceOperation( m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); slice->DisconnectPipeline(); return slice; } void QmitkSliceBasedInterpolatorWidget::OnToggleWidgetActivation(bool enabled) { Q_ASSERT(m_ToolManager); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) return; m_Controls.m_btApplyForCurrentSlice->setEnabled(enabled); m_Controls.m_btApplyForAllSlices->setEnabled(enabled); if (enabled) m_Controls.m_btStart->setText("Stop"); else m_Controls.m_btStart->setText("Start"); unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { // mitk::SegTool2D* tool = dynamic_cast(m_ToolManager->GetToolById(i)); // MLI TODO // if (tool) tool->SetEnable2DInterpolation( enabled ); } if (enabled) { if (!m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Add(m_PreviewNode); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); this->UpdateVisibleSuggestion(); } else { if (m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Remove(m_PreviewNode); } mitk::UndoController::GetCurrentUndoModel()->Clear(); } m_Activated = enabled; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } template void QmitkSliceBasedInterpolatorWidget::WritePreviewOnWorkingImage(itk::Image *targetSlice, const mitk::Image *sourceSlice, int overwritevalue) { typedef itk::Image ImageType; typename ImageType::Pointer sourceSliceITK; mitk::CastToItkImage(sourceSlice, sourceSliceITK); // now the original slice and the ipSegmentation-painted slice are in the same format, and we can just copy all pixels // that are non-zero typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator InputIteratorType; InputIteratorType inputIterator(sourceSliceITK, sourceSliceITK->GetLargestPossibleRegion()); OutputIteratorType outputIterator(targetSlice, targetSlice->GetLargestPossibleRegion()); outputIterator.GoToBegin(); inputIterator.GoToBegin(); int activePixelValue = m_WorkingImage->GetActiveLabel()->GetValue(); if (activePixelValue == 0) // if exterior is the active label { while (!outputIterator.IsAtEnd()) { if (inputIterator.Get() != 0) { outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else if (overwritevalue != 0) // if we are not erasing { while (!outputIterator.IsAtEnd()) { int targetValue = static_cast(outputIterator.Get()); if (inputIterator.Get() != 0) { if (!m_WorkingImage->GetLabel(targetValue)->GetLocked()) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else // if we are erasing { while (!outputIterator.IsAtEnd()) { const int targetValue = outputIterator.Get(); if (inputIterator.Get() != 0) { if (targetValue == activePixelValue) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } } void QmitkSliceBasedInterpolatorWidget::OnAcceptInterpolationClicked() { if (m_WorkingImage.IsNotNull() && m_PreviewNode->GetData()) { const mitk::PlaneGeometry *planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); if (!planeGeometry) return; mitk::Image::Pointer sliceImage = this->GetWorkingSlice(planeGeometry); if (sliceImage.IsNull()) return; mitk::Image::Pointer previewSlice = dynamic_cast(m_PreviewNode->GetData()); AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, previewSlice, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } // the image was modified within the pipeline, but not marked so m_WorkingImage->Modified(); int clickedSliceDimension(-1); int clickedSliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice( m_WorkingImage, planeGeometry, clickedSliceDimension, clickedSliceIndex); m_SliceInterpolatorController->SetChangedSlice(sliceImage, clickedSliceDimension, clickedSliceIndex, timeStep); // specify the undo operation with the edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(sliceImage->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_doOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); // create an operation event for the undo stack mitk::OperationEvent *undoStackItem = new mitk::OperationEvent( mitk::DiffSliceOperationApplier::GetInstance(), m_doOperation, m_undoOperation, "Slice Interpolation"); // add it to the undo controller mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); // clear the pointers as the operation are stored in the undo controller and also deleted from there m_undoOperation = nullptr; m_doOperation = nullptr; m_PreviewNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSliceBasedInterpolatorWidget::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { // Since we need to shift the plane it must be clone so that the original plane isn't altered mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); const auto timePoint = slicer->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); int sliceDimension(-1); int sliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, reslicePlane, sliceDimension, sliceIndex); unsigned int zslices = m_WorkingImage->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); mitk::Point3D origin = reslicePlane->GetOrigin(); for (unsigned int idx = 0; idx < zslices; ++idx) { // Transforming the current origin of the reslice plane // so that it matches the one of the next slice m_WorkingImage->GetSlicedGeometry()->WorldToIndex(origin, origin); origin[sliceDimension] = idx; m_WorkingImage->GetSlicedGeometry()->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(sliceDimension, idx, reslicePlane, timeStep); if (interpolation.IsNotNull()) { m_PreviewNode->SetData(interpolation); mitk::Image::Pointer sliceImage = this->GetWorkingSlice(reslicePlane); if (sliceImage.IsNull()) return; AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, interpolation, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(reslicePlane); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } m_WorkingImage->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_2DWINDOWS); } mitk::ProgressBar::GetInstance()->Progress(); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = m_ActionToSliceDimensionMap.begin(); it != m_ActionToSliceDimensionMap.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllPopupActivated(QAction *action) { ActionToSliceDimensionMapType::const_iterator iter = m_ActionToSliceDimensionMap.find(action); if (iter != m_ActionToSliceDimensionMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } void QmitkSliceBasedInterpolatorWidget::UpdateVisibleSuggestion() { if (m_Activated && m_LastSNC) { // determine which one is the current view, try to do an initial interpolation mitk::BaseRenderer *renderer = m_LastSNC->GetRenderer(); if (renderer && renderer->GetMapperID() == mitk::BaseRenderer::Standard2D) { const mitk::TimeGeometry *timeGeometry = dynamic_cast(renderer->GetWorldTimeGeometry()); if (timeGeometry) { mitk::SliceNavigationController::GeometrySliceEvent event(const_cast(timeGeometry), renderer->GetSlice()); this->TranslateAndInterpolateChangedSlice(event, m_LastSNC); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } } void QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSliceBasedInterpolatorWidget::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkSliceBasedInterpolatorWidget::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkSliceBasedInterpolatorWidget::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index e41f25d83b..cb067cfd3f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1442 +1,1441 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkSelectableGLWidget.h" -#include "QmitkStdMultiWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map QmitkSlicesInterpolator::createActionToSliceDimension() { std::map actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; } return actionToSliceDimension; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), // ACTION_TO_SLICEDIMENSION( createActionToSliceDimension() ), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); // T28261 // m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); // vboxLayout->addWidget(m_BtnSuggestPlane); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); // T28261 // connect(m_BtnSuggestPlane, SIGNAL(clicked()), this, SLOT(OnSuggestPlaneClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList &controllers) { Q_ASSERT(!controllers.empty()); if (m_Initialized) { // remove old observers Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } ACTION_TO_SLICEDIMENSION = createActionToSliceDimension(); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } ACTION_TO_SLICEDIMENSION.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); // T28261 // m_BtnSuggestPlane->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); return; } // Updating the current selected segmentation for the 3D interpolation SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } this->CheckSupportedImageDimension(); } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) Interpolate(plane, m_TimePoints[slicer], slicer); return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); if (m_Segmentation) { if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); int clickedSliceDimension(-1); int clickedSliceIndex(-1); // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (interpolatedSurface.IsNotNull() && workingNode && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } if (!m_DataStorage->Exists(m_3DContourNode)) { m_DataStorage->Add(m_3DContourNode, workingNode); } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); foreach (mitk::SliceNavigationController *slicer, m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { if (m_Segmentation && m_FeedbackNode->GetData()) { // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast(m_FeedbackNode->GetData()); reslice->SetInputSlice(slice->GetSliceData()->GetVtkImageAccessor(slice)->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_Segmentation); const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(m_LastSNC->GetCurrentPlaneGeometry()); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so m_Segmentation->Modified(); m_Segmentation->GetVtkImageData()->Modified(); m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; const auto timePoint = slicer->GetSelectedTimePoint(); if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } timeStep = geometry->TimePointToTimeStep(timePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); for (std::remove_const_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) threads.emplace_back(interpolate, threadIndex); // Run the interpolation for (auto& thread : threads) thread.join(); m_Interpolator->DisableSliceImageCache(); if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = ACTION_TO_SLICEDIMENSION.begin(); it != ACTION_TO_SLICEDIMENSION.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(timePoint) || !segmentationGeometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = interpolatedSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ImageReadAccessor readAccessor(interpolatedSegmentation, interpolatedSegmentation->GetVolumeData(timeStep)); const auto* dataPointer = readAccessor.GetData(); if (nullptr == dataPointer) return; timeStep = segmentationGeometry->TimePointToTimeStep(timePoint); segmentation->SetVolume(dataPointer, timeStep, 0); m_CmbInterpolation->setCurrentIndex(0); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + "_3D-interpolation"; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); std::array rgb; segmentationDataNode->GetColor(rgb.data()); interpolatedSurfaceDataNode->SetColor(rgb.data()); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void ::QmitkSlicesInterpolator::OnSuggestPlaneClicked() { if (m_PlaneWatcher.isRunning()) m_PlaneWatcher.waitForFinished(); m_PlaneFuture = QtConcurrent::run(this, &QmitkSlicesInterpolator::RunPlaneSuggestion); m_PlaneWatcher.setFuture(m_PlaneFuture); } void ::QmitkSlicesInterpolator::RunPlaneSuggestion() { if (m_FirstRun) mitk::ProgressBar::GetInstance()->AddStepsToDo(7); else mitk::ProgressBar::GetInstance()->AddStepsToDo(3); m_EdgeDetector->SetSegmentationMask(m_Segmentation); m_EdgeDetector->SetInput(dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData())); m_EdgeDetector->Update(); mitk::UnstructuredGrid::Pointer uGrid = mitk::UnstructuredGrid::New(); uGrid->SetVtkUnstructuredGrid(m_EdgeDetector->GetOutput()->GetVtkUnstructuredGrid()); mitk::ProgressBar::GetInstance()->Progress(); mitk::Surface::Pointer surface = dynamic_cast(m_InterpolatedSurfaceNode->GetData()); vtkSmartPointer vtkpoly = surface->GetVtkPolyData(); vtkSmartPointer vtkpoints = vtkpoly->GetPoints(); vtkSmartPointer vGrid = vtkSmartPointer::New(); vtkSmartPointer verts = vtkSmartPointer::New(); verts->GetPointIds()->SetNumberOfIds(vtkpoints->GetNumberOfPoints()); for (int i = 0; i < vtkpoints->GetNumberOfPoints(); i++) { verts->GetPointIds()->SetId(i, i); } vGrid->Allocate(1); vGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); vGrid->SetPoints(vtkpoints); mitk::UnstructuredGrid::Pointer interpolationGrid = mitk::UnstructuredGrid::New(); interpolationGrid->SetVtkUnstructuredGrid(vGrid); m_PointScorer->SetInput(0, uGrid); m_PointScorer->SetInput(1, interpolationGrid); m_PointScorer->Update(); mitk::UnstructuredGrid::Pointer scoredGrid = mitk::UnstructuredGrid::New(); scoredGrid = m_PointScorer->GetOutput(); mitk::ProgressBar::GetInstance()->Progress(); double spacing = mitk::SurfaceInterpolationController::GetInstance()->GetDistanceImageSpacing(); mitk::UnstructuredGridClusteringFilter::Pointer clusterFilter = mitk::UnstructuredGridClusteringFilter::New(); clusterFilter->SetInput(scoredGrid); clusterFilter->SetMeshing(false); clusterFilter->SetMinPts(4); clusterFilter->Seteps(spacing); clusterFilter->Update(); mitk::ProgressBar::GetInstance()->Progress(); // Create plane suggestion mitk::BaseRenderer::Pointer br = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); mitk::PlaneProposer planeProposer; std::vector grids = clusterFilter->GetAllClusters(); planeProposer.SetUnstructuredGrids(grids); mitk::SliceNavigationController::Pointer snc = br->GetSliceNavigationController(); planeProposer.SetSliceNavigationController(snc); planeProposer.SetUseDistances(true); try { planeProposer.CreatePlaneInfo(); } catch (const mitk::Exception &e) { MITK_ERROR << e.what(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_FirstRun = false; } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("3DContourContainer", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { m_BtnApply3D->setEnabled(true); m_3DContourNode = contourNodes->at(0); mitk::Surface::Pointer contours = dynamic_cast(m_3DContourNode->GetData()); if (contours) mitk::SurfaceInterpolationController::GetInstance()->ReinitializeInterpolation(contours); m_BtnReinit3DInterpolation->setEnabled(false); } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { std::map::const_iterator iter = ACTION_TO_SLICEDIMENSION.find(action); if (iter != ACTION_TO_SLICEDIMENSION.end()) { mitk::SliceNavigationController *slicer = iter->second; AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); // additional error message on std::cerr std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { mitk::Image *segmentation = dynamic_cast(workingNode->GetData()); if (segmentation) { m_Interpolator->SetSegmentationVolume(segmentation); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { m_Timer->stop(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); if (currentColor[2] == SURFACE_COLOR_RGB[2]) { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 1.0f)); } else { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); } m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; this->CheckSupportedImageDimension(); try { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { if ((workingNode->IsVisible(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))))) { int ret = QMessageBox::Yes; if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) { QMessageBox msgBox; msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); ret = msgBox.exec(); } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } else { m_CmbInterpolation->setCurrentIndex(0); } } } else { QWidget::setEnabled(false); m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); // T28261 // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); double minSpacing(100); double maxSpacing(0); for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image *segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) m_3DContourNode->SetVisibility( status, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); /*if (m_3DInterpolationEnabled && m_Segmentation && m_Segmentation->GetDimension() != 3) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); }*/ } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp index 8449344205..8179d37041 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp @@ -1,355 +1,353 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSurfaceBasedInterpolatorWidget.h" #include "mitkColorProperty.h" #include "mitkInteractionConst.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include "mitkUndoController.h" #include "mitkVtkRepresentationProperty.h" #include -#include "QmitkStdMultiWidget.h" - #include #include #include QmitkSurfaceBasedInterpolatorWidget::QmitkSurfaceBasedInterpolatorWidget(QWidget *parent, const char * /*name*/) : QWidget(parent), m_SurfaceBasedInterpolatorController(mitk::SurfaceBasedInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Activated(false), m_DataStorage(nullptr) { m_Controls.setupUi(this); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate( this, &QmitkSurfaceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); connect(m_Controls.m_btStart, SIGNAL(toggled(bool)), this, SLOT(OnToggleWidgetActivation(bool))); connect(m_Controls.m_btAccept, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_Controls.m_cbShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSurfaceBasedInterpolatorWidget::OnSurfaceInterpolationInfoChanged); m_SurfaceInterpolationInfoChangedObserverTag = m_SurfaceBasedInterpolatorController->AddObserver(itk::ModifiedEvent(), command); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetName("Surface Interpolation feedback"); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetName("Drawn Contours"); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); m_Controls.m_btAccept->setEnabled(false); m_Controls.m_cbShowPositionNodes->setEnabled(false); this->setEnabled(false); } void QmitkSurfaceBasedInterpolatorWidget::SetDataStorage(mitk::DataStorage &storage) { m_DataStorage = &storage; } QmitkSurfaceBasedInterpolatorWidget::~QmitkSurfaceBasedInterpolatorWidget() { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate( this, &QmitkSurfaceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); // remove observer m_SurfaceBasedInterpolatorController->RemoveObserver(m_SurfaceInterpolationInfoChangedObserverTag); delete m_Timer; } void QmitkSurfaceBasedInterpolatorWidget::ShowInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) m_3DContourNode->SetVisibility( status, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSurfaceBasedInterpolatorWidget::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceBasedInterpolatorController->GetInterpolationResult(); if (interpolatedSurface.IsNotNull()) { m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceBasedInterpolatorController->GetContoursAsSurface()); this->ShowInterpolationResult(true); } else { m_InterpolatedSurfaceNode->SetData(nullptr); m_3DContourNode->SetData(nullptr); this->ShowInterpolationResult(false); } } void QmitkSurfaceBasedInterpolatorWidget::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } 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) { manualSegmentationTool->SetShowMarkerNodes(state); } } } void QmitkSurfaceBasedInterpolatorWidget::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSurfaceBasedInterpolatorWidget::StopUpdateInterpolationTimer() { m_Timer->stop(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSurfaceBasedInterpolatorWidget::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); float yellow[3] = {255.0, 255.0, 0.0}; if (currentColor[2] == yellow[2]) { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 255.0)); } else { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(yellow)); } m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSurfaceBasedInterpolatorWidget::OnToolManagerWorkingDataModified() { mitk::DataNode *workingNode = this->m_ToolManager->GetWorkingData(0); if (!workingNode) { this->setEnabled(false); return; } mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); // TODO adapt tool manager so that this check is done there, e.g. convenience function // Q_ASSERT(workingImage); if (!workingImage) { this->setEnabled(false); return; } if (workingImage->GetDimension() > 4 || workingImage->GetDimension() < 3) { this->setEnabled(false); return; } m_WorkingImage = workingImage; this->setEnabled(true); } void QmitkSurfaceBasedInterpolatorWidget::OnRunInterpolation() { m_SurfaceBasedInterpolatorController->Interpolate(); } void QmitkSurfaceBasedInterpolatorWidget::OnToggleWidgetActivation(bool enabled) { Q_ASSERT(m_ToolManager); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) return; m_Controls.m_btAccept->setEnabled(enabled); m_Controls.m_cbShowPositionNodes->setEnabled(enabled); if (enabled) m_Controls.m_btStart->setText("Stop"); else m_Controls.m_btStart->setText("Start"); for (unsigned int i = 0; i < m_ToolManager->GetTools().size(); i++) { mitk::SegTool2D *tool = dynamic_cast(m_ToolManager->GetToolById(i)); if (tool) tool->SetEnable3DInterpolation(enabled); } if (enabled) { if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } if (!m_DataStorage->Exists(m_3DContourNode)) { m_DataStorage->Add(m_3DContourNode); } mitk::Vector3D spacing = m_WorkingImage->GetGeometry(0)->GetSpacing(); double minSpacing(100); double maxSpacing(0); for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } else if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceBasedInterpolatorController->SetWorkingImage(m_WorkingImage); m_SurfaceBasedInterpolatorController->SetActiveLabel(m_WorkingImage->GetActiveLabel()->GetValue()); m_SurfaceBasedInterpolatorController->SetMaxSpacing(maxSpacing); m_SurfaceBasedInterpolatorController->SetMinSpacing(minSpacing); m_SurfaceBasedInterpolatorController->SetDistanceImageVolume(50000); int ret = QMessageBox::Yes; if (m_SurfaceBasedInterpolatorController->EstimatePortionOfNeededMemory() > 0.5) { QMessageBox msgBox; msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); ret = msgBox.exec(); } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { m_Future = QtConcurrent::run(this, &QmitkSurfaceBasedInterpolatorWidget::OnRunInterpolation); m_Watcher.setFuture(m_Future); } } else { if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Remove(m_InterpolatedSurfaceNode); } if (m_DataStorage->Exists(m_3DContourNode)) { m_DataStorage->Remove(m_3DContourNode); } mitk::UndoController::GetCurrentUndoModel()->Clear(); } m_Activated = enabled; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSurfaceBasedInterpolatorWidget::OnAcceptInterpolationClicked() { if (m_InterpolatedSurfaceNode.IsNotNull() && m_InterpolatedSurfaceNode->GetData()) { // m_WorkingImage->SurfaceStamp(dynamic_cast(m_InterpolatedSurfaceNode->GetData()), false); this->ShowInterpolationResult(false); } } void QmitkSurfaceBasedInterpolatorWidget::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_Activated) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSurfaceBasedInterpolatorWidget::OnRunInterpolation); m_Watcher.setFuture(m_Future); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h index 50e188bd8f..02eac9ea51 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h @@ -1,301 +1,301 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file.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 all the task names present in the root node with possible duplicates. * Template function, type can be any of stl or Qt containers which supports push_back call. * - * @tparam T + * @param T * @param taskName * @return T (any of stl or Qt containers which supports push_back call) */ template T getAllTasks() { T allTasks; auto models = GetSubFolderNamesFromNode(m_RootNode); foreach (QString model, models) { allTasks << getTasksForModel(model); } return allTasks; } /** * @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/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp index c451e52506..bacff31ea4 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -1,639 +1,630 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkProcessExecutor.h" #include "mitknnUnetTool.h" #include #include #include -#include -#include #include #include #include #include +#include + MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") -QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkAutoMLSegmentationToolGUIBase() +QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase() { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The 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()); m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); } -void QmitknnUNetToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) +void QmitknnUNetToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *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(); SetGPUInfo(); 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.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); connect(m_Controls.multiModalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalitiesNumberChanged(int))); connect(m_Controls.pythonEnvComboBox, #if QT_VERSION >= 0x050F00 // 5.15 SIGNAL(textActivated(const QString &)), #elif QT_VERSION >= 0x050C00 // 5.12 SIGNAL(currentTextChanged(const QString &)), #endif this, SLOT(OnPythonPathChanged(const QString &))); connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); connect(m_Controls.clearCacheButton, SIGNAL(clicked()), this, SLOT(OnClearCachePressed())); connect(m_Controls.downloadModelButton, SIGNAL(clicked()), this, SLOT(OnDownloadModel())); m_Controls.multiModalSpinBox->setVisible(false); m_Controls.multiModalSpinBox->setEnabled(false); m_Controls.multiModalSpinLabel->setVisible(false); m_Controls.previewButton->setEnabled(false); QIcon refreshIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/view-refresh.svg")); m_Controls.refreshdirectoryBox->setIcon(refreshIcon); QIcon dirIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg")); m_Controls.modeldirectoryBox->setIcon(dirIcon); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); if (m_GpuLoader.GetGPUCount() != 0) { WriteStatusMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } else { WriteErrorMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. DisableEverything(); QString lastSelectedPyEnv = m_Settings.value("nnUNet/LastPythonPath").toString(); m_Controls.pythonEnvComboBox->setCurrentText(lastSelectedPyEnv); } void QmitknnUNetToolGUI::OnPreviewRequested() { mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); if (nullptr != tool) { QString pythonPathTextItem = ""; try { size_t hashKey(0); 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); } pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); QString pythonPath = m_PythonPath; 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->SetPythonPath(pythonPath.toStdString()); tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); // checkboxes tool->SetMirror(m_Controls.mirrorBox->isChecked()); tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); tool->SetNoPip(false); bool doCache = m_Controls.enableCachingCheckBox->isChecked(); // Spinboxes tool->SetGpuId(FetchSelectedGPUFromUI()); // Multi-Modal tool->MultiModalOff(); if (m_Controls.multiModalBox->isChecked()) { if (m_Controls.multiModalSpinBox->value() > 0) { tool->m_OtherModalPaths.clear(); tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); tool->MultiModalOn(); } else { throw std::runtime_error("Please select more than one modalities for a multi-modal task. If you " "would like to use only one modality then uncheck the Multi-Modal option."); } } if (doCache) { hashKey = nnUNetCache::GetUniqueHash(tool->m_ParamQ); if (m_Cache.contains(hashKey)) { tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } } if (tool->GetPredict()) { tool->m_InputBuffer = nullptr; WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); tool->UpdatePreview(); if (nullptr == tool->GetOutputBuffer()) { SegmentationProcessFailed(); } else { SegmentationResultHandler(tool); if (doCache) { - AddToCache(hashKey, tool->GetMLPreview()); + AddToCache(hashKey, tool->GetOutputBuffer()); } + tool->ClearOutputBuffer(); } tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } else { MITK_INFO << "won't do segmentation. Key found: " << QString::number(hashKey).toStdString(); if (m_Cache.contains(hashKey)) { nnUNetCache *cacheObject = m_Cache[hashKey]; MITK_INFO << "fetched pointer " << cacheObject->m_SegCache.GetPointer(); tool->SetOutputBuffer(const_cast(cacheObject->m_SegCache.GetPointer())); SegmentationResultHandler(tool, true); } } m_Controls.previewButton->setEnabled(true); } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); ShowErrorMessage(errorMsg.str()); WriteErrorMessage(QString::fromStdString(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; } if (!pythonPathTextItem.isEmpty()) { // only cache if the prediction ended without errors. m_Settings.setValue("nnUNet/LastPythonPath", pythonPathTextItem); } } } std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() { std::vector modals; if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) { std::set nodeNames; // set container for keeping names of all nodes to check if they are added twice. for (QmitkDataStorageComboBox *modality : m_Modalities) { if (nodeNames.find(modality->GetSelectedNode()->GetName()) == nodeNames.end()) { modals.push_back(dynamic_cast(modality->GetSelectedNode()->GetData())); nodeNames.insert(modality->GetSelectedNode()->GetName()); } else { throw std::runtime_error("Same modality is selected more than once. Please change your selection."); break; } } } return modals; } 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); bool isExists = QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")) && QFile::exists(fullPath + QDir::separator() + QString("python3")); return isExists; } 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::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); } void QmitknnUNetToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); } 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(); bool isPPJson = m_Controls.postProcessingCheckBox->isChecked(); std::vector requestQ; QString ppDirFolderNamePart1 = "ensemble_"; QStringList ppDirFolderNameParts; for (auto &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 = FetchSelectedFoldsFromUI(layout->foldBox); mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); requestQ.push_back(modelObject); ppDirFolderNameParts << ppDirFolderName.join(QString("")); } tool->EnsembleOn(); if (isPPJson) { 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()); const QString statusMsg = "Post Processing JSON file found: " + ppJsonFilePossibility1; WriteStatusMessage(statusMsg); } else if (QFile(ppJsonFilePossibility2).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); const QString statusMsg = "Post Processing JSON file found:" + ppJsonFilePossibility2; WriteStatusMessage(statusMsg); } else { std::string errorMsg = "No post processing file was found for the selected ensemble combination. Continuing anyway..."; ShowErrorMessage(errorMsg); } } 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(m_Controls.foldBox); 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; } void QmitknnUNetToolGUI::CheckAllInCheckableComboBox(ctkCheckableComboBox *foldBox) { // Recalling all added items to check-mark it. const QAbstractItemModel *qaim = foldBox->checkableModel(); auto rows = qaim->rowCount(); for (std::remove_const_t i = 0; i < rows; ++i) { const QModelIndex mi = qaim->index(i, 0); foldBox->setCheckState(mi, Qt::Checked); } } std::pair QmitknnUNetToolGUI::ExtractTrainerPlannerFromString(QStringList trainerPlanners) { QString splitterString = "__"; QStringList trainers, planners; for (const auto &trainerPlanner : trainerPlanners) { trainers << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).first(); planners << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).last(); } trainers.removeDuplicates(); planners.removeDuplicates(); return std::make_pair(trainers, planners); } std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI(ctkCheckableComboBox *foldBox) { std::vector folds; if (foldBox->noneChecked()) { CheckAllInCheckableComboBox(foldBox); } QModelIndexList foldList = foldBox->checkedIndexes(); for (const auto &index : foldList) { QString foldQString = foldBox->itemText(index.row()).split("_", QString::SplitBehavior::SkipEmptyParts).last(); folds.push_back(foldQString.toStdString()); } return folds; } void QmitknnUNetToolGUI::OnClearCachePressed() { m_Cache.clear(); UpdateCacheCountOnUI(); } void QmitknnUNetToolGUI::UpdateCacheCountOnUI() { QString cacheText = m_CACHE_COUNT_BASE_LABEL + QString::number(m_Cache.size()); m_Controls.cacheCountLabel->setText(cacheText); } void QmitknnUNetToolGUI::AddToCache(size_t &hashKey, mitk::LabelSetImage::ConstPointer mlPreview) { nnUNetCache *newCacheObj = new nnUNetCache; newCacheObj->m_SegCache = mlPreview; m_Cache.insert(hashKey, newCacheObj); MITK_INFO << "New hash: " << hashKey << " " << newCacheObj->m_SegCache.GetPointer(); UpdateCacheCountOnUI(); } void QmitknnUNetToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitknnUNetToolGUI::FetchSelectedGPUFromUI() { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } QString QmitknnUNetToolGUI::FetchResultsFolderFromEnv() { const char *pathVal = itksys::SystemTools::GetEnv("RESULTS_FOLDER"); QString retVal; if (pathVal) { retVal = QString::fromUtf8(pathVal); } else { retVal = m_Settings.value("nnUNet/LastRESULTS_FOLDERPath").toString(); } return retVal; } void QmitknnUNetToolGUI::DumpAllJSONs(const QString &path) { DumpJSONfromPickle(path); ExportAvailableModelsAsJSON(m_ParentFolder->getResultsFolder()); } void QmitknnUNetToolGUI::DumpJSONfromPickle(const QString &picklePath) { const QString pickleFile = picklePath + QDir::separator() + m_PICKLE_FILENAME; const QString jsonFile = picklePath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; if (!QFile::exists(jsonFile)) { mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("-c"); std::string pythonCode; // python syntax to parse plans.pkl file and export as Json file. pythonCode.append("import pickle;"); pythonCode.append("import json;"); pythonCode.append("loaded_pickle = pickle.load(open('"); pythonCode.append(pickleFile.toStdString()); pythonCode.append("','rb'));"); pythonCode.append("modal_dict = {key: loaded_pickle[key] for key in loaded_pickle.keys() if key in " "['modalities','num_modalities']};"); pythonCode.append("json.dump(modal_dict, open('"); pythonCode.append(jsonFile.toStdString()); pythonCode.append("', 'w'))"); args.push_back(pythonCode); try { spExec->Execute(m_PythonPath.toStdString(), "python3", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Pickle parsing FAILED!" << e.GetDescription(); // SHOW ERROR WriteStatusMessage( "Parsing failed in backend. Multiple Modalities will now have to be manually entered by the user."); } } } void QmitknnUNetToolGUI::ExportAvailableModelsAsJSON(const QString &resultsFolder) { const QString jsonPath = resultsFolder + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (!QFile::exists(jsonPath)) { mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("--export"); args.push_back(resultsFolder.toStdString()); try { spExec->Execute(m_PythonPath.toStdString(), "nnUNet_print_available_pretrained_models", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Exporting information FAILED." << e.GetDescription(); // SHOW ERROR WriteStatusMessage("Exporting information FAILED."); } } } void QmitknnUNetToolGUI::DisplayMultiModalInfoFromJSON(const QString &jsonPath) { - if (QFile::exists(jsonPath)) + std::ifstream file(jsonPath.toStdString()); + + if (file.is_open()) { - QFile file(jsonPath); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + auto jsonObj = nlohmann::json::parse(file, nullptr, false); + + if (jsonObj.is_discarded() || !jsonObj.is_object()) { - QByteArray bytes = file.readAll(); - file.close(); + MITK_ERROR << "Could not parse \"" << jsonPath.toStdString() << "\" as JSON object!"; + return; + } - QJsonParseError jsonError; - QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - MITK_INFO << "Json parsing failed: " << jsonError.errorString().toStdString() << endl; - return; - } - if (document.isObject()) + auto num_mods = jsonObj["num_modalities"].get(); + ClearAllModalLabels(); + if (num_mods > 1) + { + m_Controls.multiModalBox->setChecked(true); + m_Controls.multiModalBox->setEnabled(false); + m_Controls.multiModalSpinBox->setValue(num_mods - 1); + m_Controls.advancedSettingsLayout->update(); + auto obj = jsonObj["modalities"]; + int count = 0; + for (const auto& value : obj) { - QJsonObject jsonObj = document.object(); - int num_mods = jsonObj["num_modalities"].toInt(); - ClearAllModalLabels(); - if (num_mods > 1) - { - m_Controls.multiModalBox->setChecked(true); - m_Controls.multiModalBox->setEnabled(false); - m_Controls.multiModalSpinBox->setValue(num_mods - 1); - m_Controls.advancedSettingsLayout->update(); - QJsonObject obj = jsonObj.value("modalities").toObject(); - QStringList keys = obj.keys(); - int count = 0; - for (auto key : keys) - { - auto value = obj.take(key); - QLabel *label = new QLabel("" + value.toString() + "", this); - m_ModalLabels.push_back(label); - m_Controls.advancedSettingsLayout->addWidget(label, m_UI_ROWS + 1 + count, 0); - count++; - } - m_Controls.multiModalSpinBox->setMinimum(num_mods - 1); - m_Controls.advancedSettingsLayout->update(); - } - else - { - m_Controls.multiModalSpinBox->setMinimum(0); - m_Controls.multiModalBox->setChecked(false); - } + QLabel *label = new QLabel(QString::fromStdString("" + value.get() + ""), this); + m_ModalLabels.push_back(label); + m_Controls.advancedSettingsLayout->addWidget(label, m_UI_ROWS + 1 + count, 0); + count++; } + m_Controls.multiModalSpinBox->setMinimum(num_mods - 1); + m_Controls.advancedSettingsLayout->update(); + } + else + { + m_Controls.multiModalSpinBox->setMinimum(0); + m_Controls.multiModalBox->setChecked(false); } } } void QmitknnUNetToolGUI::FillAvailableModelsInfoFromJSON(const QString &jsonPath) { if (QFile::exists(jsonPath) && m_Controls.availableBox->count() < 1) { QFile file(jsonPath); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QByteArray bytes = file.readAll(); file.close(); QJsonParseError jsonError; QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError); if (jsonError.error != QJsonParseError::NoError) { MITK_INFO << "Json parsing failed: " << jsonError.errorString().toStdString() << endl; return; } if (document.isObject()) { QJsonObject jsonObj = document.object(); QStringList keys = jsonObj.keys(); m_Controls.availableBox->addItems(keys); } } } } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h index 86ef0e7b45..bf61c8e67c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.h @@ -1,372 +1,372 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkMultiLabelSegWithPreviewToolGUIBase.h" #include "QmitknnUNetFolderParser.h" #include "QmitknnUNetGPU.h" #include "mitknnUnetTool.h" #include "ui_QmitknnUNetToolGUIControls.h" #include #include #include #include #include #include #include class nnUNetCache { public: mitk::LabelSetImage::ConstPointer m_SegCache; static size_t GetUniqueHash(std::vector &requestQ) { size_t hashCode = 0; for (mitk::ModelParams &request : requestQ) { boost::hash_combine(hashCode, request.generateHash()); } return hashCode; } }; -class MITKSEGMENTATIONUI_EXPORT QmitknnUNetToolGUI : public QmitkAutoMLSegmentationToolGUIBase +class MITKSEGMENTATIONUI_EXPORT QmitknnUNetToolGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitknnUNetToolGUI, QmitkAutoMLSegmentationToolGUIBase); + mitkClassMacro(QmitknnUNetToolGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); QCache m_Cache; 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 *, bool forceRender = false); /** * @brief Qt Slot * */ void OnModalitiesNumberChanged(int); /** * @brief Qt Slot * */ void OnPythonPathChanged(const QString &); /** * @brief Qt slot * */ void OnRefreshPresssed(); /** * @brief Qt slot * */ void OnClearCachePressed(); /** * @brief Qt slot * */ void OnDownloadModel(); protected: QmitknnUNetToolGUI(); ~QmitknnUNetToolGUI() = default; - void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) override; + void ConnectNewTool(mitk::SegWithPreviewTool *newTool) override; void InitializeUI(QBoxLayout *mainLayout) override; void EnableWidgets(bool enabled) override; private: /** * @brief Parses the available_models.json file from RESULTS_FOLDER and loads * the task names to the Download combobox in Advanced. */ void FillAvailableModelsInfoFromJSON(const QString &); /** * @brief Calls other JSON dumping functions. * */ void DumpAllJSONs(const QString &); /** * @brief Exports available models to download from nnUNet_print_available_pretrained_models * output. */ void ExportAvailableModelsAsJSON(const QString &); /** * @brief Clears all displayed modal labels and widgets from GUI. * */ void ClearAllModalities(); /** * @brief Parses Json file containing modality info and populates * labels and selection widgets accordingly on the GUI. */ void DisplayMultiModalInfoFromJSON(const QString &); /** * @brief Clears all modality labels previously populated from GUI. * */ void ClearAllModalLabels(); /** * @brief Runs a set of python commands to read "plans.pkl" and extract * modality information required for inferencing. This information is exported * as json file : "mitk_export.json". * * @return QString */ void DumpJSONfromPickle(const QString &); /** * @brief Searches RESULTS_FOLDER environment variable. If not found, * returns from the QSettings stored last used path value. * @return QString */ QString FetchResultsFolderFromEnv(); /** * @brief Returns GPU id of the selected GPU from the Combo box. * * @return unsigned int */ unsigned int FetchSelectedGPUFromUI(); /** * @brief Adds GPU information to the gpu combo box. * In case, there aren't any GPUs avaialble, the combo box will be * rendered editable. */ void SetGPUInfo(); /** * @brief Inserts the hash and segmentation into cache and * updates count on UI. */ void AddToCache(size_t &, mitk::LabelSetImage::ConstPointer); /** * @brief Checks all the entries of the ctkCheckableComboBox ui widget. * This feature is not present in ctkCheckableComboBox API. */ void CheckAllInCheckableComboBox(ctkCheckableComboBox *); /** * @brief Parses the folder names containing trainer and planner together and, * returns it as separate lists. * @return std::pair */ std::pair ExtractTrainerPlannerFromString(QStringList); /** * @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 Writes any message in white on the tool pane. */ void WriteStatusMessage(const QString &); /** * @brief Writes any message in red on the tool pane. */ void WriteErrorMessage(const QString &); /** * @brief 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 Disable/deactivates the nnUNet GUI. * Clears any multi modal labels and selection widgets, as well. */ void DisableEverything(); /** * @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(ctkCheckableComboBox *); /** * @brief Returns all paths from the dynamically generated ctk-path-line-edit boxes. * * @return std::vector */ std::vector FetchMultiModalImagesFromUI(); /** * @brief Updates cache count on UI. * */ void UpdateCacheCountOnUI(); 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_ModalLabels; std::vector> m_EnsembleParams; mitk::NodePredicateBase::Pointer m_MultiModalPredicate; QString m_PythonPath; /** * @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"}; const QString m_CACHE_COUNT_BASE_LABEL = "Cached Items: "; const QString m_MITK_EXPORT_JSON_FILENAME = "mitk_export.json"; const QString m_AVAILABLE_MODELS_JSON_FILENAME = "available_models.json"; const QString m_PICKLE_FILENAME = "plans.pkl"; /** * @brief For storing values across sessions. Currently, RESULTS_FOLDER value is cached using this. */ QSettings m_Settings; bool m_IsRESULTSFOLDERvalid = false; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp index d0022a0e0c..f378afa367 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp @@ -1,514 +1,515 @@ #include "QmitknnUNetToolGUI.h" #include "mitkProcessExecutor.h" #include #include #include #include #include #include void QmitknnUNetToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitknnUNetToolGUI::ClearAllModalities() { m_Controls.multiModalSpinBox->setMinimum(0); m_Controls.multiModalBox->setChecked(false); ClearAllModalLabels(); } void QmitknnUNetToolGUI::ClearAllModalLabels() { for (auto modalLabel : m_ModalLabels) { delete modalLabel; // delete the layout item m_ModalLabels.pop_back(); } m_Controls.advancedSettingsLayout->update(); } void QmitknnUNetToolGUI::DisableEverything() { m_Controls.modeldirectoryBox->setEnabled(false); m_Controls.refreshdirectoryBox->setEnabled(false); m_Controls.previewButton->setEnabled(false); m_Controls.multiModalSpinBox->setVisible(false); m_Controls.multiModalBox->setEnabled(false); ClearAllComboBoxes(); ClearAllModalities(); } void QmitknnUNetToolGUI::ClearAllComboBoxes() { m_Controls.modelBox->clear(); m_Controls.taskBox->clear(); m_Controls.foldBox->clear(); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); for (auto &layout : m_EnsembleParams) { layout->modelBox->clear(); layout->trainerBox->clear(); layout->plannerBox->clear(); layout->foldBox->clear(); } m_Controls.availableBox->clear(); } void QmitknnUNetToolGUI::OnRefreshPresssed() { const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); OnDirectoryChanged(resultsFolder); } void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) { m_Controls.previewButton->setEnabled(false); ClearAllComboBoxes(); ClearAllModalities(); m_ParentFolder = std::make_shared(resultsFolder); auto tasks = m_ParentFolder->getAllTasks(); tasks.removeDuplicates(); std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); m_Settings.setValue("nnUNet/LastRESULTS_FOLDERPath", resultsFolder); m_IsRESULTSFOLDERvalid = false; } void QmitknnUNetToolGUI::OnModelChanged(const QString &model) { if (model.isEmpty()) { return; } ClearAllModalities(); auto selectedTask = m_Controls.taskBox->currentText(); ctkComboBox *box = qobject_cast(sender()); if (box == m_Controls.modelBox) { if (model == m_VALID_MODELS.last()) { 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); auto models = m_ParentFolder->getModelsForTask(m_Controls.taskBox->currentText()); models.removeDuplicates(); models.removeOne(m_VALID_MODELS.last()); for (auto &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); }); } 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); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); 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); }); } } else if (!m_EnsembleParams.empty()) { for (auto &layout : m_EnsembleParams) { if (box == layout->modelBox) { layout->trainerBox->clear(); layout->plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); std::for_each(trainers.begin(), trainers.end(), [&layout](const QString &trainer) { layout->trainerBox->addItem(trainer); }); std::for_each(planners.begin(), planners.end(), [&layout](const QString &planner) { layout->plannerBox->addItem(planner); }); break; } } } } void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) { if (task.isEmpty()) { return; } m_Controls.modelBox->clear(); auto models = m_ParentFolder->getModelsForTask(task); models.removeDuplicates(); if (!models.contains(m_VALID_MODELS.last(), Qt::CaseInsensitive)) { models << m_VALID_MODELS.last(); // add ensemble even if folder doesn't exist } 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::OnTrainerChanged(const QString &plannerSelected) { if (plannerSelected.isEmpty()) { return; } m_IsRESULTSFOLDERvalid = false; QString parentPath; auto *box = qobject_cast(sender()); if (box == m_Controls.plannerBox) { m_Controls.foldBox->clear(); 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) { m_IsRESULTSFOLDERvalid = true; CheckAllInCheckableComboBox(m_Controls.foldBox); parentPath = QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + selectedModel + QDir::separator() + selectedTask + QDir::separator() + selectedTrainer + QString("__") + plannerSelected); } } else if (!m_EnsembleParams.empty()) { for (auto &layout : m_EnsembleParams) { if (box == layout->plannerBox) { layout->foldBox->clear(); auto selectedTrainer = layout->trainerBox->currentText(); auto selectedTask = m_Controls.taskBox->currentText(); auto selectedModel = layout->modelBox->currentText(); auto folds = m_ParentFolder->getFoldsForTrainerPlanner( selectedTrainer, plannerSelected, selectedTask, selectedModel); std::for_each(folds.begin(), folds.end(), [&layout](const QString &fold) { if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet layout->foldBox->addItem(fold); }); if (layout->foldBox->count() != 0) { CheckAllInCheckableComboBox(layout->foldBox); m_IsRESULTSFOLDERvalid = true; parentPath = QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + selectedModel + QDir::separator() + selectedTask + QDir::separator() + selectedTrainer + QString("__") + plannerSelected); } break; } } } if (m_IsRESULTSFOLDERvalid) { m_Controls.previewButton->setEnabled(true); const QString mitkJsonFile = parentPath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; DumpAllJSONs(parentPath); if (QFile::exists(mitkJsonFile)) { DisplayMultiModalInfoFromJSON(mitkJsonFile); } const QString jsonPath = m_ParentFolder->getResultsFolder() + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (QFile::exists(mitkJsonFile)) { FillAvailableModelsInfoFromJSON(jsonPath); } } } void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { OnPythonPathChanged(path); // recall same function for new path validation 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); DisableEverything(); } else { m_Controls.modeldirectoryBox->setEnabled(true); m_Controls.previewButton->setEnabled(true); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.multiModalBox->setEnabled(true); QString setVal = FetchResultsFolderFromEnv(); if (!setVal.isEmpty()) { m_Controls.modeldirectoryBox->setDirectory(setVal); } OnRefreshPresssed(); m_PythonPath = pyEnv.mid(pyEnv.indexOf(" ") + 1); if (!(m_PythonPath.endsWith("bin", Qt::CaseInsensitive) || m_PythonPath.endsWith("bin/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("bin"); } } } 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("multiModalBox")) { m_Controls.multiModalSpinLabel->setVisible(visibility); m_Controls.multiModalSpinBox->setVisible(visibility); if (visibility) { QmitkDataStorageComboBox *defaultImage = new QmitkDataStorageComboBox(this, true); defaultImage->setObjectName(QString("multiModal_" + QString::number(0))); defaultImage->SetPredicate(m_MultiModalPredicate); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); if (tool != nullptr) { defaultImage->SetDataStorage(tool->GetDataStorage()); defaultImage->SetSelectedNode(tool->GetRefNode()); } m_Controls.advancedSettingsLayout->addWidget(defaultImage, m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); m_Modalities.push_back(defaultImage); } else { OnModalitiesNumberChanged(0); m_Controls.multiModalSpinBox->setValue(0); delete m_Modalities[0]; m_Modalities.pop_back(); ClearAllModalLabels(); } } } } void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) { while (num > static_cast(m_Modalities.size() - 1)) { QmitkDataStorageComboBox *multiModalBox = new QmitkDataStorageComboBox(this, true); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); multiModalBox->SetDataStorage(tool->GetDataStorage()); multiModalBox->SetPredicate(m_MultiModalPredicate); multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); m_Modalities.push_back(multiModalBox); } while (num < static_cast(m_Modalities.size() - 1) && !m_Modalities.empty()) { QmitkDataStorageComboBox *child = m_Modalities.back(); if (child->objectName() == "multiModal_0") { std::iter_swap(m_Modalities.end() - 2, m_Modalities.end() - 1); child = m_Modalities.back(); } delete child; // delete the layout item m_Modalities.pop_back(); } 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); } 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; mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); requestObject.inputName = tool->GetRefNode()->GetName(); requestObject.timeStamp = std::to_string(mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint()); return requestObject; } void QmitknnUNetToolGUI::SegmentationProcessFailed() { WriteErrorMessage( "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()); } void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool, bool forceRender) { if (forceRender) { tool->RenderOutputBuffer(); } - this->SetLabelSetPreview(tool->GetMLPreview()); + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); WriteStatusMessage("STATUS: Segmentation task finished successfully.
If multiple Preview objects are selected to Confirm, " - "they will be merged. Any unselected Preview objects will be lost.
"); + "they will be merged. Any unselected Preview objects will be lost."); + this->ActualizePreviewLabelVisibility(); } void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) { if (m_EnsembleParams.empty()) { ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); auto lay1 = std::make_unique(groupBoxModel1); 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, 5, 0, 1, 2); connect(lay1->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay1->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay1)); ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); auto lay2 = std::make_unique(groupBoxModel2); 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, 5, 2, 1, 2); connect(lay2->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay2->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay2)); } for (auto &layout : m_EnsembleParams) { layout->setVisible(visible); } } void QmitknnUNetToolGUI::OnDownloadModel() { if (m_IsRESULTSFOLDERvalid) { auto selectedTask = m_Controls.availableBox->currentText(); mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back(selectedTask.toStdString()); WriteStatusMessage("Downloading the requested task in to the selected Results Folder. This might take some time " "depending on your internet connection..."); try { std::string resultsFolder = m_ParentFolder->getResultsFolder().toStdString(); std::string resultsFolderEnv = "RESULTS_FOLDER=" + resultsFolder; itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); spExec->Execute(m_PythonPath.toStdString(), "nnUNet_download_pretrained_model", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Download FAILED!" << e.GetDescription(); // SHOW ERROR WriteStatusMessage("Download failed. Check your internet connection."); } } } diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index 36584cbe80..26dbc03a4f 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,87 +1,76 @@ set( CPP_FILES -Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp -Qmitk/QmitkAutoSegmentationToolGUIBase.cpp -Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp +Qmitk/QmitkSegWithPreviewToolGUIBase.cpp +Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.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/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/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetToolSlots.cpp -#Added from ML -Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES -Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h -Qmitk/QmitkAutoSegmentationToolGUIBase.h -Qmitk/QmitkAutoMLSegmentationToolGUIBase.h +Qmitk/QmitkSegWithPreviewToolGUIBase.h +Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.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/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/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/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES -Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui -#Added from ML -Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Modules/ToFUI/Qmitk/QmitkToFRecorderWidget.h b/Modules/ToFUI/Qmitk/QmitkToFRecorderWidget.h index e4739df3da..ca837ef7ba 100644 --- a/Modules/ToFUI/Qmitk/QmitkToFRecorderWidget.h +++ b/Modules/ToFUI/Qmitk/QmitkToFRecorderWidget.h @@ -1,185 +1,183 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _QMITKTOFRECORDERWIDGET_H_INCLUDED #define _QMITKTOFRECORDERWIDGET_H_INCLUDED #include #include //QT headers #include #include #include #include //itk headers #include "itkCommand.h" //mitk headers #include #include -class QmitkStdMultiWidget; - struct QFileDialogArgs; class QFileIconProvider; class QFileDialogPrivate; /** * @brief Widget allowing to play / record ToF data * * @ingroup ToFUI */ class MITKTOFUI_EXPORT QmitkToFRecorderWidget :public QWidget { //this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: static const std::string VIEW_ID; QmitkToFRecorderWidget(QWidget* p = nullptr, Qt::WindowFlags f1 = nullptr); ~QmitkToFRecorderWidget() override; /* @brief This method is part of the widget an needs not to be called seperately. */ virtual void CreateQtPartControl(QWidget *parent); /* @brief This method is part of the widget an needs not to be called seperately. (Creation of the connections of main and control widget.)*/ virtual void CreateConnections(); /*! \brief Set the parameters used for this widget \param ToFImageGrabber image grabber providing images from a ToF device \param toFImageRecorder image recorder allowing to record ToF images */ void SetParameter(mitk::ToFImageGrabber* ToFImageGrabber, mitk::ToFImageRecorder* toFImageRecorder); /*! \brief resets the GUI elements to the initial state. Play button: enabled, Stop button: disabled, Recording box: disabled */ void ResetGUIToInitial(); signals: /*! \brief signal emitted when "Play" button is pressed */ void ToFCameraStarted(); /*! \brief signal emitted when "Stop" button is pressed */ void ToFCameraStopped(); /*! \brief signal emitted when recording is started */ void RecordingStarted(); /*! \brief signal emitted AbortEvent() in ToFImageRecorder is observed */ void RecordingStopped(); public slots: /*! \brief slot invoking to start the camera. Calls StartCamera() and emits ToFCameraStarted signal */ void OnPlay(); /*! \brief slot invoking to stop the camera and the recorder. Calls StopCamera() and StopRecorder and emits ToFCameraStarted signal. Resets GUI to initial state. */ void OnStop(); /*! \brief slot invoking to start the recording After letting the user chose a file location for the record, m_ImageRecorder->StartRecording() is inoved. */ void OnStartRecorder(); /*! \brief slot resetting the GUI elements of the recording box */ void OnRecordingStopped(); /*! \brief slot activating/deactivating "number of frames" spin box dependent on recording mode (PerFrame / Infinite) */ void OnChangeRecordModeComboBox(int index); protected: /*! \brief starts the camera by calling ToFImageGrabber::StartCamera() */ void StartCamera(); /*! \brief stops the camera by calling ToFImageGrabber::StopCamera() */ void StopCamera(); /*! \brief stops the recording by calling ToFImageRecorder::StopRecording() */ void StopRecorder(); /*! \brief emits RecordingStopped signal. */ void StopRecordingCallback(); /*! \brief adapted version of QFileDialog::getSaveFileName() The user is now asked to choose which images he wants to save (Distance and/or Intensity and/or Amplitude image) and which type the saved image should have (3D, 2D+t). */ static QString getSaveFileName(mitk::ToFImageWriter::ToFImageType& tofImageType, bool& distanceImageSelected, bool& amplitudeImageSelected, bool& intensityImageSelected, bool& rgbImageSelected, bool& rawDataSelected, QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = nullptr ); /*! \brief method creating a filename from the given information \param dir directory to save the file \param baseFilename base file name entered by the user \param modulationFreq modulation frequency of the camera \param integrationTime integration time of the camera \param numOfFrames number of frames recorded \param extension file extension \param imageType type of image (DistanceImage, IntensityImage, AmplitudeImage) \return dir+"/"+baseFilename+"_MF"+modulationFreq+"_IT"+integrationTime+"_"+numOfFrames+"Images"+imageType+extension */ std::string prepareFilename(std::string dir, std::string baseFilename, std::string modulationFreq, std::string integrationTime, std::string numOfFrames, std::string extension, std::string imageType); Ui::QmitkToFRecorderWidgetControls* m_Controls; ///< member holding the UI elements of this widget mitk::ToFImageGrabber::Pointer m_ToFImageGrabber; ///< member holding the ToFImageGrabber for acquiring ToF images mitk::ToFImageRecorder::Pointer m_ToFImageRecorder; ///< member holding the recorder for ToF images mitk::ToFImageRecorder::RecordMode m_RecordMode; ///< member holding the RecordMode of the recorder (PerFrame / Infinite) typedef itk::SimpleMemberCommand CommandType; CommandType::Pointer m_StopRecordingCommand; ///< itkCommand for abort of recording private: }; #endif // _QMITKTOFRECORDERWIDGET_H_INCLUDED diff --git a/Modules/ToFUI/Qmitk/QmitkToFSurfaceGenerationWidget.h b/Modules/ToFUI/Qmitk/QmitkToFSurfaceGenerationWidget.h index d5f41cd42b..762751ebbd 100644 --- a/Modules/ToFUI/Qmitk/QmitkToFSurfaceGenerationWidget.h +++ b/Modules/ToFUI/Qmitk/QmitkToFSurfaceGenerationWidget.h @@ -1,157 +1,155 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _QMITKTOFSURFACEGENERATIONWIDGET_H_INCLUDED #define _QMITKTOFSURFACEGENERATIONWIDGET_H_INCLUDED #include #include "ui_QmitkToFSurfaceGenerationWidgetControls.h" // QT headers #include // vtk includes #include #include #include //MITK #include #include #include #include #include -class QmitkStdMultiWidget; - /** Documentation: * * This widget provides GUI access for all basic surface generation properties and can * be reused in any other GUI. * \ingroup ToFUI */ class MITKTOFUI_EXPORT QmitkToFSurfaceGenerationWidget :public QWidget { //this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: static const std::string VIEW_ID; QmitkToFSurfaceGenerationWidget (QWidget* p = nullptr, Qt::WindowFlags f1 = nullptr); ~QmitkToFSurfaceGenerationWidget () override; /* @brief Automatically called method. */ virtual void CreateQtPartControl(QWidget *parent); /* @brief Automatically called method. Creation of the connections of main and control widget.)*/ virtual void CreateConnections(); /** * @brief GetToFDistanceImageToSurfaceFilter Get the internally used surface generation filter. * @return ToFDistanceImageToSurfaceFilter as filter. */ mitk::ToFDistanceImageToSurfaceFilter::Pointer GetToFDistanceImageToSurfaceFilter(); /** * @brief IsActive Check if the widget was initialized correctly. * @return True for success. */ bool IsActive(); /** * @brief Initialize Initialize the surface generation widget. * @param filter ToFDistanceImageToSurfaceFilter for surface computation. * @param grabber ToFImageGrabber to get/set device properties. * @param intrinsics Intrincs of the device. * @param surface Generated Surface. * @param camera * @param generateSurface Check the generate surface combo box. * @param showAdvancedOptions Show/Hide advanced options. */ void Initialize(mitk::ToFDistanceImageToSurfaceFilter::Pointer filter, mitk::ToFImageGrabber::Pointer grabber, mitk::CameraIntrinsics::Pointer intrinsics, mitk::DataNode::Pointer surface, vtkSmartPointer camera, bool generateSurface = false, bool showAdvancedOptions = true); /** * @brief UpdateSurface Generate new surface data according to the device properties * @return True for success. */ bool UpdateSurface(); /** * @brief GetSurface Get the generated surface. * @return Surface. */ mitk::Surface::Pointer GetSurface(); protected slots: /** * @brief OnRepresentationChanged Change the representation of the surface. In other words: disable/enable * triangulation (Point cloud/surface). If triangulation is enabled, this will also allow for editing a * threshold for triangulating vertices. */ void OnRepresentationChanged(int index); /** * @brief OnReconstructionChanged Change the reconstruction mode of the ToFDistanceImageToSurfaceFilter. */ void OnReconstructionChanged(int index); /** * @brief OnCompute3DDataCheckboxChecked Slot beeing called, if the "surface"-checkbox is clicked. This method initializes the surface once, if it is necessary. * @param checked Is it checked or not? */ void OnCompute3DDataCheckboxChecked(bool checked); /** * @brief OnShowAdvancedOptionsCheckboxChecked Show/hide advanced options. * @param checked show/hide */ void OnShowAdvancedOptionsCheckboxChecked(bool checked); /*! \brief Slot trigged from the triangulation threshold spin box. Changed the threshold for connecting a vertex during triangulation. */ void OnTriangulationThresholdSpinBoxChanged(); /** * @brief OnDistanceColorMapCheckBoxChecked Show the distance color mapping (vtkColorTransferFunction) on the surface. * @param checked Show/hide. */ void OnDistanceColorMapCheckBoxChecked(bool checked); /** * @brief OnRGBTextureCheckBoxChecked Put the RGB image as texture on the generated surface/point cloud. * @param checked Show/hide texture. */ void OnRGBTextureCheckBoxChecked(bool checked); protected: Ui::QmitkToFSurfaceGenerationWidgetControls* m_Controls; private: void FindReconstructionModeProperty(); mitk::ToFDistanceImageToSurfaceFilter::Pointer m_ToFDistanceImageToSurfaceFilter; mitk::ToFImageGrabber::Pointer m_ToFImageGrabber; mitk::CameraIntrinsics::Pointer m_CameraIntrinsics; mitk::DataNode::Pointer m_SurfaceNode; mitk::Surface::Pointer m_Surface; bool m_Active; vtkSmartPointer m_Camera3d; }; #endif // _QMITKTOFVISUALISATIONSETTINGSWIDGET_H_INCLUDED diff --git a/Modules/ToFUI/Qmitk/QmitkToFVisualisationSettingsWidget.h b/Modules/ToFUI/Qmitk/QmitkToFVisualisationSettingsWidget.h index 2e86190d4f..b888b4222e 100644 --- a/Modules/ToFUI/Qmitk/QmitkToFVisualisationSettingsWidget.h +++ b/Modules/ToFUI/Qmitk/QmitkToFVisualisationSettingsWidget.h @@ -1,172 +1,170 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _QMITKTOFVISUALISATIONSETTINGSWIDGET_H_INCLUDED #define _QMITKTOFVISUALISATIONSETTINGSWIDGET_H_INCLUDED #include #include "ui_QmitkToFVisualisationSettingsWidgetControls.h" #include "mitkDataNode.h" // QT headers #include // vtk includes #include -class QmitkStdMultiWidget; - /** Documentation: * Widget controlling the visualization of Time-of-Flight image data. A color transfer function can be configured for * a given distance, amplitude and intensity image. The pre-configured vtkColorTransferFunctions can be accessed as * an output of the widget. * * \ingroup ToFUI */ class MITKTOFUI_EXPORT QmitkToFVisualisationSettingsWidget :public QWidget { //this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: static const std::string VIEW_ID; QmitkToFVisualisationSettingsWidget (QWidget* p = nullptr, Qt::WindowFlags f1 = nullptr); ~QmitkToFVisualisationSettingsWidget () override; /* @brief This method is part of the widget an needs not to be called seperately. */ virtual void CreateQtPartControl(QWidget *parent); /* @brief This method is part of the widget an needs not to be called seperately. (Creation of the connections of main and control widget.)*/ virtual void CreateConnections(); /*! \brief initialize the widget with the images to be shown \param distanceImageNode image holding the range image of a ToF camera \param amplitudeImageNode image holding the amplitude image of a ToF camera \param intensityImageNode image holding the intensity image of a ToF camera \param surfaceNode */ void Initialize(mitk::DataNode* distanceImageNode=nullptr, mitk::DataNode* amplitudeImageNode=nullptr, mitk::DataNode* intensityImageNode=nullptr, mitk::DataNode* surfaceNode=nullptr); /*! \brief Access the color transfer function of widget 1 (distance image) \return vtkColorTransferFunction that can be used to define a TransferFunctionProperty */ vtkColorTransferFunction* GetWidget1ColorTransferFunction(); /*! \brief Access the color transfer function of widget 2 (distance image) \return vtkColorTransferFunction that can be used to define a TransferFunctionProperty */ vtkColorTransferFunction* GetWidget2ColorTransferFunction(); /*! \brief Access the color transfer function of widget 3 (distance image) \return vtkColorTransferFunction that can be used to define a TransferFunctionProperty */ vtkColorTransferFunction* GetWidget3ColorTransferFunction(); /*! \brief Access the color transfer of the currently selected widget \return vtkColorTransferFunction that can be used to define a TransferFunctionProperty */ vtkColorTransferFunction* GetSelectedColorTransferFunction(); /*! \brief Return the index of the selected image: 0 = Distance, 1 = Amplitude, 2 = Intensity */ int GetSelectedImageIndex(); protected slots: void OnShowAdvancedOptionsCheckboxChecked(bool checked); void OnSetXValueColor(); /*! \brief Slot invoking a reset of the RangeSlider to the minimal and maximal values of the according image */ void OnResetSlider(); /*! \brief Slot called when the range span has changed. */ void OnSpanChanged (int lower, int upper); /*! \brief Resets the transfer function according to the currently selected widget / image */ void OnTransferFunctionReset(); /*! \brief Updates the GUI according to the widget / image selection */ void OnWidgetSelected(int index); /*! \brief Slot called when the line edit of the maximal value of the range slider has changed. Leads to an update of the range slider. */ void OnRangeSliderMaxChanged(); /*! \brief Slot called when the line edit of the minimal value of the range slider has changed. Leads to an update of the range slider. */ void OnRangeSliderMinChanged(); /*! \brief Sets the TransferFunctionType members according to the selection of the widget and the transfer type. */ void OnTransferFunctionTypeSelected(int index); protected: /*! \brief Invokes an update of the ColorTransferFunctionCanvas. Called when the ColorTransferFunction has changed */ void UpdateCanvas(); /*! \brief Resets the ColorTransferFunctionCanvas according to the lower and upper value of the RangeSlider */ void UpdateRanges(); Ui::QmitkToFVisualisationSettingsWidgetControls* m_Controls; int m_RangeSliderMin; ///< Minimal value of the transfer function range. Initialized to the minimal value of the corresponding image. int m_RangeSliderMax; ///< Maximal value of the transfer function range. Initialized to the maximal value of the corresponding image. mitk::DataNode::Pointer m_MitkDistanceImageNode; ///< DataNode holding the range image of the ToF camera as set by Initialize() mitk::DataNode::Pointer m_MitkAmplitudeImageNode; ///< DataNode holding the amplitude image of the ToF camera as set by Initialize() mitk::DataNode::Pointer m_MitkIntensityImageNode; ///< DataNode holding the intensity image of the ToF camera as set by Initialize() mitk::DataNode::Pointer m_MitkSurfaceNode; ///< DataNode holding the surface vtkColorTransferFunction* m_Widget1ColorTransferFunction; ///< vtkColorTransferFunction of widget 1 (distance) that can be used to define a TransferFunctionProperty vtkColorTransferFunction* m_Widget2ColorTransferFunction; ///< vtkColorTransferFunction of widget 2 (amplitude) that can be used to define a TransferFunctionProperty vtkColorTransferFunction* m_Widget3ColorTransferFunction; ///< vtkColorTransferFunction of widget 3 (intensity) that can be used to define a TransferFunctionProperty int m_Widget1TransferFunctionType; ///< member holding the type of the transfer function applied to the image shown in widget 1 (distance image): 0 = gray scale, 1 = color int m_Widget2TransferFunctionType; ///< member holding the type of the transfer function applied to the image shown in widget 2 (amplitude image): 0 = gray scale, 1 = color int m_Widget3TransferFunctionType; ///< member holding the type of the transfer function applied to the image shown in widget 3 (intensity image): 0 = gray scale, 1 = color private: /** * @brief UpdateSurfaceProperty Private helper method to update the surface property color transfer function. */ void UpdateSurfaceProperty(); /*! \brief Reset the color transfer function to the given type and range \param colorTransferFunction vtkColorTransferfunction to be resetted \param type type of the transfer function: 0 = gray scale, 1 = color \param min minimal value to be set to the transfer function \param max maximal value to be set to the transfer function */ void ResetTransferFunction(vtkColorTransferFunction* colorTransferFunction, int type, double min, double max); /*! \brief Reset the color transfer function for the given widget \param widget 0: axial, 1: coronal, 2: sagittal \param type: type of the transfer function: 0 = gray scale, 1 = color */ void ReinitTransferFunction(int widget, int type); }; #endif // _QMITKTOFVISUALISATIONSETTINGSWIDGET_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.cpp b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.cpp index a64a782ba3..01a7afcf61 100644 --- a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.cpp +++ b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.cpp @@ -1,624 +1,619 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // STD #include // Blueberry #include #include // Qmitk #include "ClassificationRegionGrow.h" // Qt #include #include #include //mitk image #include #include -//#include -//#include #include #include #include "mitkVigraRandomForestClassifier.h" #include "mitkCLUtil.h" #include "qboxlayout.h" #include #include "Eigen/Dense" #include #include #include #include #include #include #include #include #include #include #include #include -//#include #include "mitkLabelSetImage.h" -//#include #include #include #include #include #include -//#include #include const std::string ClassificationRegionGrow::VIEW_ID = "org.mitk.views.ClassificationRegionGrow"; void ClassificationRegionGrow::SetFocus() { // m_Controls.buttonPerformImageProcessing->setFocus(); } void ClassificationRegionGrow::CreateQtPartControl( QWidget *parent ) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi( parent ); m_CalculateFeatures = true; m_BlockManualSegmentation = false; m_BlockPostProcessing = false; m_Controls.groupLearningParameter->setVisible(false); m_Controls.groupFeatureSelection->setVisible(false); QmitkDataStorageComboBox * cb_inputimage = new QmitkDataStorageComboBox(this->GetDataStorage(), mitk::TNodePredicateDataType::New()); QmitkDataStorageComboBox * cb_maskimage= new QmitkDataStorageComboBox(this->GetDataStorage(),mitk::TNodePredicateDataType::New()); QmitkDataStorageComboBox * cb_baseimage = new QmitkDataStorageComboBox(this->GetDataStorage(), mitk::TNodePredicateDataType::New()); m_Controls.m_InputImageLayout->addWidget(cb_inputimage); m_Controls.m_MaskImageLayout->addWidget(cb_maskimage); m_Controls.StartingPointLayout->addWidget(cb_baseimage); m_Controls.addInputButton->setIcon(QIcon::fromTheme("list-add")); m_Controls.removeInputButton->setIcon(QIcon::fromTheme("edit-delete")); connect( cb_inputimage, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnInitializeSession(const mitk::DataNode*))); connect( cb_maskimage, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnInitializeSession(const mitk::DataNode*))); connect(m_Controls.SelectAdvancedParameter, SIGNAL(toggled(bool)), m_Controls.groupLearningParameter, SLOT(setVisible(bool))); connect(m_Controls.SelectAdvancedParameter, SIGNAL(toggled(bool)), m_Controls.groupFeatureSelection, SLOT(setVisible(bool))); connect(m_Controls.SelectSimpleParameters, SIGNAL(toggled(bool)), m_Controls.parameterWidget, SLOT(setVisible(bool))); connect(m_Controls.m_DoAutomaticSecmentation, SIGNAL( clicked()), this, SLOT(DoAutomSegmentation())); connect(m_Controls.removeInputButton, SIGNAL(clicked()), this, SLOT(RemoveItemFromLabelList())); connect(m_Controls.addInputButton, SIGNAL(clicked()), this, SLOT(AddInputField())); connect(m_Controls.UseIntensity, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.Gauss1, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.Gauss2, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.Gauss3, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.Gauss4, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.Gauss5, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.DoG1, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.DoG2, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.DoG3, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.DoG4, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.DoG5, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LoG1, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LoG2, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LoG3, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LoG4, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LoG5, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.HoG1, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.HoG2, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.HoG3, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.HoG4, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.HoG5, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LH1, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LH2, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LH3, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); connect(m_Controls.LH4, SIGNAL(toggled(bool)), this, SLOT(OnFeatureSettingsChanged())); } void ClassificationRegionGrow::AddInputField() { QmitkDataStorageComboBox * cb_inputimage = new QmitkDataStorageComboBox(this->GetDataStorage(), mitk::TNodePredicateDataType::New()); //QPushButton * lockButton = new QPushButton(); //lockButton->setText(""); //lockButton->setMinimumWidth(40); //lockButton->setCheckable(true); //lockButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_MediaStop)); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(cb_inputimage,100); //layout->addWidget(lockButton,1); m_Controls.m_InputImageLayout->addLayout(layout); connect(cb_inputimage, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnInitializeSession(const mitk::DataNode*))); } void ClassificationRegionGrow::RemoveItemFromLabelList() { auto numberOfElements = m_Controls.m_InputImageLayout->count(); auto lastItem = m_Controls.m_InputImageLayout->itemAt(numberOfElements-1); QHBoxLayout *layout = dynamic_cast(lastItem); while (QLayoutItem* item = layout->takeAt(0)) { if (QWidget* widget = item->widget()) widget->deleteLater(); delete item; } m_Controls.m_InputImageLayout->removeItem(lastItem); delete lastItem; } void ClassificationRegionGrow::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes ) { // iterate all selected objects, adjust warning visibility foreach( mitk::DataNode::Pointer node, nodes ) { if( node.IsNotNull() && dynamic_cast(node->GetData()) ) { return; } } } void ClassificationRegionGrow::OnInitializeSession(const mitk::DataNode *) { OnFeatureSettingsChanged(); } void ClassificationRegionGrow::ProcessFeatureImages(const mitk::Image::Pointer & raw_image) { // RAW if (m_Controls.UseIntensity->isChecked()) { m_FeatureImageVector.push_back(raw_image); } // GAUSS if (m_Controls.Gauss1->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image, smoothed, 1); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.Gauss2->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image, smoothed, 2); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.Gauss3->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image, smoothed, 3); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.Gauss4->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image, smoothed, 4); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.Gauss5->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image, smoothed, 5); m_FeatureImageVector.push_back(smoothed); } // Difference of Gaussian if (m_Controls.DoG1->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::DifferenceOfGaussianFilter(raw_image, smoothed, 1,0.8); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.DoG2->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::DifferenceOfGaussianFilter(raw_image, smoothed, 2, 1.8); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.DoG3->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::DifferenceOfGaussianFilter(raw_image, smoothed, 3, 2.6); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.DoG4->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::DifferenceOfGaussianFilter(raw_image, smoothed, 4, 3.4); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.DoG5->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::DifferenceOfGaussianFilter(raw_image, smoothed, 5, 4.3); m_FeatureImageVector.push_back(smoothed); } // Laplacian of Gaussian if (m_Controls.LoG1->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::LaplacianOfGaussianFilter(raw_image, smoothed, 1); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.LoG2->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::LaplacianOfGaussianFilter(raw_image, smoothed, 2); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.LoG3->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::LaplacianOfGaussianFilter(raw_image, smoothed, 3); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.LoG4->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::LaplacianOfGaussianFilter(raw_image, smoothed, 4); m_FeatureImageVector.push_back(smoothed); } if (m_Controls.LoG5->isChecked()) { mitk::Image::Pointer smoothed; mitk::CLUtil::LaplacianOfGaussianFilter(raw_image, smoothed, 5); m_FeatureImageVector.push_back(smoothed); } // Hessian of Gaussian if (m_Controls.HoG1->isChecked()) { mitk::CLUtil::HessianOfGaussianFilter(raw_image, m_FeatureImageVector, 1); } if (m_Controls.HoG2->isChecked()) { mitk::CLUtil::HessianOfGaussianFilter(raw_image, m_FeatureImageVector, 2); } if (m_Controls.HoG3->isChecked()) { mitk::CLUtil::HessianOfGaussianFilter(raw_image, m_FeatureImageVector, 3); } if (m_Controls.HoG4->isChecked()) { mitk::CLUtil::HessianOfGaussianFilter(raw_image, m_FeatureImageVector, 4); } if (m_Controls.HoG5->isChecked()) { mitk::CLUtil::HessianOfGaussianFilter(raw_image, m_FeatureImageVector, 5); } // LocalHistogram if (m_Controls.LH1->isChecked()) { mitk::CLUtil::LocalHistogram(raw_image, m_FeatureImageVector, 5,3); } if (m_Controls.LH2->isChecked()) { mitk::CLUtil::LocalHistogram(raw_image, m_FeatureImageVector, 5, 5); } if (m_Controls.LH3->isChecked()) { mitk::CLUtil::LocalHistogram(raw_image, m_FeatureImageVector, 10, 3); } if (m_Controls.LH4->isChecked()) { mitk::CLUtil::LocalHistogram(raw_image, m_FeatureImageVector, 10, 5); } } void ClassificationRegionGrow::OnFeatureSettingsChanged() { MITK_INFO << "FeatureSettingsChanged"; m_CalculateFeatures = true; } void ClassificationRegionGrow::DoAutomSegmentation() { MITK_INFO << "Start Automatic Segmentation ..."; // Load Images from registration process QmitkDataStorageComboBox * cb_image = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(1)->widget()); QmitkDataStorageComboBox * cb_maskimage = dynamic_cast(m_Controls.m_MaskImageLayout->itemAt(1)->widget()); mitk::Image::Pointer raw_image; mitk::Image::Pointer mask_image; if ((cb_image != nullptr) || (cb_maskimage != nullptr)) { raw_image = dynamic_cast(cb_image->GetSelectedNode()->GetData()); mask_image = dynamic_cast(cb_maskimage->GetSelectedNode()->GetData()); } else { QMessageBox msgBox; msgBox.setText("Please specify the images that shlould be used."); msgBox.exec(); return; } if (raw_image.IsNull() || mask_image.IsNull()) { QMessageBox msgBox; msgBox.setText("Error during processing the specified images."); msgBox.exec(); return; } std::vector imageList; imageList.push_back(raw_image); for (int i = 2; i < m_Controls.m_InputImageLayout->count(); ++i) { QLayout* layout = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(i)); MITK_INFO << layout; QmitkDataStorageComboBox * tmp_cb_image = dynamic_cast(layout->itemAt(0)->widget()); MITK_INFO << tmp_cb_image; if (tmp_cb_image) { mitk::Image::Pointer tmp_image = dynamic_cast(tmp_cb_image); if (tmp_image.IsNotNull()) { MITK_INFO << "Adding Image..."; imageList.push_back(tmp_image); } } } MITK_INFO << "Start Feature Calculation ..."; if(m_CalculateFeatures) { m_FeatureImageVector.clear(); for (auto img : imageList) { ProcessFeatureImages(img); } m_CalculateFeatures = false; if (m_Controls.checkAddFeaturesToDataManager->isChecked()) { for (std::size_t i = 0; i < m_FeatureImageVector.size(); ++i) { auto newName = "Feature_" + std::to_string(i); AddAsDataNode(m_FeatureImageVector[i].GetPointer(), newName); } } } MITK_INFO << "Start Classifier Training ..."; TrainClassifier(raw_image, mask_image); MITK_INFO << "Predict extended Segmentation ..."; PredictSegmentation(raw_image, mask_image); } void ClassificationRegionGrow::TrainClassifier(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & mask_image) { typedef itk::Image DoubleImageType; typedef itk::Image ShortImageType; DoubleImageType::Pointer input; ShortImageType::Pointer mask; mitk::CastToItkImage(raw_image, input); mitk::CastToItkImage(mask_image, mask); int numberOfSegmentedVoxel = 0; int numberOfFeatures = m_FeatureImageVector.size(); auto maskIter = itk::ImageRegionConstIteratorWithIndex(mask, mask->GetLargestPossibleRegion()); m_SegmentedLocations.clear(); m_SegmentedOrganLocations.clear(); MITK_INFO << "Count Segmentation Size ..."; while ( ! maskIter.IsAtEnd()) { if (maskIter.Value() > 0) { m_SegmentedLocations.push_back(maskIter.GetIndex()); numberOfSegmentedVoxel++; if (maskIter.Value() > 1) { m_SegmentedOrganLocations.push_back(maskIter.GetIndex()); } } ++maskIter; } MITK_INFO << "Sizes: " << numberOfSegmentedVoxel << " " << m_SegmentedOrganLocations.size(); Eigen::MatrixXi Y_train = mitk::CLUtil::Transform(mask_image, mask_image); Eigen::MatrixXd X_train = Eigen::MatrixXd(numberOfSegmentedVoxel, numberOfFeatures); unsigned int index = 0; MITK_INFO << "Convert Training Data to Eigen Matrix ..."; for (const auto & image : m_FeatureImageVector) { X_train.col(index) = mitk::CLUtil::Transform(image, mask_image); ++index; } MITK_INFO << "Classifier Training ..."; m_Classifier = mitk::VigraRandomForestClassifier::New(); //this->m_Controls.Maximum m_Classifier->SetTreeCount(m_Controls.NumberOfTrees->value()); m_Classifier->SetSamplesPerTree(m_Controls.SamplesPerTree->value()); m_Classifier->SetMinimumSplitNodeSize(m_Controls.MinimumSamplesPerNode->value()); m_Classifier->SetMaximumTreeDepth(m_Controls.MaximumTreeDepth->value()); m_Classifier->Train(X_train, Y_train); } static void addNeighbours(std::stack > &stack, itk::Index<3> idx) { idx[0] -= 1; stack.push(idx); idx[0] += 2; stack.push(idx); idx[0] -= 1; idx[1] -= 1; stack.push(idx); idx[1] += 2; stack.push(idx); idx[1] -= 1; idx[2] -= 1; stack.push(idx); idx[2] += 2; stack.push(idx); } void ClassificationRegionGrow::PredictSegmentation(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & mask_image) { typedef itk::Image DoubleImageType; typedef itk::Image ShortImageType; DoubleImageType::Pointer input; ShortImageType::Pointer mask; mitk::CastToItkImage(raw_image, input); mitk::CastToItkImage(mask_image, mask); std::vector featureImages; for (auto fimage : m_FeatureImageVector) { DoubleImageType::Pointer feature; mitk::CastToItkImage(fimage, feature); featureImages.push_back(feature); } ShortImageType::Pointer usedLocation = ShortImageType::New(); usedLocation->SetRegions(mask->GetLargestPossibleRegion()); usedLocation->Allocate(); usedLocation->FillBuffer(0); ShortImageType::Pointer resultSegmentation = ShortImageType::New(); if (m_Controls.UpdateImage->isChecked()) { QmitkDataStorageComboBox * cb_maskimage = dynamic_cast(m_Controls.StartingPointLayout->itemAt(2)->widget()); mitk::Image::Pointer base_image = dynamic_cast(cb_maskimage->GetSelectedNode()->GetData()); mitk::CastToItkImage(base_image, resultSegmentation); } else { resultSegmentation->SetRegions(mask->GetLargestPossibleRegion()); resultSegmentation->Allocate(); if (m_Controls.SegmentBackground->isChecked()) { resultSegmentation->FillBuffer(1); } else { resultSegmentation->FillBuffer(0); } } // Fill list of Stacks std::vector > > listOfStacks; while (m_SegmentedOrganLocations.size() > 0) { auto currentLocation = m_SegmentedOrganLocations.back(); m_SegmentedOrganLocations.pop_back(); std::size_t cValue = mask->GetPixel(currentLocation); resultSegmentation->SetPixel(currentLocation, cValue); usedLocation->SetPixel(currentLocation, 1000); while (listOfStacks.size() < cValue+1) { listOfStacks.push_back(std::stack >()); } addNeighbours(listOfStacks[cValue],currentLocation); } int countPredicted = 0; bool connectAllLabels = m_Controls.localGrowing->isChecked(); //m_SegmentedOrganLocations.reserve(10000); Eigen::MatrixXd currentX = Eigen::MatrixXd(1, featureImages.size()); vigra::MultiArrayView<2, double> X(vigra::Shape2(currentX.rows(), currentX.cols()), currentX.data()); auto outLabel = Eigen::MatrixXi(currentX.rows(), 1); vigra::MultiArrayView<2, int> Y(vigra::Shape2(currentX.rows(), 1), outLabel.data()); for (std::size_t i = 2; i < listOfStacks.size(); ++i) { while (listOfStacks[i].size() > 0) { auto currentLocation = listOfStacks[i].top(); listOfStacks[i].pop(); if (!mask->GetLargestPossibleRegion().IsInside(currentLocation)) { continue; } if (usedLocation->GetPixel(currentLocation) > i) { continue; } usedLocation->SetPixel(currentLocation, i+1); for (std::size_t f = 0; f < featureImages.size(); ++f) { currentX(0, f) = featureImages[f]->GetPixel(currentLocation); } m_Classifier->GetRandomForest().predictLabels(X, Y); ++countPredicted; if ((static_cast(Y(0, 0)) == i ) || ((Y(0, 0) > 1) && (connectAllLabels))) { resultSegmentation->SetPixel(currentLocation, std::abs(Y(0, 0))); addNeighbours(listOfStacks[i], currentLocation); } } } MITK_INFO << "Number of Predictions: " << countPredicted; MITK_INFO << "Finished Segmentation..."; mitk::Image::Pointer result; mitk::CastToMitkImage(resultSegmentation, result); result->SetOrigin(raw_image->GetGeometry()->GetOrigin()); result->SetSpacing(raw_image->GetGeometry()->GetSpacing()); mitk::LabelSetImage::Pointer labelResult = mitk::LabelSetImage::New(); labelResult->InitializeByLabeledImage(result); mitk::LabelSetImage::Pointer oldLabelSet = dynamic_cast(mask_image.GetPointer()); labelResult->AddLabelSetToLayer(labelResult->GetActiveLayer(),oldLabelSet->GetLabelSet()); MITK_INFO << "Passing Back..."; AddAsDataNode(labelResult.GetPointer(), "ResultSegmentation"); } mitk::DataNode::Pointer ClassificationRegionGrow::AddAsDataNode(const mitk::BaseData::Pointer & data_, const std::string & name ) { mitk::DataNode::Pointer node = nullptr; node = this->GetDataStorage()->GetNamedNode(name); if(node.IsNull()) { node = mitk::DataNode::New(); node->SetData(data_); node->SetName(name); this->GetDataStorage()->Add(node); }else{ if(dynamic_cast(node->GetData()) && dynamic_cast(data_.GetPointer())) { mitk::Image::Pointer target_image = dynamic_cast(node->GetData()); mitk::Image::Pointer source_image = dynamic_cast(data_.GetPointer()); mitk::ImageReadAccessor ra(source_image); target_image->SetImportVolume(const_cast(ra.GetData())); this->RequestRenderWindowUpdate(); } if(dynamic_cast(node->GetData()) && dynamic_cast(data_.GetPointer())) { node->SetData(data_); node->Modified(); } } return node; } diff --git a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.h b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.h index bca9dcd73e..3de437f3f4 100644 --- a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.h +++ b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationRegionGrow.h @@ -1,110 +1,102 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef ClassificationRegionGrow_h #define ClassificationRegionGrow_h #include #include #include "ui_ClassificationRegionGrowControls.h" #include #include #include #include #include -//#include -//#include -//#include -#include #include #include -#include "QmitkPointListViewWidget.h" -#include #include /** \brief ClassificationRegionGrow \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ - -//class QmitkPointListWidget; class ctkSliderWidget; class ClassificationRegionGrow : 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; bool m_CalculateFeatures; std::vector m_FeatureImageVector; std::vector m_ResultImageVector; bool m_BlockManualSegmentation; QFutureWatcher> m_ManualSegmentationFutureWatcher; bool m_BlockPostProcessing; QFutureWatcher> m_PostProcessingFutureWatcher; public slots: /// \brief Called when the user clicks the GUI button void DoAutomSegmentation(); void AddInputField(); void RemoveItemFromLabelList(); void OnFeatureSettingsChanged(); void OnInitializeSession(const mitk::DataNode*); protected: std::vector > m_SegmentedLocations; std::vector > m_SegmentedOrganLocations; typedef float MeasurementType; typedef itk::Statistics::Histogram< MeasurementType, itk::Statistics::DenseFrequencyContainer2 > HistogramType; void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; mitk::DataNode::Pointer AddAsDataNode(const mitk::BaseData::Pointer & data_, const std::string & name ); void ProcessFeatureImages(const mitk::Image::Pointer & raw_image); void TrainClassifier(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & mask_image); void PredictSegmentation(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & mask_image); /// \brief called by QmitkFunctionality when DataManager's selection has changed void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes ) override; Ui::ClassificationRegionGrowControls m_Controls; mitk::VigraRandomForestClassifier::Pointer m_Classifier; }; #endif // ClassificationRegionGrow_h diff --git a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.cpp b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.cpp index 1b466f9f58..3c593e5c3c 100644 --- a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.cpp +++ b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.cpp @@ -1,975 +1,969 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Blueberry #include #include // Qmitk #include "ClassificationSegmentation.h" // Qt #include //mitk image #include #include #include #include #include #include #include "mitkVigraRandomForestClassifier.h" #include "mitkCLUtil.h" #include "qboxlayout.h" #include #include "Eigen/Dense" #include #include #include #include #include #include #include #include #include #include #include #include -//#include - #include #include #include #include #include -#include #include const std::string ClassificationSegmentation::VIEW_ID = "org.mitk.views.classificationsegmentation"; void ClassificationSegmentation::SetFocus() { // m_Controls.buttonPerformImageProcessing->setFocus(); } void ClassificationSegmentation::OnButtonCSFToggle(bool checked) { - // m_PointListWidget->SetPointSet(dynamic_cast(m_PointSetList[0]->GetData())); if(checked) m_PointSetDataInteractor->SetDataNode(m_PointSetList[0]); else m_PointSetDataInteractor->SetDataNode(nullptr); } void ClassificationSegmentation::OnButtonLESToggle(bool checked) { - // m_PointListWidget->SetPointSet(dynamic_cast(m_PointSetList[1]->GetData())); if(checked) m_PointSetDataInteractor->SetDataNode(m_PointSetList[1]); else m_PointSetDataInteractor->SetDataNode(nullptr); } void ClassificationSegmentation::OnButtonBRAToggle(bool checked) { - // m_PointListWidget->SetPointSet(dynamic_cast(m_PointSetList[2]->GetData())); if(checked) m_PointSetDataInteractor->SetDataNode(m_PointSetList[2]); else m_PointSetDataInteractor->SetDataNode(nullptr); } void ClassificationSegmentation::OnButtonNoInteractionToggle(bool checked) { if(checked) m_PointSetDataInteractor->SetDataNode(nullptr); } void ClassificationSegmentation::CreateQtPartControl( QWidget *parent ) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi( parent ); m_CalculateFeatures = true; m_BlockManualSegmentation = false; m_BlockPostProcessing = false; QmitkDataStorageComboBox * cb_inputimage = new QmitkDataStorageComboBox(this->GetDataStorage(), mitk::TNodePredicateDataType::New()); QmitkDataStorageComboBox * cb_maskimage= new QmitkDataStorageComboBox(this->GetDataStorage(),mitk::TNodePredicateDataType::New()); QmitkDataStorageComboBox * cb_classifer= new QmitkDataStorageComboBox(this->GetDataStorage(),mitk::TNodePredicateDataType::New()); m_Controls.m_InputImageLayout->addWidget(cb_inputimage); m_Controls.m_MaskImageLayout->addWidget(cb_maskimage); m_Controls.m_ClassifierLayout->addWidget(cb_classifer); m_Controls.m_ParameterLayout->layout()->addWidget(new QLabel("Gauss Sigma")); m_GaussSlider = new ctkSliderWidget(); m_GaussSlider->setMinimum(0); m_GaussSlider->setMaximum(10); m_GaussSlider->setValue(1); m_Controls.m_ParameterLayout->layout()->addWidget(m_GaussSlider); m_Controls.m_ParameterLayout->layout()->addWidget(new QLabel("Hessian Sigma")); m_HessianSlider = new ctkSliderWidget(); m_HessianSlider->setMinimum(0); m_HessianSlider->setMaximum(10); m_HessianSlider->setValue(3); m_Controls.m_ParameterLayout->layout()->addWidget(m_HessianSlider); m_Controls.m_ParameterLayout->layout()->addWidget(new QLabel("Structure Tensor Inner and Outer Scale")); m_STInnerSlider = new ctkSliderWidget(); m_STInnerSlider->setMinimum(0); m_STInnerSlider->setMaximum(10); m_STInnerSlider->setValue(1.5); m_Controls.m_ParameterLayout->layout()->addWidget(m_STInnerSlider); m_STOuterSlider = new ctkSliderWidget(); m_STOuterSlider->setMinimum(0); m_STOuterSlider->setMaximum(10); m_STOuterSlider->setValue(3); m_Controls.m_ParameterLayout->layout()->addWidget(m_STOuterSlider); m_Controls.m_PostProcessingLayout->layout()->addWidget(new QLabel("Probability map smoothing")); m_GaussCSFSlider = new ctkSliderWidget; m_GaussCSFSlider->setMinimum(0); m_GaussCSFSlider->setMaximum(10); m_GaussCSFSlider->setValue(1.5); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_GaussCSFSlider); m_GaussLESSlider = new ctkSliderWidget; m_GaussLESSlider->setMinimum(0); m_GaussLESSlider->setMaximum(10); m_GaussLESSlider->setValue(3); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_GaussLESSlider); m_GaussBRASlider = new ctkSliderWidget; m_GaussBRASlider->setMinimum(0); m_GaussBRASlider->setMaximum(10); m_GaussBRASlider->setValue(0.5); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_GaussBRASlider); m_Controls.m_PostProcessingLayout->layout()->addWidget(new QLabel("Probability map weighting")); m_WeightCSFSlider = new ctkSliderWidget; m_WeightCSFSlider->setMinimum(0.0); m_WeightCSFSlider->setMaximum(2.0); m_WeightCSFSlider->setValue(1.0); m_WeightCSFSlider->setSingleStep(0.1); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_WeightCSFSlider); m_WeightLESSlider = new ctkSliderWidget; m_WeightLESSlider->setMinimum(0.0); m_WeightLESSlider->setMaximum(2.0); m_WeightLESSlider->setValue(1.0); m_WeightLESSlider->setSingleStep(0.1); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_WeightLESSlider); m_WeightBRASlider = new ctkSliderWidget; m_WeightBRASlider->setMinimum(0.0); m_WeightBRASlider->setMaximum(2.0); m_WeightBRASlider->setValue(1.0); m_WeightBRASlider->setSingleStep(0.1); m_Controls.m_PostProcessingLayout->layout()->addWidget(m_WeightBRASlider); m_PointSetDataInteractor = mitk::PointSetDataInteractor::New(); m_PointSetDataInteractor->LoadStateMachine("PointSet.xml"); m_PointSetDataInteractor->SetEventConfig("PointSetConfig.xml"); connect( cb_inputimage, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnInitializeSession(const mitk::DataNode*))); connect( cb_maskimage, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnInitializeSession(const mitk::DataNode*))); connect( m_Controls.m_SavePointsButton, SIGNAL(clicked(bool)), this, SLOT(DoSavePointsAsMask())); connect( m_Controls.m_ButtonCSFToggle, SIGNAL(toggled(bool)), this, SLOT(OnButtonCSFToggle(bool))); connect( m_Controls.m_ButtonLESToggle, SIGNAL(toggled(bool)), this, SLOT(OnButtonLESToggle(bool))); connect( m_Controls.m_ButtonBRAToggle, SIGNAL(toggled(bool)), this, SLOT(OnButtonBRAToggle(bool))); connect( m_Controls.m_ButtonNoInteraction, SIGNAL(toggled(bool)), this, SLOT(OnButtonNoInteractionToggle(bool))); connect( &m_ManualSegmentationFutureWatcher, SIGNAL(finished()), this, SLOT(ManualSegmentationFinished())); connect( &m_PostProcessingFutureWatcher, SIGNAL(finished()), this, SLOT(PostProcessingFinished())); //connect( m_Controls.m_AddPointSets, SIGNAL(clicked()),this, SLOT(OnAddPointSets()) ); connect( m_GaussSlider, SIGNAL(valueChanged(double)), this, SLOT(OnFeatureSettingsChanged())); connect( m_HessianSlider, SIGNAL(valueChanged(double)), this, SLOT(OnFeatureSettingsChanged())); connect( m_STInnerSlider, SIGNAL(valueChanged(double)), this, SLOT(OnFeatureSettingsChanged())); connect( m_STOuterSlider, SIGNAL(valueChanged(double)), this, SLOT(OnFeatureSettingsChanged())); connect( m_GaussCSFSlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect( m_GaussLESSlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect( m_GaussBRASlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect( m_WeightCSFSlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect( m_WeightLESSlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect( m_WeightBRASlider, SIGNAL(valueChanged(double)), this, SLOT(OnPostProcessingSettingsChanged())); connect(m_Controls.m_DoAutomaticSecmentation, SIGNAL(clicked(bool)), this, SLOT(DoAutomSegmentation())); connect(m_Controls.m_AddForestToDataManager, SIGNAL(clicked(bool)), this, SLOT(OnAddForestToDataManager())); mitk::DataNode::Pointer pointSetNode = mitk::DataNode::New(); pointSetNode->SetName("CSF_Points."); pointSetNode->SetProperty("helper object", mitk::BoolProperty::New(true)); pointSetNode->SetProperty("layer", mitk::IntProperty::New(1024)); pointSetNode->SetColor(0,1.0,0); pointSetNode->SetDataInteractor(m_PointSetDataInteractor.GetPointer()); m_PointSetList.push_back(pointSetNode); GetDataStorage()->Add(pointSetNode); pointSetNode = mitk::DataNode::New(); pointSetNode->SetName("LES_Points."); pointSetNode->SetProperty("helper object", mitk::BoolProperty::New(true)); pointSetNode->SetProperty("layer", mitk::IntProperty::New(1024)); pointSetNode->SetColor(1.0,0,0); pointSetNode->SetDataInteractor(m_PointSetDataInteractor.GetPointer()); m_PointSetList.push_back(pointSetNode); GetDataStorage()->Add(pointSetNode); pointSetNode = mitk::DataNode::New(); pointSetNode->SetName("BRA_Points."); pointSetNode->SetProperty("helper object", mitk::BoolProperty::New(true)); pointSetNode->SetProperty("layer", mitk::IntProperty::New(1024)); pointSetNode->SetColor(0,0,1.0); pointSetNode->SetDataInteractor(m_PointSetDataInteractor.GetPointer()); m_PointSetList.push_back(pointSetNode); GetDataStorage()->Add(pointSetNode); m_Controls.m_PostProcessingLayout->hide(); m_Controls.m_ParameterLayout->hide(); } void ClassificationSegmentation::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes ) { // iterate all selected objects, adjust warning visibility foreach( mitk::DataNode::Pointer node, nodes ) { if( node.IsNotNull() && dynamic_cast(node->GetData()) ) { // m_Controls.labelWarning->setVisible( false ); // m_Controls.buttonPerformImageProcessing->setEnabled( true ); return; } } // m_Controls.labelWarning->setVisible( true ); // m_Controls.buttonPerformImageProcessing->setEnabled( false ); } void ClassificationSegmentation::OnAddForestToDataManager() { AddAsDataNode(m_TempClassifier.GetPointer(),"ManualSegmentation_Classifier"); } void ClassificationSegmentation::OnInitializeSession(const mitk::DataNode *) { MITK_WARN << "Data selection changed! New session initialized."; m_PointSetDataInteractor->SetDataNode(nullptr); /// CSF POINTSET mitk::PointSet::Pointer pntset = mitk::PointSet::New(); m_PointSetList[0]->SetData(pntset); itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &ClassificationSegmentation::ManualSegmentationTrigger); pntset->AddObserver( mitk::PointSetAddEvent(), command ); pntset->AddObserver( mitk::PointSetRemoveEvent(), command ); pntset->AddObserver( mitk::PointSetMoveEvent(), command ); /// LES POINTSET pntset = mitk::PointSet::New(); m_PointSetList[1]->SetData(pntset); command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &ClassificationSegmentation::ManualSegmentationTrigger); pntset->AddObserver( mitk::PointSetAddEvent(), command ); pntset->AddObserver( mitk::PointSetRemoveEvent(), command ); pntset->AddObserver( mitk::PointSetMoveEvent(), command ); /// BRA POINTSET pntset = mitk::PointSet::New(); m_PointSetList[2]->SetData(pntset); command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &ClassificationSegmentation::ManualSegmentationTrigger); pntset->AddObserver( mitk::PointSetAddEvent(), command ); pntset->AddObserver( mitk::PointSetRemoveEvent(), command ); pntset->AddObserver( mitk::PointSetMoveEvent(), command ); } void ClassificationSegmentation::DoSavePointsAsMask() { mitk::Image::Pointer sampled_image = nullptr; QmitkDataStorageComboBox * cb_image = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(1)->widget()); mitk::Image::Pointer raw_image = dynamic_cast(cb_image->GetSelectedNode()->GetData()); int label = 1; for( auto datanode : m_PointSetList) { mitk::PointSet::Pointer point_set = dynamic_cast(datanode->GetData()); mitk::Image::Pointer temp_img; SampleClassMaskByPointSet(raw_image, point_set,temp_img); mitk::CLUtil::InsertLabel(temp_img,sampled_image,label++); QmitkIOUtil::Save(datanode->GetData(),QString("PointSet.mps")); } QmitkIOUtil::Save(sampled_image,QString("PointSetMask.nrrd")); } void ClassificationSegmentation::ProcessFeatureImages(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & brain_mask) { typedef itk::Image DoubleImageType; typedef itk::Image ShortImageType; typedef itk::ConstNeighborhoodIterator NeighborhoodType; // Neighborhood iterator to access image typedef itk::Functor::NeighborhoodFirstOrderStatistics FunctorType; typedef itk::NeighborhoodFunctorImageFilter FOSFilerType; m_FeatureImageVector.clear(); // RAW m_FeatureImageVector.push_back(raw_image); // GAUSS mitk::Image::Pointer smoothed; mitk::CLUtil::GaussianFilter(raw_image,smoothed,m_GaussSlider->value()); m_FeatureImageVector.push_back(smoothed); // Calculate Probability maps (parameters used from literatur) // CSF mitk::Image::Pointer csf_prob = mitk::Image::New(); mitk::CLUtil::ProbabilityMap(smoothed,13.9, 8.3,csf_prob); m_FeatureImageVector.push_back(csf_prob); // Lesion mitk::Image::Pointer les_prob = mitk::Image::New(); mitk::CLUtil::ProbabilityMap(smoothed,59, 11.6,les_prob); m_FeatureImageVector.push_back(les_prob); // Barin (GM/WM) mitk::Image::Pointer brain_prob = mitk::Image::New(); mitk::CLUtil::ProbabilityMap(smoothed,32, 5.6,brain_prob); m_FeatureImageVector.push_back(brain_prob); std::vector FOS_sizes; FOS_sizes.push_back(1); DoubleImageType::Pointer input; ShortImageType::Pointer mask; mitk::CastToItkImage(smoothed, input); mitk::CastToItkImage(brain_mask, mask); for(unsigned int i = 0 ; i < FOS_sizes.size(); i++) { FOSFilerType::Pointer filter = FOSFilerType::New(); filter->SetNeighborhoodSize(FOS_sizes[i]); filter->SetInput(input); filter->SetMask(mask); filter->Update(); FOSFilerType::DataObjectPointerArray array = filter->GetOutputs(); for( unsigned int i = 0; i < FunctorType::OutputCount; i++) { mitk::Image::Pointer featureimage; mitk::CastToMitkImage(dynamic_cast(array[i].GetPointer()),featureimage); m_FeatureImageVector.push_back(featureimage); // AddImageAsDataNode(featureimage,FunctorType::GetFeatureName(i))->SetVisibility(show_nodes); } } { itk::HessianMatrixEigenvalueImageFilter< DoubleImageType >::Pointer filter = itk::HessianMatrixEigenvalueImageFilter< DoubleImageType >::New(); filter->SetInput(input); filter->SetImageMask(mask); filter->SetSigma(m_HessianSlider->value()); filter->Update(); mitk::Image::Pointer o1,o2,o3; mitk::CastToMitkImage(filter->GetOutput(0),o1); mitk::CastToMitkImage(filter->GetOutput(1),o2); mitk::CastToMitkImage(filter->GetOutput(2),o3); m_FeatureImageVector.push_back(o1); m_FeatureImageVector.push_back(o2); m_FeatureImageVector.push_back(o3); // AddImageAsDataNode(o1,"HE_1")->SetVisibility(show_nodes); // AddImageAsDataNode(o2,"HE_2")->SetVisibility(show_nodes); // AddImageAsDataNode(o3,"HE_3")->SetVisibility(show_nodes); } { itk::StructureTensorEigenvalueImageFilter< DoubleImageType >::Pointer filter = itk::StructureTensorEigenvalueImageFilter< DoubleImageType >::New(); filter->SetInput(input); filter->SetImageMask(mask); filter->SetInnerScale(m_STInnerSlider->value()); filter->SetOuterScale(m_STOuterSlider->value()); filter->Update(); mitk::Image::Pointer o1,o2,o3; mitk::CastToMitkImage(filter->GetOutput(0),o1); mitk::CastToMitkImage(filter->GetOutput(1),o2); mitk::CastToMitkImage(filter->GetOutput(2),o3); m_FeatureImageVector.push_back(o1); m_FeatureImageVector.push_back(o2); m_FeatureImageVector.push_back(o3); // AddImageAsDataNode(o1,"ST_1")->SetVisibility(show_nodes); // AddImageAsDataNode(o2,"ST_2")->SetVisibility(show_nodes); // AddImageAsDataNode(o3,"ST_3")->SetVisibility(show_nodes); } { itk::LineHistogramBasedMassImageFilter< DoubleImageType >::Pointer filter = itk::LineHistogramBasedMassImageFilter< DoubleImageType >::New(); filter->SetInput(input); filter->SetImageMask(mask); filter->Update(); mitk::Image::Pointer o1; mitk::CastToMitkImage(filter->GetOutput(0),o1); m_FeatureImageVector.push_back(o1); } } void ClassificationSegmentation::OnFeatureSettingsChanged() { MITK_INFO << "FeatureSettingsChanged"; m_CalculateFeatures = true; ManualSegmentationTrigger(); } void ClassificationSegmentation::OnPostProcessingSettingsChanged() { MITK_INFO << "PostProcessingSettigsChanged"; PostProcessingTrigger(); } void ClassificationSegmentation::DoAutomSegmentation() { QmitkDataStorageComboBox * cb_image = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(1)->widget()); mitk::Image::Pointer raw_image = dynamic_cast(cb_image->GetSelectedNode()->GetData()); QmitkDataStorageComboBox * cb_maskimage = dynamic_cast(m_Controls.m_MaskImageLayout->itemAt(1)->widget()); QmitkDataStorageComboBox * cb_rf = dynamic_cast(m_Controls.m_ClassifierLayout->itemAt(1)->widget()); mitk::Image::Pointer mask_image = dynamic_cast(cb_maskimage->GetSelectedNode()->GetData()); mitk::VigraRandomForestClassifier::Pointer classifier = dynamic_cast(cb_rf->GetSelectedNode()->GetData()); if(m_CalculateFeatures) { ProcessFeatureImages(raw_image,mask_image); m_CalculateFeatures = false; } Eigen::MatrixXd X_test; unsigned int count_test = 0; mitk::CLUtil::CountVoxel(mask_image, count_test); X_test = Eigen::MatrixXd(count_test, m_FeatureImageVector.size()); unsigned int index = 0; for( const auto & image : m_FeatureImageVector) { X_test.col(index) = mitk::CLUtil::Transform(image,mask_image); ++index; } Eigen::MatrixXi Y_test = classifier->Predict(X_test); mitk::Image::Pointer result_mask = mitk::CLUtil::Transform(Y_test,mask_image); auto node = AddAsDataNode(result_mask.GetPointer(),"Autom-ResultMask"); auto lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::PET_20); auto * lut_prop = dynamic_cast(node->GetProperty("LookupTable")); lut_prop->SetLookupTable(lut); mitk::LevelWindow lw(1,3); node->SetLevelWindow(lw); node->SetOpacity(0.3); std::map perlabelvoxelcount; mitk::CLUtil::CountVoxel(result_mask, perlabelvoxelcount); double voxel_volume = result_mask->GetGeometry()->GetSpacing().GetVnlVector().inf_norm(); QString newtext; newtext += "Name\tVolume\tUnit\n"; for(const auto & pair: perlabelvoxelcount) { newtext += "Label" + QString::number(pair.first) + "\t" + QString::number(pair.second*voxel_volume* 0.001) + "\tml\n"; } m_Controls.m_ResultTextEdit->setText(newtext); } std::vector ClassificationSegmentation::ManualSegmentationCallback() { QmitkDataStorageComboBox * cb_image = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(1)->widget()); QmitkDataStorageComboBox * cb_maskimage = dynamic_cast(m_Controls.m_MaskImageLayout->itemAt(1)->widget()); mitk::Image::Pointer raw_image = dynamic_cast(cb_image->GetSelectedNode()->GetData()); mitk::Image::Pointer mask_image = dynamic_cast(cb_maskimage->GetSelectedNode()->GetData()); if(raw_image.IsNull() || mask_image.IsNull()) { MITK_WARN << "Please provide input iamge and mask image"; //return nullptr; } if(m_CalculateFeatures) { ProcessFeatureImages(raw_image,mask_image); m_CalculateFeatures = false; } mitk::Image::Pointer sampled_image = nullptr; int label = 1; for( auto datanode : m_PointSetList) { mitk::PointSet::Pointer point_set = dynamic_cast(datanode->GetData()); mitk::Image::Pointer temp_img; SampleClassMaskByPointSet(raw_image, point_set,temp_img); mitk::CLUtil::InsertLabel(temp_img,sampled_image,label++); } m_TempClassifier = mitk::VigraRandomForestClassifier::New(); m_TempClassifier->SetTreeCount(50); m_TempClassifier->SetSamplesPerTree(0.66); Eigen::MatrixXd X_train; Eigen::MatrixXd X_test; unsigned int count_train = 0; unsigned int count_test = 0; mitk::CLUtil::CountVoxel(sampled_image, count_train); mitk::CLUtil::CountVoxel(mask_image, count_test); X_train = Eigen::MatrixXd(count_train, m_FeatureImageVector.size() ); X_test = Eigen::MatrixXd(count_test, m_FeatureImageVector.size()); unsigned int index = 0; for( const auto & image : m_FeatureImageVector) { X_train.col(index) = mitk::CLUtil::Transform(image,sampled_image); X_test.col(index) = mitk::CLUtil::Transform(image,mask_image); ++index; } Eigen::MatrixXi Y = mitk::CLUtil::Transform(sampled_image,sampled_image); m_TempClassifier->Train(X_train,Y); Eigen::MatrixXi Y_test = m_TempClassifier->Predict(X_test); Eigen::MatrixXd Yp_test = m_TempClassifier->GetPointWiseProbabilities(); // mitk::Image::Pointer result_mask = mitk::CLUtil::Transform(Y_test,mask_image); std::vector resultvector; resultvector.push_back( mitk::CLUtil::Transform(Y_test,mask_image) ); resultvector.push_back( mitk::CLUtil::Transform(Yp_test.col(0),mask_image) ); resultvector.push_back( mitk::CLUtil::Transform(Yp_test.col(1),mask_image) ); resultvector.push_back( mitk::CLUtil::Transform(Yp_test.col(2),mask_image) ); return resultvector; } void ClassificationSegmentation::ManualSegmentationFinished() { // Receive Future result m_ResultImageVector = m_ManualSegmentationFutureWatcher.result(); // Add result to Datastorage mitk::DataNode::Pointer node = AddAsDataNode(m_ResultImageVector[0].GetPointer(),"Manual-ResultMask"); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::PET_20); mitk::LookupTableProperty * lut_prop = dynamic_cast(node->GetProperty("LookupTable")); lut_prop->SetLookupTable(lut); mitk::LevelWindow lw(1,3); node->SetLevelWindow(lw); node->SetOpacity(0.3); m_BlockManualSegmentation = false; this->PostProcessingTrigger(); // Update Volume data std::map perlabelvoxelcount; mitk::CLUtil::CountVoxel(m_ResultImageVector[0], perlabelvoxelcount); double voxel_volume = m_ResultImageVector[0]->GetGeometry()->GetSpacing().GetVnlVector().inf_norm(); QString newtext; newtext += "Name\tVolume\tUnit\n"; for(const auto & pair: perlabelvoxelcount) newtext += "Label" + QString::number(pair.first) + "\t" + QString::number(pair.second*voxel_volume* 0.001) + "\tml\n"; m_Controls.m_ResultTextEdit->setText(newtext); } void ClassificationSegmentation::ManualSegmentationTrigger() { unsigned int samplecounter = 0; for( auto datanode : m_PointSetList) { mitk::PointSet::Pointer point_set = dynamic_cast(datanode->GetData()); samplecounter += point_set->GetSize(); } if(samplecounter < 10) return; if(!m_BlockManualSegmentation){ // Start GUI Thread m_ManualSegmentationFutureWatcher.setFuture( QtConcurrent::run(this, &ClassificationSegmentation::ManualSegmentationCallback)); // on finish call OnManualSegmentationFinished(); m_BlockManualSegmentation = true; } } void ClassificationSegmentation::SampleClassMaskByPointSet(const mitk::Image::Pointer & ref_img, mitk::PointSet::Pointer & pointset, mitk::Image::Pointer & outimage) { outimage = ref_img->Clone(); itk::Image::Pointer itk_out; mitk::CastToItkImage(outimage,itk_out); itk_out->FillBuffer(0); typedef itk::ImageRegionIteratorWithIndex > IteratorType; IteratorType oit(itk_out, itk_out->GetLargestPossibleRegion()); for(int i = 0 ; i < pointset->GetSize(); ++i) { IteratorType::IndexType index; ref_img->GetGeometry()->WorldToIndex(pointset->GetPoint(i), index); oit.SetIndex(index); oit.Set(1); } mitk::CastToMitkImage(itk_out,outimage); } // old version //void ClassificationSegmentation::DoImageProcessing() //{ // QmitkDataStorageComboBox * cb_image = dynamic_cast(m_Controls.m_InputImageLayout->itemAt(1)->widget()); // QmitkDataStorageComboBox * cb_maskimage = dynamic_cast(m_Controls.m_MaskImageLayout->itemAt(1)->widget()); // QmitkDataStorageComboBox * cb_classifier = dynamic_cast(m_Controls.m_RandomForestLayout->itemAt(1)->widget()); // if (cb_image == nullptr || cb_classifier == nullptr || cb_maskimage == nullptr) // { // QMessageBox::information( nullptr, "Template", "Please load and select an image before starting image processing."); // return; // } // mitk::VigraRandomForestClassifier::Pointer rf = dynamic_cast(cb_classifier->GetSelectedNode()->GetData()); // mitk::Image::Pointer image = dynamic_cast(cb_image->GetSelectedNode()->GetData()); // mitk::Image::Pointer mask = dynamic_cast(cb_maskimage->GetSelectedNode()->GetData()); // unsigned int n_samples = 0; // mitk::CLUtil::CountVoxel(mask, n_samples); // unsigned int n_features = 13; // InputImage, PROBA_CSF, PROBA_LES, PROBA_BRA, FOS_MEAN,FOS_VARIANCE,FOS_SKEWNESS, FOS_KURTOSIS, FOS_MIN, FOS_MAX, HE_1, HE_2, HE_3 // Eigen::MatrixXd feature_matrix = Eigen::MatrixXd(n_samples, n_features); // MITK_INFO << "Remove voxels outside the mask"; // // mitk::CLUtil::LogicalAndImages(image,brain_mask,image); // feature_matrix.block(0,0,n_samples,1) = mitk::CLUtil::Transform(image,mask); // AddImageAsDataNode(image, "UI_InputImage"); // mitk::Image::Pointer csf_prob = mitk::Image::New(); // mitk::CLUtil::ProbabilityMap(image,13.9, 8.3,csf_prob); // feature_matrix.block(0,1,n_samples,1) = mitk::CLUtil::Transform(csf_prob,mask); // AddImageAsDataNode(csf_prob, "UI_CSFProb"); // mitk::Image::Pointer les_prob = mitk::Image::New(); // mitk::CLUtil::ProbabilityMap(image,59, 11.6,les_prob); // feature_matrix.block(0,2,n_samples,1) = mitk::CLUtil::Transform(les_prob,mask); // AddImageAsDataNode(les_prob, "UI_LESProb"); // mitk::Image::Pointer brain_prob = mitk::Image::New(); // mitk::CLUtil::ProbabilityMap(image,32, 5.6,brain_prob); // feature_matrix.block(0,3,n_samples,1) = mitk::CLUtil::Transform(brain_prob,mask); // AddImageAsDataNode(brain_prob, "UI_BRAProb"); // int n = 0; // std::vector res; // DoFirstOrderFeatureCalculation(image, mask, 1, res); // for( const auto & img: res) // { // feature_matrix.block(0,3+(++n),n_samples,1) = mitk::CLUtil::Transform(img,mask); // std::string name; // img->GetPropertyList()->GetStringProperty("name", name); // AddImageAsDataNode(img.GetPointer(), name ); // } // res.clear(); // DoHessianEigenvaluesFeatureCalculation(image, mask, 3.0, res); // for( const auto & img: res) // { // feature_matrix.block(0,3+(++n),n_samples,1) = mitk::CLUtil::Transform(img,mask); // std::string name; // img->GetPropertyList()->GetStringProperty("name", name); // AddImageAsDataNode(img.GetPointer(), name ); // } // MITK_INFO << "Start Prediction"; // rf->Predict(feature_matrix); // MITK_INFO << "End Prediction"; // mitk::Image::Pointer result_image = mitk::CLUtil::Transform(rf->GetLabels(),mask); // mitk::Image::Pointer proba1_image = mitk::CLUtil::Transform(rf->GetPointWiseProbabilities().col(0),mask); // mitk::Image::Pointer proba2_image = mitk::CLUtil::Transform(rf->GetPointWiseProbabilities().col(1),mask); // mitk::Image::Pointer proba3_image = mitk::CLUtil::Transform(rf->GetPointWiseProbabilities().col(2),mask); // AddImageAsDataNode(result_image, "ClassificationResult"); // AddImageAsDataNode(proba1_image, "CSF_Proba_CL"); // AddImageAsDataNode(proba2_image, "LES_Proba_CL"); // AddImageAsDataNode(proba3_image, "BRA_Proba_CL"); // PostProcessing(rf,mask); //} mitk::DataNode::Pointer ClassificationSegmentation::AddAsDataNode(const mitk::BaseData::Pointer & data_, const std::string & name ) { mitk::DataNode::Pointer node = nullptr; node = this->GetDataStorage()->GetNamedNode(name); if(node.IsNull()) { node = mitk::DataNode::New(); node->SetData(data_); node->SetName(name); this->GetDataStorage()->Add(node); }else{ if(dynamic_cast(node->GetData()) && dynamic_cast(data_.GetPointer())) { mitk::Image::Pointer target_image = dynamic_cast(node->GetData()); mitk::Image::Pointer source_image = dynamic_cast(data_.GetPointer()); mitk::ImageReadAccessor ra(source_image); target_image->SetImportVolume(const_cast(ra.GetData())); this->RequestRenderWindowUpdate(); } if(dynamic_cast(node->GetData()) && dynamic_cast(data_.GetPointer())) { node->SetData(data_); node->Modified(); } } return node; } void ClassificationSegmentation::PostProcessingTrigger() { if(m_ResultImageVector.empty() || m_ResultImageVector.size() < 4) { MITK_ERROR("PostProcessingCallback") << "Result image vector not initialized!"; return; } if(!m_BlockPostProcessing){ m_PostProcessingFutureWatcher.setFuture( QtConcurrent::run(this, &ClassificationSegmentation::PostProcessingCallback)); // on finish call OnManualSegmentationFinished(); m_BlockPostProcessing = true; } } void ClassificationSegmentation::PostProcessingFinished() { // Receive Future result m_PostProcessingImageVector = m_PostProcessingFutureWatcher.result(); // Add result to Datastorage mitk::DataNode::Pointer node = AddAsDataNode(m_PostProcessingImageVector[0].GetPointer(),"Manual-ResultMask-PostProcessing"); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::PET_20); mitk::LookupTableProperty * lut_prop = dynamic_cast(node->GetProperty("LookupTable")); lut_prop->SetLookupTable(lut); mitk::LevelWindow lw(1,3); node->SetLevelWindow(lw); node->SetOpacity(0.3); m_BlockPostProcessing = false; } std::vector ClassificationSegmentation::PostProcessingCallback() { std::vector resultvector; mitk::Image::Pointer CSF_PMap = m_ResultImageVector[1]->Clone(); mitk::Image::Pointer LES_PMap = m_ResultImageVector[2]->Clone(); mitk::Image::Pointer BRA_PMap = m_ResultImageVector[3]->Clone(); mitk::Image::Pointer mask = m_ResultImageVector[0]->Clone(); MITK_INFO("PostProcessingCallback") << "ProbabilityMap merg strat ..."; { mitk::Image::Pointer resultmask = mask->Clone(); mitk::CLUtil::GaussianFilter(CSF_PMap, CSF_PMap, m_GaussCSFSlider->value()); mitk::CLUtil::GaussianFilter(LES_PMap, LES_PMap, m_GaussLESSlider->value()); mitk::CLUtil::GaussianFilter(BRA_PMap, BRA_PMap, m_GaussBRASlider->value()); itk::Image::Pointer itk_csf, itk_les, itk_bra; itk::Image::Pointer itk_result; mitk::CastToItkImage(CSF_PMap, itk_csf); mitk::CastToItkImage(LES_PMap, itk_les); mitk::CastToItkImage(BRA_PMap, itk_bra); mitk::CastToItkImage(resultmask, itk_result); itk::ImageRegionIterator > it_csf(itk_csf,itk_csf->GetLargestPossibleRegion()); itk::ImageRegionIterator > it_les(itk_les,itk_les->GetLargestPossibleRegion()); itk::ImageRegionIterator > it_bra(itk_bra,itk_bra->GetLargestPossibleRegion()); itk::ImageRegionIterator > it_res(itk_result,itk_result->GetLargestPossibleRegion()); while (!it_csf.IsAtEnd()) { double csf = it_csf.Value() * m_WeightCSFSlider->value(); double les = it_les.Value() * m_WeightLESSlider->value(); double bra = it_bra.Value() * m_WeightBRASlider->value(); if(csf > les && csf > bra) it_res.Set(1); if(les > csf && les > bra) it_res.Set(2); if(bra > les && bra > csf) it_res.Set(3); ++it_csf; ++it_les; ++it_bra; ++it_res; } mitk::CastToMitkImage(itk_result, resultmask); { std::map mergeinstruction ; mergeinstruction[1] = 1; mergeinstruction[2] = 1; mergeinstruction[3] = 1; mergeinstruction[4] = 1; mergeinstruction[5] = 1; mergeinstruction[6] = 1; // {{1,1},{2,1},{3,1},{4,1},{5,1},{6,1}}; mitk::Image::Pointer brain_mask = mask->Clone(); mitk::CLUtil::MergeLabels(brain_mask, mergeinstruction); mitk::CLUtil::ClosingBinary(brain_mask,brain_mask,2,mitk::CLUtil::All); mitk::CLUtil::LogicalAndImages(resultmask, brain_mask, resultmask); } MITK_INFO("PostProcessingCallback") << "ProbabilityMap merg end!"; resultvector.push_back(resultmask); } return resultvector; // { // MITK_INFO << "Morphological processing strat ..."; // mitk::Image::Pointer resultmask = mask->Clone(); // mitk::Image::Pointer csf_mask; // mitk::CLUtil::GrabLabel(resultmask, csf_mask, 1); // mitk::CLUtil::ClosingBinary(csf_mask,csf_mask,1,mitk::CLUtil::Axial); // mitk::CLUtil::ErodeBinary(csf_mask, csf_mask, 2, mitk::CLUtil::Axial); // mitk::CLUtil::DilateBinary(csf_mask, csf_mask, 1, mitk::CLUtil::Axial); // std::map merge_instruction = {{0,0},{1,3},{2,2},{3,3}}; // mitk::CLUtil::MergeLabels(resultmask, merge_instruction); // mitk::CLUtil::InsertLabel(resultmask, csf_mask, 1/*as csf mask*/); // add morpological manipulated csf_mask // // ------------ // mitk::Image::Pointer les_mask; // mitk::CLUtil::GrabLabel(resultmask, les_mask, 2); // mitk::CLUtil::ClosingBinary(les_mask,les_mask,1,mitk::CLUtil::Axial); // mitk::Image::Pointer les_cc_mask; unsigned int num = 0; // mitk::CLUtil::ConnectedComponentsImage(les_mask, mask, les_cc_mask, num); // std::map map; // mitk::CLUtil::CountVoxel(les_cc_mask,map); // unsigned int counter = 0; // while(map.size() > 2) // { // mitk::CLUtil::ErodeBinary(les_mask, les_mask, 1, mitk::CLUtil::Axial); // mitk::CLUtil::LogicalAndImages(les_cc_mask,les_mask,les_cc_mask); // map.clear(); // mitk::CLUtil::CountVoxel(les_cc_mask,map); // MITK_INFO("PostProcessing") << map.size(); // counter++; // } // while(counter != 0) // { // mitk::CLUtil::DilateBinary(les_mask, les_mask, 1, mitk::CLUtil::Axial); // counter--; // } // merge_instruction = {{0,0},{1,1},{2,3},{3,3}}; // mitk::CLUtil::MergeLabels(resultmask, merge_instruction); // mitk::CLUtil::InsertLabel(resultmask, les_mask, 2/*as les mask*/); // MITK_INFO << "Morphological processing end"; // // ------------ // mitk::CLUtil::LogicalAndImages(resultmask,mask,resultmask); // AddImageAsDataNode(resultmask,"SmoothedMaskMorphed"); // } } // mitk::Image::Pointer r= mitk::Image::New(),g= mitk::Image::New(),b = mitk::Image::New(); // unsigned int* m_ImageDimensions; // m_ImageDimensions = new unsigned int[3]; // m_ImageDimensions[0] = raw_image->GetDimensions()[0]; // m_ImageDimensions[1] = raw_image->GetDimensions()[1]; // m_ImageDimensions[2] = 1; // r->Initialize( mitk::MakePixelType() ,3,m_ImageDimensions); // g->Initialize( mitk::MakePixelType() ,3,m_ImageDimensions); // b->Initialize( mitk::MakePixelType() ,3,m_ImageDimensions); // mitk::ImageReadAccessor inputAccForRGB(raw_image, raw_image->GetSliceData(0,0,0)); // unsigned char* rbgSlice = (unsigned char*)inputAccForRGB.GetData(); // mitk::ImageReadAccessor inputAcc(r, r->GetSliceData(0,0,0)); // unsigned char* rData = (unsigned char*)inputAcc.GetData(); // mitk::ImageReadAccessor inputAcc2(g, g->GetSliceData(0,0,0)); // unsigned char* gData = (unsigned char*)inputAcc2.GetData(); // mitk::ImageReadAccessor inputAcc3(b, b->GetSliceData(0,0,0)); // unsigned char* // bData = (unsigned char*)inputAcc3.GetData(); // for(int i = 0; i < m_ImageDimensions[0] * m_ImageDimensions[1]*3; i+=3) // { // rData[i/3] = rbgSlice[i]; // gData[i/3] = rbgSlice[i+1]; // bData[i/3] = rbgSlice[i+2]; // } // m_FeatureImageVector.push_back(r); // m_FeatureImageVector.push_back(g); // m_FeatureImageVector.push_back(b); // AddImageAsDataNode(r,"r"); // AddImageAsDataNode(g,"g"); // AddImageAsDataNode(b,"b"); diff --git a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.h b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.h index 3fce603c7d..f13eb8f9ef 100644 --- a/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.h +++ b/Plugins/org.mitk.gui.qt.classificationsegmentation/src/internal/ClassificationSegmentation.h @@ -1,141 +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. ============================================================================*/ #ifndef ClassificationSegmentation_h #define ClassificationSegmentation_h #include #include #include "ui_ClassificationSegmentationControls.h" #include #include #include #include #include #include #include #include #include #include #include -#include "QmitkPointListViewWidget.h" #include #include /** \brief ClassificationSegmentation \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ - -//class QmitkPointListWidget; class ctkSliderWidget; class ClassificationSegmentation : 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; - QmitkPointListViewWidget * m_PointListWidget; std::vector m_PointSetList; bool m_CalculateFeatures; std::vector m_FeatureImageVector; std::vector m_ResultImageVector; std::vector m_PostProcessingImageVector; bool m_BlockManualSegmentation; QFutureWatcher> m_ManualSegmentationFutureWatcher; bool m_BlockPostProcessing; QFutureWatcher> m_PostProcessingFutureWatcher; protected slots: /// \brief Called when the user clicks the GUI button void DoAutomSegmentation(); void DoSavePointsAsMask(); void OnButtonCSFToggle(bool); void OnButtonLESToggle(bool); void OnButtonBRAToggle(bool); void OnButtonNoInteractionToggle(bool); void OnAddForestToDataManager(); void ManualSegmentationTrigger(); std::vector ManualSegmentationCallback(); void ManualSegmentationFinished(); void PostProcessingTrigger(); std::vector PostProcessingCallback(); void PostProcessingFinished(); void OnFeatureSettingsChanged(); void OnPostProcessingSettingsChanged(); void OnInitializeSession(const mitk::DataNode*); protected: typedef float MeasurementType; typedef itk::Statistics::Histogram< MeasurementType, itk::Statistics::DenseFrequencyContainer2 > HistogramType; // void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart); // void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart); void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; mitk::DataNode::Pointer AddAsDataNode(const mitk::BaseData::Pointer & data_, const std::string & name ); void SampleClassMaskByPointSet(const mitk::Image::Pointer & ref_img, mitk::PointSet::Pointer & pointset, mitk::Image::Pointer & outimage); void ProcessFeatureImages(const mitk::Image::Pointer & raw_image, const mitk::Image::Pointer & mask_image); /// \brief called by QmitkFunctionality when DataManager's selection has changed void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes ) override; mitk::Image::Pointer CreateClassMaskByPointsetList(std::map a_args); Ui::ClassificationSegmentationControls m_Controls; // Feature settings ctkSliderWidget * m_GaussSlider; ctkSliderWidget * m_HessianSlider; ctkSliderWidget * m_STInnerSlider; ctkSliderWidget * m_STOuterSlider; ctkSliderWidget * m_GaussCSFSlider; ctkSliderWidget * m_GaussLESSlider; ctkSliderWidget * m_GaussBRASlider; ctkSliderWidget * m_WeightCSFSlider; ctkSliderWidget * m_WeightLESSlider; ctkSliderWidget * m_WeightBRASlider; mitk::PointSetDataInteractor::Pointer m_PointSetDataInteractor; mitk::VigraRandomForestClassifier::Pointer m_TempClassifier; }; #endif // ClassificationSegmentation_h diff --git a/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.cpp b/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.cpp index b9d848b5b6..6af9236c5b 100644 --- a/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.cpp +++ b/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.cpp @@ -1,271 +1,235 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkDataManagerView.h" // mitk gui qt datamanager #include "internal/QmitkDataManagerItemDelegate.h" #include "internal/QmitkNodeTableViewKeyFilter.h" // mitk core #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qt widgets module #include #include #include #include #include -// beery plugins -#include -#include -#include -#include -#include -#include -#include -#include -#include - // mitk core services plugin #include #include // mitk gui common plugin #include -#include // mitk gui qt application plugin #include #include // mitk gui qt common plugin #include // qt #include #include -#include #include -#include const QString QmitkDataManagerView::VIEW_ID = "org.mitk.views.datamanager"; QmitkDataManagerView::QmitkDataManagerView() : m_ItemDelegate(nullptr) { } QmitkDataManagerView::~QmitkDataManagerView() { // nothing here } void QmitkDataManagerView::CreateQtPartControl(QWidget* parent) { m_CurrentRowCount = 0; m_Parent = parent; berry::IBerryPreferences::Pointer prefs = this->GetPreferences().Cast(); assert(prefs); //# GUI m_NodeTreeModel = new QmitkDataStorageTreeModel(GetDataStorage(), prefs->GetBool("Place new nodes on top", true)); m_NodeTreeModel->setParent(parent); m_NodeTreeModel->SetAllowHierarchyChange(prefs->GetBool("Allow changing of parent node", false)); m_SurfaceDecimation = prefs->GetBool("Use surface decimation", false); // Prepare filters m_HelperObjectFilterPredicate = mitk::NodePredicateOr::New( mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true)), mitk::NodePredicateProperty::New("hidden object", mitk::BoolProperty::New(true))); m_NodeWithNoDataFilterPredicate = mitk::NodePredicateData::New(nullptr); m_FilterModel = new QmitkDataStorageFilterProxyModel(); m_FilterModel->setSourceModel(m_NodeTreeModel); m_FilterModel->AddFilterPredicate(m_HelperObjectFilterPredicate); m_FilterModel->AddFilterPredicate(m_NodeWithNoDataFilterPredicate); m_NodeTreeView = new QTreeView; m_NodeTreeView->setHeaderHidden(true); m_NodeTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_NodeTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_NodeTreeView->setAlternatingRowColors(true); m_NodeTreeView->setDragEnabled(true); m_NodeTreeView->setDropIndicatorShown(true); m_NodeTreeView->setAcceptDrops(true); m_NodeTreeView->setContextMenuPolicy(Qt::CustomContextMenu); m_NodeTreeView->setModel(m_FilterModel); m_NodeTreeView->setTextElideMode(Qt::ElideMiddle); m_NodeTreeView->installEventFilter(new QmitkNodeTableViewKeyFilter(this, GetDataStorage())); m_ItemDelegate = new QmitkDataManagerItemDelegate(m_NodeTreeView); m_NodeTreeView->setItemDelegate(m_ItemDelegate); connect(m_NodeTreeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(NodeTreeViewRowsInserted(const QModelIndex&, int, int))); connect(m_NodeTreeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(NodeTreeViewRowsRemoved(const QModelIndex&, int, int))); connect(m_NodeTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(NodeSelectionChanged(const QItemSelection &, const QItemSelection &))); connect(m_NodeTreeModel, &QmitkDataStorageTreeModel::nodeVisibilityChanged, this, &QmitkDataManagerView::OnNodeVisibilityChanged); // data node context menu and menu actions m_DataNodeContextMenu = new QmitkDataNodeContextMenu(GetSite(), m_NodeTreeView); m_DataNodeContextMenu->SetDataStorage(GetDataStorage()); m_DataNodeContextMenu->SetSurfaceDecimation(m_SurfaceDecimation); connect(m_NodeTreeView, SIGNAL(customContextMenuRequested(const QPoint&)), m_DataNodeContextMenu, SLOT(OnContextMenuRequested(const QPoint&))); - berry::IEditorRegistry* editorRegistry = berry::PlatformUI::GetWorkbench()->GetEditorRegistry(); - QList editors = editorRegistry->GetEditors("*.mitk"); - if (editors.size() > 1) - { - m_ShowInMapper = new QSignalMapper(this); - foreach(berry::IEditorDescriptor::Pointer descriptor, editors) - { - QAction* action = new QAction(descriptor->GetLabel(), this); - m_ShowInActions << action; - m_ShowInMapper->connect(action, SIGNAL(triggered()), m_ShowInMapper, SLOT(map())); - m_ShowInMapper->setMapping(action, descriptor->GetId()); - } - connect(m_ShowInMapper, SIGNAL(mapped(QString)), this, SLOT(ShowIn(QString))); - } - QGridLayout* dndFrameWidgetLayout = new QGridLayout; dndFrameWidgetLayout->addWidget(m_NodeTreeView, 0, 0); dndFrameWidgetLayout->setContentsMargins(0, 0, 0, 0); m_DnDFrameWidget = new QmitkDnDFrameWidget(m_Parent); m_DnDFrameWidget->setLayout(dndFrameWidgetLayout); QVBoxLayout* layout = new QVBoxLayout(parent); layout->addWidget(m_DnDFrameWidget); layout->setContentsMargins(0, 0, 0, 0); m_Parent->setLayout(layout); } void QmitkDataManagerView::SetFocus() { } ////////////////////////////////////////////////////////////////////////// // Node tree modification ////////////////////////////////////////////////////////////////////////// void QmitkDataManagerView::NodeTreeViewRowsInserted(const QModelIndex& parent, int /*start*/, int /*end*/) { QModelIndex viewIndex = m_FilterModel->mapFromSource(parent); m_NodeTreeView->setExpanded(viewIndex, true); // a new row was inserted if (m_CurrentRowCount == 0 && m_NodeTreeModel->rowCount() == 1) { mitk::WorkbenchUtil::OpenRenderWindowPart(GetSite()->GetPage()); m_CurrentRowCount = m_NodeTreeModel->rowCount(); } } void QmitkDataManagerView::NodeTreeViewRowsRemoved(const QModelIndex& /*parent*/, int /*start*/, int /*end*/) { m_CurrentRowCount = m_NodeTreeModel->rowCount(); } void QmitkDataManagerView::NodeSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { auto selectedNodes = GetCurrentSelection(); auto nodeSet = m_NodeTreeModel->GetNodeSet(); for (auto node : qAsConst(nodeSet)) { if (node.IsNotNull()) { node->SetSelected(selectedNodes.contains(node)); } } m_DataNodeContextMenu->SetSelectedNodes(selectedNodes); } void QmitkDataManagerView::OnNodeVisibilityChanged() { ToggleVisibilityAction::Run(GetSite(), GetDataStorage(), QList()); } -void QmitkDataManagerView::ShowIn(const QString& editorId) -{ - berry::IWorkbenchPage::Pointer page = GetSite()->GetPage(); - berry::IEditorInput::Pointer input(new mitk::DataStorageEditorInput(GetDataStorageReference())); - page->OpenEditor(input, editorId, false, berry::IWorkbenchPage::MATCH_ID); -} - void QmitkDataManagerView::NodeChanged(const mitk::DataNode* /*node*/) { // m_FilterModel->invalidate(); // fix as proposed by R. Khlebnikov in the mitk-users mail from 02.09.2014 QMetaObject::invokeMethod(m_FilterModel, "invalidate", Qt::QueuedConnection); } void QmitkDataManagerView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_NodeTreeModel->GetPlaceNewNodesOnTopFlag() != prefs->GetBool("Place new nodes on top", true)) { m_NodeTreeModel->SetPlaceNewNodesOnTop(!m_NodeTreeModel->GetPlaceNewNodesOnTopFlag()); } bool hideHelperObjects = !prefs->GetBool("Show helper objects", false); if (m_FilterModel->HasFilterPredicate(m_HelperObjectFilterPredicate) != hideHelperObjects) { if (hideHelperObjects) { m_FilterModel->AddFilterPredicate(m_HelperObjectFilterPredicate); } else { m_FilterModel->RemoveFilterPredicate(m_HelperObjectFilterPredicate); } } bool hideNodesWithNoData = !prefs->GetBool("Show nodes containing no data", false); if (m_FilterModel->HasFilterPredicate(m_NodeWithNoDataFilterPredicate) != hideNodesWithNoData) { if (hideNodesWithNoData) { m_FilterModel->AddFilterPredicate(m_NodeWithNoDataFilterPredicate); } else { m_FilterModel->RemoveFilterPredicate(m_NodeWithNoDataFilterPredicate); } } m_NodeTreeView->expandAll(); m_SurfaceDecimation = prefs->GetBool("Use surface decimation", false); m_DataNodeContextMenu->SetSurfaceDecimation(m_SurfaceDecimation); m_NodeTreeModel->SetAllowHierarchyChange(prefs->GetBool("Allow changing of parent node", false)); GlobalReinitAction::Run(GetSite(), GetDataStorage()); } QItemSelectionModel* QmitkDataManagerView::GetDataNodeSelectionModel() const { return m_NodeTreeView->selectionModel(); } diff --git a/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.h b/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.h index cf6e3fd0d1..d788e63c56 100644 --- a/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.h +++ b/Plugins/org.mitk.gui.qt.datamanager/src/QmitkDataManagerView.h @@ -1,138 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKDATAMANAGERVIEW_H #define QMITKDATAMANAGERVIEW_H #include // mitk core #include // berry plugin #include // mitk gui qt common plugin #include // mitk gui qt application #include // qt #include // forward declarations -class QAction; class QModelIndex; class QTreeView; -class QSignalMapper; class QmitkDnDFrameWidget; class QmitkDataStorageTreeModel; class QmitkDataManagerItemDelegate; class QmitkDataStorageFilterProxyModel; /** * @brief A view that shows all data nodes of the data storage in a qt tree view. * */ class MITK_QT_DATAMANAGER QmitkDataManagerView : public QmitkAbstractView { Q_OBJECT public: static const QString VIEW_ID; // = "org.mitk.views.datamanager" QmitkDataManagerView(); ~QmitkDataManagerView() override; public Q_SLOTS: // invoked when the berry preferences were changed void OnPreferencesChanged(const berry::IBerryPreferences* prefs) override; ////////////////////////////////////////////////////////////////////////// // Slots for Qt node tree signals ////////////////////////////////////////////////////////////////////////// /// When rows are inserted auto expand them void NodeTreeViewRowsInserted(const QModelIndex& parent, int start, int end); /// will setup m_CurrentRowCount void NodeTreeViewRowsRemoved(const QModelIndex& parent, int start, int end); /// Whenever the selection changes set the "selected" property respectively void NodeSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void OnNodeVisibilityChanged(); - /// Opens the editor with the given id using the current data storage - void ShowIn(const QString& editorId); - protected: void CreateQtPartControl(QWidget* parent) override; void SetFocus() override; /// /// React to node changes. Overridden from QmitkAbstractView. /// void NodeChanged(const mitk::DataNode* node) override; protected: QWidget* m_Parent; QmitkDnDFrameWidget* m_DnDFrameWidget; /// /// \brief A plain widget as the base pane. /// QmitkDataStorageTreeModel* m_NodeTreeModel; QmitkDataStorageFilterProxyModel* m_FilterModel; mitk::NodePredicateBase::Pointer m_HelperObjectFilterPredicate; mitk::NodePredicateBase::Pointer m_NodeWithNoDataFilterPredicate; /// /// Holds the preferences for the data manager. /// berry::IBerryPreferences::Pointer m_DataManagerPreferencesNode; /// /// \brief The Table view to show the selected nodes. /// QTreeView* m_NodeTreeView; /// /// \brief The context menu that shows up when right clicking on a node. /// QmitkDataNodeContextMenu* m_DataNodeContextMenu; /// /// \brief flag indicating whether a surface created from a selected decimation is decimated with vtkQuadricDecimation or not /// bool m_SurfaceDecimation; - /// Maps "Show in" actions to editor ids - QSignalMapper* m_ShowInMapper; - - /// A list of "Show in" actions - QList m_ShowInActions; - /// saves the current amount of rows shown in the data manager size_t m_CurrentRowCount; QmitkDataManagerItemDelegate* m_ItemDelegate; private: QItemSelectionModel* GetDataNodeSelectionModel() const override; }; #endif // QMITKDATAMANAGERVIEW_H diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/org_mitk_gui_qt_fit_inspector_Activator.h b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/org_mitk_gui_qt_fit_inspector_Activator.h index a2e404a0a4..3b0a0fe8d2 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/org_mitk_gui_qt_fit_inspector_Activator.h +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/org_mitk_gui_qt_fit_inspector_Activator.h @@ -1,32 +1,32 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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_fit_inspector_Activator_h #define org_mitk_gui_qt_fit_inspector_Activator_h #include class org_mitk_gui_qt_fit_inspector_Activator : public QObject, public ctkPluginActivator { Q_OBJECT - Q_PLUGIN_METADATA(IID "org_mitk_guit_qt_fit_inspector") + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_fit_inspector") Q_INTERFACES(ctkPluginActivator) public: void start(ctkPluginContext* context) override; void stop(ctkPluginContext* context) override; }; // org_mitk_modelfit_visualization_Activator #endif // org_mitk_modelfit_visualization_Activator_h diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp index 4027f2b176..bc674b04d4 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp @@ -1,126 +1,104 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "org_mitk_gui_qt_flow_segmentation_Activator.h" // Blueberry #include #include #include //MITK -#include "mitkLabelSetImage.h" -#include "mitkNodePredicateAnd.h" -#include "mitkNodePredicateNot.h" -#include "mitkNodePredicateProperty.h" -#include "mitkNodePredicateDataType.h" -#include "mitkIOUtil.h" +#include +#include +#include +#include +#include +#include +#include // Qmitk #include "QmitkSegmentationFlowControlView.h" // Qt #include #include const std::string QmitkSegmentationFlowControlView::VIEW_ID = "org.mitk.views.flow.control"; QmitkSegmentationFlowControlView::QmitkSegmentationFlowControlView() : m_Parent(nullptr) { auto nodePredicate = mitk::NodePredicateAnd::New(); nodePredicate->AddPredicate(mitk::TNodePredicateDataType::New()); nodePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate = nodePredicate; - - m_OutputDir = QString::fromStdString(itksys::SystemTools::GetCurrentWorkingDirectory()); - m_FileExtension = "nrrd"; } void QmitkSegmentationFlowControlView::SetFocus() { m_Controls.btnStoreAndAccept->setFocus(); } void QmitkSegmentationFlowControlView::CreateQtPartControl(QWidget* parent) { - // create GUI widgets from the Qt Designer's .ui file - m_Controls.setupUi(parent); - - m_Parent = parent; - - connect(m_Controls.btnStoreAndAccept, SIGNAL(clicked()), this, SLOT(OnAcceptButtonPushed())); - - m_Controls.labelStored->setVisible(false); - UpdateControls(); - - auto arguments = QCoreApplication::arguments(); - - bool isFlagFound = false; - for (auto arg : arguments) - { - if (isFlagFound) - { - m_OutputDir = arg; - break; - } - isFlagFound = arg.startsWith("--flow.outputdir"); - } - isFlagFound = false; - for (auto arg : arguments) - { - if (isFlagFound) - { - m_FileExtension = arg; - break; - } - isFlagFound = arg.startsWith("--flow.outputextension"); - } - - m_OutputDir = QDir::fromNativeSeparators(m_OutputDir); + // create GUI widgets from the Qt Designer's .ui file + m_Controls.setupUi(parent); + + m_Parent = parent; + + connect(m_Controls.btnStoreAndAccept, SIGNAL(clicked()), this, SLOT(OnAcceptButtonPushed())); + + m_Controls.labelStored->setVisible(false); + UpdateControls(); + + m_OutputDir = QString::fromStdString(mitk::BaseApplication::instance().config().getString("flow.outputdir", itksys::SystemTools::GetCurrentWorkingDirectory())); + m_OutputDir = QDir::fromNativeSeparators(m_OutputDir); + + m_FileExtension = QString::fromStdString(mitk::BaseApplication::instance().config().getString("flow.outputextension", "nrrd")); } void QmitkSegmentationFlowControlView::OnAcceptButtonPushed() { auto nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (auto node : *nodes) { QString outputpath = m_OutputDir + "/" + QString::fromStdString(node->GetName()) + "." + m_FileExtension; outputpath = QDir::toNativeSeparators(QDir::cleanPath(outputpath)); mitk::IOUtil::Save(node->GetData(), outputpath.toStdString()); } m_Controls.labelStored->setVisible(true); } void QmitkSegmentationFlowControlView::UpdateControls() { auto nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); m_Controls.btnStoreAndAccept->setEnabled(!nodes->empty()); }; void QmitkSegmentationFlowControlView::NodeAdded(const mitk::DataNode* /*node*/) { UpdateControls(); }; void QmitkSegmentationFlowControlView::NodeChanged(const mitk::DataNode* /*node*/) { UpdateControls(); }; void QmitkSegmentationFlowControlView::NodeRemoved(const mitk::DataNode* /*node*/) { UpdateControls(); }; diff --git a/Plugins/org.mitk.gui.qt.flowapplication/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.flowapplication/manifest_headers.cmake index 0892740d80..d1227975c7 100644 --- a/Plugins/org.mitk.gui.qt.flowapplication/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.flowapplication/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "MITK Flow Application") set(Plugin-Version "1.0.0") set(Plugin-Vendor "DKFZ, Medical Image Computing") set(Plugin-ContactAddress "http://www.mitk.org") -set(Require-Plugin org.mitk.core.ext org.mitk.gui.qt.application) +set(Require-Plugin org.mitk.gui.qt.ext) diff --git a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationPlugin.cpp b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationPlugin.cpp index 11a5a20215..cd487ca748 100644 --- a/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationPlugin.cpp +++ b/Plugins/org.mitk.gui.qt.flowapplication/src/internal/QmitkFlowApplicationPlugin.cpp @@ -1,203 +1,199 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkFlowApplicationPlugin.h" #include "QmitkFlowApplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QmitkFlowApplicationPlugin* QmitkFlowApplicationPlugin::inst = nullptr; QmitkFlowApplicationPlugin::QmitkFlowApplicationPlugin() { inst = this; } QmitkFlowApplicationPlugin::~QmitkFlowApplicationPlugin() { } QmitkFlowApplicationPlugin* QmitkFlowApplicationPlugin::GetDefault() { return inst; } void QmitkFlowApplicationPlugin::start(ctkPluginContext* context) { berry::AbstractUICTKPlugin::start(context); this->_context = context; QtWidgetsExtRegisterClasses(); BERRY_REGISTER_EXTENSION_CLASS(QmitkFlowApplication, context); ctkServiceReference cmRef = context->getServiceReference(); ctkConfigurationAdmin* configAdmin = nullptr; if (cmRef) { configAdmin = context->getService(cmRef); } // Use the CTK Configuration Admin service to configure the BlueBerry help system if (configAdmin) { ctkConfigurationPtr conf = configAdmin->getConfiguration("org.blueberry.services.help", QString()); ctkDictionary helpProps; helpProps.insert("homePage", "qthelp://org.mitk.gui.qt.flowapplication/bundle/index.html"); conf->update(helpProps); context->ungetService(cmRef); } else { MITK_WARN << "Configuration Admin service unavailable, cannot set home page url."; } if (qApp->metaObject()->indexOfSignal("messageReceived(QByteArray)") > -1) { connect(qApp, SIGNAL(messageReceived(QByteArray)), this, SLOT(handleIPCMessage(QByteArray))); } // This is a potentially long running operation. loadDataFromDisk(berry::Platform::GetApplicationArgs(), true); } void QmitkFlowApplicationPlugin::stop(ctkPluginContext* context) { Q_UNUSED(context) this->_context = nullptr; } ctkPluginContext* QmitkFlowApplicationPlugin::GetPluginContext() const { return _context; } void QmitkFlowApplicationPlugin::loadDataFromDisk(const QStringList &arguments, bool globalReinit) { if (!arguments.empty()) { ctkServiceReference serviceRef = _context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService* dataStorageService = _context->getService(serviceRef); mitk::DataStorage::Pointer dataStorage = dataStorageService->GetDefaultDataStorage()->GetDataStorage(); int argumentsAdded = 0; for (int i = 0; i < arguments.size(); ++i) { - if (arguments[i].startsWith("--flow.")) - { //By convention no further files are specified as soon as a flow arguments comes. - break; - } - else if (arguments[i].right(5) == ".mitk") + if (arguments[i].right(5) == ".mitk") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadScene(arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else if (arguments[i].right(15) == ".mitksceneindex") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadSceneUnzipped(arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else { try { const std::string path(arguments[i].toStdString()); auto addedNodes = mitk::IOUtil::Load(path, *dataStorage); for (const auto& node : *addedNodes) { node->SetIntProperty("layer", argumentsAdded); } argumentsAdded++; } catch (...) { MITK_WARN << "Failed to load command line argument: " << arguments[i].toStdString(); } } } // end for each command line argument if (argumentsAdded > 0 && globalReinit) { // calculate bounding geometry mitk::RenderingManager::GetInstance()->InitializeViews(dataStorage->ComputeBoundingGeometry3D()); } } else { MITK_ERROR << "A service reference for mitk::IDataStorageService does not exist"; } } } void QmitkFlowApplicationPlugin::handleIPCMessage(const QByteArray& msg) { QDataStream ds(msg); QString msgType; ds >> msgType; // we only handle messages containing command line arguments if (msgType != "$cmdLineArgs") return; // activate the current workbench window berry::IWorkbenchWindow::Pointer window = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); mainWindow->setWindowState(mainWindow->windowState() & ~Qt::WindowMinimized); mainWindow->raise(); mainWindow->activateWindow(); QStringList args; ds >> args; loadDataFromDisk(args, false); } diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index aca4f168a7..9eab83e044 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,510 +1,513 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageCropperView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); - m_Controls.imageSelectionWidget->SetNodePredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + m_Controls.imageSelectionWidget->SetNodePredicate( + mitk::NodePredicateAnd::New(mitk::TNodePredicateDataType::New(), + mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); m_Controls.imageSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); m_Controls.boundingBoxSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; this->OnImageSelectionChanged(m_Controls.imageSelectionWidget->GetSelectedNodes()); this->OnBoundingBoxSelectionChanged(m_Controls.boundingBoxSelectionWidget->GetSelectedNodes()); } void QmitkImageCropperView::OnImageSelectionChanged(QList) { bool rotationEnabled = false; + m_Controls.labelWarningRotation->setVisible(false); + auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::IOPixelEnum::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } if (nullptr == imageNode->GetData()) { return; } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Shape"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); boundingBoxNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("handle size factor", mitk::DoubleProperty::New(1.0 / 40.0)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); return; } const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto image = dynamic_cast(imageNode->GetData()); auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { // Check if initial node name is already in box name std::string imagePrefix = ""; if (boundingBoxNode->GetName().find(imageNode->GetName()) != 0) { imagePrefix = imageNode->GetName() + "_"; } QString imageName; if (mask) { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) auto labelsetImageInput = dynamic_cast(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); croppedImageNode->SetLevelWindow(levelWindow); if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); imageNode->SetVisibility(mask); // Give the user a visual clue that something happened when image was cropped } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); imageNode->SetData(cutter->GetOutput()); imageNode->SetLevelWindow(levelWindow); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { - m_Controls.labelWarningRotation->setVisible(false); m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; } diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperViewControls.ui b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperViewControls.ui index d7e2d78d35..6afea7fa3c 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperViewControls.ui +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperViewControls.ui @@ -1,311 +1,311 @@ QmitkImageCropperViewControls 0 0 300 600 0 0 Data Selection 0 0 Image 0 0 0 40 0 0 Bounding Box 0 0 0 40 0 0 0 40 New 0 0 0 90 Processing 0 0 Mask 0 0 Crop true - Image geometry is rotated, result won't be pixel-aligned. You can reinit your image to get a pixel-aligned result, though. + The selected image has a rotated geometry, result won't be pixel-aligned Qt::PlainText true 0 0 0 25 Advanced settings false 100 Qt::ToolButtonTextBesideIcon false 0 0 0 120 Output image settings 0 0 Outside pixel value (masking): false 0 0 0 0 Overrride original image false 0 0 Only crop current timestep / cut off other timesteps Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
ctkExpandButton QToolButton
ctkExpandButton.h
diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.multilabelsegmentation/manifest_headers.cmake index 54eb90ad33..97ba4ee1c4 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "MITK MultiLabelSegmentation") set(Plugin-Version "1.0.0") set(Plugin-Vendor "German Cancer Research Center (DKFZ)") set(Plugin-ContactAddress "http://www.mitk.org") -set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.datamanager) +set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.datamanager org.mitk.gui.qt.segmentation) diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui index 1f2ad71121..2d8cf565c5 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui @@ -1,274 +1,274 @@ QmitkImageMaskingWidgetControls 0 0 179 296 2 4 0 Masks 3 4 0 0 Masking Mode Image Masking true Surface Masking Mask make output binary overwrite foreground overwrite background Background Value: Qt::Horizontal 40 20 50 0 50 16777215 0.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Foreground Value: Qt::Horizontal 40 20 50 0 50 16777215 1.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 Labels 4 0 0 0 50 Qt::Vertical 20 40 QmitkDataSelectionWidget QWidget
internal/Common/QmitkDataSelectionWidget.h
QmitkLabelSetWidget QWidget -
Qmitk/QmitkLabelSetWidget.h
+
QmitkLabelSetWidget.h
1
QmitkMaskStampWidget QWidget
QmitkMaskStampWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui index 976fffbdc3..0e8a3c92b5 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui @@ -1,427 +1,427 @@ QmitkMorphologicalOperationsWidgetControls 0 0 155 414 0 0 0 Masks 3 4 0 0 Structuring Element Ball true Cross Radius 1 20 1 Qt::Horizontal 1 20 false 0 0 Dilatation :/SegmentationUtilities/MorphologicalOperations/Dilate_48x48.png:/SegmentationUtilities/MorphologicalOperations/Dilate_48x48.png 32 32 Qt::ToolButtonTextUnderIcon false 0 0 Globally fills holes in segmentation (radius not required) Fill Holes :/SegmentationUtilities/MorphologicalOperations/FillHoles_48x48.png:/SegmentationUtilities/MorphologicalOperations/FillHoles_48x48.png 32 32 Qt::ToolButtonTextUnderIcon false 0 0 Erosion :/SegmentationUtilities/MorphologicalOperations/Erode_48x48.png:/SegmentationUtilities/MorphologicalOperations/Erode_48x48.png 32 32 Qt::ToolButtonTextUnderIcon false 0 0 Closing :/SegmentationUtilities/MorphologicalOperations/Closing_48x48.png:/SegmentationUtilities/MorphologicalOperations/Closing_48x48.png 32 32 Qt::ToolButtonTextUnderIcon false 0 0 Opening :/SegmentationUtilities/MorphologicalOperations/Opening_48x48.png:/SegmentationUtilities/MorphologicalOperations/Opening_48x48.png 32 32 Qt::ToolButtonTextUnderIcon Qt::Vertical 20 40 Labels 3 4 0 0 0 50 0 0 15 0 50 false 0 0 0 15 50 false Qt::Vertical 20 40 QmitkDataSelectionWidget QWidget
internal/Common/QmitkDataSelectionWidget.h
1
QmitkToolSelectionBox QWidget
QmitkToolSelectionBox.h
QmitkToolGUIArea QWidget
QmitkToolGUIArea.h
QmitkLabelSetWidget QWidget -
Qmitk/QmitkLabelSetWidget.h
+
QmitkLabelSetWidget.h
1
sliderMorphFactor valueChanged(int) spinBoxMorphFactor setValue(int) 240 27 766 36 spinBoxMorphFactor valueChanged(int) sliderMorphFactor setValue(int) 784 38 657 38
diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui index 511fcc381f..52b7fc86e4 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui @@ -1,252 +1,252 @@ QmitkSurfaceToImageWidgetControls 0 0 179 183 3 4 0 Masks 3 4 0 0 make output binary overwrite background Background Value: Qt::Horizontal 40 20 50 0 50 16777215 0.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Foreground Value: Qt::Horizontal 40 20 50 0 50 16777215 1.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Convert Qt::Vertical 20 40 Labels 2 2 0 0 0 50 0 20 Qt::Vertical 20 40 QmitkDataSelectionWidget QWidget
internal/Common/QmitkDataSelectionWidget.h
1
QmitkLabelSetWidget QWidget -
Qmitk/QmitkLabelSetWidget.h
+
QmitkLabelSetWidget.h
1
QmitkSurfaceStampWidget QWidget
QmitkSurfaceStampWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.preprocessing.resampling/src/internal/QmitkPreprocessingResamplingView.cpp b/Plugins/org.mitk.gui.qt.preprocessing.resampling/src/internal/QmitkPreprocessingResamplingView.cpp index 0befa8495d..bed002ff32 100644 --- a/Plugins/org.mitk.gui.qt.preprocessing.resampling/src/internal/QmitkPreprocessingResamplingView.cpp +++ b/Plugins/org.mitk.gui.qt.preprocessing.resampling/src/internal/QmitkPreprocessingResamplingView.cpp @@ -1,457 +1,456 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkPreprocessingResamplingView.h" // QT includes (GUI) #include #include #include #include #include #include #include // Berry includes (selection service) #include #include // MITK includes (GUI) -#include "QmitkStdMultiWidget.h" #include "QmitkDataNodeSelectionProvider.h" #include "mitkDataNodeObject.h" // MITK includes (general) #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateAnd.h" #include "mitkImageTimeSelector.h" #include "mitkVectorImageMapper2D.h" #include "mitkProperties.h" #include "mitkLevelWindowProperty.h" // Includes for image casting between ITK and MITK #include "mitkImageCast.h" #include "mitkITKImageImport.h" // ITK includes (general) #include #include // Resampling #include #include #include #include #include #include #include // STD #include // Convenient Definitions typedef itk::Image ImageType; typedef itk::Image SegmentationImageType; typedef itk::Image DoubleImageType; typedef itk::Image, 3> VectorImageType; typedef itk::ResampleImageFilter< ImageType, ImageType > ResampleImageFilterType; typedef itk::ResampleImageFilter< ImageType, ImageType > ResampleImageFilterType2; typedef itk::CastImageFilter< ImageType, DoubleImageType > ImagePTypeToFloatPTypeCasterType; typedef itk::LinearInterpolateImageFunction< ImageType, double > LinearInterpolatorType; typedef itk::NearestNeighborInterpolateImageFunction< ImageType, double > NearestInterpolatorType; typedef itk::BSplineInterpolateImageFunction BSplineInterpolatorType; QmitkPreprocessingResampling::QmitkPreprocessingResampling() : QmitkAbstractView(), m_Controls(nullptr), m_SelectedImageNode(nullptr), m_TimeStepperAdapter(nullptr) { } QmitkPreprocessingResampling::~QmitkPreprocessingResampling() { } void QmitkPreprocessingResampling::CreateQtPartControl(QWidget *parent) { if (m_Controls == nullptr) { m_Controls = new Ui::QmitkPreprocessingResamplingViewControls; m_Controls->setupUi(parent); this->CreateConnections(); mitk::NodePredicateDimension::Pointer dimensionPredicate = mitk::NodePredicateDimension::New(3); mitk::NodePredicateDataType::Pointer imagePredicate = mitk::NodePredicateDataType::New("Image"); } m_SelectedImageNode = mitk::DataStorageSelection::New(this->GetDataStorage(), false); // Setup Controls this->m_Controls->cbParam4->clear(); this->m_Controls->cbParam4->insertItem(LINEAR, "Linear"); this->m_Controls->cbParam4->insertItem(NEAREST, "Nearest neighbor"); this->m_Controls->cbParam4->insertItem(SPLINE, "B-Spline"); } void QmitkPreprocessingResampling::CreateConnections() { if ( m_Controls ) { connect((QObject*)(m_Controls->btnDoIt), SIGNAL(clicked()), (QObject*) this, SLOT(StartButtonClicked())); connect((QObject*)(m_Controls->buttonExecuteOnMultipleImages), SIGNAL(clicked()), (QObject*) this, SLOT(StartMultipleImagesButtonClicked())); connect( (QObject*)(m_Controls->cbParam4), SIGNAL( activated(int) ), this, SLOT( SelectInterpolator(int) ) ); } } void QmitkPreprocessingResampling::InternalGetTimeNavigationController() { auto renwin_part = GetRenderWindowPart(); if( renwin_part != nullptr ) { auto tnc = renwin_part->GetTimeNavigationController(); if( tnc != nullptr ) { m_TimeStepperAdapter = new QmitkStepperAdapter((QObject*) m_Controls->sliceNavigatorTime, tnc->GetTime(), "sliceNavigatorTimeFromBIP"); } } } void QmitkPreprocessingResampling::SetFocus() { } //datamanager selection changed void QmitkPreprocessingResampling::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { ResetOneImageOpPanel(); //any nodes there? if (!nodes.empty()) { // reset GUI m_Controls->sliceNavigatorTime->setEnabled(false); m_Controls->leImage1->setText(tr("Select an Image in Data Manager")); m_SelectedNodes.clear(); for (mitk::DataNode* _DataNode : nodes) { m_SelectedImageNode->RemoveAllNodes(); *m_SelectedImageNode = _DataNode; mitk::Image::Pointer tempImage = dynamic_cast(m_SelectedImageNode->GetNode()->GetData()); //no image if (tempImage.IsNull() || (tempImage->IsInitialized() == false)) { if (m_SelectedNodes.size() < 1) { m_Controls->leImage1->setText(tr("Not an image.")); } continue; } //2D image if (tempImage->GetDimension() < 3) { if (m_SelectedNodes.size() < 1) { m_Controls->leImage1->setText(tr("2D images are not supported.")); } continue; } if (m_SelectedNodes.size() < 1) { m_Controls->leImage1->setText(QString(m_SelectedImageNode->GetNode()->GetName().c_str())); mitk::Vector3D aSpacing = tempImage->GetGeometry()->GetSpacing(); std::string text("x-spacing (" + std::to_string(aSpacing[0]) + ")"); m_Controls->tlParam1->setText(text.c_str()); text = "y-spacing (" + std::to_string(aSpacing[1]) + ")"; m_Controls->tlParam2->setText(text.c_str()); text = "z-spacing (" + std::to_string(aSpacing[2]) + ")"; m_Controls->tlParam3->setText(text.c_str()); if (tempImage->GetDimension() > 3) { // try to retrieve the TNC (for 4-D Processing ) this->InternalGetTimeNavigationController(); m_Controls->sliceNavigatorTime->setEnabled(true); m_Controls->tlTime->setEnabled(true); } } m_SelectedNodes.push_back(_DataNode); } if (m_SelectedNodes.size() > 0) { *m_SelectedImageNode = m_SelectedNodes[0]; } ResetParameterPanel(); } } void QmitkPreprocessingResampling::ResetOneImageOpPanel() { m_Controls->tlTime->setEnabled(false); m_Controls->btnDoIt->setEnabled(false); m_Controls->buttonExecuteOnMultipleImages->setEnabled(false); m_Controls->cbHideOrig->setEnabled(false); m_Controls->leImage1->setText(tr("Select an Image in Data Manager")); m_Controls->tlParam1->setText("x-spacing"); m_Controls->tlParam1->setText("y-spacing"); m_Controls->tlParam1->setText("z-spacing"); } void QmitkPreprocessingResampling::ResetParameterPanel() { m_Controls->btnDoIt->setEnabled(true); m_Controls->buttonExecuteOnMultipleImages->setEnabled(true); m_Controls->cbHideOrig->setEnabled(true); } void QmitkPreprocessingResampling::ResetTwoImageOpPanel() { } void QmitkPreprocessingResampling::StartMultipleImagesButtonClicked() { for (auto currentSelectedNode : m_SelectedNodes) { m_SelectedImageNode->RemoveAllNodes(); *m_SelectedImageNode = currentSelectedNode; StartButtonClicked(); } } void QmitkPreprocessingResampling::StartButtonClicked() { if(!m_SelectedImageNode->GetNode()) return; this->BusyCursorOn(); mitk::Image::Pointer newImage; try { newImage = dynamic_cast(m_SelectedImageNode->GetNode()->GetData()); } catch ( std::exception &e ) { QString exceptionString = tr("An error occured during image loading:\n"); exceptionString.append( e.what() ); QMessageBox::warning( nullptr, "Preprocessing - Resampling: ", exceptionString , QMessageBox::Ok, QMessageBox::NoButton ); this->BusyCursorOff(); return; } // check if input image is valid, casting does not throw exception when casting from 'NULL-Object' if ( (! newImage) || (newImage->IsInitialized() == false) ) { this->BusyCursorOff(); QMessageBox::warning( nullptr, "Preprocessing - Resampling", tr("Input image is broken or not initialized. Returning."), QMessageBox::Ok, QMessageBox::NoButton ); return; } // check if operation is done on 4D a image time step if(newImage->GetDimension() > 3) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(newImage); timeSelector->SetTimeNr( ((QmitkSliderNavigatorWidget*)m_Controls->sliceNavigatorTime)->GetPos() ); timeSelector->Update(); newImage = timeSelector->GetOutput(); } // check if image or vector image ImageType::Pointer itkImage = ImageType::New(); VectorImageType::Pointer itkVecImage = VectorImageType::New(); int isVectorImage = newImage->GetPixelType().GetNumberOfComponents(); if(isVectorImage > 1) { CastToItkImage( newImage, itkVecImage ); } else { CastToItkImage( newImage, itkImage ); } std::stringstream nameAddition(""); double dparam1 = m_Controls->dsbParam1->value(); double dparam2 = m_Controls->dsbParam2->value(); double dparam3 = m_Controls->dsbParam3->value(); try{ std::string selectedInterpolator; ResampleImageFilterType::Pointer resampler = ResampleImageFilterType::New(); switch (m_SelectedInterpolation) { case LINEAR: { LinearInterpolatorType::Pointer interpolator = LinearInterpolatorType::New(); resampler->SetInterpolator(interpolator); selectedInterpolator = "Linear"; break; } case NEAREST: { NearestInterpolatorType::Pointer interpolator = NearestInterpolatorType::New(); resampler->SetInterpolator(interpolator); selectedInterpolator = "Nearest"; break; } case SPLINE: { BSplineInterpolatorType::Pointer interpolator = BSplineInterpolatorType::New(); interpolator->SetSplineOrder(3); resampler->SetInterpolator(interpolator); selectedInterpolator = "B-Spline"; break; } default: { LinearInterpolatorType::Pointer interpolator = LinearInterpolatorType::New(); resampler->SetInterpolator(interpolator); selectedInterpolator = "Linear"; break; } } resampler->SetInput( itkImage ); resampler->SetOutputOrigin( itkImage->GetOrigin() ); ImageType::SizeType input_size = itkImage->GetLargestPossibleRegion().GetSize(); ImageType::SpacingType input_spacing = itkImage->GetSpacing(); ImageType::SizeType output_size; ImageType::SpacingType output_spacing; if (dparam1 > 0) { output_size[0] = std::ceil(input_size[0] * (input_spacing[0] / dparam1)); output_spacing[0] = dparam1; } else { output_size[0] = std::ceil(input_size[0] * (-1.0 / dparam1)); output_spacing[0] = -1.0*input_spacing[0] * dparam1; } if (dparam2 > 0) { output_size[1] = std::ceil(input_size[1] * (input_spacing[1] / dparam2)); output_spacing[1] = dparam2; } else { output_size[1] = std::ceil(input_size[1] * (-1.0 / dparam2)); output_spacing[1] = -1.0*input_spacing[1] * dparam2; } if (dparam3 > 0) { output_size[2] = std::ceil(input_size[2] * (input_spacing[2] / dparam3)); output_spacing[2] = dparam3; } else { output_size[2] = std::ceil(input_size[2] * (-1.0 / dparam3)); output_spacing[2] = -1.0*input_spacing[2] * dparam3; } resampler->SetSize( output_size ); resampler->SetOutputSpacing( output_spacing ); resampler->SetOutputDirection( itkImage->GetDirection() ); resampler->UpdateLargestPossibleRegion(); ImageType::Pointer resampledImage = resampler->GetOutput(); newImage = mitk::ImportItkImage( resampledImage )->Clone(); nameAddition << "_Resampled_" << selectedInterpolator; std::cout << "Resampling successful." << std::endl; } catch (...) { this->BusyCursorOff(); QMessageBox::warning(nullptr, "Warning", "Problem when applying filter operation. Check your input..."); return; } newImage->DisconnectPipeline(); // adjust level/window to new image mitk::LevelWindow levelwindow; levelwindow.SetAuto( newImage ); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); levWinProp->SetLevelWindow( levelwindow ); // compose new image name std::string name = m_SelectedImageNode->GetNode()->GetName(); if (name.find(".nrrd") == name.size() -5 ) { name = name.substr(0,name.size() -5); } name.append( nameAddition.str() ); // create final result MITK data storage node mitk::DataNode::Pointer result = mitk::DataNode::New(); result->SetProperty( "levelwindow", levWinProp ); result->SetProperty( "name", mitk::StringProperty::New( name.c_str() ) ); result->SetData( newImage ); // for vector images, a different mapper is needed if(isVectorImage > 1) { mitk::VectorImageMapper2D::Pointer mapper = mitk::VectorImageMapper2D::New(); result->SetMapper(1,mapper); } // add new image to data storage and set as active to ease further processing GetDataStorage()->Add( result, m_SelectedImageNode->GetNode() ); if ( m_Controls->cbHideOrig->isChecked() == true ) m_SelectedImageNode->GetNode()->SetProperty( "visible", mitk::BoolProperty::New(false) ); // show the results mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->BusyCursorOff(); } void QmitkPreprocessingResampling::SelectInterpolator(int interpolator) { switch (interpolator) { case 0: { m_SelectedInterpolation = LINEAR; break; } case 1: { m_SelectedInterpolation = NEAREST; break; } case 2: { m_SelectedInterpolation = SPLINE; } } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/CMakeLists.txt b/Plugins/org.mitk.gui.qt.segmentation/CMakeLists.txt index dd2ab4fc65..92eac09671 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.segmentation/CMakeLists.txt @@ -1,10 +1,10 @@ project(org_mitk_gui_qt_segmentation) include_directories(${CTK_INCLUDE_DIRS}) mitk_create_plugin( EXPORT_DIRECTIVE MITK_QT_SEGMENTATION EXPORTED_INCLUDE_SUFFIXES src MODULE_DEPENDS MitkAppUtil MitkQtWidgetsExt MitkSegmentationUI - PACKAGE_DEPENDS Qt5|OpenGL + PACKAGE_DEPENDS Qt5|OpenGL nlohmann_json ) 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 6041c5c04a..a3ef93f2a0 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,394 +1,403 @@ /** \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_segmentationoverview 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. A multilabel segmentation can contain more than one label and more than one layer. This allows you to create different labels for different regions of interest encapsulated in one single image. The difference between labels and layers is that labels on one layer cannot overlap but labels on different layers can. The MITK segmentation plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of two 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 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 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
  • Show only selected nodes: Enable if only the selected segmentation and the reference image should be visible
  • Smoothed surface creation: Set certain smoothing parameters for surface creation
  • Default label set preset: Start a new segmentation with this preset instead of a default label +
  • Label creation: Assign default names and colors to new labels or ask users for them +
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtechnicalissues 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 multilabel images of the same extent as the original image.
\section org_mitk_views_segmentationdataselection Data selection & creating new segmentations To select a reference image for the segmentation, click on the Selected image selection widget and choose a suitable image from the selection available in the data manager. Once an image is selected, a new segmentation can be created on this reference image by clicking the button to the right of the Selected segmentation selection widget. A new multilabel segmentation with an initial label is automatically generated. The new segmentation will be added to the data manager as sub-node of the reference image. This item is then automatically selected in the data selection, which allows to start editing the new segmentation right away. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection",12} Alternatively to creating a new segmentation, an existing one can be edited as well. If a reference image is selected for which a segmentation already exists in the data manager, the auto selection mode will automatically select a fitting segmentation. Clicking on the segmentation selection widget a drop down list will open, containing all suitable segmentations for the selected reference dataset available in the data manager. \section org_mitk_views_segmentationlayers Segmentation layers For each multilabel segmentation different layers can be added or deleted. The layers can be used independently and layers can be switched using the left and right arrows. A layer is a set of labels that occupy a non-overlapping anatomical space. The best way to describe them is by a real use case: Imagine you are working on a radiotherapy planning application. In the first layer of your segmentation session, you would like to trace the contours of the liver and neighboring organs. You can accommodate all these segmentations in separate labels because they all occupy different anatomical regions and do not overlap. Now say you would like to segment the arteries and veins inside the liver. If you don't trace them in a different layer, you will overwrite the previous ones. You may also need a third layer for segmenting the different irrigation territories in the liver and a fourth layer to contain the lesion you would like to treat. \imageMacro{"QmitkSegmentation_LayerSelection.png","Layer selection",12} \section org_mitk_views_segmentationlabels Segmentation labels For each layer, one or more labels can be added. Pressing the double arrow on the right, all created labels are shown in the 'Label Table'. The following label properties are available:
  • Name:
  • the name of the label. Can be a predefined one or any other.
  • Locked:
  • whether the label is locked or editable. A locked label cannot be overwritten by another.
  • Color:
  • the color of the label.
  • Visible:
  • whether the label is currently visible or hidden.
\imageMacro{"QmitkSegmentation_LabelTable.png","The 'Label Table' shows all labels in the current segmentation session",12} The 'New Label' button can be used to add a new label. This will automatically add a new label with a distinct name and color to the list of available labels.\n In the current implementation of the plugin, the maximum number of labels is restricted to 255. If you need more, you will have to create a new segmentation session. +\subsection org_mitk_views_segmentationlabelsuggestions Label name and color suggestions + +When renaming labels or creating new labels with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. +The list of predefined label names and colors for the auto-completion feature can be either extented or replaced by a custom list of label name and color suggestions. +This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. +The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. + \subsection org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all labels in all layers like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label for a certain value already exists, its properties are overridden by the preset. If a label for a certain value does not yet exist, an empty label with the label properties of the preset is created. The actual segmentations of labels are unaffected as label set presets only store label properties. \subsubsection org_mitk_views_segmentationdefaultlabelpresets Applying label set presets by default If you work on a repetetive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "New label 1" label. \subsection org_mitk_views_segmentationlabelsearch Searching for a label It may happen that many labels (e.g. > 200) are present in a segmentation session and therefore manual searching can be time-consuming. The 'Label Search' edit box allows for quickly finding a label by providing assistance for label name completion. If the label is found, it will become the active one after pressing 'enter'. To start editing a label needs to be activated by clicking on the corresponding row in the 'Label Table'. Only one label can be active at the time. Then the segmentation tools in the toolbox can be used for mask generation. \subsection org_mitk_views_multilabelsegmentationoperationsonlabels Operations on labels Depending on the selection in the 'Label Table', several actions are offered: \subsubsection org_mitk_views_segmentationoperationssingleselection Operations with single label selection Right-clicking on any label opens a pop-up menu that offers the following actions to be performed on the selected label:
  • Rename...
  • : change the name and / or color of the selected label.
  • Remove...
  • : delete the selected label.
  • Erase...
  • : only clear the contents of the selected label.
  • Merge...
  • : merge two labels by selecting a second label.
  • Random color
  • : assign a random color to the label.
  • View only
  • : make all labels except the current selected label invisible.
  • View/Hide all
  • : make all labels visible / invisible
  • Lock/Unlock all
  • : lock or unlock all labels.
  • Create surface
  • : generate a surface out of the selected label.
  • Create mask
  • : generate a mask out of the selected label. A mask is a binary image with "1" inside and "0" outside.
  • Create cropped mask
  • : generate a binary mask out of the selected label. Crop changes the extent of the resulting image to the extent of the label.
\imageMacro{"QmitkSegmentation_OperationsSingleSelection.png","Context menu for single label selection",12} \subsubsection org_mitk_views_segmentationoperationsmultiselection Operations with multiple label selection Shift-clickink on multiple labels allows to select more than one label. If more than one label is selected, different options will appear in the menu:
  • Merge selection on current label
  • : transfer the contents of the selected labels in the 'Label Table' into the current one.
  • Remove selected labels
  • : delete the selected labels.
  • Erase selected labels
  • : only clear the contents of the selected labels.
\imageMacro{"QmitkSegmentation_OperationsMultiSelection.png","Context menu for multiple label selection",12} \section org_mitk_views_segmentationtooloverview 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_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which 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. 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.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as 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_segmentationaddsubtracttools 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_segmentationpaintwipetools 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_segmentationregiongrowingtool 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 allows 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_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool 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_segmentationerasetool 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_segmentationlivewiretool 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_segmentationinterpolation 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. Note: Interpolation is currently disabled for segmentations containing more than one label. 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 undo / redo 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_segmentation3dsegmentation 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_segmentation3dthresholdtool 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_segmentation3dulthresholdTool 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_segmentation3dotsutool 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_segmentation3drgtool 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_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "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. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} 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. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# 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, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to 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. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even 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 irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded in the "No. of Extra Modalities" spinbox. Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc 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. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \section org_mitk_views_segmentationpostprocessing 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_segmentationtechnicaldetail 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/files.cmake b/Plugins/org.mitk.gui.qt.segmentation/files.cmake index bb27b67836..584a0cf103 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.segmentation/files.cmake @@ -1,81 +1,87 @@ set(SRC_CPP_FILES QmitkSegmentationPreferencePage.cpp + QmitkNewSegmentationDialog.cpp + QmitkLabelSetWidget.cpp ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkSegmentationView.cpp QmitkAutocropAction.cpp QmitkAutocropLabelSetImageAction.cpp QmitkCreatePolygonModelAction.cpp QmitkLoadMultiLabelPresetAction.cpp QmitkSaveMultiLabelPresetAction.cpp Common/QmitkDataSelectionWidget.cpp Common/QmitkLabelsWidget.cpp Common/QmitkLayersWidget.cpp SegmentationUtilities/QmitkSegmentationUtilitiesView.cpp SegmentationUtilities/QmitkSegmentationUtilityWidget.cpp SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.cpp ) set(UI_FILES src/QmitkSegmentationPreferencePageControls.ui + src/QmitkNewSegmentationDialog.ui + src/QmitkLabelSetWidgetControls.ui src/internal/QmitkSegmentationViewControls.ui src/internal/Common/QmitkDataSelectionWidgetControls.ui src/internal/Common/QmitkLabelsWidgetControls.ui src/internal/Common/QmitkLayersWidgetControls.ui src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesViewControls.ui src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidgetControls.ui src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidgetControls.ui src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui ) set(MOC_H_FILES src/QmitkSegmentationPreferencePage.h + src/QmitkNewSegmentationDialog.h + src/QmitkLabelSetWidget.h src/internal/mitkPluginActivator.h src/internal/QmitkSegmentationView.h src/internal/QmitkAutocropAction.h src/internal/QmitkAutocropLabelSetImageAction.h src/internal/QmitkCreatePolygonModelAction.h src/internal/QmitkLoadMultiLabelPresetAction.h src/internal/QmitkSaveMultiLabelPresetAction.h src/internal/Common/QmitkDataSelectionWidget.h src/internal/Common/QmitkLabelsWidget.h src/internal/Common/QmitkLayersWidget.h src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesView.h src/internal/SegmentationUtilities/QmitkSegmentationUtilityWidget.h src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.h src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.h src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.h src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.h ) set(CACHED_RESOURCE_FILES resources/segmentation.svg resources/segmentation_utilities.svg plugin.xml ) set(QRC_FILES resources/segmentation.qrc resources/SegmentationUtilities.qrc resources/BooleanOperationsWidget.qrc resources/MorphologicalOperationsWidget.qrc ) set(CPP_FILES) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.cpp similarity index 98% rename from Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.cpp rename to Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.cpp index 032a71aff1..0742129dfa 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.cpp @@ -1,1155 +1,1147 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkLabelSetWidget.h" // mitk #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // Qt #include #include #include #include #include #include #include #include #include // itk #include QmitkLabelSetWidget::QmitkLabelSetWidget(QWidget *parent) : QWidget(parent), m_DataStorage(nullptr), m_Completer(nullptr), m_ToolManager(nullptr), m_ProcessingManualSelection(false) { m_Controls.setupUi(this); m_ColorSequenceRainbow.GoToBegin(); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_Controls.m_LabelSearchBox->setAlwaysShowClearIcon(true); m_Controls.m_LabelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls.m_LabelSearchBox->setCompleter(m_Completer); connect(m_Controls.m_LabelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); m_Controls.m_LabelSearchBox->setEnabled(false); m_Controls.m_lblCaption->setText(""); InitializeTableWidget(); } QmitkLabelSetWidget::~QmitkLabelSetWidget() {} void QmitkLabelSetWidget::OnTableViewContextMenuRequested(const QPoint & /*pos*/) { int pixelValue = GetPixelValueOfSelectedItem(); if (-1 == pixelValue) return; QMenu *menu = new QMenu(m_Controls.m_LabelSetTableWidget); if (m_Controls.m_LabelSetTableWidget->selectedItems().size() > 1) { QAction *mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); mergeAction->setEnabled(true); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction *removeLabelsAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove selected labels", this); removeLabelsAction->setEnabled(true); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabels(bool))); menu->addAction(removeLabelsAction); QAction *eraseLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase selected labels", this); eraseLabelsAction->setEnabled(true); QObject::connect(eraseLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabels(bool))); menu->addAction(eraseLabelsAction); } else { QAction *renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "Rename...", this); renameAction->setEnabled(true); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction *removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove...", this); removeAction->setEnabled(true); QObject::connect(removeAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabel(bool))); menu->addAction(removeAction); QAction *eraseAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase...", this); eraseAction->setEnabled(true); QObject::connect(eraseAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabel(bool))); menu->addAction(eraseAction); QAction *randomColorAction = new QAction(QIcon(":/Qmitk/RandomColor.png"), "Random color", this); randomColorAction->setEnabled(true); QObject::connect(randomColorAction, SIGNAL(triggered(bool)), this, SLOT(OnRandomColor(bool))); menu->addAction(randomColorAction); QAction *viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", this); viewOnlyAction->setEnabled(true); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); QAction *viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View all", this); viewAllAction->setEnabled(true); QObject::connect(viewAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsVisible(bool))); menu->addAction(viewAllAction); QAction *hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide all", this); hideAllAction->setEnabled(true); QObject::connect(hideAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsInvisible(bool))); menu->addAction(hideAllAction); QAction *lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock all", this); lockAllAction->setEnabled(true); QObject::connect(lockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnLockAllLabels(bool))); menu->addAction(lockAllAction); QAction *unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock all", this); unlockAllAction->setEnabled(true); QObject::connect(unlockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnUnlockAllLabels(bool))); menu->addAction(unlockAllAction); QAction *createSurfaceAction = new QAction(QIcon(":/Qmitk/CreateSurface.png"), "Create surface", this); createSurfaceAction->setEnabled(true); createSurfaceAction->setMenu(new QMenu()); QAction *tmp1 = createSurfaceAction->menu()->addAction(QString("Detailed")); QAction *tmp2 = createSurfaceAction->menu()->addAction(QString("Smoothed")); QObject::connect(tmp1, SIGNAL(triggered(bool)), this, SLOT(OnCreateDetailedSurface(bool))); QObject::connect(tmp2, SIGNAL(triggered(bool)), this, SLOT(OnCreateSmoothedSurface(bool))); menu->addAction(createSurfaceAction); QAction *createMaskAction = new QAction(QIcon(":/Qmitk/CreateMask.png"), "Create mask", this); createMaskAction->setEnabled(true); QObject::connect(createMaskAction, SIGNAL(triggered(bool)), this, SLOT(OnCreateMask(bool))); menu->addAction(createMaskAction); QAction *createCroppedMaskAction = new QAction(QIcon(":/Qmitk/CreateMask.png"), "Create cropped mask", this); createCroppedMaskAction->setEnabled(true); QObject::connect(createCroppedMaskAction, SIGNAL(triggered(bool)), this, SLOT(OnCreateCroppedMask(bool))); menu->addAction(createCroppedMaskAction); QSlider *opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); QObject::connect(opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(OnOpacityChanged(int))); QLabel *_OpacityLabel = new QLabel("Opacity: "); QVBoxLayout *_OpacityWidgetLayout = new QVBoxLayout; _OpacityWidgetLayout->setContentsMargins(4, 4, 4, 4); _OpacityWidgetLayout->addWidget(_OpacityLabel); _OpacityWidgetLayout->addWidget(opacitySlider); QWidget *_OpacityWidget = new QWidget; _OpacityWidget->setLayout(_OpacityWidgetLayout); QWidgetAction *OpacityAction = new QWidgetAction(this); OpacityAction->setDefaultWidget(_OpacityWidget); // QObject::connect( m_OpacityAction, SIGNAL( changed() ), this, SLOT( OpacityActionChanged() ) ); auto workingImage = this->GetWorkingImage(); auto activeLayer = workingImage->GetActiveLayer(); auto label = workingImage->GetLabel(pixelValue, activeLayer); if (nullptr != label) { auto opacity = label->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); } menu->addAction(OpacityAction); } menu->popup(QCursor::pos()); } void QmitkLabelSetWidget::OnUnlockAllLabels(bool /*value*/) { GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsLocked(false); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::OnLockAllLabels(bool /*value*/) { GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsLocked(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::OnSetAllLabelsVisible(bool /*value*/) { GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsVisible(true); UpdateAllTableWidgetItems(); } void QmitkLabelSetWidget::OnSetAllLabelsInvisible(bool /*value*/) { GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsVisible(false); UpdateAllTableWidgetItems(); } void QmitkLabelSetWidget::OnSetOnlyActiveLabelVisible(bool /*value*/) { mitk::LabelSetImage *workingImage = GetWorkingImage(); int pixelValue = GetPixelValueOfSelectedItem(); workingImage->GetActiveLabelSet()->SetAllLabelsVisible(false); workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->SetVisible(true); workingImage->GetActiveLabelSet()->UpdateLookupTable(pixelValue); this->WaitCursorOn(); const mitk::Point3D &pos = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit goToLabel(pos); } UpdateAllTableWidgetItems(); } void QmitkLabelSetWidget::OnEraseLabel(bool /*value*/) { int pixelValue = GetPixelValueOfSelectedItem(); QString question = "Do you really want to erase the contents of label \""; question.append( QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Erase label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); GetWorkingImage()->EraseLabel(pixelValue); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLabelSetWidget::OnRemoveLabel(bool /*value*/) { int pixelValue = GetPixelValueOfSelectedItem(); QString question = "Do you really want to remove label \""; question.append( QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Remove label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); GetWorkingImage()->RemoveLabel(pixelValue, GetWorkingImage()->GetActiveLayer()); this->WaitCursorOff(); } ResetAllTableWidgetItems(); } void QmitkLabelSetWidget::OnRenameLabel(bool /*value*/) { int pixelValue = GetPixelValueOfSelectedItem(); - QmitkNewSegmentationDialog dialog(this); - dialog.setWindowTitle("Rename Label"); - dialog.SetSuggestionList(m_OrganColors); + QmitkNewSegmentationDialog dialog(this, this->GetWorkingImage(), QmitkNewSegmentationDialog::RenameLabel); dialog.SetColor(GetWorkingImage()->GetActiveLabelSet()->GetLabel(pixelValue)->GetColor()); - dialog.SetSegmentationName( - QString::fromStdString(GetWorkingImage()->GetActiveLabelSet()->GetLabel(pixelValue)->GetName())); + dialog.SetName(QString::fromStdString(GetWorkingImage()->GetActiveLabelSet()->GetLabel(pixelValue)->GetName())); if (dialog.exec() == QDialog::Rejected) { return; } - QString segmentationName = dialog.GetSegmentationName(); + QString segmentationName = dialog.GetName(); if (segmentationName.isEmpty()) { segmentationName = "Unnamed"; } GetWorkingImage()->GetActiveLabelSet()->RenameLabel(pixelValue, segmentationName.toStdString(), dialog.GetColor()); GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); UpdateAllTableWidgetItems(); } void QmitkLabelSetWidget::OnCombineAndCreateMask(bool /*value*/) { m_Controls.m_LabelSetTableWidget->selectedRanges(); // ...to do... // } void QmitkLabelSetWidget::OnCreateMasks(bool /*value*/) { m_Controls.m_LabelSetTableWidget->selectedRanges(); // ..to do.. // } void QmitkLabelSetWidget::OnCombineAndCreateSurface(bool /*value*/) { m_Controls.m_LabelSetTableWidget->selectedRanges(); // ..to do.. // } void QmitkLabelSetWidget::OnEraseLabels(bool /*value*/) { QString question = "Do you really want to erase the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Erase selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); if (ranges.isEmpty()) return; std::vector VectorOfLablePixelValues; foreach (QTableWidgetSelectionRange a, ranges) for (int i = a.topRow(); i <= a.bottomRow(); i++) VectorOfLablePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); this->WaitCursorOn(); GetWorkingImage()->EraseLabels(VectorOfLablePixelValues); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLabelSetWidget::OnRemoveLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); if (ranges.isEmpty()) { return; } std::vector VectorOfLablePixelValues; foreach (QTableWidgetSelectionRange a, ranges) { for (int i = a.topRow(); i <= a.bottomRow(); ++i) { VectorOfLablePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); } } this->WaitCursorOn(); GetWorkingImage()->RemoveLabels(VectorOfLablePixelValues, GetWorkingImage()->GetActiveLayer()); this->WaitCursorOff(); } ResetAllTableWidgetItems(); } void QmitkLabelSetWidget::OnMergeLabels(bool /*value*/) { int pixelValue = GetPixelValueOfSelectedItem(); QString question = "Do you really want to merge selected labels into \""; question.append( QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); if (ranges.isEmpty()) { return; } std::vector vectorOfSourcePixelValues; foreach (QTableWidgetSelectionRange a, ranges) { for (int i = a.topRow(); i <= a.bottomRow(); ++i) { vectorOfSourcePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); } } this->WaitCursorOn(); GetWorkingImage()->MergeLabels(pixelValue, vectorOfSourcePixelValues, GetWorkingImage()->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLabelSetWidget::OnLockedButtonClicked() { int row = -1; for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) { if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, LOCKED_COL)) { row = i; } } if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) { int pixelValue = m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt(); GetWorkingImage() ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) ->SetLocked(!GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetLocked()); } } void QmitkLabelSetWidget::OnVisibleButtonClicked() { int row = -1; for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) { if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, VISIBLE_COL)) { row = i; break; } } if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) { QTableWidgetItem *item = m_Controls.m_LabelSetTableWidget->item(row, 0); int pixelValue = item->data(Qt::UserRole).toInt(); GetWorkingImage() ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) ->SetVisible(!GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetVisible()); GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::OnColorButtonClicked() { int row = -1; for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) { if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, COLOR_COL)) { row = i; } } if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) { int pixelValue = m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt(); const mitk::Color &color = GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetColor(); QColor initial(color.GetRed() * 255, color.GetGreen() * 255, color.GetBlue() * 255); QColor qcolor = QColorDialog::getColor(initial, nullptr, QString("Change color")); if (!qcolor.isValid()) { return; } QPushButton *button = static_cast(m_Controls.m_LabelSetTableWidget->cellWidget(row, COLOR_COL)); if (!button) { return; } button->setAutoFillBackground(true); QString styleSheet = "background-color:rgb("; styleSheet.append(QString::number(qcolor.red())); styleSheet.append(","); styleSheet.append(QString::number(qcolor.green())); styleSheet.append(","); styleSheet.append(QString::number(qcolor.blue())); styleSheet.append("); border: 0;"); button->setStyleSheet(styleSheet); mitk::Color newColor; newColor.SetRed(qcolor.red() / 255.0); newColor.SetGreen(qcolor.green() / 255.0); newColor.SetBlue(qcolor.blue() / 255.0); GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->SetColor(newColor); GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); } } void QmitkLabelSetWidget::OnRandomColor(bool /*value*/) { int pixelValue = GetPixelValueOfSelectedItem(); GetWorkingImage() ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) ->SetColor(m_ColorSequenceRainbow.GetNextColor()); GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); UpdateAllTableWidgetItems(); } -void QmitkLabelSetWidget::SetOrganColors(const QStringList &organColors) -{ - m_OrganColors = organColors; -} - void QmitkLabelSetWidget::OnActiveLabelChanged(int pixelValue) { mitk::LabelSetImage *workingImage = GetWorkingImage(); assert(workingImage); workingImage->GetActiveLabelSet()->SetActiveLabel(pixelValue); // MITK_INFO << "Active Label set to << " << pixelValue; mitk::SurfaceBasedInterpolationController *interpolator = mitk::SurfaceBasedInterpolationController::GetInstance(); if (interpolator) { interpolator->SetActiveLabel(pixelValue); } workingImage->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::OnItemClicked(QTableWidgetItem *item) { if (!item) return; int pixelValue = item->data(Qt::UserRole).toInt(); QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); if (!ranges.empty() && ranges.back().rowCount() == 1) { m_ProcessingManualSelection = true; OnActiveLabelChanged(pixelValue); m_ProcessingManualSelection = false; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLabelSetWidget::OnItemDoubleClicked(QTableWidgetItem *item) { if (!item) return; int pixelValue = item->data(Qt::UserRole).toInt(); // OnItemClicked(item); <<-- Double click first call OnItemClicked WaitCursorOn(); mitk::LabelSetImage *workingImage = GetWorkingImage(); workingImage->UpdateCenterOfMass(pixelValue, workingImage->GetActiveLayer()); const mitk::Point3D &pos = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetCenterOfMassCoordinates(); WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit goToLabel(pos); } workingImage->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::SelectLabelByPixelValue(mitk::Label::PixelType pixelValue) { if (m_ProcessingManualSelection || !GetWorkingImage()->ExistLabel(pixelValue)) return; for (int row = 0; row < m_Controls.m_LabelSetTableWidget->rowCount(); row++) { if (m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt() == pixelValue) { m_Controls.m_LabelSetTableWidget->clearSelection(); m_Controls.m_LabelSetTableWidget->selectRow(row); m_Controls.m_LabelSetTableWidget->scrollToItem(m_Controls.m_LabelSetTableWidget->item(row, 0)); return; } } } void QmitkLabelSetWidget::InsertTableWidgetItem(mitk::Label *label) { const mitk::Color &color = label->GetColor(); QString styleSheet = "background-color:rgb("; styleSheet.append(QString::number(color[0] * 255)); styleSheet.append(","); styleSheet.append(QString::number(color[1] * 255)); styleSheet.append(","); styleSheet.append(QString::number(color[2] * 255)); styleSheet.append("); border: 0;"); QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; int colWidth = (tableWidget->columnWidth(NAME_COL) < 180) ? 180 : tableWidget->columnWidth(NAME_COL) - 2; QString text = fontMetrics().elidedText(label->GetName().c_str(), Qt::ElideMiddle, colWidth); QTableWidgetItem *nameItem = new QTableWidgetItem(text); nameItem->setTextAlignment(Qt::AlignCenter | Qt::AlignLeft); // ---!--- // IMPORTANT: ADD PIXELVALUE TO TABLEWIDGETITEM.DATA nameItem->setData(Qt::UserRole, QVariant(label->GetValue())); // ---!--- QPushButton *pbColor = new QPushButton(tableWidget); pbColor->setFixedSize(24, 24); pbColor->setCheckable(false); pbColor->setAutoFillBackground(true); pbColor->setToolTip("Change label color"); pbColor->setStyleSheet(styleSheet); connect(pbColor, SIGNAL(clicked()), this, SLOT(OnColorButtonClicked())); QString transparentStyleSheet = QLatin1String("background-color: transparent; border: 0;"); QPushButton *pbLocked = new QPushButton(tableWidget); pbLocked->setFixedSize(24, 24); QIcon *iconLocked = new QIcon(); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); iconLocked->addPixmap(lockIcon.pixmap(64), QIcon::Normal, QIcon::Off); iconLocked->addPixmap(unlockIcon.pixmap(64), QIcon::Normal, QIcon::On); pbLocked->setIcon(*iconLocked); pbLocked->setIconSize(QSize(24, 24)); pbLocked->setCheckable(true); pbLocked->setToolTip("Lock/unlock label"); pbLocked->setChecked(!label->GetLocked()); pbLocked->setStyleSheet(transparentStyleSheet); connect(pbLocked, SIGNAL(clicked()), this, SLOT(OnLockedButtonClicked())); QPushButton *pbVisible = new QPushButton(tableWidget); pbVisible->setFixedSize(24, 24); pbVisible->setAutoRepeat(false); QIcon *iconVisible = new QIcon(); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); iconVisible->addPixmap(visibleIcon.pixmap(64), QIcon::Normal, QIcon::Off); iconVisible->addPixmap(invisibleIcon.pixmap(64), QIcon::Normal, QIcon::On); pbVisible->setIcon(*iconVisible); pbVisible->setIconSize(QSize(24, 24)); pbVisible->setCheckable(true); pbVisible->setToolTip("Show/hide label"); pbVisible->setChecked(!label->GetVisible()); pbVisible->setStyleSheet(transparentStyleSheet); connect(pbVisible, SIGNAL(clicked()), this, SLOT(OnVisibleButtonClicked())); int row = tableWidget->rowCount(); tableWidget->insertRow(row); tableWidget->setRowHeight(row, 24); tableWidget->setItem(row, 0, nameItem); tableWidget->setCellWidget(row, 1, pbLocked); tableWidget->setCellWidget(row, 2, pbColor); tableWidget->setCellWidget(row, 3, pbVisible); tableWidget->selectRow(row); // m_LabelSetImage->SetActiveLabel(label->GetPixelValue()); // m_ToolManager->WorkingDataModified.Send(); // emit activeLabelChanged(label->GetPixelValue()); if (row == 0) { tableWidget->hideRow(row); // hide exterior label } } void QmitkLabelSetWidget::UpdateAllTableWidgetItems() { mitk::LabelSetImage *workingImage = GetWorkingImage(); if (!workingImage) return; // add all labels QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; m_LabelStringList.clear(); for (int i = 0; i < tableWidget->rowCount(); ++i) { UpdateTableWidgetItem(tableWidget->item(i, 0)); m_LabelStringList.append(tableWidget->item(i, 0)->text()); } OnLabelListModified(m_LabelStringList); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::UpdateTableWidgetItem(QTableWidgetItem *item) { mitk::LabelSetImage *workingImage = GetWorkingImage(); mitk::Label *label = workingImage->GetLabel(item->data(Qt::UserRole).toInt(), workingImage->GetActiveLayer()); const mitk::Color &color = label->GetColor(); QString styleSheet = "background-color:rgb("; styleSheet.append(QString::number(color[0] * 255)); styleSheet.append(","); styleSheet.append(QString::number(color[1] * 255)); styleSheet.append(","); styleSheet.append(QString::number(color[2] * 255)); styleSheet.append("); border: 0;"); QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; int colWidth = (tableWidget->columnWidth(NAME_COL) < 180) ? 180 : tableWidget->columnWidth(NAME_COL) - 2; QString text = fontMetrics().elidedText(label->GetName().c_str(), Qt::ElideMiddle, colWidth); item->setText(text); QPushButton *pbLocked = dynamic_cast(tableWidget->cellWidget(item->row(), 1)); pbLocked->setChecked(!label->GetLocked()); QPushButton *pbColor = dynamic_cast(tableWidget->cellWidget(item->row(), 2)); pbColor->setStyleSheet(styleSheet); QPushButton *pbVisible = dynamic_cast(tableWidget->cellWidget(item->row(), 3)); pbVisible->setChecked(!label->GetVisible()); if (item->row() == 0) { tableWidget->hideRow(item->row()); // hide exterior label } } void QmitkLabelSetWidget::ResetAllTableWidgetItems() { QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; // remove all rows while (tableWidget->rowCount()) { tableWidget->removeRow(0); } mitk::DataNode * workingNode = GetWorkingNode(); auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } // add all labels m_LabelStringList.clear(); mitk::LabelSet::LabelContainerConstIteratorType it = workingImage->GetActiveLabelSet()->IteratorConstBegin(); mitk::LabelSet::LabelContainerConstIteratorType end = workingImage->GetActiveLabelSet()->IteratorConstEnd(); int pixelValue = -1; while (it != end) { InsertTableWidgetItem(it->second); if (workingImage->GetActiveLabel(workingImage->GetActiveLayer()) == it->second) // get active pixelValue = it->first; m_LabelStringList.append(QString(it->second->GetName().c_str())); it++; } SelectLabelByPixelValue(pixelValue); OnLabelListModified(m_LabelStringList); std::stringstream captionText; captionText << "Number of labels: " << workingImage->GetNumberOfLabels(workingImage->GetActiveLayer()) - 1; m_Controls.m_lblCaption->setText(captionText.str().c_str()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); emit LabelSetWidgetReset(); } int QmitkLabelSetWidget::GetPixelValueOfSelectedItem() { if (m_Controls.m_LabelSetTableWidget->currentItem()) { return m_Controls.m_LabelSetTableWidget->currentItem()->data(Qt::UserRole).toInt(); } return -1; } QStringList &QmitkLabelSetWidget::GetLabelStringList() { return m_LabelStringList; } void QmitkLabelSetWidget::InitializeTableWidget() { QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); tableWidget->setTabKeyNavigation(false); tableWidget->setAlternatingRowColors(false); tableWidget->setFocusPolicy(Qt::NoFocus); tableWidget->setColumnCount(4); tableWidget->resizeColumnToContents(NAME_COL); tableWidget->setColumnWidth(LOCKED_COL, 25); tableWidget->setColumnWidth(COLOR_COL, 25); tableWidget->setColumnWidth(VISIBLE_COL, 25); tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); tableWidget->setContextMenuPolicy(Qt::CustomContextMenu); tableWidget->horizontalHeader()->hide(); tableWidget->setSortingEnabled(false); tableWidget->verticalHeader()->hide(); tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(tableWidget, SIGNAL(itemClicked(QTableWidgetItem *)), this, SLOT(OnItemClicked(QTableWidgetItem *))); connect( tableWidget, SIGNAL(itemDoubleClicked(QTableWidgetItem *)), this, SLOT(OnItemDoubleClicked(QTableWidgetItem *))); connect(tableWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(OnTableViewContextMenuRequested(const QPoint &))); } void QmitkLabelSetWidget::OnOpacityChanged(int value) { int pixelValue = GetPixelValueOfSelectedItem(); float opacity = static_cast(value) / 100.0f; GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->SetOpacity(opacity); GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); } void QmitkLabelSetWidget::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkLabelSetWidget::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkLabelSetWidget::OnSearchLabel() { std::string text = m_Controls.m_LabelSearchBox->text().toStdString(); int pixelValue = -1; int row = -1; for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) { if (m_Controls.m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) { pixelValue = m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); row = i; break; } } if (pixelValue == -1) { return; } GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); QTableWidgetItem *nameItem = m_Controls.m_LabelSetTableWidget->item(row, NAME_COL); if (!nameItem) { return; } m_Controls.m_LabelSetTableWidget->clearSelection(); m_Controls.m_LabelSetTableWidget->selectRow(row); m_Controls.m_LabelSetTableWidget->scrollToItem(nameItem); GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); this->WaitCursorOn(); mitk::Point3D pos = GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); m_ToolManager->WorkingDataChanged(); if (pos.GetVnlVector().max_value() > 0.0) { emit goToLabel(pos); } else { GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); mitk::Point3D pos = GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); emit goToLabel(pos); } this->WaitCursorOff(); } void QmitkLabelSetWidget::OnLabelListModified(const QStringList &list) { QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(list); } mitk::LabelSetImage *QmitkLabelSetWidget::GetWorkingImage() { mitk::DataNode *workingNode = GetWorkingNode(); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); return workingImage; } mitk::DataNode *QmitkLabelSetWidget::GetWorkingNode() { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); return workingNode; } void QmitkLabelSetWidget::UpdateControls() { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingData = (workingNode != nullptr); m_Controls.m_LabelSetTableWidget->setEnabled(hasWorkingData); m_Controls.m_LabelSearchBox->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkLabelSetWidget::OnCreateCroppedMask(bool) { m_ToolManager->ActivateTool(-1); mitk::LabelSetImage *workingImage = GetWorkingImage(); mitk::Image::Pointer maskImage; int pixelValue = GetPixelValueOfSelectedItem(); try { this->WaitCursorOn(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(workingImage->CreateLabelMask(pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, GetWorkingNode()); } void QmitkLabelSetWidget::OnCreateMask(bool /*triggered*/) { m_ToolManager->ActivateTool(-1); mitk::LabelSetImage *workingImage = GetWorkingImage(); mitk::Image::Pointer maskImage; int pixelValue = GetPixelValueOfSelectedItem(); try { this->WaitCursorOn(); maskImage = workingImage->CreateLabelMask(pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, GetWorkingNode()); } void QmitkLabelSetWidget::OnToggleOutline(bool value) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); workingNode->SetBoolProperty("labelset.contour.active", value); workingNode->GetData()->Modified(); // fixme: workaround to force data-type rendering (and not only property-type) mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLabelSetWidget::OnCreateSmoothedSurface(bool /*triggered*/) { m_ToolManager->ActivateTool(-1); mitk::DataNode::Pointer workingNode = GetWorkingNode(); mitk::LabelSetImage *workingImage = GetWorkingImage(); int pixelValue = GetPixelValueOfSelectedItem(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = workingNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", workingImage); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", true); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkLabelSetWidget::OnCreateDetailedSurface(bool /*triggered*/) { m_ToolManager->ActivateTool(-1); mitk::DataNode::Pointer workingNode = GetWorkingNode(); mitk::LabelSetImage *workingImage = GetWorkingImage(); int pixelValue = GetPixelValueOfSelectedItem(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = workingNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", workingImage); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", false); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkLabelSetWidget::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkLabelSetWidget::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkLabelSetWidget::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkLabelSetWidget::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h similarity index 94% rename from Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.h rename to Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h index f5f968b440..9fb7a1c0e8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelSetWidget.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h @@ -1,167 +1,165 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QmitkLabelSetWidget_h #define QmitkLabelSetWidget_h -#include "MitkSegmentationUIExports.h" +#include -#include "mitkColorSequenceRainbow.h" -#include "mitkLabel.h" -#include "mitkNumericTypes.h" +#include +#include +#include #include class QmitkDataStorageComboBox; class QCompleter; namespace mitk { class LabelSetImage; class LabelSet; class Label; class DataStorage; class ToolManager; class DataNode; } -class MITKSEGMENTATIONUI_EXPORT QmitkLabelSetWidget : public QWidget +class MITK_QT_SEGMENTATION QmitkLabelSetWidget : public QWidget { Q_OBJECT public: explicit QmitkLabelSetWidget(QWidget *parent = nullptr); ~QmitkLabelSetWidget() override; void SetDataStorage(mitk::DataStorage *storage); - void SetOrganColors(const QStringList &organColors); - void UpdateControls(); virtual void setEnabled(bool enabled); QStringList &GetLabelStringList(); signals: /// \brief Send a signal when it was requested to go to a label. void goToLabel(const mitk::Point3D &); void LabelSetWidgetReset(); public slots: /** * @brief Updates the current labels in the label set widget table. For each label (widget item) the 'UpdateTableWidgetItem' is called. * * Updating means setting the color box of the table, setting the column with and fill it with the label name. * Furthermore the two push buttons for locking and showing/hiding the layer are checked/unchecked. * This functions only changes the appearance of the table widget and no render window update is necessary. */ void UpdateAllTableWidgetItems(); /** * @brief Resets the current labels in the label set widget table. For each label a widget item is inserted into the table. * * Resetting means removing all rows of the widget table and inserting new rows (labels) from the active label set (= layer) of the current working node. * The currently active label is selected and 'Number of labels' is set. * As this function is typically used after one label has been removed or the reference node has been changed (e.g.) the render windows have to be updated. */ void ResetAllTableWidgetItems(); void SelectLabelByPixelValue(mitk::Label::PixelType pixelValue); private slots: // LabelSet dependent void OnOpacityChanged(int); void OnUnlockAllLabels(bool); void OnLockAllLabels(bool); void OnSetAllLabelsVisible(bool); void OnSetAllLabelsInvisible(bool); void OnSetOnlyActiveLabelVisible(bool); void OnRandomColor(bool); void OnRemoveLabel(bool); void OnRemoveLabels(bool); void OnRenameLabel(bool); void OnLockedButtonClicked(); void OnVisibleButtonClicked(); void OnColorButtonClicked(); void OnItemClicked(QTableWidgetItem *item); void OnItemDoubleClicked(QTableWidgetItem *item); void OnTableViewContextMenuRequested(const QPoint &); void InsertTableWidgetItem(mitk::Label *label); void UpdateTableWidgetItem(QTableWidgetItem *item); // reaction to "returnPressed" signal from ... void OnSearchLabel(); // reaction to the button "Change Label" void OnActiveLabelChanged(int pixelValue); // LabelSetImage Dependet void OnCreateDetailedSurface(bool); void OnCreateSmoothedSurface(bool); // reaction to the signal "createMask" from QmitkLabelSetTableWidget void OnCreateMask(bool); void OnCreateMasks(bool); // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget void OnCreateCroppedMask(bool); void OnCombineAndCreateMask(bool); void OnCombineAndCreateSurface(bool); void OnEraseLabel(bool); void OnEraseLabels(bool); void OnMergeLabels(bool); // reaction to signal "labelListModified" from QmitkLabelSetTableWidget void OnLabelListModified(const QStringList &list); // reaction to the signal "toggleOutline" from QmitkLabelSetTableWidget void OnToggleOutline(bool); private: enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); void OnThreadedCalculationDone(); void InitializeTableWidget(); int GetPixelValueOfSelectedItem(); mitk::LabelSetImage *GetWorkingImage(); mitk::DataNode *GetWorkingNode(); Ui::QmitkLabelSetWidgetControls m_Controls; mitk::ColorSequenceRainbow m_ColorSequenceRainbow; mitk::DataStorage *m_DataStorage; QCompleter *m_Completer; mitk::ToolManager *m_ToolManager; QStringList m_OrganColors; QStringList m_LabelStringList; bool m_ProcessingManualSelection; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelSetWidgetControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidgetControls.ui similarity index 100% rename from Modules/SegmentationUI/Qmitk/QmitkLabelSetWidgetControls.ui rename to Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidgetControls.ui diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp new file mode 100644 index 0000000000..02ed890382 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp @@ -0,0 +1,317 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +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 "QmitkNewSegmentationDialog.h" +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace +{ + // Get standard label name and color suggestions from embedded XML preset file for anatomical structures. + QmitkNewSegmentationDialog::SuggestionsType GetStandardSuggestions() + { + QmitkNewSegmentationDialog::SuggestionsType standardSuggestions; + + vtkNew presets; + presets->LoadPreset(); + + for (const auto& preset : presets->GetColorPresets()) + { + auto name = QString::fromStdString(preset.first); + auto color = QColor::fromRgbF(preset.second.GetRed(), preset.second.GetGreen(), preset.second.GetBlue()); + standardSuggestions.emplace(name, color); + } + + return standardSuggestions; + } + + // Parse label name and color suggestions from a JSON file. An array of objects is expected, each consisting + // of a "name" string and an optional "color" string. If present, the "color" string must follow the conventions + // of QColor::setNamedColor(), i.e., #rrggbb or any SVG color keyword name like CornflowerBlue. Everything else + // in the JSON file is simply ignored. In case of any error, an empty map is returned. + QmitkNewSegmentationDialog::SuggestionsType ParseSuggestions(const std::string& filename) + { + std::ifstream file(filename); + + if (!file.is_open()) + { + MITK_ERROR << "Could not open \"" << filename << "\"!"; + return {}; + } + + auto json = nlohmann::json::parse(file, nullptr, false); + + if (json.is_discarded() || !json.is_array()) + { + MITK_ERROR << "Could not parse \"" << filename << "\" as JSON array!"; + return {}; + } + + QmitkNewSegmentationDialog::SuggestionsType parsedSuggestions; + + for (const auto& obj : json) + { + if (!obj.is_object() || !obj.contains("name")) + continue; + + auto name = QString::fromStdString(obj["name"]); + + QColor color(QColor::Invalid); + + if (obj.contains("color")) + color.setNamedColor(QString::fromStdString(obj["color"])); + + parsedSuggestions.emplace(name, color); + } + + if (parsedSuggestions.empty()) + MITK_WARN << "Could not parse any suggestions from \"" << filename << "\"!"; + + return parsedSuggestions; + } + + struct Preferences + { + QString labelSuggestions; + bool replaceStandardSuggestions; + bool suggestOnce; + }; + + // Get all relevant preferences and consider command-line arguments overrides. + Preferences GetPreferences() + { + auto nodePrefs = berry::Platform::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); + + Preferences prefs; + + prefs.labelSuggestions = QString::fromStdString(mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), "")); + + if (prefs.labelSuggestions.isEmpty()) + prefs.labelSuggestions = nodePrefs->Get("label suggestions", ""); + + prefs.replaceStandardSuggestions = nodePrefs->GetBool("replace standard suggestions", true); + prefs.suggestOnce = nodePrefs->GetBool("suggest once", true); + + return prefs; + } + + // Get names of all labels in all layers of a LabelSetImage. + QStringList GetExistingLabelNames(mitk::LabelSetImage* labelSetImage) + { + QStringList existingLabelNames; + existingLabelNames.reserve(labelSetImage->GetTotalNumberOfLabels()); + + const auto numLayers = labelSetImage->GetNumberOfLayers(); + for (std::remove_const_t layerIndex = 0; layerIndex < numLayers; ++layerIndex) + { + const auto* labelSet = labelSetImage->GetLabelSet(layerIndex); + + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + if (0 == labelIter->first) + continue; // Ignore background label + + auto name = QString::fromStdString(labelIter->second->GetName()); + + if (!name.isEmpty()) // Potential duplicates do not matter for our purpose + existingLabelNames.push_back(name); + } + } + + return existingLabelNames; + } + + // Remove blacklisted suggestions. + QmitkNewSegmentationDialog::SuggestionsType FilterSuggestions(const QmitkNewSegmentationDialog::SuggestionsType& suggestions, const QStringList& blacklist) + { + QmitkNewSegmentationDialog::SuggestionsType filteredSuggestions; + + std::remove_copy_if(suggestions.begin(), suggestions.end(), std::inserter(filteredSuggestions, filteredSuggestions.end()), [&blacklist](const auto& suggestion) { + return blacklist.contains(suggestion.first); + }); + + return filteredSuggestions; + } +} + +QmitkNewSegmentationDialog::QmitkNewSegmentationDialog(QWidget *parent, mitk::LabelSetImage* labelSetImage, Mode mode) + : QDialog(parent), + m_Ui(new Ui::QmitkNewSegmentationDialog), + m_SuggestOnce(true), + m_Color(Qt::red) +{ + m_Ui->setupUi(this); + + if (RenameLabel == mode) + { + this->setWindowTitle("Rename Label"); + m_Ui->label->setText("New name and color of the label"); + m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Rename label"); + } + else + { + m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Create label"); + } + + auto* completer = new QCompleter(QStringList()); + completer->setCaseSensitivity(Qt::CaseInsensitive); + + m_Ui->nameLineEdit->setCompleter(completer); + m_Ui->nameLineEdit->setFocus(); + + connect(completer, qOverload(&QCompleter::activated), this, qOverload(&QmitkNewSegmentationDialog::OnSuggestionSelected)); + connect(m_Ui->colorButton, &QToolButton::clicked, this, &QmitkNewSegmentationDialog::OnColorButtonClicked); + connect(m_Ui->buttonBox, &QDialogButtonBox::accepted, this, &QmitkNewSegmentationDialog::OnAccept); + + this->UpdateColorButtonBackground(); + + auto prefs = GetPreferences(); + + if (!prefs.labelSuggestions.isEmpty()) + { + auto suggestions = ParseSuggestions(prefs.labelSuggestions.toStdString()); + this->SetSuggestions(suggestions, prefs.replaceStandardSuggestions && !suggestions.empty()); + } + else + { + this->SetSuggestions(GetStandardSuggestions(), true); + } + + if (nullptr != labelSetImage && prefs.suggestOnce) + { + auto existingLabelNames = GetExistingLabelNames(labelSetImage); + m_Suggestions = FilterSuggestions(m_Suggestions, existingLabelNames); + + this->UpdateCompleterModel(); + } +} + +QmitkNewSegmentationDialog::~QmitkNewSegmentationDialog() +{ +} + +void QmitkNewSegmentationDialog::UpdateColorButtonBackground() +{ + m_Ui->colorButton->setStyleSheet("background-color:" + m_Color.name()); +} + +QString QmitkNewSegmentationDialog::GetName() const +{ + return m_Name; +} + +mitk::Color QmitkNewSegmentationDialog::GetColor() const +{ + mitk::Color color; + + if (m_Color.isValid()) + { + color.SetRed(m_Color.redF()); + color.SetGreen(m_Color.greenF()); + color.SetBlue(m_Color.blueF()); + } + else + { + color.Set(1.0f, 0.0f, 0.0f); + } + + return color; +} + +void QmitkNewSegmentationDialog::SetName(const QString& name) +{ + m_Ui->nameLineEdit->setText(name); +} + +void QmitkNewSegmentationDialog::SetColor(const mitk::Color& color) +{ + m_Color.setRgbF(color.GetRed(), color.GetGreen(), color.GetBlue()); + this->UpdateColorButtonBackground(); +} + +void QmitkNewSegmentationDialog::SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions) +{ + if (replaceStandardSuggestions) + { + m_Suggestions = suggestions; + } + else + { + m_Suggestions = GetStandardSuggestions(); + + for (const auto& [name, color] : suggestions) + { + if (m_Suggestions.end() == m_Suggestions.find(name)) + m_Suggestions[name] = color; + } + } + + this->UpdateCompleterModel(); +} + +void QmitkNewSegmentationDialog::UpdateCompleterModel() +{ + QStringList names; + + for (const auto& suggestion : m_Suggestions) + names << suggestion.first; + + auto* completerModel = static_cast(m_Ui->nameLineEdit->completer()->model()); + completerModel->setStringList(names); +} + +void QmitkNewSegmentationDialog::OnAccept() +{ + m_Name = m_Ui->nameLineEdit->text(); + this->accept(); +} + +void QmitkNewSegmentationDialog::OnColorButtonClicked() +{ + auto color = QColorDialog::getColor(m_Color); + + if (color.isValid()) + { + m_Color = color; + this->UpdateColorButtonBackground(); + } +} + +void QmitkNewSegmentationDialog::OnSuggestionSelected(const QString &name) +{ + auto color = m_Suggestions[name]; + + if (color.isValid()) + { + m_Color = color; + this->UpdateColorButtonBackground(); + } +} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h new file mode 100644 index 0000000000..9f188540d1 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h @@ -0,0 +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 QmitkNewSegmentationDialog_h +#define QmitkNewSegmentationDialog_h + +#include + +#include + +#include + +#include +#include +#include + +namespace mitk +{ + class LabelSetImage; +} + +namespace Ui +{ + class QmitkNewSegmentationDialog; +} + +/** + \brief Dialog for naming labels. +*/ +class MITK_QT_SEGMENTATION QmitkNewSegmentationDialog : public QDialog +{ + Q_OBJECT + +public: + using SuggestionsType = std::map; + + enum Mode + { + NewLabel, + RenameLabel + }; + + explicit QmitkNewSegmentationDialog(QWidget *parent = nullptr, mitk::LabelSetImage* labelSetImage = nullptr, Mode mode = NewLabel); + ~QmitkNewSegmentationDialog() override; + + QString GetName() const; + mitk::Color GetColor() const; + + void SetName(const QString& name); + void SetColor(const mitk::Color& color); + +private: + void OnAccept(); + void OnSuggestionSelected(const QString& name); + void OnColorButtonClicked(); + + void SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions = false); + void UpdateColorButtonBackground(); + void UpdateCompleterModel(); + + Ui::QmitkNewSegmentationDialog* m_Ui; + + bool m_SuggestOnce; + + QString m_Name; + QColor m_Color; + + SuggestionsType m_Suggestions; +}; + +#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.ui new file mode 100644 index 0000000000..ae71e75c55 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.ui @@ -0,0 +1,98 @@ + + + QmitkNewSegmentationDialog + + + + 0 + 0 + 250 + 105 + + + + Create Label + + + + + + + + Name and color of the label + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QmitkNewSegmentationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QmitkNewSegmentationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp index f77ebd2f21..0ea32131f3 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.cpp @@ -1,135 +1,174 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSegmentationPreferencePage.h" #include #include #include #include #include QmitkSegmentationPreferencePage::QmitkSegmentationPreferencePage() : m_Ui(new Ui::QmitkSegmentationPreferencePageControls), m_Control(nullptr), m_Initializing(false) { } QmitkSegmentationPreferencePage::~QmitkSegmentationPreferencePage() { } void QmitkSegmentationPreferencePage::Init(berry::IWorkbench::Pointer) { } void QmitkSegmentationPreferencePage::CreateQtControl(QWidget* parent) { m_Initializing = true; berry::IPreferencesService* prefService = berry::Platform::GetPreferencesService(); m_SegmentationPreferencesNode = prefService->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); m_Control = new QWidget(parent); m_Ui->setupUi(m_Control); connect(m_Ui->smoothingCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSmoothingCheckboxChecked(int))); connect(m_Ui->labelSetPresetToolButton, SIGNAL(clicked()), this, SLOT(OnLabelSetPresetButtonClicked())); + connect(m_Ui->suggestionsToolButton, SIGNAL(clicked()), this, SLOT(OnSuggestionsButtonClicked())); this->Update(); m_Initializing = false; } QWidget* QmitkSegmentationPreferencePage::GetQtControl() const { return m_Control; } bool QmitkSegmentationPreferencePage::PerformOk() { m_SegmentationPreferencesNode->PutBool("slim view", m_Ui->slimViewCheckBox->isChecked()); m_SegmentationPreferencesNode->PutBool("draw outline", m_Ui->outlineRadioButton->isChecked()); m_SegmentationPreferencesNode->PutBool("selection mode", m_Ui->selectionModeCheckBox->isChecked()); m_SegmentationPreferencesNode->PutBool("smoothing hint", m_Ui->smoothingCheckBox->isChecked()); m_SegmentationPreferencesNode->PutDouble("smoothing value", m_Ui->smoothingSpinBox->value()); m_SegmentationPreferencesNode->PutDouble("decimation rate", m_Ui->decimationSpinBox->value()); m_SegmentationPreferencesNode->PutDouble("closing ratio", m_Ui->closingSpinBox->value()); m_SegmentationPreferencesNode->Put("label set preset", m_Ui->labelSetPresetLineEdit->text()); + m_SegmentationPreferencesNode->PutBool("default label naming", m_Ui->defaultNameRadioButton->isChecked()); + m_SegmentationPreferencesNode->Put("label suggestions", m_Ui->suggestionsLineEdit->text()); + m_SegmentationPreferencesNode->PutBool("replace standard suggestions", m_Ui->replaceStandardSuggestionsCheckBox->isChecked()); + m_SegmentationPreferencesNode->PutBool("suggest once", m_Ui->suggestOnceCheckBox->isChecked()); return true; } void QmitkSegmentationPreferencePage::PerformCancel() { } void QmitkSegmentationPreferencePage::Update() { m_Ui->slimViewCheckBox->setChecked(m_SegmentationPreferencesNode->GetBool("slim view", false)); if (m_SegmentationPreferencesNode->GetBool("draw outline", true)) { m_Ui->outlineRadioButton->setChecked(true); } else { m_Ui->overlayRadioButton->setChecked(true); } m_Ui->selectionModeCheckBox->setChecked(m_SegmentationPreferencesNode->GetBool("selection mode", false)); if (m_SegmentationPreferencesNode->GetBool("smoothing hint", true)) { m_Ui->smoothingCheckBox->setChecked(true); m_Ui->smoothingSpinBox->setDisabled(true); } else { m_Ui->smoothingCheckBox->setChecked(false); m_Ui->smoothingSpinBox->setEnabled(true); } m_Ui->smoothingSpinBox->setValue(m_SegmentationPreferencesNode->GetDouble("smoothing value", 1.0)); m_Ui->decimationSpinBox->setValue(m_SegmentationPreferencesNode->GetDouble("decimation rate", 0.5)); m_Ui->closingSpinBox->setValue(m_SegmentationPreferencesNode->GetDouble("closing ratio", 0.0)); auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); - bool isOverridenByCmdLineArg = !labelSetPreset.empty(); + bool isOverriddenByCmdLineArg = !labelSetPreset.empty(); - if (!isOverridenByCmdLineArg) + if (!isOverriddenByCmdLineArg) labelSetPreset = m_SegmentationPreferencesNode->Get("label set preset", "").toStdString(); - m_Ui->labelSetPresetLineEdit->setDisabled(isOverridenByCmdLineArg); - m_Ui->labelSetPresetToolButton->setDisabled(isOverridenByCmdLineArg); - m_Ui->labelSetPresetCmdLineArgLabel->setVisible(isOverridenByCmdLineArg); + m_Ui->labelSetPresetLineEdit->setDisabled(isOverriddenByCmdLineArg); + m_Ui->labelSetPresetToolButton->setDisabled(isOverriddenByCmdLineArg); + m_Ui->labelSetPresetCmdLineArgLabel->setVisible(isOverriddenByCmdLineArg); m_Ui->labelSetPresetLineEdit->setText(QString::fromStdString(labelSetPreset)); + + if (m_SegmentationPreferencesNode->GetBool("default label naming", true)) + { + m_Ui->defaultNameRadioButton->setChecked(true); + } + else + { + m_Ui->askForNameRadioButton->setChecked(true); + } + + auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); + isOverriddenByCmdLineArg = !labelSuggestions.empty(); + + if (!isOverriddenByCmdLineArg) + labelSuggestions = m_SegmentationPreferencesNode->Get("label suggestions", "").toStdString(); + + m_Ui->defaultNameRadioButton->setDisabled(isOverriddenByCmdLineArg); + m_Ui->askForNameRadioButton->setDisabled(isOverriddenByCmdLineArg); + m_Ui->suggestionsLineEdit->setDisabled(isOverriddenByCmdLineArg); + m_Ui->suggestionsToolButton->setDisabled(isOverriddenByCmdLineArg); + m_Ui->suggestionsCmdLineArgLabel->setVisible(isOverriddenByCmdLineArg); + + m_Ui->suggestionsLineEdit->setText(QString::fromStdString(labelSuggestions)); + + m_Ui->replaceStandardSuggestionsCheckBox->setChecked(m_SegmentationPreferencesNode->GetBool("replace standard suggestions", true)); + m_Ui->suggestOnceCheckBox->setChecked(m_SegmentationPreferencesNode->GetBool("suggest once", true)); } void QmitkSegmentationPreferencePage::OnSmoothingCheckboxChecked(int state) { if (state != Qt::Unchecked) m_Ui->smoothingSpinBox->setDisabled(true); else m_Ui->smoothingSpinBox->setEnabled(true); } void QmitkSegmentationPreferencePage::OnLabelSetPresetButtonClicked() { const auto filename = QFileDialog::getOpenFileName(m_Control, QStringLiteral("Load Label Set Preset"), QString(), QStringLiteral("Label set preset (*.lsetp)")); if (!filename.isEmpty()) m_Ui->labelSetPresetLineEdit->setText(filename); } + +void QmitkSegmentationPreferencePage::OnSuggestionsButtonClicked() +{ + const auto filename = QFileDialog::getOpenFileName(m_Control, QStringLiteral("Load Label Suggestions"), QString(), QStringLiteral("Label suggestions (*.json)")); + + if (!filename.isEmpty()) + m_Ui->suggestionsLineEdit->setText(filename); +} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h index fc6406faa2..8c04073a86 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePage.h @@ -1,65 +1,66 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 QMITKSEGMENTATIONPREFERENCEPAGE_H #define QMITKSEGMENTATIONPREFERENCEPAGE_H #include "org_mitk_gui_qt_segmentation_Export.h" #include #include class QWidget; namespace Ui { class QmitkSegmentationPreferencePageControls; } class MITK_QT_SEGMENTATION QmitkSegmentationPreferencePage : public QObject, public berry::IQtPreferencePage { Q_OBJECT Q_INTERFACES(berry::IPreferencePage) public: QmitkSegmentationPreferencePage(); ~QmitkSegmentationPreferencePage() override; void Init(berry::IWorkbench::Pointer workbench) override; void CreateQtControl(QWidget* widget) override; QWidget* GetQtControl() const override; bool PerformOk() override; void PerformCancel() override; void Update() override; protected Q_SLOTS: void OnSmoothingCheckboxChecked(int); void OnLabelSetPresetButtonClicked(); + void OnSuggestionsButtonClicked(); protected: Ui::QmitkSegmentationPreferencePageControls* m_Ui; QWidget* m_Control; bool m_Initializing; berry::IPreferences::Pointer m_SegmentationPreferencesNode; }; #endif // QMITKSEGMENTATIONPREFERENCEPAGE_H diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui index 7728e50e20..ee4674f966 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentationPreferencePageControls.ui @@ -1,205 +1,323 @@ QmitkSegmentationPreferencePageControls 0 0 - 640 - 480 + 656 + 779 Form Slim view Hide tool button texts and increase icon size 2D display Draw as outline + + true + + + displayButtonGroup + Draw as transparent overlay + + displayButtonGroup + Data node selection mode If checked the segmentation plugin ensures that only the selected segmentation and the reference image are visible at one time. Show only selected nodes Smoothed surface creation 8 8 Smoothing value (mm) The Smoothing value is used as variance for a gaussian blur. 0.500000000000000 1.000000000000000 Decimation rate Valid range is [0, 1). High values increase decimation, especially when very close to 1. A value of 0 disables decimation. 0.990000000000000 0.100000000000000 0.500000000000000 Closing Ratio Valid range is [0, 1]. Higher values increase closing. A value of 0 disables closing. 1.000000000000000 0.100000000000000 If checked the segmentation plugin ensures that only the selected segmentation and the reference image are visible at one time. Use image spacing as smoothing value hint Default label set preset true ... <html><head/><body><p><span style=" color:#ff0000;">The default label set preset is currently overriden by the </span><span style=" font-family:'Courier New'; color:#ff0000;">Segmentation.labelSetPreset</span><span style=" color:#ff0000;"> command-line argument.</span></p></body></html> Qt::RichText true + + + + Label creation + + + + + + + + + Assign default name and color + + + true + + + labelCreationButtonGroup + + + + + + + Ask for name and color + + + labelCreationButtonGroup + + + + + + + + + Label suggestions + + + + + + + + + true + + + + + + + ... + + + + + + + <html><head/><body><p><span style=" color:#ff0000;">Suggestions are currently enforced by the </span><span style=" font-family:'Courier New'; color:#ff0000;">Segmentation.labelSuggestions</span><span style=" color:#ff0000;"> command-line argument.</span></p></body></html> + + + Qt::RichText + + + true + + + + + + + + + Replace standard organ suggestions + + + true + + + + + + + Suggest once per segmentation + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp index 3345f642f1..9e354d92ae 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp @@ -1,173 +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. ============================================================================*/ #include "QmitkLabelsWidget.h" #include // mitk #include #include #include #include #include // Qmitk #include #include +#include #include "../QmitkSaveMultiLabelPresetAction.h" #include "../QmitkLoadMultiLabelPresetAction.h" // Qt #include QmitkLabelsWidget::QmitkLabelsWidget(QWidget *parent) : QWidget(parent) , m_Controls(new Ui::QmitkLabelsWidgetControls) , m_ToolManager(nullptr) + , m_DefaultLabelNaming(true) { m_Controls->setupUi(this); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_Controls->savePresetButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->loadPresetButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); connect(m_Controls->newLabelButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnNewLabel); connect(m_Controls->lockExteriorButton, &QToolButton::toggled, this, &QmitkLabelsWidget::OnLockExterior); connect(m_Controls->savePresetButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnSavePreset); connect(m_Controls->loadPresetButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnLoadPreset); connect(m_Controls->showLabelTableButton, &QToolButton::toggled, this, &QmitkLabelsWidget::ShowLabelTable); this->UpdateGUI(); } QmitkLabelsWidget::~QmitkLabelsWidget() { delete m_Controls; } void QmitkLabelsWidget::UpdateGUI() { m_Controls->newLabelButton->setEnabled(false); m_Controls->lockExteriorButton->setEnabled(false); m_Controls->lockExteriorButton->setChecked(false); m_Controls->savePresetButton->setEnabled(false); m_Controls->loadPresetButton->setEnabled(false); m_Controls->showLabelTableButton->setEnabled(false); m_Controls->showLabelTableButton->setChecked(false); mitk::LabelSetImage* workingImage = this->GetWorkingImage(); if (nullptr == workingImage) { return; } int activeLayer = workingImage->GetActiveLayer(); m_Controls->lockExteriorButton->setEnabled(true); m_Controls->lockExteriorButton->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); m_Controls->showLabelTableButton->setEnabled(true); m_Controls->showLabelTableButton->setChecked(true); m_Controls->newLabelButton->setEnabled(true); m_Controls->savePresetButton->setEnabled(true); m_Controls->loadPresetButton->setEnabled(true); } +void QmitkLabelsWidget::SetDefaultLabelNaming(bool defaultLabelNaming) +{ + m_DefaultLabelNaming = defaultLabelNaming; +} + mitk::LabelSetImage* QmitkLabelsWidget::GetWorkingImage() { mitk::DataNode* workingNode = this->GetWorkingNode(); if (nullptr == workingNode) { return nullptr; } auto workingImage = dynamic_cast(workingNode->GetData()); return workingImage; } mitk::DataNode* QmitkLabelsWidget::GetWorkingNode() { mitk::DataNode* referenceNode = m_ToolManager->GetWorkingData(0); return referenceNode; } void QmitkLabelsWidget::OnNewLabel() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = this->GetWorkingNode(); if (nullptr == workingNode) { return; } auto workingImage = dynamic_cast(workingNode->GetData()); if (nullptr == workingImage) { return; } - this->WaitCursorOn(); mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(workingImage); + + if (!m_DefaultLabelNaming) + { + QmitkNewSegmentationDialog dialog(this, workingImage); + dialog.SetName(QString::fromStdString(newLabel->GetName())); + dialog.SetColor(newLabel->GetColor()); + + if (QDialog::Rejected == dialog.exec()) + return; + + auto name = dialog.GetName(); + + if (!name.isEmpty()) + newLabel->SetName(name.toStdString()); + + newLabel->SetColor(dialog.GetColor()); + } + workingImage->GetActiveLabelSet()->AddLabel(newLabel); - this->WaitCursorOff(); this->UpdateGUI(); emit LabelsChanged(); } void QmitkLabelsWidget::OnLockExterior(bool checked) { auto workingImage = this->GetWorkingImage(); if (nullptr == workingImage) { return; } workingImage->GetLabel(0)->SetLocked(checked); } void QmitkLabelsWidget::OnSavePreset() { auto workingNode = this->GetWorkingNode(); QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(workingNode); QmitkSaveMultiLabelPresetAction action; action.Run(nodes); } void QmitkLabelsWidget::OnLoadPreset() { auto workingNode = this->GetWorkingNode(); QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(workingNode); QmitkLoadMultiLabelPresetAction action; action.Run(nodes); } void QmitkLabelsWidget::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkLabelsWidget::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkLabelsWidget::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h index bd74ae2ead..127775d686 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h @@ -1,82 +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. ============================================================================*/ #ifndef QMITKLABELSWIDGET_H #define QMITKLABELSWIDGET_H // mitk core #include // Qt #include namespace Ui { class QmitkLabelsWidgetControls; } namespace mitk { class DataNode; class Image; class LabelSetImage; class ToolManager; } class QmitkLabelsWidget : public QWidget { Q_OBJECT public: explicit QmitkLabelsWidget(QWidget* parent = nullptr); ~QmitkLabelsWidget() override; void UpdateGUI(); + void SetDefaultLabelNaming(bool defaultLabelNaming); + Q_SIGNALS: void LabelsChanged(); void ShowLabelTable(bool); private: mitk::LabelSetImage* GetWorkingImage(); mitk::DataNode* GetWorkingNode(); // reaction to button "New Label" void OnNewLabel(); // reaction to the button "Lock exterior" void OnLockExterior(bool); // reaction to button "Save Preset" void OnSavePreset(); // reaction to button "Load Preset" void OnLoadPreset(); void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); Ui::QmitkLabelsWidgetControls* m_Controls; mitk::ToolManager* m_ToolManager; + bool m_DefaultLabelNaming; + }; #endif 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 64a896ca97..98faec74c8 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,963 +1,989 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 #include #include // Qmitk #include -#include #include +#include // us #include #include // Qt #include #include #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_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) + , m_DefaultLabelNaming(true) { 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"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { OnLooseLabelSetConnection(); // 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->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->workingNodeSelector->SetNodePredicate(segPredicate); if (m_SelectionMode) { // 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_SelectionMode) { // hide all segmentation nodes to later show only the 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->labelSetWidget->ResetAllTableWidgetItems(); 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::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_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(m_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; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { - mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); + auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); + + if (!m_DefaultLabelNaming) + { + QmitkNewSegmentationDialog dialog(m_Parent); + dialog.SetName(QString::fromStdString(newLabel->GetName())); + dialog.SetColor(newLabel->GetColor()); + + if (QDialog::Rejected == dialog.exec()) + return; + + auto name = dialog.GetName(); + + if (!name.isEmpty()) + newLabel->SetName(name.toStdString()); + + newLabel->SetColor(dialog.GetColor()); + } + newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } 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 (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnLayersChanged() { this->OnEstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); } void QmitkSegmentationView::OnShowLabelTable(bool value) { m_Controls->labelSetWidget->setVisible(value); } void QmitkSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->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->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ 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'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' 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())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); m_Controls->toolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); m_Controls->toolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->layersWidget, &QmitkLayersWidget::LayersChanged, this, &QmitkSegmentationView::OnLayersChanged); connect(m_Controls->labelsWidget, &QmitkLabelsWidget::ShowLabelTable, this, &QmitkSegmentationView::OnShowLabelTable); // *------------------------ // * LABELSETWIDGET // *------------------------ connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::LabelSetWidgetReset, this, &QmitkSegmentationView::OnLabelSetWidgetReset); m_Controls->labelSetWidget->SetDataStorage(this->GetDataStorage()); - m_Controls->labelSetWidget->SetOrganColors(mitk::OrganNamesHandling::GetDefaultOrganColorString()); m_Controls->labelSetWidget->hide(); 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->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } // tell the interpolation about tool manager, data storage and render window part if (nullptr != m_RenderWindowPart) { 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->slicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { + auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); + + m_DefaultLabelNaming = labelSuggestions.empty() + ? prefs->GetBool("default label naming", true) + : false; // No default label naming when label suggestions are enforced via command-line argument + if (nullptr != m_Controls) { + m_Controls->labelsWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); + bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = prefs->Get("label set preset", ""); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { this->ApplyDisplayOptions(const_cast(node)); } } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // 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); } void QmitkSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls->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, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls->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, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } 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 labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is a multi label segmentation // its outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline node->GetData()->Modified(); } else if (nullptr != node->GetData()) { // node is a legacy binary segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", mitk::BoolProperty::New(m_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* threeDRenderWindow = 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, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != 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 (0 == nodes.size()) { return; } 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->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } m_Controls->layersWidget->UpdateGUI(); m_Controls->labelsWidget->UpdateGUI(); this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); m_Controls->layersWidget->setEnabled(false); m_Controls->labelsWidget->setEnabled(false); m_Controls->labelSetWidget->setEnabled(false); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->toolSelectionBox2D->setEnabled(false); m_Controls->toolSelectionBox3D->setEnabled(false); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); mitk::DataNode* referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->workingNodeSelector->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->layersWidget->setEnabled(true); m_Controls->labelsWidget->setEnabled(true); m_Controls->labelSetWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr != labelSetImage) { int numberOfLabels = labelSetImage->GetNumberOfLabels(labelSetImage->GetActiveLayer()); if (2 == numberOfLabels) // fix for T27319: exterior is label 0, first label is label 1 { m_Controls->slicesInterpolator->setEnabled(true); } else { m_Controls->interpolatorWarningLabel->show(); m_Controls->interpolatorWarningLabel->setText("Interpolation only works for single label segmentations."); } } 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->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->show(); m_Controls->selectionWarningLabel->setText("" + text + ""); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h index 4a9c65a86b..7874805f2d 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h @@ -1,152 +1,153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKSEGMENTATIONVIEW_H #define QMITKSEGMENTATIONVIEW_H #include "ui_QmitkSegmentationViewControls.h" #include #include #include /** * @brief The segmentation view provides a set of tool to use different segmentation algorithms. * It provides two selection widgets to load an image node and a segmentation node * on which to perform the segmentation. Creating new segmentation nodes is also possible. * The available segmentation tools are grouped into "2D"- and "3D"-tools. * * Most segmentation tools / algorithms need some kind of user interaction, where the * user is asked to draw something in the image display or set some seed points / start values. * The tools also often provide additional propeties so that a user can modify the * algorithm's behavior. * * This class additionally provides options to work with different layers (create new layers, * switch between layers). * Moreover, a multilabel widget displays all the existing labels of a multilabel segmentation * for the currently active layer. * The multilabel widget allows to control the labels by creatin new one, removing existing ones, * showing / hiding single labels, merging labels, (re-)naming them etc. * * Additionally the view provides an option to create "2D"- and "3D"-interpolations between * neighboring segmentation masks on unsegmented slices. * Interpolation for multilabel segmentations is currently not implemented. */ class QmitkSegmentationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkSegmentationView(); ~QmitkSegmentationView() override; private Q_SLOTS: // reaction to the selection of a new reference image in the selection widget void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new segmentation image in the selection widget void OnSegmentationSelectionChanged(QList nodes); // reaction to the shortcut ("CTRL+H") for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut ("CTRL+L") for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the button "New segmentation" void OnNewSegmentation(); void OnManualTool2DSelected(int id); void OnShowMarkerNodes(bool); void OnLayersChanged(); void OnShowLabelTable(bool); void OnGoToLabel(const mitk::Point3D &pos); void OnLabelSetWidgetReset(); private: void CreateQtPartControl(QWidget* parent) override; void SetFocus() override {} void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; void OnPreferencesChanged(const berry::IBerryPreferences* prefs) override; void NodeAdded(const mitk::DataNode* node) override; void NodeRemoved(const mitk::DataNode* node) override; void OnEstablishLabelSetConnection(); void OnLooseLabelSetConnection(); // make sure all images / segmentations look according to the user preference settings void ApplyDisplayOptions(); // decorates a DataNode according to the user preference settings void ApplyDisplayOptions(mitk::DataNode* node); // If a contourmarker is selected, the plane in the related widget will be reoriented according to the marker`s geometry void OnContourMarkerSelected(const mitk::DataNode* node); void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) override; void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource&, int hotspotX, int hotspotY); void UpdateGUI(); void ValidateSelectionInput(); void UpdateWarningLabel(QString text); std::string GetDefaultLabelSetPreset() const; QWidget* m_Parent; Ui::QmitkSegmentationViewControls* m_Controls; mitk::IRenderWindowPart* m_RenderWindowPart; mitk::ToolManager* m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; typedef std::map NodeTagMapType; NodeTagMapType m_WorkingDataObserverTags; unsigned int m_RenderingManagerObserverTag; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_DrawOutline; bool m_SelectionMode; bool m_MouseCursorSet; QString m_LabelSetPresetPreference; + bool m_DefaultLabelNaming; }; #endif // QMITKSEGMENTATIONVIEW_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake index 9da36a3fbc..a413decc69 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "MITK Semantic relations") set(Plugin-Version "0.1") set(Plugin-Vendor "DKFZ") set(Plugin-ContactAddress "http://www.mitk.org") -set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.application) +set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.application org.mitk.gui.qt.segmentation) diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp index 08102751a3..4570a97efb 100644 --- a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp @@ -1,495 +1,495 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // semantic relations plugin #include "QmitkLesionInfoWidget.h" #include "QmitkDataNodeAddToSemanticRelationsAction.h" #include "QmitkFocusOnLesionAction.h" #include "QmitkSemanticRelationsNodeSelectionDialog.h" // semantic relations UI module #include // semantic relations module #include #include #include #include #include // segmentation #include #include // qt #include #include #include #include #include QmitkLesionInfoWidget::QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, berry::IWorkbenchPartSite::Pointer workbenchPartSite, QWidget* parent /*= nullptr*/) : QWidget(parent) , m_DataStorage(dataStorage) , m_WorkbenchPartSite(workbenchPartSite) , m_SemanticRelationsDataStorageAccess(std::make_unique(dataStorage)) , m_SemanticRelationsIntegration(std::make_unique()) { Initialize(); } void QmitkLesionInfoWidget::Initialize() { m_Controls.setupUi(this); m_Controls.lesionTreeView->setAlternatingRowColors(true); m_Controls.lesionTreeView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.lesionTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.lesionTreeView->setContextMenuPolicy(Qt::CustomContextMenu); m_StorageModel = new QmitkLesionTreeModel(m_Controls.lesionTreeView); auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } m_StorageModel->SetDataStorage(dataStorage); m_Controls.lesionTreeView->setModel(m_StorageModel); SetUpConnections(); } void QmitkLesionInfoWidget::SetUpConnections() { connect(m_StorageModel, &QmitkLesionTreeModel::ModelUpdated, this, &QmitkLesionInfoWidget::OnModelUpdated); connect(m_Controls.addLesionPushButton, &QPushButton::clicked, this, &QmitkLesionInfoWidget::OnAddLesionButtonClicked); connect(m_Controls.lesionTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QmitkLesionInfoWidget::OnSelectionChanged); connect(m_Controls.lesionTreeView, &QTreeView::customContextMenuRequested, this, &QmitkLesionInfoWidget::OnLesionListContextMenuRequested); } void QmitkLesionInfoWidget::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) { m_CaseID = caseID; m_StorageModel->SetCaseID(caseID); } void QmitkLesionInfoWidget::SetDataNodeSelection(const QList& dataNodeSelection) { m_StorageModel->SetDataNodeSelection(dataNodeSelection); } ////////////////////////////////////////////////////////////////////////// // Implementation of the QT_SLOTS ////////////////////////////////////////////////////////////////////////// void QmitkLesionInfoWidget::OnModelUpdated() { m_Controls.lesionTreeView->expandAll(); int columns = m_Controls.lesionTreeView->model()->columnCount(); for (int i = 0; i < columns; ++i) { m_Controls.lesionTreeView->resizeColumnToContents(i); } } void QmitkLesionInfoWidget::OnAddLesionButtonClicked() { if (m_CaseID.empty()) { QMessageBox msgBox(QMessageBox::Warning, "No case ID set.", "In order to add a lesion, please specify the current case / patient."); msgBox.exec(); return; } mitk::SemanticTypes::Lesion newLesion = mitk::GenerateNewLesion(); try { m_SemanticRelationsIntegration->AddLesion(m_CaseID, newLesion); } catch (mitk::SemanticRelationException& e) { MITK_INFO << "Could not add a new lesion. " << e; } } void QmitkLesionInfoWidget::OnSelectionChanged(const QModelIndex& current, const QModelIndex& /*previous*/) { // only the UID is needed to identify a representing lesion QVariant data = m_StorageModel->data(current, Qt::UserRole); if (!data.canConvert()) { return; } auto lesion = data.value()->GetData().GetLesion(); if (false == mitk::SemanticRelationsInference::InstanceExists(m_CaseID, lesion)) { // no UID of a existing lesion found; cannot create a lesion return; } // if selected data nodes are set, reset to empty list to // hide "selected data nodes presence background highlighting" in the model if (!m_StorageModel->GetSelectedDataNodes().isEmpty()) { m_StorageModel->SetDataNodeSelection(QList()); } emit LesionSelectionChanged(lesion); } void QmitkLesionInfoWidget::OnLesionListContextMenuRequested(const QPoint& pos) { if (nullptr == m_SemanticRelationsIntegration) { return; } if (m_CaseID.empty()) { QMessageBox msgBox(QMessageBox::Warning, "No case ID set.", "In order to access the context menu entries a case ID has to be set."); msgBox.exec(); return; } QModelIndex index = m_Controls.lesionTreeView->indexAt(pos); if (!index.isValid()) { // no item clicked; cannot retrieve the current lesion return; } QVariant data = m_StorageModel->data(index, Qt::UserRole); mitk::SemanticTypes::Lesion selectedLesion; if (data.canConvert()) { selectedLesion = data.value()->GetData().GetLesion(); } else { return; } QMenu* menu = new QMenu(m_Controls.lesionTreeView); QAction* linkToSegmentation = new QAction("Link to segmentation", this); linkToSegmentation->setEnabled(true); connect(linkToSegmentation, &QAction::triggered, [this, selectedLesion] { OnLinkToSegmentation(selectedLesion); }); menu->addAction(linkToSegmentation); QAction* setLesionName = new QAction("Set lesion name", this); setLesionName->setEnabled(true); connect(setLesionName, &QAction::triggered, [this, selectedLesion] { OnSetLesionName(selectedLesion); }); menu->addAction(setLesionName); QAction* setLesionClass = new QAction("Set lesion class", this); setLesionClass->setEnabled(true); connect(setLesionClass, &QAction::triggered, [this, selectedLesion] { OnSetLesionClass(selectedLesion); }); menu->addAction(setLesionClass); QAction* createNewSegmentation = new QAction("Create new lesion", this); createNewSegmentation->setEnabled(true); connect(createNewSegmentation, &QAction::triggered, [this, selectedLesion] { OnCreateNewSegmentation(selectedLesion); }); menu->addAction(createNewSegmentation); QAction* removeLesion = new QAction("Remove lesion", this); removeLesion->setEnabled(true); connect(removeLesion, &QAction::triggered, [this, selectedLesion] { OnRemoveLesion(selectedLesion); }); menu->addAction(removeLesion); auto workbenchPartSite = m_WorkbenchPartSite.Lock(); if (workbenchPartSite.IsNotNull()) { QmitkFocusOnLesionAction* focusOnLesion = new QmitkFocusOnLesionAction(this, workbenchPartSite); focusOnLesion->SetDataStorage(m_DataStorage.Lock()); focusOnLesion->SetSelectedLesion(selectedLesion); menu->addAction(focusOnLesion); } menu->popup(QCursor::pos()); } void QmitkLesionInfoWidget::OnLinkToSegmentation(mitk::SemanticTypes::Lesion selectedLesion) { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select segmentation to link to the selected lesion.", ""); dialog->setWindowTitle("Select segmentation node"); dialog->SetDataStorage(dataStorage); dialog->SetNodePredicate(mitk::NodePredicates::GetSegmentationPredicate()); dialog->SetSelectOnlyVisibleNodes(true); dialog->SetCaseID(m_CaseID); // set the last added segmentation node as pre-selected data node const mitk::DataNode* lastSegmentation = m_StorageModel->GetLastSegmentation(); QList selectedDataNodes; if (nullptr != lastSegmentation) { selectedDataNodes.push_back(const_cast(lastSegmentation)); dialog->SetCurrentSelection(selectedDataNodes); } int dialogReturnValue = dialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } mitk::DataNode::Pointer selectedDataNode = nullptr; selectedDataNodes = dialog->GetSelectedNodes(); if (!selectedDataNodes.isEmpty()) { // only single selection allowed selectedDataNode = selectedDataNodes.front(); } if (nullptr == selectedDataNode || false == mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(selectedDataNode)) { QMessageBox msgBox(QMessageBox::Warning, "No valid segmentation node selected.", "In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.exec(); return; } mitk::BaseData* baseData = selectedDataNode->GetData(); if (nullptr == baseData) { QMessageBox msgBox(QMessageBox::Warning, "No valid base data.", "In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); msgBox.exec(); return; } LinkSegmentationToLesion(selectedDataNode, selectedLesion); } void QmitkLesionInfoWidget::OnSetLesionName(mitk::SemanticTypes::Lesion selectedLesion) { // use the lesion information to set the input text for the dialog QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); inputDialog->setWindowTitle("Set lesion name"); inputDialog->SetLineEditText(selectedLesion.name); int dialogReturnValue = inputDialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } selectedLesion.name = inputDialog->GetLineEditText().toStdString(); m_SemanticRelationsIntegration->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnSetLesionClass(mitk::SemanticTypes::Lesion selectedLesion) { // use the lesion information to set the input text for the dialog QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); inputDialog->setWindowTitle("Set lesion class"); inputDialog->SetLineEditText(selectedLesion.lesionClass.classType); // prepare the completer for the dialogs input text field mitk::LesionClassVector allLesionClasses = mitk::SemanticRelationsInference::GetAllLesionClassesOfCase(m_CaseID); QStringList wordList; for (const auto& lesionClass : allLesionClasses) { wordList << QString::fromStdString(lesionClass.classType); } QCompleter* completer = new QCompleter(wordList, this); completer->setCaseSensitivity(Qt::CaseInsensitive); inputDialog->GetLineEdit()->setCompleter(completer); int dialogReturnValue = inputDialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } // retrieve the new input lesion class type and check for an already existing lesion class types std::string newLesionClassType = inputDialog->GetLineEditText().toStdString(); mitk::SemanticTypes::LesionClass existingLesionClass = mitk::FindExistingLesionClass(m_CaseID, newLesionClassType); if (existingLesionClass.UID.empty()) { // could not find lesion class information for the new lesion class type // create a new lesion class for the selected lesion existingLesionClass = mitk::GenerateNewLesionClass(newLesionClassType); } selectedLesion.lesionClass = existingLesionClass; m_SemanticRelationsIntegration->OverwriteLesion(m_CaseID, selectedLesion); } void QmitkLesionInfoWidget::OnCreateNewSegmentation(mitk::SemanticTypes::Lesion selectedLesion) { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select image to segment lesion on.", ""); dialog->setWindowTitle("Select image node"); dialog->SetDataStorage(dataStorage); dialog->SetNodePredicate(mitk::NodePredicates::GetImagePredicate()); dialog->SetSelectOnlyVisibleNodes(true); dialog->SetCaseID(m_CaseID); dialog->SetLesion(selectedLesion); int dialogReturnValue = dialog->exec(); if (QDialog::Rejected == dialogReturnValue) { return; } auto nodes = dialog->GetSelectedNodes(); mitk::DataNode::Pointer selectedDataNode = nullptr; if (!nodes.isEmpty()) { // only single selection allowed selectedDataNode = nodes.front(); } if (nullptr == selectedDataNode || false == mitk::NodePredicates::GetImagePredicate()->CheckNode(selectedDataNode)) { QMessageBox msgBox(QMessageBox::Warning, "No valid image node selected.", "In order to create a new segmentation, please specify a valid image node."); msgBox.exec(); return; } mitk::Image* selectedImage = dynamic_cast(selectedDataNode->GetData()); if (nullptr == selectedImage) { QMessageBox msgBox(QMessageBox::Warning, "No valid image.", "In order to create a new segmentation, please specify a valid image node."); msgBox.exec(); return; } mitk::LabelSetImage::Pointer segmentation = mitk::LabelSetImage::New(); try { segmentation->Initialize(selectedImage); } catch (mitk::Exception& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not initialize segmentation.", "The segmentation could not be correctly initialized with the selected image geometry.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); return; } auto segmentationDialog = new QmitkNewSegmentationDialog(this); segmentationDialog->setWindowTitle("New lesion segmentation"); dialogReturnValue = segmentationDialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } - QString segmentatioName = segmentationDialog->GetSegmentationName(); + QString segmentatioName = segmentationDialog->GetName(); if (segmentatioName.isEmpty()) { segmentatioName = "Unnamed"; } segmentation->GetActiveLabelSet()->AddLabel(segmentatioName.toStdString(), segmentationDialog->GetColor()); mitk::DataNode::Pointer segmentationNode = mitk::DataNode::New(); segmentationNode->SetData(segmentation); segmentationNode->SetName(segmentatioName.toStdString()); dataStorage->Add(segmentationNode, selectedDataNode); LinkSegmentationToLesion(segmentationNode, selectedLesion); } void QmitkLesionInfoWidget::OnRemoveLesion(mitk::SemanticTypes::Lesion selectedLesion) { try { m_SemanticRelationsIntegration->RemoveLesion(m_CaseID, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not remove the selected lesion.", "The program wasn't able to correctly remove the selected lesion from the semantic relations model.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); } } void QmitkLesionInfoWidget::LinkSegmentationToLesion(const mitk::DataNode* selectedDataNode, mitk::SemanticTypes::Lesion selectedLesion) { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } // if the segmentation is not contained in the semantic relations, add it if (!mitk::SemanticRelationsInference::InstanceExists(selectedDataNode)) { try { AddToSemanticRelationsAction::Run(dataStorage, selectedDataNode); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not link the selected lesion.", "The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str() + "\n")); msgBox.exec(); } } // link the segmentation try { m_SemanticRelationsIntegration->LinkSegmentationToLesion(selectedDataNode, selectedLesion); } catch (const mitk::SemanticRelationException& e) { std::stringstream exceptionMessage; exceptionMessage << e; QMessageBox msgBox(QMessageBox::Warning, "Could not link the selected lesion.", "The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" "Reason:\n" + QString::fromStdString(exceptionMessage.str())); msgBox.exec(); } }