diff --git a/CMake/Whitelists/CoreCmdApps.cmake b/CMake/Whitelists/CoreCmdApps.cmake index f74c63a9c6..f76813ad56 100644 --- a/CMake/Whitelists/CoreCmdApps.cmake +++ b/CMake/Whitelists/CoreCmdApps.cmake @@ -1,25 +1,20 @@ include(${CMAKE_CURRENT_LIST_DIR}/Minimal.cmake) list(APPEND enabled_modules AlgorithmsExt Annotation CommandLine ContourModel CoreCmdApps DataTypesExt DICOMPM - DICOMPMIO DICOMQI DICOM - DICOMImageIO RT - DICOMRTIO - DICOMSegIO ModelFit Multilabel - MultilabelIO LegacyGL SceneSerialization SceneSerializationBase + ROI ) - diff --git a/Modules/Core/CMakeLists.txt b/Modules/Core/CMakeLists.txt index 74cada7281..7bc61bab26 100644 --- a/Modules/Core/CMakeLists.txt +++ b/Modules/Core/CMakeLists.txt @@ -1,74 +1,83 @@ 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(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.1) + set(optional_public_target_depends stdc++fs) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + set(optional_public_target_depends c++fs) +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 MitkLog CppMicroServices PACKAGE_DEPENDS PUBLIC Boost nlohmann_json ITK|IOImageBase+SpatialObjects+Statistics #ITK|Statistics+Transform VTK|FiltersTexture+FiltersParallel+ImagingStencil+ImagingMath+InteractionStyle+RenderingOpenGL2+RenderingVolumeOpenGL2+RenderingFreeType+RenderingLabel+InteractionWidgets+IOGeometry+IOImage+IOXML PRIVATE ITK|IOBioRad+IOBMP+IOBruker+IOCSV+IOGDCM+IOGE+IOGIPL+IOHDF5+IOIPL+IOJPEG+IOJPEG2000+IOLSM+IOMesh+IOMeta+IOMINC+IOMRC+IONIFTI+IONRRD+IOPNG+IOSiemens+IOSpatialObjects+IOStimulate+IOTIFF+IOTransformBase+IOTransformHDF5+IOTransformInsightLegacy+IOTransformMatlab+IOVTK+IOXML tinyxml2 ${optional_private_package_depends} + TARGET_DEPENDS + PUBLIC + ${optional_public_target_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/include/mitkFileSystem.h b/Modules/Core/include/mitkFileSystem.h new file mode 100644 index 0000000000..e7c4df187f --- /dev/null +++ b/Modules/Core/include/mitkFileSystem.h @@ -0,0 +1,25 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +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 mitkFileSystem_h +#define mitkFileSystem_h + +#if __has_include() + #define MITK_HAS_FILESYSTEM + #include + namespace fs = std::filesystem; +#elif __has_include() + #include + namespace fs = std::experimental::filesystem; +#endif + +#endif diff --git a/Modules/Core/include/mitkIPreferencesService.h b/Modules/Core/include/mitkIPreferencesService.h index 3597af1aff..e4434df42c 100644 --- a/Modules/Core/include/mitkIPreferencesService.h +++ b/Modules/Core/include/mitkIPreferencesService.h @@ -1,84 +1,84 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkIPreferencesService_h #define mitkIPreferencesService_h #include #include -#include +#include namespace mitk { class IPreferences; /** * \brief A service for persistent application preferences. * * \sa CoreServices::GetPreferencesService() * \sa IPreferences * * \ingroup MicroServices_Interfaces */ class MITKCORE_EXPORT IPreferencesService { public: /** * \brief If initialized, ask the preferences backend to flush preferences, i.e. write them to disk. */ virtual ~IPreferencesService(); /** * \brief Initialize the preferences backend. * * Load preferences from the specified file. If the file does not yet exist, * create an empty preferences storage. * * This method must be called once at the start of an application before * accessing any preferences via GetSystemPreferences(). * * \throw Exception The method is called more than once. * * \sa IPreferencesStorage */ - virtual void InitializeStorage(const std::filesystem::path& filename) = 0; + virtual void InitializeStorage(const fs::path& filename) = 0; /** * \brief For internal use only. * * This method is only used for testing purposes. * Do not use in production code. */ virtual void UninitializeStorage(bool removeFile = true) = 0; /** * \brief Access preferences. * * The system preferences should be considered as resource and as such their * lifetime is coupled to this service. It is recommended to access the * preferences always through this method as needed instead of keeping * a pointer permanently. * * \note The term "system preferences" is kept for historical reasons to stay as * API-compatible as possible to the previous preferences service. A more * precise term would be "application preferences". * * \return The root node of the preferences tree. */ virtual IPreferences* GetSystemPreferences() = 0; }; } MITK_DECLARE_SERVICE_INTERFACE(mitk::IPreferencesService, "org.mitk.IPreferencesService") #endif diff --git a/Modules/Core/include/mitkIPreferencesStorage.h b/Modules/Core/include/mitkIPreferencesStorage.h index 82c1342711..7ca8c3db64 100644 --- a/Modules/Core/include/mitkIPreferencesStorage.h +++ b/Modules/Core/include/mitkIPreferencesStorage.h @@ -1,76 +1,76 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkIPreferencesStorage_h #define mitkIPreferencesStorage_h #include #include -#include +#include #include namespace mitk { /** * \brief The backend for persistent preferences. * * This interface and its implementation is internally used by the IPreferencesService * to hold the preferences root node and to store and restore the preferences from disk. */ class MITKCORE_EXPORT IPreferencesStorage { public: /** * \brief Constructor. Load preferences from the specified file. * * If the file does not yet exist, create an empty preferences storage. */ - explicit IPreferencesStorage(const std::filesystem::path& filename); + explicit IPreferencesStorage(const fs::path& filename); /** * \brief Destructor. Write preferences to disk for the last time. */ virtual ~IPreferencesStorage(); /** * \brief Get the preferences root node. * * The preferences root node is owned by the preferences storage. */ virtual IPreferences* GetRoot(); /** * \sa GetRoot() */ virtual const IPreferences* GetRoot() const; /** * \brief Get the filename of the preferences storage. */ - virtual std::filesystem::path GetFilename() const; + virtual fs::path GetFilename() const; /** * \brief Write the in-memory preferences to disk. * * Usually called by clients indirectly through IPreferences::Flush(). */ virtual void Flush() = 0; protected: - std::filesystem::path m_Filename; + fs::path m_Filename; std::unique_ptr m_Root; }; } #endif diff --git a/Modules/Core/include/mitkVideoRecorder.h b/Modules/Core/include/mitkVideoRecorder.h index ee93447afb..2d7f4a4a80 100644 --- a/Modules/Core/include/mitkVideoRecorder.h +++ b/Modules/Core/include/mitkVideoRecorder.h @@ -1,86 +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 mitkVideoRecorder_h #define mitkVideoRecorder_h -#include +#include #include #include #include namespace mitk { /** \brief Record the contents of a render window as video using FFmpeg as external command-line application. * * Before recording, set the render window, the path to FFmpeg, the path to the video output file, * its format/codec, and frame rate. * * Most settings have decent defaults, e.g., the royalty-free and open VP9 video codec in a WebM container as * output format and a frame rate of 30 frames per second. * * If not set explicitly, the FFmpeg path and output format are queried from the preferences, if available. * * Call StartRecording() to begin a recording session, record each frame with RecordFrame(), and end the recording * session with a call to StopRecording(). StopRecording() is a blocking call that may take a long time to return * since it calls FFmpeg to encode the recorded frames into a video. Consider calling it from a separate thread. * * The VideoRecorder throws an Exception on any error. It is advised to use it within a try/catch block. */ class MITKCORE_EXPORT VideoRecorder { public: enum class OutputFormat { WebM_VP9, MP4_H264 }; /** \brief Get the file extension corresponding to the specified video output format. * * \return A file extension string like ".webm" or ".mp4". */ static std::string GetFileExtension(OutputFormat format); VideoRecorder(); ~VideoRecorder(); VideoRecorder(const VideoRecorder&) = delete; VideoRecorder& operator=(const VideoRecorder&) = delete; - std::filesystem::path GetFFmpegPath() const; - void SetFFmpegPath(const std::filesystem::path& path); + fs::path GetFFmpegPath() const; + void SetFFmpegPath(const fs::path& path); - std::filesystem::path GetOutputPath() const; - void SetOutputPath(const std::filesystem::path& path); + fs::path GetOutputPath() const; + void SetOutputPath(const fs::path& path); OutputFormat GetOutputFormat() const; void SetOutputFormat(OutputFormat format); std::string GetRenderWindowName() const; void SetRenderWindowName(const std::string& renderWindowName); int GetFrameRate() const; void SetFrameRate(unsigned int fps); void StartRecording(); void RecordFrame() const; int StopRecording(); private: class Impl; std::unique_ptr m_Impl; }; } #endif diff --git a/Modules/Core/src/IO/mitkIPreferencesStorage.cpp b/Modules/Core/src/IO/mitkIPreferencesStorage.cpp index 9d9141ee6d..4fb4b5586f 100644 --- a/Modules/Core/src/IO/mitkIPreferencesStorage.cpp +++ b/Modules/Core/src/IO/mitkIPreferencesStorage.cpp @@ -1,37 +1,37 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include -mitk::IPreferencesStorage::IPreferencesStorage(const std::filesystem::path& filename) +mitk::IPreferencesStorage::IPreferencesStorage(const fs::path& filename) : m_Filename(filename) { } mitk::IPreferencesStorage::~IPreferencesStorage() { } mitk::IPreferences* mitk::IPreferencesStorage::GetRoot() { return m_Root.get(); } const mitk::IPreferences* mitk::IPreferencesStorage::GetRoot() const { return m_Root.get(); } -std::filesystem::path mitk::IPreferencesStorage::GetFilename() const +fs::path mitk::IPreferencesStorage::GetFilename() const { return m_Filename; } diff --git a/Modules/Core/src/IO/mitkPreferencesService.cpp b/Modules/Core/src/IO/mitkPreferencesService.cpp index 731ed4da39..5957e54161 100644 --- a/Modules/Core/src/IO/mitkPreferencesService.cpp +++ b/Modules/Core/src/IO/mitkPreferencesService.cpp @@ -1,49 +1,49 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkPreferencesService.h" #include "mitkXMLPreferencesStorage.h" #include mitk::PreferencesService::PreferencesService() { } mitk::PreferencesService::~PreferencesService() { if (m_Storage) m_Storage->Flush(); } -void mitk::PreferencesService::InitializeStorage(const std::filesystem::path& filename) +void mitk::PreferencesService::InitializeStorage(const fs::path& filename) { if (m_Storage) mitkThrow() << "The preferences service must be initialized only once to guarantee valid preferences pointers during its lifetime."; m_Storage = std::make_unique(filename); } void mitk::PreferencesService::UninitializeStorage(bool removeFile) { if (m_Storage && removeFile) - std::filesystem::remove(m_Storage->GetFilename()); + fs::remove(m_Storage->GetFilename()); m_Storage.reset(nullptr); } mitk::IPreferences* mitk::PreferencesService::GetSystemPreferences() { return m_Storage ? m_Storage->GetRoot() : nullptr; } diff --git a/Modules/Core/src/IO/mitkPreferencesService.h b/Modules/Core/src/IO/mitkPreferencesService.h index 43bf80dc9c..71a0e87506 100644 --- a/Modules/Core/src/IO/mitkPreferencesService.h +++ b/Modules/Core/src/IO/mitkPreferencesService.h @@ -1,46 +1,46 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPreferencesService_h #define mitkPreferencesService_h #include #include namespace mitk { class IPreferencesStorage; /** * \brief Implementation of the IPreferencesService interface. * * Only used through the IPreferencesService interface. * * \sa IPreferencesService */ class PreferencesService : public IPreferencesService { public: PreferencesService(); ~PreferencesService() override; - void InitializeStorage(const std::filesystem::path& filename) override; + void InitializeStorage(const fs::path& filename) override; void UninitializeStorage(bool removeFile) override; IPreferences* GetSystemPreferences() override; private: std::unique_ptr m_Storage; }; } #endif diff --git a/Modules/Core/src/IO/mitkXMLPreferencesStorage.cpp b/Modules/Core/src/IO/mitkXMLPreferencesStorage.cpp index 3e56b2de4a..1dbfc2f5bd 100644 --- a/Modules/Core/src/IO/mitkXMLPreferencesStorage.cpp +++ b/Modules/Core/src/IO/mitkXMLPreferencesStorage.cpp @@ -1,125 +1,125 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkXMLPreferencesStorage.h" #include "mitkPreferences.h" #include #include namespace { std::string GetStringAttribute(const tinyxml2::XMLElement* xmlElement, const char* name) { const auto* attribute = xmlElement->Attribute(name); return attribute != nullptr ? attribute : ""; } mitk::Preferences* Deserialize(const tinyxml2::XMLElement* xmlPreferenceElement, mitk::Preferences* parentPreferences, mitk::IPreferencesStorage* storage) { const std::string name = GetStringAttribute(xmlPreferenceElement, "name"); const auto* xmlPropertyElement = xmlPreferenceElement->FirstChildElement("property"); mitk::Preferences::Properties properties; while (xmlPropertyElement != nullptr) { properties[GetStringAttribute(xmlPropertyElement, "name")] = GetStringAttribute(xmlPropertyElement, "value"); xmlPropertyElement = xmlPropertyElement->NextSiblingElement("property"); } auto* preferences = new mitk::Preferences(properties, name, parentPreferences, storage); const auto* xmlPreferenceChildElement = xmlPreferenceElement->FirstChildElement("preferences"); while (xmlPreferenceChildElement != nullptr) { Deserialize(xmlPreferenceChildElement, preferences, storage); xmlPreferenceChildElement = xmlPreferenceChildElement->NextSiblingElement("preferences"); } return preferences; } void Serialize(const mitk::Preferences* preferences, tinyxml2::XMLNode* xmlParentNode) { auto* xmlDocument = xmlParentNode->GetDocument(); auto* xmlPreferenceElement = xmlDocument->NewElement("preferences"); xmlPreferenceElement->SetAttribute("name", preferences->Name().c_str()); for (const auto& [name, value] : preferences->GetProperties()) { auto* xmlPropertyElement = xmlDocument->NewElement("property"); xmlPropertyElement->SetAttribute("name", name.c_str()); xmlPropertyElement->SetAttribute("value", value.c_str()); xmlPreferenceElement->InsertEndChild(xmlPropertyElement); } xmlParentNode->InsertEndChild(xmlPreferenceElement); for (const auto& child : preferences->GetChildren()) Serialize(child.get(), xmlPreferenceElement); } } -mitk::XMLPreferencesStorage::XMLPreferencesStorage(const std::filesystem::path& filename) +mitk::XMLPreferencesStorage::XMLPreferencesStorage(const fs::path& filename) : IPreferencesStorage(filename) { - if (std::filesystem::exists(filename)) + if (fs::exists(filename)) { tinyxml2::XMLDocument xmlDocument; if (xmlDocument.LoadFile(filename.string().c_str()) == tinyxml2::XML_SUCCESS) { m_Root.reset(Deserialize(xmlDocument.RootElement(), nullptr, this)); return; } else { MITK_ERROR << "Could not load preferences from \"" << filename.string() << "\"!"; } } else { try { - std::filesystem::create_directories(filename.parent_path()); + fs::create_directories(filename.parent_path()); } catch (const std::exception& e) { MITK_ERROR << "Could not create directories for \"" << filename.string() << "\": " << e.what(); } } m_Root = std::make_unique(Preferences::Properties(), "", nullptr, this); } mitk::XMLPreferencesStorage::~XMLPreferencesStorage() { } void mitk::XMLPreferencesStorage::Flush() { tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); Serialize(static_cast(m_Root.get()), &xmlDocument); if (xmlDocument.SaveFile(m_Filename.string().c_str()) != tinyxml2::XML_SUCCESS) MITK_ERROR << "Could not save preferences to \"" << m_Filename.string() << "\"!"; } diff --git a/Modules/Core/src/IO/mitkXMLPreferencesStorage.h b/Modules/Core/src/IO/mitkXMLPreferencesStorage.h index b982ee62a1..eb57d5720e 100644 --- a/Modules/Core/src/IO/mitkXMLPreferencesStorage.h +++ b/Modules/Core/src/IO/mitkXMLPreferencesStorage.h @@ -1,33 +1,33 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkXMLPreferencesStorage_h #define mitkXMLPreferencesStorage_h #include namespace mitk { /** * \brief See IPreferencesStorage. */ class XMLPreferencesStorage : public IPreferencesStorage { public: - explicit XMLPreferencesStorage(const std::filesystem::path& filename); + explicit XMLPreferencesStorage(const fs::path& filename); ~XMLPreferencesStorage() override; void Flush() override; }; } #endif diff --git a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp index 1de3fcfb9a..feb313cc55 100644 --- a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp +++ b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp @@ -1,409 +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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::string mitk::VideoRecorder::GetFileExtension(OutputFormat format) { switch (format) { case OutputFormat::WebM_VP9: return ".webm"; case OutputFormat::MP4_H264: return ".mp4"; default: break; } mitkThrow() << "Unknown output format for video recording."; } namespace { mitk::IPreferences* GetPreferences() { auto* preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.moviemaker"); } class RecordingSession { public: RecordingSession(vtkRenderWindow* renderWindow, mitk::VideoRecorder::OutputFormat format) : m_FrameDir(mitk::IOUtil::CreateTemporaryDirectory("MITK_RecordingSession_XXXXXX")), m_NumberOfFrames(0) { m_WindowToImageFilter->SetInput(renderWindow); if (mitk::VideoRecorder::OutputFormat::MP4_H264 == format) { // H.264 only supports image dimensions that are a multiple of 2. Resize if necessary. auto* size = renderWindow->GetActualSize(); if (size[0] & 1 || size[1] & 1) { m_ImageResize->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); m_ImageResize->SetOutputDimensions(size[0] & ~1, size[1] & ~1, -1); m_ImageResize->SetInterpolate(0); m_ImageResize->BorderOn(); m_ImageWriter->SetInputConnection(m_ImageResize->GetOutputPort()); return; } } m_ImageWriter->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); } ~RecordingSession() { std::error_code errorCode; - std::filesystem::remove_all(m_FrameDir, errorCode); + fs::remove_all(m_FrameDir, errorCode); } RecordingSession(const RecordingSession&) = delete; RecordingSession& operator=(const RecordingSession&) = delete; - std::filesystem::path GetFrameDir() const + fs::path GetFrameDir() const { return m_FrameDir; } void RecordFrame() { m_WindowToImageFilter->Modified(); std::stringstream frameFilename; frameFilename << std::setw(6) << std::setfill('0') << m_NumberOfFrames << ".png"; const auto framePath = m_FrameDir / frameFilename.str(); m_ImageWriter->SetFileName(framePath.string().c_str()); m_ImageWriter->Write(); ++m_NumberOfFrames; } private: - std::filesystem::path m_FrameDir; + fs::path m_FrameDir; unsigned int m_NumberOfFrames; vtkNew m_WindowToImageFilter; vtkNew m_ImageResize; vtkNew m_ImageWriter; }; } namespace mitk { class VideoRecorder::Impl { public: Impl() : m_FrameRate(30) { } ~Impl() = default; Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; - std::filesystem::path GetFFmpegPath() const + fs::path GetFFmpegPath() const { if (m_FFmpegPath) return m_FFmpegPath.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) { auto ffmpegPath = preferences->Get("ffmpeg", ""); if (!ffmpegPath.empty()) return ffmpegPath; } - return std::filesystem::path(); + return fs::path(); } - void SetFFmpegPath(const std::filesystem::path& path) + void SetFFmpegPath(const fs::path& path) { m_FFmpegPath = path; } - std::filesystem::path GetOutputPath() const + fs::path GetOutputPath() const { return m_OutputPath; } - void SetOutputPath(const std::filesystem::path& path) + void SetOutputPath(const fs::path& path) { m_OutputPath = path; } mitk::VideoRecorder::OutputFormat GetOutputFormat() const { if (m_OutputFormat) return m_OutputFormat.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) return static_cast(preferences->GetInt("format", 0)); return OutputFormat::WebM_VP9; } void SetOutputFormat(OutputFormat format) { m_OutputFormat = format; } std::string GetRenderWindowName() const { return m_RenderWindowName; } void SetRenderWindowName(const std::string& renderWindowName) { m_RenderWindowName = renderWindowName; } int GetFrameRate() const { return m_FrameRate; } void SetFrameRate(unsigned int fps) { m_FrameRate = fps; } bool OnAir() const { return nullptr != m_RecordingSession.get(); } void StartRecording() { if (this->OnAir()) mitkThrow() << "Recording session already running."; auto renderWindowName = this->GetRenderWindowName(); if (renderWindowName.empty()) mitkThrow() << "No render window specified for recording."; auto* renderWindow = BaseRenderer::GetRenderWindowByName(renderWindowName); if (nullptr == renderWindow) mitkThrow() << "\"" << renderWindowName << "\" references unknown render window for recording."; m_RecordingSession = std::make_unique(renderWindow, this->GetOutputFormat()); } void RecordFrame() { if (!this->OnAir()) mitkThrow() << "Cannot record frame. No recording session running."; m_RecordingSession->RecordFrame(); } std::string GetFFmpegCommandLine() const { bool vp9 = OutputFormat::WebM_VP9 == this->GetOutputFormat(); std::stringstream stream; stream << this->GetFFmpegPath() << ' ' << "-y" << ' ' << "-r " << std::to_string(this->GetFrameRate()) << ' ' << "-i %6d.png" << ' ' << "-c:v " << (vp9 ? "libvpx-vp9" : "libx264") << ' ' << "-crf " << (vp9 ? "31" : "23") << ' ' << "-pix_fmt yuv420p" << ' ' << "-b:v 0" << ' ' << this->GetOutputPath(); return stream.str(); } int ExecuteFFmpeg() const { auto commandLine = this->GetFFmpegCommandLine(); auto commandLineCStr = commandLine.c_str(); auto workingDirectory = m_RecordingSession->GetFrameDir().string(); auto* ffmpeg = itksysProcess_New(); itksysProcess_SetOption(ffmpeg, itksysProcess_Option_Verbatim, 1); itksysProcess_SetCommand(ffmpeg, &commandLineCStr); itksysProcess_SetWorkingDirectory(ffmpeg, workingDirectory.c_str()); itksysProcess_Execute(ffmpeg); itksysProcess_WaitForExit(ffmpeg, nullptr); auto state = itksysProcess_GetState(ffmpeg); if (itksysProcess_State_Exited != state) { std::stringstream message; message << "FFmpeg process did not exit as expected: "; if (itksysProcess_State_Error == state) { message << itksysProcess_GetErrorString(ffmpeg); } else if (itksysProcess_State_Exception == state) { message << itksysProcess_GetExceptionString(ffmpeg); } message << "\n Command: " << commandLineCStr; message << "\n Working directory: " << workingDirectory.c_str(); itksysProcess_Delete(ffmpeg); mitkThrow() << message.str(); } auto exitCode = itksysProcess_GetExitValue(ffmpeg); itksysProcess_Delete(ffmpeg); return exitCode; } int StopRecording() { if (!this->OnAir()) mitkThrow() << "No recording session running."; if (this->GetFFmpegPath().empty()) mitkThrow() << "Path to FFmpeg not set."; if (this->GetOutputPath().empty()) mitkThrow() << "Path to output video file not set."; auto exitCode = this->ExecuteFFmpeg(); m_RecordingSession = nullptr; return exitCode; } private: - std::optional m_FFmpegPath; - std::filesystem::path m_OutputPath; + std::optional m_FFmpegPath; + fs::path m_OutputPath; std::optional m_OutputFormat; std::string m_RenderWindowName; unsigned int m_FrameRate; std::unique_ptr m_RecordingSession; }; } mitk::VideoRecorder::VideoRecorder() : m_Impl(std::make_unique()) { } mitk::VideoRecorder::~VideoRecorder() { } -std::filesystem::path mitk::VideoRecorder::GetFFmpegPath() const +fs::path mitk::VideoRecorder::GetFFmpegPath() const { return m_Impl->GetFFmpegPath(); } -void mitk::VideoRecorder::SetFFmpegPath(const std::filesystem::path& path) +void mitk::VideoRecorder::SetFFmpegPath(const fs::path& path) { m_Impl->SetFFmpegPath(path); } -std::filesystem::path mitk::VideoRecorder::GetOutputPath() const +fs::path mitk::VideoRecorder::GetOutputPath() const { return m_Impl->GetOutputPath(); } -void mitk::VideoRecorder::SetOutputPath(const std::filesystem::path& path) +void mitk::VideoRecorder::SetOutputPath(const fs::path& path) { m_Impl->SetOutputPath(path); } mitk::VideoRecorder::OutputFormat mitk::VideoRecorder::GetOutputFormat() const { return m_Impl->GetOutputFormat(); } void mitk::VideoRecorder::SetOutputFormat(OutputFormat format) { m_Impl->SetOutputFormat(format); } std::string mitk::VideoRecorder::GetRenderWindowName() const { return m_Impl->GetRenderWindowName(); } void mitk::VideoRecorder::SetRenderWindowName(const std::string& renderWindowName) { m_Impl->SetRenderWindowName(renderWindowName); } int mitk::VideoRecorder::GetFrameRate() const { return m_Impl->GetFrameRate(); } void mitk::VideoRecorder::SetFrameRate(unsigned int fps) { m_Impl->SetFrameRate(fps); } void mitk::VideoRecorder::StartRecording() { m_Impl->StartRecording(); } void mitk::VideoRecorder::RecordFrame() const { m_Impl->RecordFrame(); } int mitk::VideoRecorder::StopRecording() { return m_Impl->StopRecording(); } diff --git a/Modules/Core/test/mitkPreferencesTest.cpp b/Modules/Core/test/mitkPreferencesTest.cpp index d8e4fe6bca..71347a1f83 100644 --- a/Modules/Core/test/mitkPreferencesTest.cpp +++ b/Modules/Core/test/mitkPreferencesTest.cpp @@ -1,175 +1,175 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 class mitkPreferencesTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkPreferencesTestSuite); MITK_TEST(Node); MITK_TEST(OnChanged); MITK_TEST(OnPropertyChanged); MITK_TEST(GetInt); MITK_TEST(GetFloat); CPPUNIT_TEST_SUITE_END(); int m_NumberOfOnChangedEvents = 0; int m_NumberOfOnPropertyChangedEvents = 0; public: void setUp() override { const auto filename = mitk::IOUtil::CreateTemporaryFile("prefs_XXXXXX.xml"); - std::filesystem::remove(filename); // We need a temporary filename, not an actual file + fs::remove(filename); // We need a temporary filename, not an actual file auto* preferencesService = mitk::CoreServices::GetPreferencesService(); preferencesService->InitializeStorage(filename); } void tearDown() override { auto* preferencesService = mitk::CoreServices::GetPreferencesService(); preferencesService->UninitializeStorage(); } void Node() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); // a > aa // b > bb > bbb // b > bb2 auto* aPrefs = preferences->Node("a"); aPrefs->Node("aa"); auto* bPrefs = aPrefs->Node("/b"); bPrefs->Node("bb/bbb"); preferences->Node("/b/bb2"); auto* bbPrefs = bPrefs->Node("bb"); auto nodeNames = preferences->ChildrenNames(); CPPUNIT_ASSERT_EQUAL(2, static_cast(nodeNames.size())); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "a") != nodeNames.end()); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "b") != nodeNames.end()); nodeNames = aPrefs->ChildrenNames(); CPPUNIT_ASSERT_EQUAL(1, static_cast(nodeNames.size())); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "aa") != nodeNames.end()); nodeNames = bPrefs->ChildrenNames(); CPPUNIT_ASSERT_EQUAL(2, static_cast(nodeNames.size())); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "bb") != nodeNames.end()); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "bb2") != nodeNames.end()); nodeNames = bbPrefs->ChildrenNames(); CPPUNIT_ASSERT_EQUAL(1, static_cast(nodeNames.size())); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "bbb") != nodeNames.end()); CPPUNIT_ASSERT_EQUAL(std::string("/b/bb/bbb"), bbPrefs->Node("bbb")->AbsolutePath()); } void CountOnChangedEvents(const mitk::IPreferences*) { ++m_NumberOfOnChangedEvents; } void OnChanged() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); preferences->OnChanged += mitk::MessageDelegate1(this, &mitkPreferencesTestSuite::CountOnChangedEvents); CPPUNIT_ASSERT_EQUAL(0, m_NumberOfOnChangedEvents); preferences->Node("a"); CPPUNIT_ASSERT_EQUAL(1, m_NumberOfOnChangedEvents); preferences->Node("a"); CPPUNIT_ASSERT_EQUAL(1, m_NumberOfOnChangedEvents); preferences->Node("a/aa/aaa"); CPPUNIT_ASSERT_EQUAL(1, m_NumberOfOnChangedEvents); preferences->Node("b/bb"); CPPUNIT_ASSERT_EQUAL(2, m_NumberOfOnChangedEvents); preferences->Node("b"); CPPUNIT_ASSERT_EQUAL(2, m_NumberOfOnChangedEvents); } void CountOnPropertyChangedEvents(const mitk::IPreferences::ChangeEvent& e) { ++m_NumberOfOnPropertyChangedEvents; CPPUNIT_ASSERT_EQUAL(std::string("pref"), e.GetProperty()); CPPUNIT_ASSERT_EQUAL(std::string(""), e.GetOldValue()); CPPUNIT_ASSERT_EQUAL(std::string("something"), e.GetNewValue()); } void OnPropertyChanged() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); preferences->OnPropertyChanged += mitk::MessageDelegate1(this, &mitkPreferencesTestSuite::CountOnPropertyChangedEvents); CPPUNIT_ASSERT_EQUAL(0, m_NumberOfOnPropertyChangedEvents); preferences->Put("pref", ""); CPPUNIT_ASSERT_EQUAL(0, m_NumberOfOnPropertyChangedEvents); preferences->Put("pref", "something"); CPPUNIT_ASSERT_EQUAL(1, m_NumberOfOnPropertyChangedEvents); } void GetInt() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); const int expectedValue = 42; preferences->PutInt("integer", expectedValue); CPPUNIT_ASSERT_EQUAL(expectedValue, preferences->GetInt("integer", 0)); preferences->Put("overflow", "4200000000"); CPPUNIT_ASSERT_THROW(preferences->GetInt("overflow", 0), mitk::Exception); preferences->Put("string", "fourty two"); CPPUNIT_ASSERT_THROW(preferences->GetInt("string", 0), mitk::Exception); } void GetFloat() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences(); float expectedValue = 3.14f; preferences->PutFloat("float", expectedValue); CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedValue, preferences->GetFloat("float", 0.0f), mitk::eps); preferences->Put("overflow", "3.14e100"); CPPUNIT_ASSERT_THROW(preferences->GetFloat("overflow", 0.0f), mitk::Exception); preferences->Put("string", "pi"); CPPUNIT_ASSERT_THROW(preferences->GetFloat("string", 0.0f), mitk::Exception); } }; MITK_TEST_SUITE_REGISTRATION(mitkPreferences) diff --git a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp index ae7d47f01d..dc50c4c610 100644 --- a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp +++ b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp @@ -1,199 +1,199 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include -#include +#include #include void InitializeCommandLineParser(mitkCommandLineParser& parser) { parser.setTitle("DICOM Volume Diagnostics"); parser.setCategory("DICOM"); parser.setDescription("Gives insights how MITK readers would convert a set of DICOM files into image volumes (e.g. number of volumes and the sorting of the files)"); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.addArgument("only-own-series", "s", mitkCommandLineParser::Bool, "Only own series", "Analyze only files in the same directory that have the same DICOM Series UID, if a file is provided as input.", us::Any()); parser.addArgument("check-3d", "d", mitkCommandLineParser::Bool, "Check 3D configs", "Analyze the input by using all known 3D configurations. If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); parser.addArgument("check-3d+t", "t", mitkCommandLineParser::Bool, "Check 3D+t configs", "Analyze the input by using all known 3D+t configurations (thus dynamic image configurations). If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file or path", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "Output file where the diagnostics results are stored as json.", us::Any()); } int main(int argc, char* argv[]) { int returnValue = EXIT_SUCCESS; mitkCommandLineParser parser; InitializeCommandLineParser(parser); auto args = parser.parseArguments(argc, argv); if (args.empty()) { std::cout << parser.helpText(); return EXIT_FAILURE; } nlohmann::json diagnosticsResult; try { auto inputFilename = us::any_cast(args["input"]); auto outputFilename = args.count("output")==0 ? std::string() : us::any_cast(args["output"]); bool onlyOwnSeries = args.count("only-own-series"); bool check3D = args.count("check-3d"); bool check3DPlusT = args.count("check-3d+t"); if (!check3D && !check3DPlusT) { //if no check option is selected all are activated by default. check3D = true; check3DPlusT = true; } diagnosticsResult["input"] = inputFilename; diagnosticsResult["only-own-series"] = onlyOwnSeries; diagnosticsResult["check-3d"] = check3D; diagnosticsResult["check-3d+t"] = check3DPlusT; mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(inputFilename); if (relevantFiles.empty()) { mitkThrow() << "DICOM Volume Diagnostics found no relevant files in specified location. No data is loaded. Location: " << inputFilename; } else { - bool pathIsDirectory = std::filesystem::is_directory(inputFilename); + bool pathIsDirectory = fs::is_directory(inputFilename); if (!pathIsDirectory && onlyOwnSeries) { relevantFiles = mitk::FilterDICOMFilesForSameSeries(inputFilename, relevantFiles); } diagnosticsResult["analyzed_files"] = relevantFiles; auto selector = mitk::DICOMFileReaderSelector::New(); if (check3D) selector->LoadBuiltIn3DConfigs(); if (check3DPlusT) selector->LoadBuiltIn3DnTConfigs(); nlohmann::json readerInfos; for (const auto& reader : selector->GetAllConfiguredReaders()) { nlohmann::json readerInfo; readerInfo["class_name"] = reader->GetNameOfClass(); readerInfo["configuration_label"] = reader->GetConfigurationLabel(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); readerInfos.push_back(readerInfo); } diagnosticsResult["checked_readers"] = readerInfos; selector->SetInputFiles(relevantFiles); auto reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); if (reader.IsNull()) { mitkThrow() << "DICOM Volume Diagnostics service found no suitable reader configuration for relevant files."; } else { nlohmann::json readerInfo; readerInfo["class_name"] = reader->GetNameOfClass(); readerInfo["configuration_label"] = reader->GetConfigurationLabel(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); readerInfo["configuration_description"] = reader->GetConfigurationDescription(); std::stringstream config; reader->PrintConfiguration(config); readerInfo["config_details"] = config.str(); diagnosticsResult["selected_reader"] = readerInfo; nlohmann::json outputInfos; unsigned int relevantOutputCount = 0; const auto nrOfOutputs = reader->GetNumberOfOutputs(); for (std::remove_const_t outputIndex = 0; outputIndex < nrOfOutputs; ++outputIndex) { bool isRelevantOutput = true; if (!pathIsDirectory) { const auto frameList = reader->GetOutput(outputIndex).GetImageFrameList(); auto finding = std::find_if(frameList.begin(), frameList.end(), [&](const mitk::DICOMImageFrameInfo::Pointer& frame) { - std::filesystem::path framePath(frame->Filename); - std::filesystem::path inputPath(inputFilename); + fs::path framePath(frame->Filename); + fs::path inputPath(inputFilename); return framePath == inputPath; }); isRelevantOutput = finding != frameList.end(); } if (isRelevantOutput) { ++relevantOutputCount; nlohmann::json outputInfo; const auto output = reader->GetOutput(outputIndex); const auto frameList = output.GetImageFrameList(); mitk::DICOMFilePathList outputFiles; outputFiles.resize(frameList.size()); std::transform(frameList.begin(), frameList.end(), outputFiles.begin(), [](const mitk::DICOMImageFrameInfo::Pointer& frame) { return frame->Filename; }); outputInfo["files"] = outputFiles; outputInfo["timesteps"] = output.GetNumberOfTimeSteps(); outputInfo["frames_per_timesteps"] = output.GetNumberOfFramesPerTimeStep(); outputInfos.push_back(outputInfo); } } diagnosticsResult["volume_count"] = relevantOutputCount; diagnosticsResult["volumes"] = outputInfos; } } std::cout << "\n### DIAGNOSTICS REPORT ###\n" << std::endl; std::cout << std::setw(2) << diagnosticsResult << std::endl; if (!outputFilename.empty()) { std::ofstream fileout(outputFilename); fileout << diagnosticsResult; fileout.close(); } } catch (const mitk::Exception& e) { MITK_ERROR << e.GetDescription(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "An unknown error occurred!"; return EXIT_FAILURE; } return returnValue; } diff --git a/Modules/ModelFit/autoload/Models/CMakeLists.txt b/Modules/ModelFit/autoload/Models/CMakeLists.txt index 1f15532ba7..aede36eb3a 100644 --- a/Modules/ModelFit/autoload/Models/CMakeLists.txt +++ b/Modules/ModelFit/autoload/Models/CMakeLists.txt @@ -1,6 +1,6 @@ MITK_CREATE_MODULE(ModelFitModelsServices DEPENDS PUBLIC MitkModelFit PRIVATE MitkCore - AUTOLOAD_WITH MitkModelFit + AUTOLOAD_WITH MitkCore ) diff --git a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp index 194fc89387..635d664ccc 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp @@ -1,146 +1,146 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultilabelIOMimeTypes.h" #include #include -#include +#include #include #include #include #include "itkMetaDataObject.h" mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::MitkSegmentationTaskListMimeType() : CustomMimeType(SEGMENTATIONTASKLIST_MIMETYPE_NAME()) { this->AddExtension("json"); this->SetCategory("MITK Segmentation Task List"); this->SetComment("MITK Segmentation Task List"); } bool mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::AppliesTo(const std::string& path) const { bool result = CustomMimeType::AppliesTo(path); - if (!std::filesystem::exists(path)) // T18572 + if (!fs::exists(path)) // T18572 return result; std::ifstream file(path); if (!file.is_open()) return false; auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_object()) return false; if ("MITK Segmentation Task List" != json.value("FileFormat", "")) return false; if (1 != json.value("Version", 0)) return false; return true; } mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType* mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::Clone() const { return new MitkSegmentationTaskListMimeType(*this); } mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType mitk::MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE() { return MitkSegmentationTaskListMimeType(); } std::string mitk::MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE_NAME() { return IOMimeTypes::DEFAULT_BASE_NAME() + ".segmentationtasklist"; } mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::LegacyLabelSetMimeType() : CustomMimeType(LEGACYLABELSET_MIMETYPE_NAME()) { this->AddExtension("nrrd"); this->SetCategory("MITK LabelSetImage"); this->SetComment("MITK LabelSetImage (legacy format)"); } bool mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::AppliesTo(const std::string& path) const { bool canRead = CustomMimeType::AppliesTo(path); - if (!std::filesystem::exists(path)) // T18572 + if (!fs::exists(path)) // T18572 return canRead; if (!canRead) { return false; } std::string value(""); try { std::ifstream file(path); if (!file.is_open()) return false; itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(path); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); } catch(const std::exception& e) { MITK_DEBUG << "Error while try to anylize NRRD file for LegacyLabelSetMimeType. File: " << path <<"; Error: " << e.what(); } catch(...) { MITK_DEBUG << "Unknown error while try to analyze NRRD file for LegacyLabelSetMimeType. File: " << path; } if (value.compare("org.mitk.image.multilabel") != 0) { return false; } return true; } mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType* mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::Clone() const { return new LegacyLabelSetMimeType(*this); } mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType mitk::MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE() { return LegacyLabelSetMimeType(); } std::string mitk::MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE_NAME() { return IOMimeTypes::DEFAULT_BASE_NAME() + ".legacylabelsetimage"; } std::vector mitk::MitkMultilabelIOMimeTypes::Get() { std::vector mimeTypes; mimeTypes.push_back(SEGMENTATIONTASKLIST_MIMETYPE().Clone()); mimeTypes.push_back(LEGACYLABELSET_MIMETYPE().Clone()); return mimeTypes; } diff --git a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp index 6afa7c5a81..e883c20426 100644 --- a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp @@ -1,267 +1,267 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSegmentationTaskListIO.h" #include "mitkMultilabelIOMimeTypes.h" #include #include -#include +#include #include namespace mitk { void to_json(nlohmann::json& json, const SegmentationTaskList::Task& task) { if (task.HasName()) json["Name"] = task.GetName(); if (task.HasDescription()) json["Description"] = task.GetDescription(); if (task.HasImage()) json["Image"] = task.GetImage().string(); if (task.HasSegmentation()) json["Segmentation"] = task.GetSegmentation().string(); if (task.HasLabelName()) json["LabelName"] = task.GetLabelName(); if (task.HasLabelNameSuggestions()) json["LabelNameSuggestions"] = task.GetLabelNameSuggestions().string(); if (task.HasPreset()) json["Preset"] = task.GetPreset().string(); if (task.HasResult()) json["Result"] = task.GetResult().string(); if (task.HasDynamic()) json["Dynamic"] = task.GetDynamic(); } void from_json(const nlohmann::json& json, SegmentationTaskList::Task& task) { auto iter = json.find("Name"); if (iter != json.end()) task.SetName(json["Name"].get()); iter = json.find("Description"); if (iter != json.end()) task.SetDescription(json["Description"].get()); iter = json.find("Image"); if (iter != json.end()) task.SetImage(json["Image"].get()); iter = json.find("Segmentation"); if (iter != json.end()) task.SetSegmentation(json["Segmentation"].get()); iter = json.find("LabelName"); if (iter != json.end()) task.SetLabelName(json["LabelName"].get()); iter = json.find("LabelNameSuggestions"); if (iter != json.end()) task.SetLabelNameSuggestions(json["LabelNameSuggestions"].get()); iter = json.find("Preset"); if (iter != json.end()) task.SetPreset(json["Preset"].get()); iter = json.find("Result"); if (iter != json.end()) task.SetResult(json["Result"].get()); iter = json.find("Dynamic"); if (iter != json.end()) task.SetDynamic(json["Dynamic"].get()); } } mitk::SegmentationTaskListIO::SegmentationTaskListIO() : AbstractFileIO(SegmentationTaskList::GetStaticNameOfClass(), MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE(), "MITK Segmentation Task List") { this->RegisterService(); } std::vector mitk::SegmentationTaskListIO::DoRead() { auto* stream = this->GetInputStream(); std::ifstream fileStream; if (nullptr == stream) { auto filename = this->GetInputLocation(); - if (filename.empty() || !std::filesystem::exists(filename)) + if (filename.empty() || !fs::exists(filename)) mitkThrow() << "Invalid or nonexistent filename: \"" << filename << "\"!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for reading!"; stream = &fileStream; } nlohmann::json json; try { json = nlohmann::json::parse(*stream); } catch (const nlohmann::json::exception& e) { mitkThrow() << e.what(); } if (!json.is_object()) mitkThrow() << "Unknown file format (expected JSON object as root)!"; if ("MITK Segmentation Task List" != json.value("FileFormat", "")) mitkThrow() << "Unknown file format (expected \"MITK Segmentation Task List\")!"; if (1 != json.value("Version", 0)) mitkThrow() << "Unknown file format version (expected \"1\")!"; if (!json.contains("Tasks") || !json["Tasks"].is_array()) mitkThrow() << "Tasks array not found!"; auto segmentationTaskList = SegmentationTaskList::New(); if (json.contains("Name")) segmentationTaskList->SetProperty("name", StringProperty::New(json["Name"].get())); try { if (json.contains("Defaults")) { segmentationTaskList->SetDefaults(json["Defaults"].get()); if (segmentationTaskList->GetDefaults().HasResult()) mitkThrow() << "Defaults must not contain \"Result\"!"; } for (const auto& task : json["Tasks"]) { auto i = segmentationTaskList->AddTask(task.get()); if (!segmentationTaskList->HasImage(i)) mitkThrow() << "Task " << i << " must contain \"Image\"!"; - std::filesystem::path imagePath(segmentationTaskList->GetImage(i)); + fs::path imagePath(segmentationTaskList->GetImage(i)); if (imagePath.is_relative()) { auto inputLocation = this->GetInputLocation(); /* If we have access to properties, we are reading from an MITK scene * file. In this case, paths are still relative to the original input * location, which is preserved in the properties. */ const auto* properties = this->GetProperties(); if (properties != nullptr) properties->GetStringProperty("MITK.IO.reader.inputlocation", inputLocation); - imagePath = std::filesystem::path(inputLocation).remove_filename() / imagePath; + imagePath = fs::path(inputLocation).remove_filename() / imagePath; } - if (!std::filesystem::exists(imagePath)) + if (!fs::exists(imagePath)) mitkThrow() << "Referenced image \"" << imagePath << "\" in task " << i << " does not exist!"; if (!segmentationTaskList->HasResult(i)) mitkThrow() << "Task " << i << " must contain \"Result\"!"; } } catch (const nlohmann::json::type_error& e) { mitkThrow() << e.what(); } std::vector result; result.push_back(segmentationTaskList.GetPointer()); return result; } void mitk::SegmentationTaskListIO::Write() { auto segmentationTaskList = dynamic_cast(this->GetInput()); if (nullptr == segmentationTaskList) mitkThrow() << "Invalid input for writing!"; if (segmentationTaskList->GetNumberOfTasks() == 0) mitkThrow() << "No tasks found!"; auto* stream = this->GetOutputStream(); std::ofstream fileStream; if (nullptr == stream) { auto filename = this->GetOutputLocation(); if (filename.empty()) mitkThrow() << "Neither an output stream nor an output filename was specified!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for writing!"; stream = &fileStream; } if (!stream->good()) mitkThrow() << "Stream for writing is not good!"; nlohmann::ordered_json json = { { "FileFormat", "MITK Segmentation Task List" }, { "Version", 1 }, { "Name", segmentationTaskList->GetProperty("name")->GetValueAsString() } }; nlohmann::json defaults = segmentationTaskList->GetDefaults(); if (!defaults.is_null()) json["Defaults"] = defaults; nlohmann::json tasks; for (const auto& task : *segmentationTaskList) tasks.push_back(task); json["Tasks"] = tasks; *stream << std::setw(2) << json << std::endl; } mitk::SegmentationTaskListIO* mitk::SegmentationTaskListIO::IOClone() const { return new SegmentationTaskListIO(*this); } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 7a4aa72a47..2510ef44ad 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1750 +1,1755 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 #include #include #include #include #include #include #include #include #include namespace mitk { template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->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(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UNLABELED_VALUE = 0; mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLabelValue(0), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLabelValue(other.m_ActiveLabelValue), m_LookupTable(other.m_LookupTable->Clone()), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { GroupIndexType i = 0; for (auto groupImage : other.m_LayerContainer) { this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); i++; } m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } 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 ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add an initial LabelSet and corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto [value, label] : m_LabelMap) { + (void)value; // Prevent unused variable error in older compilers this->ReleaseLabel(label); } m_LabelMap.clear(); } unsigned int mitk::LabelSetImage::GetActiveLayer() const { if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/subtract one more because we have not removed the layer yet, thus the group count is to 1 high. newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; } } if (activeIndex == indexToDelete) { // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; //copy the image content of the upcoming new active layer; SetActiveLayer(newActiveIndexBeforeDeletion); } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; { std::lock_guard guard(m_LabelNGroupMapsMutex); // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->InvokeEvent(LabelRemovedEvent(labelValue)); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); } //update old indexes in m_GroupToLabelMap to new layer indexes for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->InvokeEvent(LabelsChangedEvent(relevantLabels)); this->InvokeEvent(GroupRemovedEvent(indexToDelete)); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const { LabelValueVectorType result; for (auto [value, label] : m_LabelMap) { + (void)label; // Prevent unused variable error in older compilers result.emplace_back(value); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UNLABELED_VALUE }; for (auto [value, label] : m_LabelMap) { + (void)label; // Prevent unused variable error in older compilers result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image* layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); if (nullptr == layerImage) mitkThrow() << "Cannot add group. Passed group image is nullptr."; bool equalGeometries = Equal( *(this->GetTimeGeometry()), *(layerImage->GetTimeGeometry()), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false); if (!equalGeometries) mitkThrow() << "Cannot add group. Passed group image has not the same geometry like segmentation."; if (layerImage->GetPixelType() != MakePixelType()) mitkThrow() << "Cannot add group. Passed group image has incorrect pixel type. Only LabelValueType is supported. Invalid pixel type: "<< layerImage->GetPixelType().GetTypeAsString(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); m_GroupToLabelMap.push_back({}); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); } this->Modified(); this->InvokeEvent(GroupAddedEvent(newGroupID)); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-existing group. Invalid group id: "< guard(m_LabelNGroupMapsMutex); oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->InvokeEvent(LabelRemovedEvent(labelID)); } } this->InvokeEvent(LabelsChangedEvent(oldLabels)); this->InvokeEvent(GroupModifiedEvent(groupID)); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer.at(groupID).GetPointer(); } const mitk::Image* mitk::LabelSetImage::GetGroupImageWorkaround(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; if (groupID == this->GetActiveLayer() && this->GetMTime()> m_LayerContainer[groupID]->GetMTime()) { //we have to transfer the content first into the group image if (4 == this->GetDimension()) { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (groupID)); } else { AccessByItk_1(this, ImageToLayerContainerProcessing, groupID); } } return m_LayerContainer[groupID].GetPointer(); } const std::string& mitk::LabelSetImage::GetGroupName(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group name. Group ID is invalid. Invalid ID: " << groupID; return m_Groups[groupID]; } void mitk::LabelSetImage::SetGroupName(GroupIndexType groupID, const std::string& name) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot set group name. Group ID is invalid. Invalid ID: " << groupID; m_Groups[groupID] = name; this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; 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; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UNLABELED_VALUE) { auto groupID = this->GetGroupIndexOfLabel(label); if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(sourcePixelValue)); this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ sourcePixelValue, pixelValue })); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->InvokeEvent(LabelModifiedEvent(vectorOfSourcePixelValues[idx])); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(pixelValue)); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->InvokeEvent(LabelsChangedEvent(modifiedValues)); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { GroupIndexType groupID = 0; { std::lock_guard guard(m_LabelNGroupMapsMutex); if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } } this->InvokeEvent(LabelRemovedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of instance. RemoveLabelFromMap was called for unknown label id. invalid label id: "<GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->InvokeEvent(LabelsChangedEvent(vectorOfLabelPixelValues)); } void mitk::LabelSetImage::EraseLabel(LabelValueType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetGroupImage(groupID); if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } groupImage->Modified(); } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); Modified(); } void mitk::LabelSetImage::EraseLabels(const LabelValueVectorType& labelValues) { for (auto labelValue : labelValues) { this->EraseLabel(labelValue); } } mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { auto usedValues = this->GetUsedLabelValues(); return usedValues.back() + 1; } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { if (nullptr == label) mitkThrow() << "Invalid use of AddLabel. label is not valid."; mitk::Label::Pointer newLabel = label; { std::lock_guard guard(m_LabelNGroupMapsMutex); unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; if (addAsClone) newLabel = label->Clone(); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = this->GetUnusedLabelValue(); newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); } this->InvokeEvent(LabelAddedEvent(newLabel->GetValue())); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabelWithContent(Label* label, const Image* labelContent, GroupIndexType groupID, LabelValueType contentLabelValue, bool addAsClone, bool correctLabelValue) { if (nullptr == labelContent) mitkThrow() << "Invalid use of AddLabel. labelContent is not valid."; if (!Equal(*(this->GetTimeGeometry()), *(labelContent->GetTimeGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) mitkThrow() << "Invalid use of AddLabel. labelContent has not the same geometry like the segmentation."; auto newLabel = this->AddLabel(label, groupID, addAsClone, correctLabelValue); mitk::TransferLabelContent(labelContent, this->GetGroupImage(groupID), this->GetConstLabelsByValue(this->GetLabelValuesByGroup(groupID)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, { {contentLabelValue, newLabel->GetValue()}}, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(LabelValueType pixelValue, const std::string& name, const mitk::Color& color) { std::shared_lock guard(m_LabelNGroupMapsMutex); auto label = GetLabel(pixelValue); if (label.IsNull()) mitkThrow() << "Cannot rename label. Unknown label value provided. Unknown label value:" << pixelValue; label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); m_LookupTable->Modified(); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, pixelValue); } } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { auto label = this->GetLabel(pixelValue); if (label.IsNull()) mitkThrow() << "Cannot update lookup table. Unknown label value provided. Unknown label value:" << pixelValue; const mitk::Color& color = label->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (label->GetVisible()) rgba[3] = label->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { if (layer >= m_Groups.size()) mitkThrow() << "Cannot get number of labels in group. Group is unknown. Invalid index:" << layer; return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } 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."; } } 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 (Exception& e) { mitkReThrow(e) << "Could not initialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } 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()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UNLABELED_VALUE!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->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->AddLabel(label,0,false); } ++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(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UNLABELED_VALUE) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; auto label = this->GetLabel(pixelValue); if (label.IsNotNull()) { label->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); label->SetCenterOfMassCoordinates(pos); } } 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(const 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; } m_LayerContainer[layer]->Modified(); } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unknown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of RegisterLabel with a nullptr."; UpdateLookupTable(label->GetValue()); m_LookupTable->Modified(); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); m_LabelModEventGuardMap.emplace(label->GetValue(), ITKEventObserverGuard(label, itk::ModifiedEvent(), command)); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; m_LabelModEventGuardMap.erase(label->GetValue()); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); this->InvokeEvent(LabelsChangedEvent(values)); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const { auto labels = this->GetConstLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); } void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->InvokeEvent(LabelModifiedEvent(label->GetValue())); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { return finding->second == groupIndex; } return false; } bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LayerContainer.size(); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } mitk::Label::ConstPointer mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label::Pointer mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UNLABELED_VALUE) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { + (void)value; // Prevent unused variable error in older compilers result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { + (void)value; // Prevent unused variable error in older compilers result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { LabelVectorType result; for (const auto& labelValue : labelValues) { Label::Pointer label = this->GetLabel(labelValue); if (label.IsNotNull()) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const { ConstLabelVectorType result; for (const auto& labelValue : labelValues) { Label::ConstPointer label = this->GetLabel(labelValue); if (label.IsNotNull()) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; return m_GroupToLabelMap[index]; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, const std::string_view name) const { LabelValueVectorType result; auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } std::vector mitk::LabelSetImage::GetLabelClassNames() const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector(names.begin(), names.end()); } std::vector mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector(names.begin(), names.end()); } void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) { auto setVisibility = [visible,this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) { auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, const std::string_view name, bool visible) { auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetAllLabelValues(), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, const std::string_view name, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } 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 ---"; // m_LookupTable; const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tables not equal."; return returnValue; ; } // 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; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are 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.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // label data auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** 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 ConstLabelMapType& destinationLabels, 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_DestinationLabels(destinationLabels), 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_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->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: ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } if (!Equal(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { if (IsSubGeometry(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, true)) { //we have to pad the source image //because ImageToImageFilters always check for origin matching even if //the requested output region is fitting :( auto padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, sourceImageAtTimeStep); padFilter->SetInput(1, destinationImageAtTimeStep); padFilter->SetPadConstant(Label::UNLABELED_VALUE); padFilter->SetBinaryFilter(false); padFilter->Update(); sourceImageAtTimeStep = padFilter->GetOutput(); } else { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; source image has neither the same geometry than destination image nor has the source image a sub geometry."; } } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, timeStep, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkSegmentationTaskList.cpp b/Modules/Multilabel/mitkSegmentationTaskList.cpp index 33f130a6e5..97523730e6 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.cpp +++ b/Modules/Multilabel/mitkSegmentationTaskList.cpp @@ -1,196 +1,204 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSegmentationTaskList.h" #include #include mitk::SegmentationTaskList::Task::Task() : m_Defaults(nullptr) { } mitk::SegmentationTaskList::Task::~Task() { } void mitk::SegmentationTaskList::Task::SetDefaults(const Task* defaults) { m_Defaults = defaults; } mitk::SegmentationTaskList::SegmentationTaskList() { // A base data cannot be serialized if empty. To be not considered empty its // geometry must consist of at least one time step. However, a segmentation // task would then appear as invisible spatial object in a scene. This can // be prevented by excluding it from the scene's bounding box calculations. this->GetTimeGeometry()->Expand(1); this->SetProperty("includeInBoundingBox", BoolProperty::New(false)); } mitk::SegmentationTaskList::SegmentationTaskList(const Self& other) : BaseData(other) { } mitk::SegmentationTaskList::~SegmentationTaskList() { } size_t mitk::SegmentationTaskList::GetNumberOfTasks() const { return m_Tasks.size(); } size_t mitk::SegmentationTaskList::AddTask(const Task& subtask) { m_Tasks.push_back(subtask); m_Tasks.back().SetDefaults(&m_Defaults); return m_Tasks.size() - 1; } const mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) const { return &m_Tasks.at(index); } mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) { return &m_Tasks.at(index); } const mitk::SegmentationTaskList::Task& mitk::SegmentationTaskList::GetDefaults() const { return m_Defaults; } void mitk::SegmentationTaskList::SetDefaults(const Task& defaults) { m_Defaults = defaults; for (auto& subtask : m_Tasks) subtask.SetDefaults(&m_Defaults); } bool mitk::SegmentationTaskList::IsDone() const { for (size_t i = 0; i < m_Tasks.size(); ++i) { if (!this->IsDone(i)) return false; } return true; } bool mitk::SegmentationTaskList::IsDone(size_t index) const { - return std::filesystem::exists(this->GetAbsolutePath(m_Tasks.at(index).GetResult())); + return fs::exists(this->GetAbsolutePath(m_Tasks.at(index).GetResult())); } -std::filesystem::path mitk::SegmentationTaskList::GetInputLocation() const +fs::path mitk::SegmentationTaskList::GetInputLocation() const { std::string inputLocation; this->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", inputLocation); return !inputLocation.empty() - ? std::filesystem::path(inputLocation).lexically_normal() - : std::filesystem::path(); +#ifdef MITK_HAS_FILESYSTEM + ? fs::path(inputLocation).lexically_normal() +#else + ? fs::path(inputLocation) +#endif + : fs::path(); } -std::filesystem::path mitk::SegmentationTaskList::GetBasePath() const +fs::path mitk::SegmentationTaskList::GetBasePath() const { return this->GetInputLocation().remove_filename(); } -std::filesystem::path mitk::SegmentationTaskList::GetAbsolutePath(const std::filesystem::path& path) const +fs::path mitk::SegmentationTaskList::GetAbsolutePath(const fs::path& path) const { if (path.empty()) return path; +#ifdef MITK_HAS_FILESYSTEM auto normalizedPath = path.lexically_normal(); +#else + auto normalizedPath = path; +#endif return !normalizedPath.is_absolute() ? this->GetBasePath() / normalizedPath : normalizedPath; } -std::filesystem::path mitk::SegmentationTaskList::GetInterimPath(const std::filesystem::path& path) const +fs::path mitk::SegmentationTaskList::GetInterimPath(const fs::path& path) const { if (path.empty() || !path.has_filename()) return path; auto interimPath = path; return interimPath.replace_extension(".interim" + path.extension().string()); } void mitk::SegmentationTaskList::SaveTask(size_t index, const BaseData* segmentation, bool saveAsInterimResult) { if (segmentation == nullptr) return; auto path = this->GetAbsolutePath(this->GetResult(index)); auto interimPath = this->GetInterimPath(path); - if (std::filesystem::exists(path)) + if (fs::exists(path)) saveAsInterimResult = false; IOUtil::Save(segmentation, saveAsInterimResult ? interimPath.string() : path.string()); - if (!saveAsInterimResult && std::filesystem::exists(interimPath)) + if (!saveAsInterimResult && fs::exists(interimPath)) { std::error_code ec; - std::filesystem::remove(interimPath, ec); + fs::remove(interimPath, ec); } } std::vector::const_iterator mitk::SegmentationTaskList::begin() const { return m_Tasks.begin(); } std::vector::const_iterator mitk::SegmentationTaskList::end() const { return m_Tasks.end(); } std::vector::iterator mitk::SegmentationTaskList::begin() { return m_Tasks.begin(); } std::vector::iterator mitk::SegmentationTaskList::end() { return m_Tasks.end(); } void mitk::SegmentationTaskList::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::SegmentationTaskList::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::SegmentationTaskList::VerifyRequestedRegion() { return true; } void mitk::SegmentationTaskList::SetRequestedRegion(const itk::DataObject*) { } diff --git a/Modules/Multilabel/mitkSegmentationTaskList.h b/Modules/Multilabel/mitkSegmentationTaskList.h index d57dfd555e..9a90f1210d 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.h +++ b/Modules/Multilabel/mitkSegmentationTaskList.h @@ -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. ============================================================================*/ #ifndef mitkSegmentationTaskList_h #define mitkSegmentationTaskList_h #include #include #include -#include +#include #include namespace mitk { /** \brief A list of segmentation tasks. * * See \ref MITKSegmentationTaskListsPage for more information. */ class MITKMULTILABEL_EXPORT SegmentationTaskList : public BaseData { public: class MITKMULTILABEL_EXPORT Task { public: Task(); ~Task(); void SetDefaults(const Task* defaults); mitkSegmentationTaskValueMacro(std::string, Name) mitkSegmentationTaskValueMacro(std::string, Description) - mitkSegmentationTaskValueMacro(std::filesystem::path, Image) - mitkSegmentationTaskValueMacro(std::filesystem::path, Segmentation) + mitkSegmentationTaskValueMacro(fs::path, Image) + mitkSegmentationTaskValueMacro(fs::path, Segmentation) mitkSegmentationTaskValueMacro(std::string, LabelName) - mitkSegmentationTaskValueMacro(std::filesystem::path, LabelNameSuggestions) - mitkSegmentationTaskValueMacro(std::filesystem::path, Preset) - mitkSegmentationTaskValueMacro(std::filesystem::path, Result) + mitkSegmentationTaskValueMacro(fs::path, LabelNameSuggestions) + mitkSegmentationTaskValueMacro(fs::path, Preset) + mitkSegmentationTaskValueMacro(fs::path, Result) mitkSegmentationTaskValueMacro(bool, Dynamic) private: const Task* m_Defaults; }; mitkClassMacro(SegmentationTaskList, BaseData) itkFactorylessNewMacro(Self) itkCloneMacro(Self) mitkSegmentationTaskListValueMacro(std::string, Name) mitkSegmentationTaskListValueMacro(std::string, Description) - mitkSegmentationTaskListValueMacro(std::filesystem::path, Image) - mitkSegmentationTaskListValueMacro(std::filesystem::path, Segmentation) + mitkSegmentationTaskListValueMacro(fs::path, Image) + mitkSegmentationTaskListValueMacro(fs::path, Segmentation) mitkSegmentationTaskListValueMacro(std::string, LabelName) - mitkSegmentationTaskListValueMacro(std::filesystem::path, LabelNameSuggestions) - mitkSegmentationTaskListValueMacro(std::filesystem::path, Preset) - mitkSegmentationTaskListValueMacro(std::filesystem::path, Result) + mitkSegmentationTaskListValueMacro(fs::path, LabelNameSuggestions) + mitkSegmentationTaskListValueMacro(fs::path, Preset) + mitkSegmentationTaskListValueMacro(fs::path, Result) mitkSegmentationTaskListValueMacro(bool, Dynamic) size_t GetNumberOfTasks() const; size_t AddTask(const Task& subtask); const Task* GetTask(size_t index) const; Task* GetTask(size_t index); const Task& GetDefaults() const; void SetDefaults(const Task& defaults); bool IsDone() const; bool IsDone(size_t index) const; - std::filesystem::path GetInputLocation() const; - std::filesystem::path GetBasePath() const; - std::filesystem::path GetAbsolutePath(const std::filesystem::path& path) const; - std::filesystem::path GetInterimPath(const std::filesystem::path& path) const; + fs::path GetInputLocation() const; + fs::path GetBasePath() const; + fs::path GetAbsolutePath(const fs::path& path) const; + fs::path GetInterimPath(const fs::path& path) const; void SaveTask(size_t index, const BaseData* segmentation, bool saveAsInterimResult = false); std::vector::const_iterator begin() const; std::vector::const_iterator end() const; std::vector::iterator begin(); std::vector::iterator end(); void SetRequestedRegionToLargestPossibleRegion() override; bool RequestedRegionIsOutsideOfTheBufferedRegion() override; bool VerifyRequestedRegion() override; void SetRequestedRegion(const itk::DataObject*) override; protected: mitkCloneMacro(Self) SegmentationTaskList(); SegmentationTaskList(const Self& other); ~SegmentationTaskList() override; private: Task m_Defaults; std::vector m_Tasks; }; } #endif diff --git a/Modules/ROI/autoload/IO/src/mitkROIIO.cpp b/Modules/ROI/autoload/IO/src/mitkROIIO.cpp index 9817496d05..d28994ae53 100644 --- a/Modules/ROI/autoload/IO/src/mitkROIIO.cpp +++ b/Modules/ROI/autoload/IO/src/mitkROIIO.cpp @@ -1,236 +1,236 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkROIIO.h" #include #include #include -#include +#include #include namespace { int CheckFileFormat(const nlohmann::json& json) { if ("MITK ROI" != json["FileFormat"].get()) mitkThrow() << "Unknown file format (expected \"MITK ROI\")!"; auto version = json["Version"].get(); if (version < 1 || version > 2) mitkThrow() << "Unknown file format version (expected version 1 or 2)!"; return version; } mitk::Vector3D GetSize(const mitk::BaseGeometry* geometry) { auto bounds = geometry->GetBounds(); mitk::Vector3D result; result[0] = bounds[1]; result[1] = bounds[3]; result[2] = bounds[5]; return result; } void SetSize(mitk::BaseGeometry* geometry, const mitk::Vector3D& size) { mitk::BaseGeometry::BoundsArrayType bounds({ 0.0, size[0], 0.0, size[1], 0.0, size[2] }); geometry->SetBounds(bounds); } mitk::TimeGeometry::Pointer ReadGeometry(const nlohmann::json& jGeometry, int version) { auto geometry = mitk::Geometry3D::New(); geometry->ImageGeometryOn(); if (!jGeometry.is_object()) mitkThrow() << "Geometry is expected to be a JSON object."; bool hasTransform = false; if (jGeometry.contains("Transform")) { if (version < 2) mitkThrow() << "Transforms are not supported in MITK ROI file format version 1."; auto transform = mitk::AffineTransform3D::New(); mitk::FromJSON(jGeometry["Transform"], transform); geometry->SetIndexToWorldTransform(transform); hasTransform = true; } if (jGeometry.contains("Origin")) { if (hasTransform) mitkThrow() << "Origin is already defined by Transform."; geometry->SetOrigin(jGeometry["Origin"].get()); } if (jGeometry.contains("Spacing")) { if (hasTransform) mitkThrow() << "Spacing is already defined by Transform."; geometry->SetSpacing(jGeometry["Spacing"].get()); } if (jGeometry.contains("Size")) SetSize(geometry, jGeometry["Size"].get()); auto timeSteps = jGeometry.contains("TimeSteps") ? jGeometry["TimeSteps"].get() : 1; auto result = mitk::ProportionalTimeGeometry::New(); result->Initialize(geometry, timeSteps); return result; } nlohmann::json WriteGeometry(const mitk::TimeGeometry* timeGeometry) { auto geometry = timeGeometry->GetGeometryForTimeStep(0); nlohmann::json result; mitk::ToJSON(result["Transform"], geometry->GetIndexToWorldTransform()); result["Size"] = GetSize(geometry); auto timeSteps = timeGeometry->CountTimeSteps(); if (timeSteps > 1) result["TimeSteps"] = timeSteps; return result; } } mitk::ROIIO::ROIIO() : AbstractFileIO(ROI::GetStaticNameOfClass(), MitkROIIOMimeTypes::ROI_MIMETYPE(), "MITK ROI") { this->RegisterService(); } std::vector mitk::ROIIO::DoRead() { auto *stream = this->GetInputStream(); std::ifstream fileStream; if (nullptr == stream) { auto filename = this->GetInputLocation(); - if (filename.empty() || !std::filesystem::exists(filename)) + if (filename.empty() || !fs::exists(filename)) mitkThrow() << "Invalid or nonexistent filename: \"" << filename << "\"!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for reading!"; stream = &fileStream; } auto result = ROI::New(); try { auto j = nlohmann::json::parse(*stream); auto version = CheckFileFormat(j); auto geometry = ReadGeometry(j["Geometry"], version); result->SetTimeGeometry(geometry); if (j.contains("Name")) result->SetProperty("name", mitk::StringProperty::New(j["Name"].get())); if (j.contains("Caption")) result->SetProperty("caption", mitk::StringProperty::New(j["Caption"].get())); for (const auto& roi : j["ROIs"]) result->AddElement(roi.get()); } catch (const nlohmann::json::exception &e) { mitkThrow() << e.what(); } return { result }; } void mitk::ROIIO::Write() { auto input = dynamic_cast(this->GetInput()); if (input == nullptr) mitkThrow() << "Invalid input for writing!"; if (input->GetNumberOfElements() == 0) mitkThrow() << "No ROIs found!"; auto* stream = this->GetOutputStream(); std::ofstream fileStream; if (stream == nullptr) { auto filename = this->GetOutputLocation(); if (filename.empty()) mitkThrow() << "Neither an output stream nor an output filename was specified!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for writing!"; stream = &fileStream; } if (!stream->good()) mitkThrow() << "Stream for writing is not good!"; nlohmann::ordered_json j = { { "FileFormat", "MITK ROI" }, { "Version", 2 } }; if (auto name = input->GetProperty("name"); name.IsNotNull()) j["Name"] = name->GetValueAsString(); j["Geometry"] = WriteGeometry(input->GetTimeGeometry()); auto caption = input->GetConstProperty("caption"); if (caption.IsNotNull()) j["Caption"] = caption->GetValueAsString(); nlohmann::json rois; for (const auto& roi : *input) rois.push_back(roi.second); j["ROIs"] = rois; *stream << std::setw(2) << j << std::endl; } mitk::ROIIO* mitk::ROIIO::IOClone() const { return new ROIIO(*this); } diff --git a/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp b/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp index 40d8697186..dc4b3430ad 100644 --- a/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp +++ b/Modules/ROI/autoload/IO/src/mitkROIIOMimeTypes.cpp @@ -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. ============================================================================*/ #include #include -#include +#include #include #include mitk::MitkROIIOMimeTypes::MitkROIMimeType::MitkROIMimeType() : CustomMimeType(ROI_MIMETYPE_NAME()) { this->AddExtension("json"); this->SetCategory("MITK ROI"); this->SetComment("MITK ROI"); } bool mitk::MitkROIIOMimeTypes::MitkROIMimeType::AppliesTo(const std::string& path) const { bool result = CustomMimeType::AppliesTo(path); - if (!std::filesystem::exists(path)) // T18572 + if (!fs::exists(path)) // T18572 return result; std::ifstream file(path); if (!file.is_open()) return false; auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_object()) return false; if ("MITK ROI" != json.value("FileFormat", "")) return false; auto version = json.value("Version", 0); if (version < 1 || version > 2) return false; return true; } mitk::MitkROIIOMimeTypes::MitkROIMimeType* mitk::MitkROIIOMimeTypes::MitkROIMimeType::Clone() const { return new MitkROIMimeType(*this); } mitk::MitkROIIOMimeTypes::MitkROIMimeType mitk::MitkROIIOMimeTypes::ROI_MIMETYPE() { return MitkROIMimeType(); } std::string mitk::MitkROIIOMimeTypes::ROI_MIMETYPE_NAME() { return IOMimeTypes::DEFAULT_BASE_NAME() + ".roi"; } std::vector mitk::MitkROIIOMimeTypes::Get() { std::vector mimeTypes; mimeTypes.push_back(ROI_MIMETYPE().Clone()); return mimeTypes; } diff --git a/Modules/ROI/src/mitkROI.cpp b/Modules/ROI/src/mitkROI.cpp index 6f5e614b1a..e0d73fab09 100644 --- a/Modules/ROI/src/mitkROI.cpp +++ b/Modules/ROI/src/mitkROI.cpp @@ -1,399 +1,401 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include void mitk::to_json(nlohmann::json& j, const ROI::Element& roi) { j["ID"] = roi.GetID(); if (roi.HasTimeSteps()) { auto timeSteps = roi.GetTimeSteps(); for (const auto t : timeSteps) { nlohmann::json jTimeStep = { { "t", t }, { "Min", roi.GetMin(t) }, { "Max", roi.GetMax(t) } }; if (auto* properties = roi.GetProperties(t); properties != nullptr && !properties->IsEmpty()) properties->ToJSON(jTimeStep["Properties"]); j["TimeSteps"].push_back(jTimeStep); } } else { j["Min"] = roi.GetMin(); j["Max"] = roi.GetMax(); } if (auto* properties = roi.GetDefaultProperties(); properties != nullptr && !properties->IsEmpty()) properties->ToJSON(j["Properties"]); } void mitk::from_json(const nlohmann::json& j, ROI::Element& roi) { auto id = j["ID"].get(); roi.SetID(id); if (j.contains("TimeSteps")) { for (const auto& jTimeStep : j["TimeSteps"]) { auto t = jTimeStep["t"].get(); roi.SetMin(jTimeStep["Min"].get(), t); roi.SetMax(jTimeStep["Max"].get(), t); if (jTimeStep.contains("Properties")) { auto properties = mitk::PropertyList::New(); properties->FromJSON(jTimeStep["Properties"]); roi.SetProperties(properties, t); } } } else { roi.SetMin(j["Min"].get()); roi.SetMax(j["Max"].get()); } if (j.contains("Properties")) { auto properties = mitk::PropertyList::New(); properties->FromJSON(j["Properties"]); roi.SetDefaultProperties(properties); } } mitk::ROI::Element::Element() : Element(0) { } mitk::ROI::Element::Element(unsigned int id) : m_ID(id), m_DefaultProperties(PropertyList::New()) { } unsigned int mitk::ROI::Element::GetID() const { return m_ID; } void mitk::ROI::Element::SetID(unsigned int id) { m_ID = id; } bool mitk::ROI::Element::HasTimeStep(TimeStepType t) const { return m_Min.count(t) != 0 && m_Max.count(t) != 0; } bool mitk::ROI::Element::HasTimeSteps() const { // Check for multiple time steps. if (m_Min.size() > 1 && m_Max.size() > 1) return true; // Check for single time step that is not 0. if (m_Min.size() >= 1 && m_Max.size() >= 1) return m_Min.count(0) == 0 && m_Max.count(0) == 0; // Single time step 0. return false; } std::vector mitk::ROI::Element::GetTimeSteps() const { std::vector result; result.reserve(m_Min.size()); for (const auto& [t, min] : m_Min) { + (void)min; // Prevent unused variable error in older compilers + if (m_Max.count(t) != 0) result.push_back(t); } return result; } mitk::Point3D mitk::ROI::Element::GetMin(TimeStepType t) const { return m_Min.at(t); } void mitk::ROI::Element::SetMin(const Point3D& min, TimeStepType t) { m_Min[t] = min; } mitk::Point3D mitk::ROI::Element::GetMax(TimeStepType t) const { return m_Max.at(t); } void mitk::ROI::Element::SetMax(const Point3D& max, TimeStepType t) { m_Max[t] = max; } mitk::PropertyList* mitk::ROI::Element::GetDefaultProperties() const { return m_DefaultProperties; } void mitk::ROI::Element::SetDefaultProperties(PropertyList* properties) { m_DefaultProperties = properties; } mitk::PropertyList* mitk::ROI::Element::GetProperties(TimeStepType t) const { if (m_Properties.count(t) != 0) return m_Properties.at(t); return nullptr; } void mitk::ROI::Element::SetProperties(PropertyList* properties, TimeStepType t) { m_Properties[t] = properties; } mitk::BaseProperty::ConstPointer mitk::ROI::Element::GetConstProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) const { return !contextName.empty() ? this->GetConstProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext) : m_DefaultProperties->GetConstProperty(propertyKey); } mitk::BaseProperty::ConstPointer mitk::ROI::Element::GetConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) const { auto it = m_Properties.find(t); if (it != m_Properties.end() && it->second.IsNotNull()) { auto property = it->second->GetConstProperty(propertyKey); if (property.IsNotNull()) return property; } if (!fallBackOnDefaultContext) return nullptr; return m_DefaultProperties->GetConstProperty(propertyKey); } std::vector mitk::ROI::Element::GetPropertyKeys(const std::string& contextName, bool includeDefaultContext) const { return !contextName.empty() ? this->GetPropertyKeys(std::stoul(contextName), includeDefaultContext) : m_DefaultProperties->GetPropertyKeys(); } std::vector mitk::ROI::Element::GetPropertyKeys(TimeStepType t, bool includeDefaultContext) const { auto it = m_Properties.find(t); std::vector result; if (it != m_Properties.end() && it->second.IsNotNull()) result = it->second->GetPropertyKeys(); if (includeDefaultContext) { auto keys = m_DefaultProperties->GetPropertyKeys(); auto end = result.cend(); std::remove_copy_if(keys.cbegin(), keys.cend(), std::back_inserter(result), [&, result, end](const std::string& key) { return end != std::find(result.cbegin(), end, key); }); } return result; } std::vector mitk::ROI::Element::GetPropertyContextNames() const { std::vector result; result.reserve(m_Properties.size()); std::transform(m_Properties.cbegin(), m_Properties.cend(), std::back_inserter(result), [](const PropertyListsType::value_type& property) { return std::to_string(property.first); }); return result; } mitk::BaseProperty* mitk::ROI::Element::GetNonConstProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) { return !contextName.empty() ? this->GetNonConstProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext) : m_DefaultProperties->GetNonConstProperty(propertyKey); } mitk::BaseProperty* mitk::ROI::Element::GetNonConstProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) { auto it = m_Properties.find(t); if (it != m_Properties.end() && it->second.IsNotNull()) { auto* property = it->second->GetNonConstProperty(propertyKey); if (property != nullptr) return property; } if (!fallBackOnDefaultContext) return nullptr; return m_DefaultProperties->GetNonConstProperty(propertyKey); } void mitk::ROI::Element::SetProperty(const std::string& propertyKey, BaseProperty* property, const std::string& contextName, bool fallBackOnDefaultContext) { if (!contextName.empty()) { this->SetProperty(propertyKey, property, std::stoul(contextName), fallBackOnDefaultContext); return; } m_DefaultProperties->SetProperty(propertyKey, property); } void mitk::ROI::Element::SetProperty(const std::string& propertyKey, BaseProperty* property, TimeStepType t, bool fallBackOnDefaultContext) { auto it = m_Properties.find(t); if (it != m_Properties.end() && it->second.IsNotNull()) { it->second->SetProperty(propertyKey, property); return; } if (!fallBackOnDefaultContext) mitkThrow() << "Time step " << t << " does not exist!"; m_DefaultProperties->SetProperty(propertyKey, property); } void mitk::ROI::Element::RemoveProperty(const std::string& propertyKey, const std::string& contextName, bool fallBackOnDefaultContext) { if (!contextName.empty()) { this->RemoveProperty(propertyKey, std::stoul(contextName), fallBackOnDefaultContext); return; } m_DefaultProperties->RemoveProperty(propertyKey); } void mitk::ROI::Element::RemoveProperty(const std::string& propertyKey, TimeStepType t, bool fallBackOnDefaultContext) { auto it = m_Properties.find(t); if (it != m_Properties.end() && it->second.IsNotNull()) { it->second->RemoveProperty(propertyKey); return; } if (!fallBackOnDefaultContext) mitkThrow() << "Time step " << t << " does not exist!"; m_DefaultProperties->RemoveProperty(propertyKey); } mitk::ROI::ROI() { } mitk::ROI::ROI(const Self& other) : BaseData(other) { } mitk::ROI::~ROI() { } size_t mitk::ROI::GetNumberOfElements() const { return m_Elements.size(); } void mitk::ROI::AddElement(const Element& element) { const auto id = element.GetID(); if (m_Elements.count(id) != 0) mitkThrow() << "ROI already contains an element with ID " << std::to_string(id) << '.'; m_Elements[id] = element; } const mitk::ROI::Element& mitk::ROI::GetElement(unsigned int id) const { return m_Elements.at(id); } mitk::ROI::Element& mitk::ROI::GetElement(unsigned int id) { return m_Elements.at(id); } mitk::ROI::ConstIterator mitk::ROI::begin() const { return m_Elements.begin(); } mitk::ROI::ConstIterator mitk::ROI::end() const { return m_Elements.end(); } mitk::ROI::Iterator mitk::ROI::begin() { return m_Elements.begin(); } mitk::ROI::Iterator mitk::ROI::end() { return m_Elements.end(); } void mitk::ROI::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::ROI::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::ROI::VerifyRequestedRegion() { return true; } void mitk::ROI::SetRequestedRegion(const itk::DataObject*) { } diff --git a/Modules/ROI/src/mitkROIMapper2D.cpp b/Modules/ROI/src/mitkROIMapper2D.cpp index 035249e7e8..052150c5c0 100644 --- a/Modules/ROI/src/mitkROIMapper2D.cpp +++ b/Modules/ROI/src/mitkROIMapper2D.cpp @@ -1,186 +1,188 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include "mitkROIMapperHelper.h" #include #include #include #include #include #include #include #include namespace { mitk::Point3D GetBottomLeftPoint(vtkPoints* points, mitk::BaseRenderer* renderer) { mitk::Point3D point = points->GetPoint(0); mitk::Point2D bottomLeftDisplayPoint; renderer->WorldToDisplay(point, bottomLeftDisplayPoint); auto numPoints = points->GetNumberOfPoints(); mitk::Point2D displayPoint; for (decltype(numPoints) i = 1; i < numPoints; ++i) { point.FillPoint(points->GetPoint(i)); renderer->WorldToDisplay(point, displayPoint); bottomLeftDisplayPoint[0] = std::min(bottomLeftDisplayPoint[0], displayPoint[0]); bottomLeftDisplayPoint[1] = std::min(bottomLeftDisplayPoint[1], displayPoint[1]); } renderer->DisplayToWorld(bottomLeftDisplayPoint, point); return point; } } mitk::ROIMapper2D::LocalStorage::LocalStorage() { } mitk::ROIMapper2D::LocalStorage::~LocalStorage() { } const mitk::PlaneGeometry* mitk::ROIMapper2D::LocalStorage::GetLastPlaneGeometry() const { return m_LastPlaneGeometry; } void mitk::ROIMapper2D::LocalStorage::SetLastPlaneGeometry(const PlaneGeometry* planeGeometry) { m_LastPlaneGeometry = planeGeometry; } void mitk::ROIMapper2D::SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override) { Superclass::SetDefaultProperties(node, renderer, override); ROIMapperHelper::SetDefaultProperties(node, renderer, override); } mitk::ROIMapper2D::ROIMapper2D() { } mitk::ROIMapper2D::~ROIMapper2D() { } void mitk::ROIMapper2D::GenerateDataForRenderer(BaseRenderer* renderer) { const auto timePoint = renderer->GetWorldTimeGeometry()->TimeStepToTimePoint(renderer->GetTimeStep()); const auto* planeGeometry = renderer->GetCurrentWorldPlaneGeometry(); auto* localStorage = m_LocalStorageHandler.GetLocalStorage(renderer); const auto* dataNode = this->GetDataNode(); if (localStorage->GetLastPlaneGeometry() != nullptr && localStorage->GetLastPlaneGeometry()->IsOnPlane(planeGeometry) && localStorage->GetLastGenerateDataTime() >= dataNode->GetMTime() && localStorage->GetLastTimePoint() == timePoint) { return; } localStorage->SetLastPlaneGeometry(planeGeometry->Clone()); localStorage->SetLastTimePoint(timePoint); auto data = static_cast(this->GetData()); if (!data->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; const auto t = data->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto propAssembly = vtkSmartPointer::New(); if (dataNode->IsVisible(renderer)) { const auto* geometry = data->GetGeometry(t); auto plane = vtkSmartPointer::New(); plane->SetOrigin(planeGeometry->GetOrigin().data()); plane->SetNormal(planeGeometry->GetNormal().data()); for (const auto& [id, roi] : *data) { + (void)id; // Prevent unused variable error in older compilers + if (!roi.HasTimeStep(t)) continue; Point3D min = roi.GetMin(t); Point3D max = roi.GetMax(t); auto cube = vtkSmartPointer::New(); cube->SetBounds(min[0], max[0], min[1], max[1], min[2], max[2]); auto transform = vtkSmartPointer::New(); transform->SetTransform(geometry->GetVtkTransform()); transform->SetInputConnection(cube->GetOutputPort()); auto cutter = vtkSmartPointer::New(); cutter->SetInputConnection(transform->GetOutputPort()); cutter->SetPlane(plane); cutter->Update(); auto* slicePolyData = cutter->GetOutput(); if (slicePolyData->GetNumberOfLines() == 0) continue; auto mapper = vtkSmartPointer::New(); mapper->SetInputConnection(cutter->GetOutputPort()); auto actor = vtkSmartPointer::New(); actor->SetMapper(mapper); this->ApplyColorAndOpacityProperties(renderer, actor); ROIMapperHelper::ApplyIndividualProperties(roi, t, actor); propAssembly->AddPart(actor); if (std::string caption; dataNode->GetStringProperty("caption", caption, renderer)) { caption = ROIMapperHelper::ParseCaption(caption, roi, t); if (!caption.empty()) { auto bottomLeftPoint = GetBottomLeftPoint(slicePolyData->GetPoints(), renderer); auto captionActor = ROIMapperHelper::CreateCaptionActor(caption, bottomLeftPoint, actor->GetProperty(), dataNode, renderer); propAssembly->AddPart(captionActor); } } } } localStorage->SetPropAssembly(propAssembly); localStorage->UpdateGenerateDataTime(); } void mitk::ROIMapper2D::ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) { auto* property = actor->GetProperty(); float opacity = 1.0f; this->GetDataNode()->GetOpacity(opacity, renderer); property->SetOpacity(opacity); } vtkProp* mitk::ROIMapper2D::GetVtkProp(BaseRenderer* renderer) { return m_LocalStorageHandler.GetLocalStorage(renderer)->GetPropAssembly(); } diff --git a/Modules/ROI/src/mitkROIMapper3D.cpp b/Modules/ROI/src/mitkROIMapper3D.cpp index 06425a8cdc..6ab6c0c73f 100644 --- a/Modules/ROI/src/mitkROIMapper3D.cpp +++ b/Modules/ROI/src/mitkROIMapper3D.cpp @@ -1,116 +1,118 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include "mitkROIMapperHelper.h" #include #include #include #include #include mitk::ROIMapper3D::LocalStorage::LocalStorage() { } mitk::ROIMapper3D::LocalStorage::~LocalStorage() { } void mitk::ROIMapper3D::SetDefaultProperties(DataNode* node, BaseRenderer* renderer, bool override) { Superclass::SetDefaultProperties(node, renderer, override); ROIMapperHelper::SetDefaultProperties(node, renderer, override); } mitk::ROIMapper3D::ROIMapper3D() { } mitk::ROIMapper3D::~ROIMapper3D() { } void mitk::ROIMapper3D::GenerateDataForRenderer(BaseRenderer* renderer) { const auto timePoint = renderer->GetWorldTimeGeometry()->TimeStepToTimePoint(renderer->GetTimeStep()); auto* localStorage = m_LocalStorageHandler.GetLocalStorage(renderer); const auto* dataNode = this->GetDataNode(); if (localStorage->GetLastGenerateDataTime() >= dataNode->GetMTime() && localStorage->GetLastTimePoint() == timePoint) { return; } localStorage->SetLastTimePoint(timePoint); auto data = static_cast(this->GetData()); if (!data->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; const auto t = data->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto propAssembly = vtkSmartPointer::New(); if (dataNode->IsVisible(renderer)) { const auto* geometry = data->GetGeometry(); for (const auto& [id, roi] : *data) { + (void)id; // Prevent unused variable error in older compilers + if (!roi.HasTimeStep(t)) continue; Point3D min = roi.GetMin(t); Point3D max = roi.GetMax(t); auto cube = vtkSmartPointer::New(); cube->SetBounds(min[0], max[0], min[1], max[1], min[2], max[2]); auto transform = vtkSmartPointer::New(); transform->SetTransform(geometry->GetVtkTransform()); transform->SetInputConnection(cube->GetOutputPort()); auto mapper = vtkSmartPointer::New(); mapper->SetInputConnection(transform->GetOutputPort()); auto actor = vtkSmartPointer::New(); actor->SetMapper(mapper); this->ApplyColorAndOpacityProperties(renderer, actor); ROIMapperHelper::ApplyIndividualProperties(roi, t, actor); propAssembly->AddPart(actor); } } localStorage->SetPropAssembly(propAssembly); localStorage->UpdateGenerateDataTime(); } void mitk::ROIMapper3D::ApplyColorAndOpacityProperties(BaseRenderer* renderer, vtkActor* actor) { auto* property = actor->GetProperty(); float opacity = 1.0f; this->GetDataNode()->GetOpacity(opacity, renderer); property->SetOpacity(opacity); } vtkProp* mitk::ROIMapper3D::GetVtkProp(BaseRenderer* renderer) { return m_LocalStorageHandler.GetLocalStorage(renderer)->GetPropAssembly(); } diff --git a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp index 82ef75b42d..271bb45d9b 100644 --- a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp +++ b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp @@ -1,468 +1,468 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSceneReaderV1.h" #include "Poco/Path.h" #include "mitkBaseRenderer.h" #include "mitkIOUtil.h" #include "mitkProgressBar.h" #include "mitkPropertyListDeserializer.h" #include "mitkSerializerMacros.h" #include #include #include -#include +#include #include MITK_REGISTER_SERIALIZER(SceneReaderV1) namespace { typedef std::pair> NodesAndParentsPair; bool NodeSortByLayerIsLessThan(const NodesAndParentsPair &left, const NodesAndParentsPair &right) { if (left.first.IsNotNull() && right.first.IsNotNull()) { int leftLayer; int rightLayer; if (left.first->GetIntProperty("layer", leftLayer) && right.first->GetIntProperty("layer", rightLayer)) { return leftLayer < rightLayer; } else { // fall back to name sort return left.first->GetName() < right.first->GetName(); } } // in all other cases, fall back to stupid pointer comparison // this is not reasonable but at least answers the sorting // question clearly return left.first.GetPointer() < right.first.GetPointer(); } // This is a workaround until we are able to save time-related information in an // actual file format of surfaces. void ApplyProportionalTimeGeometryProperties(mitk::BaseData* data) { auto* geometry = dynamic_cast(data->GetTimeGeometry()); if (nullptr == geometry) return; auto properties = data->GetPropertyList(); float value = 0.0f; if (properties->GetFloatProperty("ProportionalTimeGeometry.FirstTimePoint", value)) { if (value == -std::numeric_limits::infinity()) value = std::numeric_limits::lowest(); geometry->SetFirstTimePoint(value); } if (properties->GetFloatProperty("ProportionalTimeGeometry.StepDuration", value)) geometry->SetStepDuration(value); } - mitk::PropertyList::Pointer DeserializeProperties(const tinyxml2::XMLElement *propertiesElement, const std::filesystem::path& basePath) + mitk::PropertyList::Pointer DeserializeProperties(const tinyxml2::XMLElement *propertiesElement, const fs::path& basePath) { if (propertiesElement == nullptr) return nullptr; - std::filesystem::path path(propertiesElement->Attribute("file")); + fs::path path(propertiesElement->Attribute("file")); if (path.empty()) return nullptr; if (!basePath.empty()) path = basePath / path; auto deserializer = mitk::PropertyListDeserializer::New(); deserializer->SetFilename(path.string()); deserializer->Deserialize(); return deserializer->GetOutput(); } } bool mitk::SceneReaderV1::LoadScene(tinyxml2::XMLDocument &document, const std::string &workingDirectory, DataStorage *storage) { assert(storage); bool error(false); // TODO prepare to detect errors (such as cycles) from wrongly written or edited xml files // Get number of elements to initialize progress bar // 1. if there is a element, // - construct a name for the appropriate serializer // - try to instantiate this serializer via itk object factory // - if serializer could be created, use it to read the file into a BaseData object // - if successful, call the new node's SetData(..) // create a node for the tag "data" and test if node was created typedef std::vector DataNodeVector; DataNodeVector DataNodes; unsigned int listSize = 0; for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { ++listSize; } ProgressBar::GetInstance()->AddStepsToDo(listSize * 2); // Deserialize base data properties before reading the actual data to be // able to provide them as read-only meta data to the data reader. std::map baseDataPropertyLists; for (auto *nodeElement = document.FirstChildElement("node"); nodeElement != nullptr; nodeElement = nodeElement->NextSiblingElement("node")) { const auto *uid = nodeElement->Attribute("UID"); if (uid == nullptr) continue; auto *dataElement = nodeElement->FirstChildElement("data"); if (dataElement != nullptr) { auto properties = DeserializeProperties(dataElement->FirstChildElement("properties"), workingDirectory); if (properties.IsNotNull()) baseDataPropertyLists[uid] = properties; } } for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { mitk::PropertyList* properties = nullptr; const auto *uid = element->Attribute("UID"); if (uid != nullptr) { auto iter = baseDataPropertyLists.find(uid); if (iter != baseDataPropertyLists.end()) properties = iter->second; } const auto *dataElement = element->FirstChildElement("data"); auto dataNode = this->LoadBaseDataFromDataTag(dataElement, properties, workingDirectory, error); if (dataNode.IsNull()) continue; auto* baseData = dataNode->GetData(); if (baseData != nullptr && properties != nullptr) { baseData->SetPropertyList(properties); ApplyProportionalTimeGeometryProperties(baseData); } DataNodes.push_back(dataNode); ProgressBar::GetInstance()->Progress(); } // iterate all nodes // first level nodes should be elements auto nit = DataNodes.begin(); for (auto *element = document.FirstChildElement("node"); element != nullptr || nit != DataNodes.end(); element = element->NextSiblingElement("node"), ++nit) { mitk::DataNode::Pointer node = *nit; // 1. check child nodes const char *uida = element->Attribute("UID"); std::string uid(""); if (uida) { uid = uida; m_NodeForID[uid] = node.GetPointer(); m_IDForNode[node.GetPointer()] = uid; } else { MITK_ERROR << "No UID found for current node. Node will have no parents."; error = true; } // 2. if there are nodes, // - instantiate the appropriate PropertyListDeSerializer // - use them to construct PropertyList objects // - add these properties to the node (if necessary, use renderwindow name) bool success = DecorateNodeWithProperties(node, element, workingDirectory); if (!success) { MITK_ERROR << "Could not load properties for node."; error = true; } // remember node for later adding to DataStorage m_OrderedNodePairs.push_back(std::make_pair(node, std::list())); // 3. if there are elements, remember parent objects for (auto *source = element->FirstChildElement("source"); source != nullptr; source = source->NextSiblingElement("source")) { const char *sourceUID = source->Attribute("UID"); if (sourceUID) { m_OrderedNodePairs.back().second.push_back(std::string(sourceUID)); } } ProgressBar::GetInstance()->Progress(); } // end for all // sort our nodes by their "layer" property // (to be inserted in that order) m_OrderedNodePairs.sort(&NodeSortByLayerIsLessThan); // remove all unknown parent UIDs for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end();) { if (m_NodeForID.find(*parentsIter) == m_NodeForID.end()) { parentsIter = nodesIter->second.erase(parentsIter); MITK_WARN << "Found a DataNode with unknown parents. Will add it to DataStorage without any parent objects."; error = true; } else { ++parentsIter; } } } // repeat the following loop ... // ... for all created nodes unsigned int lastMapSize(0); while (lastMapSize != m_OrderedNodePairs .size()) // this is to prevent infinite loops; each iteration must at least add one node to DataStorage { lastMapSize = m_OrderedNodePairs.size(); // iterate (layer) ordered nodes backwards // we insert the highest layers first for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { bool addThisNode(true); // if any parent node is not yet in DataStorage, skip node for now and check later for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { if (!storage->Exists(m_NodeForID[*parentsIter])) { addThisNode = false; break; } } if (addThisNode) { DataStorage::SetOfObjects::Pointer parents = DataStorage::SetOfObjects::New(); for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { parents->push_back(m_NodeForID[*parentsIter]); } // if all parents are found in datastorage (or are unknown), add node to DataStorage storage->Add(nodesIter->first, parents); // remove this node from m_OrderedNodePairs m_OrderedNodePairs.erase(nodesIter); // break this for loop because iterators are probably invalid break; } } } // All nodes that are still in m_OrderedNodePairs at this point are not part of a proper directed graph structure. // We'll add such nodes without any parent information. for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { storage->Add(nodesIter->first); MITK_WARN << "Encountered node that is not part of a directed graph structure. Will be added to DataStorage " "without parents."; error = true; } return !error; } mitk::DataNode::Pointer mitk::SceneReaderV1::LoadBaseDataFromDataTag(const tinyxml2::XMLElement *dataElement, const PropertyList *properties, const std::string &workingDirectory, bool &error) { DataNode::Pointer node; if (dataElement) { const char *filename = dataElement->Attribute("file"); if (filename && strlen(filename) != 0) { try { auto baseData = IOUtil::Load(workingDirectory + Poco::Path::separator() + filename, properties); node = DataNode::New(); node->SetData(baseData); } catch (std::exception &e) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Exception says: " << e.what(); error = true; } if (node.IsNull()) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Factory returned nullptr object."; error = true; } } else { MITK_ERROR << "File attribute of data tag is empty!"; error = true; } const char* dataUID = dataElement->Attribute("UID"); if (!error && dataUID != nullptr) { UIDManipulator manip(node->GetData()); manip.SetUID(dataUID); } } // in case there was no element we create a new empty node (for appending a propertylist later) if (node.IsNull()) { node = DataNode::New(); } return node; } void mitk::SceneReaderV1::ClearNodePropertyListWithExceptions(DataNode &node, PropertyList &propertyList) { // Basically call propertyList.Clear(), but implement exceptions (see bug 19354) BaseData *data = node.GetData(); PropertyList::Pointer propertiesToKeep = PropertyList::New(); if (dynamic_cast(data)) { /* Older scene files (before changes of bug 17547) could contain a RenderingMode property with value "LevelWindow_Color". Since bug 17547 this value has been removed and replaced by the default value LookupTable_LevelWindow_Color. This new default value does only result in "black-to-white" CT images (or others) if there is a corresponding lookup table. Such a lookup table is provided as a default value by the Image mapper. Since that value was never present in older scene files, we do well in not removing the new default value here. Otherwise the mapper would fall back to another default which is all the colors of the rainbow :-( */ BaseProperty::Pointer lutProperty = propertyList.GetProperty("LookupTable"); propertiesToKeep->SetProperty("LookupTable", lutProperty); /* Older scene files (before changes of T14807) may contain multi-component images without the "Image.Displayed Component" property. As the treatment as multi-component image and the corresponding visualization options hinges on that property we should not delete it, if it was added by the mapper. This is a fix for the issue reported in T19919. */ BaseProperty::Pointer compProperty = propertyList.GetProperty("Image.Displayed Component"); if (compProperty.IsNotNull()) { propertiesToKeep->SetProperty("Image.Displayed Component", compProperty); } } propertyList.Clear(); propertyList.ConcatenatePropertyList(propertiesToKeep); } bool mitk::SceneReaderV1::DecorateNodeWithProperties(DataNode *node, const tinyxml2::XMLElement *nodeElement, const std::string &workingDirectory) { assert(node); assert(nodeElement); bool error(false); for (auto *properties = nodeElement->FirstChildElement("properties"); properties != nullptr; properties = properties->NextSiblingElement("properties")) { const char *propertiesfilea(properties->Attribute("file")); std::string propertiesfile(propertiesfilea ? propertiesfilea : ""); const char *renderwindowa(properties->Attribute("renderwindow")); std::string renderwindow(renderwindowa ? renderwindowa : ""); PropertyList::Pointer propertyList = node->GetPropertyList(renderwindow); // DataNode implementation always returns a propertylist ClearNodePropertyListWithExceptions(*node, *propertyList); // use deserializer to construct new properties PropertyListDeserializer::Pointer deserializer = PropertyListDeserializer::New(); deserializer->SetFilename(workingDirectory + Poco::Path::separator() + propertiesfile); bool success = deserializer->Deserialize(); error |= !success; PropertyList::Pointer readProperties = deserializer->GetOutput(); if (readProperties.IsNotNull()) { propertyList->ConcatenatePropertyList(readProperties, true); // true = replace } else { MITK_ERROR << "Property list reader did not return a property list. This is an implementation error. Please tell " "your developer."; error = true; } } return !error; } diff --git a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp index 27ce24aa9e..b1b1c8f614 100644 --- a/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp +++ b/Modules/Segmentation/Interactions/mitkMonaiLabelTool.cpp @@ -1,660 +1,660 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkMonaiLabelTool.h" #ifndef CPPHTTPLIB_OPENSSL_SUPPORT #define CPPHTTPLIB_OPENSSL_SUPPORT #endif -#include +#include #include #include #include #include #include #include #include #include "mitkImageAccessByItk.h" mitk::MonaiLabelTool::MonaiLabelTool() : SegWithPreviewTool(true, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); this->IsTimePointChangeAwareOff(); } mitk::MonaiLabelTool::~MonaiLabelTool() { - std::filesystem::remove_all(this->GetTempDir()); + fs::remove_all(this->GetTempDir()); } void mitk::MonaiLabelTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddNegativePoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPositivePoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::MonaiLabelTool::Activated() { Superclass::Activated(); m_PointSetPositive = mitk::PointSet::New(); m_PointSetNodePositive = mitk::DataNode::New(); m_PointSetNodePositive->SetData(m_PointSetPositive); m_PointSetNodePositive->SetName(std::string(this->GetName()) + "_PointSetPositive"); m_PointSetNodePositive->SetBoolProperty("helper object", true); m_PointSetNodePositive->SetColor(0.0, 1.0, 0.0); m_PointSetNodePositive->SetVisibility(true); m_PointSetNodePositive->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodePositive->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodePositive, this->GetToolManager()->GetWorkingData(0)); m_PointSetNegative = mitk::PointSet::New(); m_PointSetNodeNegative = mitk::DataNode::New(); m_PointSetNodeNegative->SetData(m_PointSetNegative); m_PointSetNodeNegative->SetName(std::string(this->GetName()) + "_PointSetNegative"); m_PointSetNodeNegative->SetBoolProperty("helper object", true); m_PointSetNodeNegative->SetColor(1.0, 0.0, 0.0); m_PointSetNodeNegative->SetVisibility(true); m_PointSetNodeNegative->SetProperty("Pointset.2D.shape", mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); m_PointSetNodeNegative->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(m_PointSetNodeNegative, this->GetToolManager()->GetWorkingData(0)); } void mitk::MonaiLabelTool::Deactivated() { this->ClearSeeds(); this->GetDataStorage()->Remove(m_PointSetNodePositive); this->GetDataStorage()->Remove(m_PointSetNodeNegative); m_PointSetNodePositive = nullptr; m_PointSetNodeNegative = nullptr; m_PointSetPositive = nullptr; m_PointSetNegative = nullptr; Superclass::Deactivated(); } void mitk::MonaiLabelTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); } void mitk::MonaiLabelTool::OnAddPositivePoint(StateMachineAction *, InteractionEvent *interactionEvent) { if (m_RequestParameters->model.IsInteractive()) { if (m_RequestParameters->model.Is2D() && ((nullptr == this->GetWorkingPlaneGeometry()) || !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry())))) { this->ClearSeeds(); this->SetWorkingPlaneGeometry(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone()); this->ResetPreviewContent(); } if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSetPositive->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); ++m_PointSetCount; this->UpdatePreview(); } } } } void mitk::MonaiLabelTool::OnAddNegativePoint(StateMachineAction *, InteractionEvent *interactionEvent) { if ("deepgrow" != m_RequestParameters->model.type) { return; } if (m_RequestParameters->model.Is2D() && (m_PointSetPositive->GetSize() == 0 || nullptr == this->GetWorkingPlaneGeometry() || !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry())))) { return; } if (!this->IsUpdating() && m_PointSetNegative.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSetNegative->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); m_PointSetCount++; this->UpdatePreview(); } } } void mitk::MonaiLabelTool::OnDelete(StateMachineAction *, InteractionEvent *) { if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) { PointSet::Pointer removeSet = m_PointSetPositive; itk::IdentifierType maxId = 0; if (m_PointSetPositive->GetSize() > 0) { maxId = m_PointSetPositive->GetMaxId().Index(); } if (m_PointSetNegative->GetSize() > 0 && (maxId < m_PointSetNegative->GetMaxId().Index())) { removeSet = m_PointSetNegative; } removeSet->RemovePointAtEnd(0); --m_PointSetCount; this->UpdatePreview(); } } void mitk::MonaiLabelTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::MonaiLabelTool::HasPicks() const { return this->m_PointSetPositive.IsNotNull() && this->m_PointSetPositive->GetSize() > 0; } void mitk::MonaiLabelTool::ClearSeeds() { if (this->m_PointSetPositive.IsNotNull()) { m_PointSetCount -= m_PointSetPositive->GetSize(); this->m_PointSetPositive = mitk::PointSet::New(); // renew pointset this->m_PointSetNodePositive->SetData(this->m_PointSetPositive); } if (this->m_PointSetNegative.IsNotNull()) { m_PointSetCount -= m_PointSetNegative->GetSize(); this->m_PointSetNegative = mitk::PointSet::New(); // renew pointset this->m_PointSetNodeNegative->SetData(this->m_PointSetNegative); } } bool mitk::MonaiLabelTool::IsMonaiServerOn(const std::string &hostName, const int &port) const { httplib::SSLClient cli(hostName, port); cli.enable_server_certificate_verification(false); while (cli.is_socket_open()); return cli.Get("/info/"); } namespace mitk { // Converts the json GET response from the MonaiLabel server to MonaiAppMetadata object. void from_json(const nlohmann::json &jsonObj, mitk::MonaiAppMetadata &appData) { jsonObj["name"].get_to(appData.name); jsonObj["description"].get_to(appData.description); jsonObj["labels"].get_to(appData.labels); auto modelJsonMap = jsonObj["models"].get>(); for (const auto &[_name, _jsonObj] : modelJsonMap) { if (_jsonObj.is_discarded() || !_jsonObj.is_object()) { MITK_ERROR << "Could not parse JSON object."; } mitk::MonaiModelInfo modelInfo; modelInfo.name = _name; try { auto labels = _jsonObj["labels"].get>(); modelInfo.labels = labels; } catch (const std::exception &) { auto labels = _jsonObj["labels"].get>(); for (const auto &label : labels) { modelInfo.labels[label] = -1; // Hardcode -1 as label id } } _jsonObj["type"].get_to(modelInfo.type); _jsonObj["dimension"].get_to(modelInfo.dimension); _jsonObj["description"].get_to(modelInfo.description); appData.models.push_back(modelInfo); } } } namespace { /** * @brief Returns boundary string from Httplib response. */ std::string GetBoundaryString(const httplib::Result &response) { httplib::Headers headers = response->headers; std::string contentType = headers.find("content-type")->second; std::string delimiter = "boundary="; std::string boundaryString = contentType.substr(contentType.find(delimiter) + delimiter.length(), std::string::npos); boundaryString.insert(0, "--"); return boundaryString; } /** * @brief Returns image data string from monai label overall response. */ std::string GetResponseImageString(const std::string &imagePart) { std::string contentTypeStream = "Content-Type: application/octet-stream"; size_t ctPos = imagePart.find(contentTypeStream) + contentTypeStream.length(); std::string imageDataPart = imagePart.substr(ctPos); std::string whitespaces = " \n\r"; imageDataPart.erase(0, imageDataPart.find_first_not_of(whitespaces)); // clean up return imageDataPart; } /** * @brief Helper function to get the Parts of the POST response. */ std::vector GetPartsBetweenBoundary(const std::string &body, const std::string &boundary) { std::vector retVal; std::string master = body; size_t boundaryPos = master.find(boundary); size_t beginPos = 0; while (boundaryPos != std::string::npos) { std::string part = master.substr(beginPos, boundaryPos); if (!part.empty()) { retVal.push_back(part); } master.erase(beginPos, boundaryPos + boundary.length()); boundaryPos = master.find(boundary); } return retVal; } /** * @brief Applies the give std::map lookup table on the preview segmentation LabelSetImage. */ void MapLabelsToSegmentation(const mitk::LabelSetImage *source, mitk::LabelSetImage *dest, const std::map &labelMap) { if (labelMap.empty()) { auto label = mitk::LabelSetImageHelper::CreateNewLabel(dest, "object"); label->SetValue(1); dest->AddLabel(label, false); return; } std::map flippedLabelMap; for (auto const &[key, val] : labelMap) { flippedLabelMap[val] = key; } auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : flippedLabelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { auto label = mitk::Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); mitk::Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); dest->AddLabel(label, false); } else { MITK_INFO << "Label not found for " << val; } } } template void ITKWindowing(const itk::Image *inputImage, mitk::Image *mitkImage, mitk::ScalarType min, mitk::ScalarType max) { typedef itk::Image ImageType; typedef itk::IntensityWindowingImageFilter IntensityFilterType; typename IntensityFilterType::Pointer filter = IntensityFilterType::New(); filter->SetInput(inputImage); filter->SetWindowMinimum(min); filter->SetWindowMaximum(max); filter->SetOutputMinimum(min); filter->SetOutputMaximum(max); filter->Update(); mitkImage->SetImportVolume( (void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), 0, 0, mitk::Image::ManageMemory); filter->GetOutput()->GetPixelContainer()->ContainerManageMemoryOff(); } } mitk::Image::Pointer mitk::MonaiLabelTool::ApplyLevelWindowEffect(const Image *inputAtTimeStep) const { mitk::LevelWindow levelWindow; this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); auto filteredImage = mitk::Image::New(); filteredImage->Initialize(inputAtTimeStep); AccessByItk_n(inputAtTimeStep, ::ITKWindowing, // apply level window filter (filteredImage, levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound())); return filteredImage; } void mitk::MonaiLabelTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (nullptr == m_RequestParameters || (m_RequestParameters->model.IsInteractive() && !this->HasPicks())) { this->ResetPreviewContentAtTimeStep(timeStep); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); return; } const std::string &hostName = m_RequestParameters->hostName; const int port = m_RequestParameters->port; if (!IsMonaiServerOn(hostName, port)) { mitkThrow() << m_SERVER_503_ERROR_TEXT; } if (this->m_TempDir.empty()) { this->SetTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } std::string inputImagePath, outputImagePath; std::tie(inputImagePath, outputImagePath) = this->CreateTempDirs(m_TEMPLATE_FILENAME); try { mitk::Image::Pointer filteredImage = this->ApplyLevelWindowEffect(inputAtTimeStep); this->WriteImage(filteredImage, inputImagePath); this->PostInferRequest(hostName, port, inputImagePath, outputImagePath, inputAtTimeStep->GetGeometry()); auto outputImage = IOUtil::Load(outputImagePath); auto outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); std::map labelMap; // empty map if (m_RequestParameters->model.IsInteractive()) { this->SetLabelTransferMode(LabelTransferMode::MapLabel); this->SetSelectedLabels({MASK_VALUE}); } else { outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); labelMap = m_ResultMetadata["label_names"]; this->SetLabelTransferMode(LabelTransferMode::AddLabel); } ::MapLabelsToSegmentation(outputBuffer, previewImage, labelMap); this->WriteBackResults(previewImage, outputBuffer.GetPointer(), timeStep); MonaiStatusEvent.Send(true); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); mitkThrow() << e.GetDescription(); MonaiStatusEvent.Send(false); } } void mitk::MonaiLabelTool::FetchOverallInfo(const std::string &hostName, const int &port) { m_InfoParameters.reset(); if (!IsMonaiServerOn(hostName, port)) { Tool::ErrorMessage.Send(m_SERVER_503_ERROR_TEXT); mitkThrow() << m_SERVER_503_ERROR_TEXT; } httplib::SSLClient cli(hostName, port); cli.enable_server_certificate_verification(false); if (auto response = cli.Get("/info/")) { if (response->status == 200) { auto jsonObj = nlohmann::json::parse(response->body); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse response from MONAILabel server as JSON object!"; return; } auto appData = jsonObj.template get(); m_InfoParameters = std::make_unique(appData); if (nullptr != m_InfoParameters) { m_InfoParameters->hostName = hostName; m_InfoParameters->port = port; } } } else { Tool::ErrorMessage.Send(httplib::to_string(response.error()) + " error occured."); } } void mitk::MonaiLabelTool::PostInferRequest(const std::string &hostName, const int &port, const std::string &filePath, const std::string &outFile, const mitk::BaseGeometry *baseGeometry) { std::string &modelName = m_RequestParameters->model.name; // Get this from args as well. std::string postPath = "/infer/"; // make this separate class of constants postPath.append(modelName); std::ifstream input(filePath, std::ios::binary); if (!input) { MITK_WARN << "could not read file to POST"; } std::stringstream buffer_lf_img; buffer_lf_img << input.rdbuf(); input.close(); httplib::MultipartFormDataItems items; if (m_RequestParameters->model.IsInteractive()) { std::string foreground = this->ConvertPointsAsListString(baseGeometry, m_PointSetPositive); std::string background = this->ConvertPointsAsListString(baseGeometry, m_PointSetNegative); std::stringstream paramString; paramString << "{" << "\"foreground\":" << foreground << ",\"background\":" << background << "}"; MITK_DEBUG << paramString.str(); items.push_back({"params", paramString.str(), "", ""}); } else // Auto models { items.push_back({"params", "{\"restore_label_idx\": true}", "", ""}); } items.push_back({"file", buffer_lf_img.str(), "post_from_mitk.nii.gz", "application/octet-stream"}); httplib::SSLClient cli(hostName, port); cli.set_read_timeout(60); // arbitary 1 minute time-out to avoid corner cases. cli.enable_server_certificate_verification(false); if (auto response = cli.Post(postPath, items)) { if (response->status == 200) { // Find boundary std::string boundaryString = ::GetBoundaryString(response); MITK_DEBUG << "boundary hash: " << boundaryString; // Parse metadata JSON std::string resBody = response->body; std::vector multiPartResponse = ::GetPartsBetweenBoundary(resBody, boundaryString); std::string metaData = multiPartResponse[0]; std::string contentTypeJson = "Content-Type: application/json"; size_t ctPos = metaData.find(contentTypeJson) + contentTypeJson.length(); std::string metaDataPart = metaData.substr(ctPos); MITK_DEBUG << metaDataPart; auto jsonObj = nlohmann::json::parse(metaDataPart); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse response from MONAILabel server as JSON object!"; return; } else // use the metadata { m_ResultMetadata = jsonObj; } // Parse response image string std::string imagePart = multiPartResponse[1]; std::string imageData = ::GetResponseImageString(imagePart); std::ofstream output(outFile, std::ios::out | std::ios::app | std::ios::binary); output << imageData; output.unsetf(std::ios::skipws); output.flush(); } else { auto err = response.error(); MITK_ERROR << "An HTTP POST error: " << httplib::to_string(err) << " occured."; mitkThrow() << "An HTTP POST error: " << httplib::to_string(err) << " occured."; } } } const std::vector mitk::MonaiLabelTool::GetAutoSegmentationModels(const int dim) const { std::vector autoModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_AUTO_SEG_TYPE_NAME.find(model.type) != m_AUTO_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { autoModels.push_back(model); } } } return autoModels; } const std::vector mitk::MonaiLabelTool::GetInteractiveSegmentationModels(const int dim) const { std::vector interactiveModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_INTERACTIVE_SEG_TYPE_NAME.find(model.type) != m_INTERACTIVE_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { interactiveModels.push_back(model); } } } return interactiveModels; } const std::vector mitk::MonaiLabelTool::GetScribbleSegmentationModels(const int dim) const { std::vector scribbleModels; if (nullptr != m_InfoParameters) { for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (m_SCRIBBLE_SEG_TYPE_NAME.find(model.type) != m_SCRIBBLE_SEG_TYPE_NAME.end() && (!(dim > -1) || model.dimension == dim)) { scribbleModels.push_back(model); } } } return scribbleModels; } mitk::MonaiModelInfo mitk::MonaiLabelTool::GetModelInfoFromName(const std::string modelName) const { if (nullptr == m_InfoParameters) { mitkThrow() << "No model information found."; } mitk::MonaiModelInfo retVal; for (mitk::MonaiModelInfo &model : m_InfoParameters->models) { if (model.name == modelName) { retVal = model; break; } } return retVal; } std::pair mitk::MonaiLabelTool::CreateTempDirs(const std::string &filePattern) const { std::string inDir, outDir, inputImagePath, outputImagePath; inDir = IOUtil::CreateTemporaryDirectory("monai-in-XXXXXX", this->GetTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, filePattern, 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("_")); outDir = IOUtil::CreateTemporaryDirectory("monai-out-XXXXXX", this->GetTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; return std::make_pair(inputImagePath, outputImagePath); } std::string mitk::MonaiLabelTool::ConvertPointsAsListString(const mitk::BaseGeometry *baseGeometry, const PointSet::Pointer pointSet) const { bool is3DMode = nullptr == this->GetWorkingPlaneGeometry(); MITK_INFO << "No.of points: " << pointSet->GetSize(); std::stringstream pointsAndLabels; pointsAndLabels << "["; mitk::PointSet::PointsConstIterator pointSetIter = pointSet->Begin(); const char COMMA = ','; while (pointSetIter != pointSet->End()) { mitk::Point3D point3d = pointSetIter.Value(); if (baseGeometry->IsInside(point3d)) { mitk::Point3D index3D; baseGeometry->WorldToIndex(point3d, index3D); pointsAndLabels << "["; pointsAndLabels << static_cast(index3D[0]) << COMMA << static_cast(index3D[1]) << COMMA; if (is3DMode) { pointsAndLabels << static_cast(index3D[2]); } else { pointsAndLabels << 0; } pointsAndLabels << "],"; } ++pointSetIter; } if (pointsAndLabels.tellp() > 1) { pointsAndLabels.seekp(-1, pointsAndLabels.end); // remove last added comma character } pointsAndLabels << "]"; return pointsAndLabels.str(); } const mitk::MonaiAppMetadata *mitk::MonaiLabelTool::GetInfoParameters() const { return m_InfoParameters.get(); } diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp index 08d8e86494..0b90a0a377 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp @@ -1,265 +1,265 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "mitkSegmentAnythingPythonService.h" #include "mitkIOUtil.h" #include #include #include #include -#include +#include #include #include "mitkImageAccessByItk.h" #include using namespace std::chrono_literals; using sys_clock = std::chrono::system_clock; namespace mitk { const std::string SIGNALCONSTANTS::READY = "READY"; const std::string SIGNALCONSTANTS::KILL = "KILL"; const std::string SIGNALCONSTANTS::OFF = "OFF"; const std::string SIGNALCONSTANTS::CUDA_OUT_OF_MEMORY_ERROR = "CudaOutOfMemoryError"; const std::string SIGNALCONSTANTS::TIMEOUT_ERROR = "TimeOut"; SegmentAnythingPythonService::Status SegmentAnythingPythonService::CurrentStatus = SegmentAnythingPythonService::Status::OFF; } mitk::SegmentAnythingPythonService::SegmentAnythingPythonService( std::string workingDir, std::string modelType, std::string checkPointPath, unsigned int gpuId, std::string backend) : m_PythonPath(workingDir), m_ModelType(modelType), m_CheckpointPath(checkPointPath), m_Backend(backend), m_GpuId(gpuId) { this->CreateTempDirs(PARENT_TEMP_DIR_PATTERN); } mitk::SegmentAnythingPythonService::~SegmentAnythingPythonService() { if (CurrentStatus == Status::READY) { this->StopAsyncProcess(); } CurrentStatus = Status::OFF; - std::filesystem::remove_all(this->GetMitkTempDir()); + fs::remove_all(this->GetMitkTempDir()); } void mitk::SegmentAnythingPythonService::onPythonProcessEvent(itk::Object*, const itk::EventObject &e, void*) { std::string testCOUT,testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); testCOUT.erase(std::find_if(testCOUT.rbegin(), testCOUT.rend(), [](unsigned char ch) { return !std::isspace(ch);}).base(), testCOUT.end()); // remove trailing whitespaces, if any if (SIGNALCONSTANTS::READY == testCOUT) { CurrentStatus = Status::READY; } if (SIGNALCONSTANTS::KILL == testCOUT) { CurrentStatus = Status::KILLED; } if (SIGNALCONSTANTS::CUDA_OUT_OF_MEMORY_ERROR == testCOUT) { CurrentStatus = Status::CUDAError; } MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void mitk::SegmentAnythingPythonService::StopAsyncProcess() { this->WriteControlFile(SIGNALCONSTANTS::KILL); m_DaemonExec->SetStop(true); m_Future.get(); } void mitk::SegmentAnythingPythonService::StartAsyncProcess() { if (nullptr != m_DaemonExec) { this->StopAsyncProcess(); } if (this->GetMitkTempDir().empty()) { this->CreateTempDirs(PARENT_TEMP_DIR_PATTERN); } this->WriteControlFile(SIGNALCONSTANTS::READY); double timeout = 1; m_DaemonExec = SegmentAnythingProcessExecutor::New(timeout); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&mitk::SegmentAnythingPythonService::onPythonProcessEvent); m_DaemonExec->AddObserver(ExternalProcessOutputEvent(), spCommand); m_Future = std::async(std::launch::async, &mitk::SegmentAnythingPythonService::start_python_daemon, this); } void mitk::SegmentAnythingPythonService::TransferPointsToProcess(const std::string &triggerCSV) const { this->CheckStatus(); std::string triggerFilePath = m_InDir + IOUtil::GetDirectorySeparator() + TRIGGER_FILENAME; std::ofstream csvfile; csvfile.open(triggerFilePath, std::ofstream::out | std::ofstream::trunc); csvfile << triggerCSV; csvfile.close(); } void mitk::SegmentAnythingPythonService::WriteControlFile(const std::string &statusString) const { std::string controlFilePath = m_InDir + IOUtil::GetDirectorySeparator() + "control.txt"; std::ofstream controlFile; controlFile.open(controlFilePath, std::ofstream::out | std::ofstream::trunc); controlFile << statusString; controlFile.close(); } void mitk::SegmentAnythingPythonService::start_python_daemon() const { ProcessExecutor::ArgumentListType args; std::string command = "python"; args.push_back("-u"); args.push_back(SAM_PYTHON_FILE_NAME); args.push_back("--input-folder"); args.push_back(m_InDir); args.push_back("--output-folder"); args.push_back(m_OutDir); args.push_back("--trigger-file"); args.push_back(TRIGGER_FILENAME); args.push_back("--model-type"); args.push_back(m_ModelType); args.push_back("--checkpoint"); args.push_back(m_CheckpointPath); args.push_back("--backend"); args.push_back(m_Backend); args.push_back("--device"); if (m_GpuId == -1) { args.push_back("cpu"); } else { args.push_back("cuda"); std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(m_GpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); } try { std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << m_PythonPath; MITK_INFO << logStream.str(); m_DaemonExec->Execute(m_PythonPath, command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } MITK_INFO << "Python process ended."; } bool mitk::SegmentAnythingPythonService::CheckStatus() { switch (CurrentStatus) { case mitk::SegmentAnythingPythonService::Status::READY: return true; case mitk::SegmentAnythingPythonService::Status::CUDAError: mitkThrow() << "Error: Cuda Out of Memory. Change your model type in Preferences and Activate Segment Anything tool again."; case mitk::SegmentAnythingPythonService::Status::KILLED: mitkThrow() << "Error: Python process is already terminated. Cannot load requested segmentation. Activate Segment Anything tool again."; default: return false; } } void mitk::SegmentAnythingPythonService::CreateTempDirs(const std::string &dirPattern) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory(dirPattern)); m_InDir = IOUtil::CreateTemporaryDirectory("sam-in-XXXXXX", m_MitkTempDir); m_OutDir = IOUtil::CreateTemporaryDirectory("sam-out-XXXXXX", m_MitkTempDir); } mitk::LabelSetImage::Pointer mitk::SegmentAnythingPythonService::RetrieveImageFromProcess(long timeOut) const { std::string outputImagePath = m_OutDir + IOUtil::GetDirectorySeparator() + m_CurrentUId + ".nrrd"; auto start = sys_clock::now(); - while (!std::filesystem::exists(outputImagePath)) + while (!fs::exists(outputImagePath)) { this->CheckStatus(); std::this_thread::sleep_for(100ms); if (timeOut != -1 && std::chrono::duration_cast(sys_clock::now() - start).count() > timeOut) { CurrentStatus = Status::OFF; m_DaemonExec->SetStop(true); mitkThrow() << SIGNALCONSTANTS::TIMEOUT_ERROR; } } LabelSetImage::Pointer outputBuffer = mitk::IOUtil::Load(outputImagePath); return outputBuffer; } void mitk::SegmentAnythingPythonService::TransferImageToProcess(const Image *inputAtTimeStep, std::string &UId) { std::string inputImagePath = m_InDir + IOUtil::GetDirectorySeparator() + UId + ".nrrd"; if (inputAtTimeStep->GetPixelType().GetNumberOfComponents() < 2) { AccessByItk_n(inputAtTimeStep, ITKWriter, (inputImagePath)); } else { mitk::IOUtil::Save(inputAtTimeStep, inputImagePath); } m_CurrentUId = UId; } template void mitk::SegmentAnythingPythonService::ITKWriter(const itk::Image *image, std::string& outputFilename) const { typedef itk::Image ImageType; typedef itk::ImageFileWriter WriterType; typename WriterType::Pointer writer = WriterType::New(); mitk::LocaleSwitch localeSwitch("C"); writer->SetFileName(outputFilename); writer->SetInput(image); try { writer->Update(); } catch (const itk::ExceptionObject &error) { MITK_ERROR << "Error: " << error << std::endl; mitkThrow() << "Error: " << error; } } diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index 83d50e0fdd..fe4f34e5e6 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,359 +1,359 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkTotalSegmentatorTool.h" #include #include #include -#include +#include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { - std::filesystem::remove_all(this->GetMitkTempDir()); + fs::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); this->RequestDeactivationConfirmationOn(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::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; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); m_ProgressCommand->SetProgress(5); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, 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("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; m_ProgressCommand->SetProgress(20); IOUtil::Save(inputAtTimeStep, inputImagePath); m_ProgressCommand->SetProgress(50); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK) && (this->GetSubTask() != DEFAULT_TOTAL_TASK_MRI); if (isSubTask) { outputImagePath = outDir; this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), this->GetSubTask()); Image::Pointer outputImage = IOUtil::Load(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } m_ProgressCommand->SetProgress(180); mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK) && (this->GetSubTask() != DEFAULT_TOTAL_TASK_MRI); if (isSubTask) { std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); const auto layerIndex = aggloLabelImage->AddLayer(); aggloLabelImage->SetActiveLayer(layerIndex); for (auto const &outputImagePath : filePaths) { double rgba[4]; aggloLabelImage->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); aggloLabelImage->AddLabel(label, layerIndex, false, false); Image::Pointer outputImage = IOUtil::Load(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); mitk::TransferLabelContent(source, aggloLabelImage, aggloLabelImage->GetConstLabelsByValue(aggloLabelImage->GetLabelValuesByGroup(layerIndex)), 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; #ifdef _WIN32 command += ".exe"; #endif args.clear(); args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask == DEFAULT_TOTAL_TASK_MRI) { args.push_back("--task"); args.push_back(subTask); } else if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { int start_line = 0, end_line = 0; if (this->GetSubTask() == DEFAULT_TOTAL_TASK) start_line = 111, end_line = 229; else if (this->GetSubTask() == DEFAULT_TOTAL_TASK_MRI) start_line = 231, end_line = 288; std::regex sanitizer(R"([^A-Za-z0-9_])"); std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { if (line > start_line && line < end_line) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { std::string sanitized = std::regex_replace(val, sanitizer, ""); m_LabelMapTotal[std::stoi(key)] = sanitized; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map &labelMap) { auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); dest->AddLabel(label, 0,false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; - std::filesystem::path pathToLabelMap(this->GetPythonPath()); + fs::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); - for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) + for (auto const &dir_entry : fs::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index 6af435b28e..2bbb425592 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,322 +1,322 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitknnUnetTool.h" #include "mitkIOUtil.h" #include "mitkProcessExecutor.h" #include #include #include #include #include -#include +#include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); } mitk::nnUNetTool::~nnUNetTool() { - std::filesystem::remove_all(this->GetMitkTempDir()); + fs::remove_all(this->GetMitkTempDir()); } void mitk::nnUNetTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } void mitk::nnUNetTool::RenderOutputBuffer() { if (m_OutputBuffer != nullptr) { try { if (nullptr != this->GetPreviewSegmentationNode()) { 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.svg"); 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); } void mitk::nnUNetTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetLabelValuesByGroup(preview->GetActiveLayer())); } 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 void mitk::nnUNetTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType /*timeStep*/) { if (this->GetMitkTempDir().empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-nnunet-XXXXXX")); } 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) { MITK_ERROR << e.GetDescription(); 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; } } 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 { Image::Pointer outputImage = IOUtil::Load(outputImagePath); previewImage->InitializeByLabeledImage(outputImage); previewImage->SetGeometry(inputAtTimeStep->GetGeometry()); m_InputBuffer = inputAtTimeStep; m_OutputBuffer = mitk::LabelSetImage::New(); m_OutputBuffer->InitializeByLabeledImage(outputImage); m_OutputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } diff --git a/Modules/Segmentation/cmdapps/ContoursToImage.cpp b/Modules/Segmentation/cmdapps/ContoursToImage.cpp index 8d63fca2b1..424678ba01 100644 --- a/Modules/Segmentation/cmdapps/ContoursToImage.cpp +++ b/Modules/Segmentation/cmdapps/ContoursToImage.cpp @@ -1,312 +1,312 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 enum class OutputFormat { Binary, Label, Multilabel }; void InitializeCommandLineParser(mitkCommandLineParser& parser) { parser.setTitle("Contour to Image Converter"); parser.setCategory("Segmentation"); parser.setDescription("Converts contours (i. e. RTSTRUCT or MITK Contour Model Set) to binary image masks or (multi-)label segmentations."); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file:", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("reference", "r", mitkCommandLineParser::Image, "Reference image:", "Input reference image", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::Image, "Output file:", "Output image", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.addArgument("format", "f", mitkCommandLineParser::String, "Output format:", "Output format (binary, label, or multilabel)", std::string("binary")); } std::string GetSafeName(const mitk::IPropertyProvider* propertyProvider) { std::string name; if (propertyProvider != nullptr) { name = propertyProvider->GetConstProperty("name")->GetValueAsString(); if (!name.empty()) { boost::trim(name); boost::replace_all(name, "/", "_"); boost::replace_all(name, "\\", "_"); // If you read this, feel free to handle invalid filename characters here. :) } } return name; } -void CreateParentDirectories(const std::filesystem::path& path) +void CreateParentDirectories(const fs::path& path) { if (path.has_parent_path()) { auto parentPath = path.parent_path(); - if (!std::filesystem::exists(parentPath)) - std::filesystem::create_directories(parentPath); + if (!fs::exists(parentPath)) + fs::create_directories(parentPath); } } bool SetLabelName(const mitk::IPropertyProvider* propertyProvider, mitk::Label* label) { if (propertyProvider != nullptr) { if (auto property = propertyProvider->GetConstProperty("name"); property.IsNotNull()) { if (auto nameProperty = dynamic_cast(property.GetPointer()); nameProperty != nullptr) { if (auto name = nameProperty->GetValueAsString(); !name.empty()) { label->SetName(name); return true; } } } } return false; } bool SetLabelColor(const mitk::IPropertyProvider* propertyProvider, mitk::Label* label) { if (propertyProvider != nullptr) { if (auto property = propertyProvider->GetConstProperty("color"); property.IsNotNull()) { if (auto colorProperty = dynamic_cast(property.GetPointer()); colorProperty != nullptr) { label->SetColor(colorProperty->GetColor()); return true; } } } return false; } void CopyImageToActiveLayerImage(const mitk::Image* image, mitk::LabelSetImage* labelSetImage) { mitk::ImageReadAccessor readAccessor(image); mitk::ImageWriteAccessor writeAccessor(labelSetImage); auto size = sizeof(mitk::Label::PixelType); for (size_t dim = 0; dim < image->GetDimension(); ++dim) size *= image->GetDimension(dim); memcpy(writeAccessor.GetData(), readAccessor.GetData(), size); } OutputFormat ParseOutputFormat(const mitk::IFileIO::Options& args) { auto it = args.find("format"); if (it != args.end()) { auto format = us::any_cast(it->second); if (format == "multilabel") return OutputFormat::Multilabel; if (format == "label") return OutputFormat::Label; if (format != "binary") mitkThrow() << "Unknown output format \"" << format << "\" (must be \"binary\", \"label\" or \"multilabel\")."; } return OutputFormat::Binary; } std::vector FilterValidInputs(const std::vector& inputs) { std::vector validInputs; for (auto input : inputs) { if (input.IsNull()) { MITK_WARN << "Skipping null input."; continue; } auto* validInput = dynamic_cast(input.GetPointer()); if (validInput == nullptr) { MITK_WARN << "Skipping input of type \"" << input->GetNameOfClass() << "\"."; continue; } validInputs.push_back(validInput); } return validInputs; } int main(int argc, char* argv[]) { int returnValue = EXIT_SUCCESS; mitkCommandLineParser parser; InitializeCommandLineParser(parser); auto args = parser.parseArguments(argc, argv); if (args.empty()) return EXIT_FAILURE; try { auto inputFilename = us::any_cast(args["input"]); auto referenceFilename = us::any_cast(args["reference"]); auto outputFilename = us::any_cast(args["output"]); auto format = ParseOutputFormat(args); auto referenceImage = mitk::IOUtil::Load(referenceFilename); auto inputs = FilterValidInputs(mitk::IOUtil::Load(inputFilename)); MITK_INFO << "Found " << inputs.size() << " input contour set(s)"; - std::filesystem::path outputPath(outputFilename); + fs::path outputPath(outputFilename); CreateParentDirectories(outputPath); mitk::LabelSetImage::Pointer labelSetImage; // Only used for "multilabel" output unsigned int nonameCounter = 0; // Helper variable to generate placeholder names for nameless contour sets for (auto input : inputs) { // If the input file contains multiple contour sets but the output format is not set to "multilabel", // we create separate output files for each contour set. In this case the specified output filename // is used only as a base filename and the names of the individual contour sets are appended accordingly. if (inputs.size() > 1 && format != OutputFormat::Multilabel) { outputPath = outputFilename; auto name = GetSafeName(input); if (name.empty()) name = "nameless_" + std::to_string(nonameCounter++); outputPath.replace_filename(outputPath.stem().string() + '_' + name + outputPath.extension().string()); } // Do the actual conversion from a contour set to an image with a background pixel value of 0. // - For "binary" output, use pixel value 1 and unsigned char as pixel type. // - For "label" output, use pixel value 1 and our label pixel type. // - For "multilabel" output, use the next available label value instead. const auto labelValue = labelSetImage.IsNotNull() ? labelSetImage->GetUnusedLabelValue() : 1; auto filter = mitk::ContourModelSetToImageFilter::New(); filter->SetMakeOutputLabelPixelType(format != OutputFormat::Binary); filter->SetPaintingPixelValue(labelValue); filter->SetImage(referenceImage); filter->SetInput(input); filter->Update(); mitk::Image::Pointer image = filter->GetOutput(); filter = nullptr; if (image.IsNull()) { MITK_ERROR << "Contour set to image conversion failed without exception. Continue with next contour set... "; returnValue = EXIT_FAILURE; continue; } if (format == OutputFormat::Binary) { mitk::IOUtil::Save(image, outputPath.string()); } else { if (labelSetImage.IsNull()) { labelSetImage = mitk::LabelSetImage::New(); labelSetImage->Initialize(image); CopyImageToActiveLayerImage(image, labelSetImage); } else { labelSetImage->AddLayer(image); } auto label = mitk::LabelSetImageHelper::CreateNewLabel(labelSetImage); label->SetValue(labelValue); SetLabelName(input, label); SetLabelColor(input, label); if (format == OutputFormat::Multilabel) MITK_INFO << "Creating label: " << label->GetName() << " [" << labelValue << ']'; labelSetImage->AddLabel(label, labelSetImage->GetActiveLayer(), false, false); if (format == OutputFormat::Label) { mitk::IOUtil::Save(labelSetImage, outputPath.string()); labelSetImage = nullptr; } } } // In case of the "multilabel" output format, eventually save the single output file. // For all other output formats, the output file(s) have been saved already while iterating // over the inputs. if (labelSetImage.IsNotNull()) mitk::IOUtil::Save(labelSetImage, outputPath.string()); } catch (const mitk::Exception& e) { MITK_ERROR << e.GetDescription(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { return EXIT_FAILURE; } return returnValue; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp index 25f783db43..602b78747c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp @@ -1,1012 +1,1012 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) 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 "QmitkSegmentationTaskListWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include namespace { mitk::IPreferences* GetSegmentationPreferences() { return mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); } - std::filesystem::path GetInputLocation(const mitk::BaseData* data) + fs::path GetInputLocation(const mitk::BaseData* data) { std::string result; if (data != nullptr) data->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result); return result; } QString ColorString(const QString& string, const QColor& color, const QColor& backgroundColor = QColor::Invalid) { if (!color.isValid() && !backgroundColor.isValid()) return string; auto result = QStringLiteral("%1").arg(string); return result; } mitk::DataStorage::SetOfObjects::ConstPointer GetSubset(const mitk::DataStorage* dataStorage, const mitk::NodePredicateBase* condition, const mitk::DataNode* removedDataNode) { auto subset = dataStorage->GetSubset(condition); if (nullptr != removedDataNode) { auto actualSubset = mitk::DataStorage::SetOfObjects::New(); for (auto node : *subset) { if (node != removedDataNode) actualSubset->push_back(node); } return actualSubset; } return subset; } } /* This constructor has three objectives: * 1. Do widget initialization that cannot be done in the .ui file * 2. Connect signals and slots * 3. Explicitly trigger a reset to a valid initial widget state */ QmitkSegmentationTaskListWidget::QmitkSegmentationTaskListWidget(QWidget* parent) : QWidget(parent), m_Ui(new Ui::QmitkSegmentationTaskListWidget), m_FileSystemWatcher(new QFileSystemWatcher(this)), m_DataStorage(nullptr), m_UnsavedChanges(false) { m_Ui->setupUi(this); m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType::New()); m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor())); m_Ui->findButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_find.svg"))); m_Ui->storeButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); using Self = QmitkSegmentationTaskListWidget; connect(m_Ui->selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSelectionChanged); connect(m_Ui->previousButton, &QToolButton::clicked, this, &Self::OnPreviousButtonClicked); connect(m_Ui->nextButton, &QToolButton::clicked, this, &Self::OnNextButtonClicked); connect(m_Ui->findButton, &QToolButton::clicked, this, &Self::OnFindButtonClicked); connect(m_Ui->loadButton, &QPushButton::clicked, this, &Self::OnLoadButtonClicked); connect(m_Ui->storeButton, &QPushButton::clicked, this, &Self::OnStoreButtonClicked); connect(m_Ui->acceptButton, &QPushButton::clicked, this, &Self::OnAcceptButtonClicked); connect(m_FileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &Self::OnResultDirectoryChanged); auto* prevShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_P), this); connect(prevShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* prevUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_P), this); connect(prevUndoneShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* nextShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_N), this); connect(nextShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto* nextUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_N), this); connect(nextUndoneShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto *findTaskShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_F), this); connect(findTaskShortcut, &QShortcut::activated, this, &Self::OnFindTaskShortcutActivated); auto* loadShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_L), this); connect(loadShortcut, &QShortcut::activated, this, &Self::OnLoadTaskShortcutActivated); auto* storeShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_S), parent); connect(storeShortcut, &QShortcut::activated, this, &Self::OnStoreInterimResultShortcutActivated); auto* acceptShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_A), parent); connect(acceptShortcut, &QShortcut::activated, this, &Self::OnAcceptSegmentationShortcutActivated); this->ResetControls(); this->CheckDataStorage(); } QmitkSegmentationTaskListWidget::~QmitkSegmentationTaskListWidget() { } void QmitkSegmentationTaskListWidget::SetDataStorage(mitk::DataStorage* dataStorage) { m_DataStorage = dataStorage; m_Ui->selectionWidget->SetDataStorage(dataStorage); // Triggers OnSelectionChanged() m_Ui->selectionWidget->SetAutoSelectNewNodes(true); this->CheckDataStorage(); } void QmitkSegmentationTaskListWidget::CheckDataStorage(const mitk::DataNode* removedNode) { QString warning; if (nullptr == m_DataStorage) { warning = QStringLiteral( "

Developer warning

Call SetDataStorage() to fully initialize " "this instance of QmitkSegmentationTaskListWidget.

"); } else { auto isTaskList = mitk::TNodePredicateDataType::New(); auto taskListNodes = GetSubset(m_DataStorage, isTaskList, removedNode); if (taskListNodes->empty()) { warning = QStringLiteral( "

No segmentation task list found

Load a segmentation task list to use " "this plugin.

"); } else if (taskListNodes->Size() > 1) { warning = QStringLiteral( "

More than one segmentation task list found

Unload everything but a " "single segmentation task list to use this plugin.

"); } else { const auto* taskListNode = (*taskListNodes)[0].GetPointer(); auto isTaskListNode = mitk::NodePredicateFunction::New([taskListNode](const mitk::DataNode* node) { return node == taskListNode; }); auto isChildOfTaskListNode = mitk::NodePredicateFunction::New([this, isTaskListNode](const mitk::DataNode* node) { return !m_DataStorage->GetSources(node, isTaskListNode, false)->empty(); }); auto isHelperObject = mitk::NodePredicateProperty::New("helper object"); auto isUndesiredNode = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( isTaskListNode, isChildOfTaskListNode, isHelperObject)); if (!GetSubset(m_DataStorage, isUndesiredNode, removedNode)->empty()) { warning = QStringLiteral( "

Unrelated data found

Unload everything but a single segmentation task " "list to use this plugin.

"); } } } m_Ui->label->setText("" + warning + ""); m_Ui->label->setVisible(!warning.isEmpty()); m_Ui->widget->setVisible(warning.isEmpty()); } void QmitkSegmentationTaskListWidget::OnUnsavedChangesSaved() { if (m_UnsavedChanges) { m_UnsavedChanges = false; if (this->ActiveTaskIsShown()) this->UpdateDetailsLabel(); } } /* Make sure that the widget transitions into a valid state whenever the * selection changes. */ void QmitkSegmentationTaskListWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { this->UnloadTasks(); this->ResetControls(); if (!nodes.empty()) { m_TaskListNode = nodes.front(); auto taskList = dynamic_cast(m_TaskListNode->GetData()); if (taskList != nullptr) { this->OnTaskListChanged(taskList); return; } } this->SetTaskList(nullptr); m_TaskListNode = nullptr; } /* Reset all controls to a default state as a common basis for further * adjustments. */ void QmitkSegmentationTaskListWidget::ResetControls() { m_Ui->progressBar->setEnabled(false); m_Ui->progressBar->setFormat(""); m_Ui->progressBar->setValue(0); m_Ui->progressBar->setMaximum(1); m_Ui->previousButton->setEnabled(false); m_Ui->nextButton->setEnabled(false); this->UpdateLoadButton(); this->UpdateDetailsLabel(); this->UpdateStoreAndAcceptButtons(); } /* If the segmentation task changed, reset all member variables to expected * default values and reset the file system watcher. */ void QmitkSegmentationTaskListWidget::SetTaskList(mitk::SegmentationTaskList* taskList) { if (m_TaskList != taskList) { m_TaskList = taskList; if (taskList != nullptr) { this->SetCurrentTaskIndex(0); } else { this->SetCurrentTaskIndex(std::nullopt); } this->ResetFileSystemWatcher(); } } void QmitkSegmentationTaskListWidget::ResetFileSystemWatcher() { auto paths = m_FileSystemWatcher->directories(); if (!paths.empty()) m_FileSystemWatcher->removePaths(paths); if (m_TaskList.IsNotNull()) { for (const auto& task : *m_TaskList) { auto resultPath = m_TaskList->GetAbsolutePath(task.GetResult()).remove_filename(); - if (!std::filesystem::exists(resultPath)) + if (!fs::exists(resultPath)) { try { - std::filesystem::create_directories(resultPath); + fs::create_directories(resultPath); } - catch (const std::filesystem::filesystem_error& e) + catch (const fs::filesystem_error& e) { MITK_ERROR << e.what(); } } - if (std::filesystem::exists(resultPath)) + if (fs::exists(resultPath)) m_FileSystemWatcher->addPath(QString::fromStdString(resultPath.string())); } } } void QmitkSegmentationTaskListWidget::OnResultDirectoryChanged(const QString&) { // TODO: If a segmentation was modified ("Unsaved changes"), saved ("Done"), and then the file is deleted, the status should be "Unsaved changes" instead of "Not done". this->UpdateProgressBar(); this->UpdateDetailsLabel(); } void QmitkSegmentationTaskListWidget::UpdateProgressBar() { int progress = 0; for (size_t i = 0; i < m_TaskList->GetNumberOfTasks(); ++i) { if (m_TaskList->IsDone(i)) ++progress; } m_Ui->progressBar->setValue(progress); } /* Provided that a valid segmentation task list is currently selected and the * widget is in its default state, update all controls accordingly. * TODO: Then, load the first unfinished task, if any. */ void QmitkSegmentationTaskListWidget::OnTaskListChanged(mitk::SegmentationTaskList* taskList) { this->SetTaskList(taskList); const auto numTasks = taskList->GetNumberOfTasks(); m_Ui->progressBar->setMaximum(numTasks); m_Ui->progressBar->setFormat(QStringLiteral("%v/%m Task(s) done")); m_Ui->progressBar->setEnabled(true); this->UpdateProgressBar(); m_Ui->loadButton->setEnabled(true); if (numTasks > 1) m_Ui->nextButton->setEnabled(true); // TODO: This line should be enough but it is happening too early even before // the RenderingManager has any registered render windows, resulting in mismatching // renderer and data geometries. // this->LoadNextUnfinishedTask(); } /* If possible, change the currently displayed task to the previous task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnPreviousButtonClicked() { auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the previous undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { if (current > 0) { for (decltype(current) i = current; i > 0; --i) { if (!m_TaskList->IsDone(i - 1)) { this->SetCurrentTaskIndex(i - 1); break; } } } } else { if (current != 0) this->SetCurrentTaskIndex(current - 1); } this->UpdateNavigationButtons(); } /* If possible, change the currently displayed task to the next task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnNextButtonClicked() { const auto numTasks = m_TaskList->GetNumberOfTasks(); auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the next undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { for (std::remove_const_t i = current + 1; i < numTasks; ++i) { if (!m_TaskList->IsDone(i)) { this->SetCurrentTaskIndex(i); break; } } } else { if (current < numTasks - 1) this->SetCurrentTaskIndex(current + 1); } this->UpdateNavigationButtons(); } void QmitkSegmentationTaskListWidget::OnFindButtonClicked() { if (m_TaskList.IsNull()) return; QmitkFindSegmentationTaskDialog dialog; dialog.SetTaskList(m_TaskList); if (dialog.exec() != QDialog::Accepted) return; if (!dialog.GetSelectedTask().has_value()) return; this->SetCurrentTaskIndex(dialog.GetSelectedTask()); if (dialog.LoadSelectedTask()) { if (!m_ActiveTaskIndex.has_value() || m_ActiveTaskIndex.value() != dialog.GetSelectedTask().value()) this->OnLoadButtonClicked(); } } void QmitkSegmentationTaskListWidget::UpdateNavigationButtons() { if (m_TaskList.IsNull() || m_TaskList->GetNumberOfTasks() == 0) { m_Ui->previousButton->setEnabled(false); m_Ui->nextButton->setEnabled(false); return; } const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1; const auto current = m_CurrentTaskIndex.value(); m_Ui->previousButton->setEnabled(current != 0); m_Ui->nextButton->setEnabled(current != maxIndex); } /* Update affected controls when the currently displayed task changed. */ void QmitkSegmentationTaskListWidget::OnCurrentTaskChanged() { this->UpdateLoadButton(); this->UpdateNavigationButtons(); this->UpdateDetailsLabel(); this->UpdateStoreAndAcceptButtons(); } /* Update the load button according to the currently displayed task. */ void QmitkSegmentationTaskListWidget::UpdateLoadButton() { auto text = !this->ActiveTaskIsShown() ? QStringLiteral("Load task") : QStringLiteral("Task"); if (m_CurrentTaskIndex.has_value()) { const auto current = m_CurrentTaskIndex.value(); if (m_TaskList.IsNotNull()) { text += QString(" %1/%2").arg(current + 1).arg(m_TaskList->GetNumberOfTasks()); if (m_TaskList->HasName(current)) text += QStringLiteral(":\n") + QString::fromStdString(m_TaskList->GetName(current)); } m_Ui->loadButton->setDisabled(this->ActiveTaskIsShown()); } else { m_Ui->loadButton->setEnabled(false); } m_Ui->loadButton->setText(text); } /* Update the details label according to the currently display task. * The text is composed of the status of the task and a variable number * of text blocks according to the optional values provided by the task. */ void QmitkSegmentationTaskListWidget::UpdateDetailsLabel() { if (!m_CurrentTaskIndex.has_value()) { m_Ui->detailsLabel->clear(); return; } const auto current = m_CurrentTaskIndex.value(); bool isDone = m_TaskList->IsDone(current); auto details = QString("

Status: %1 / ").arg(this->ActiveTaskIsShown() ? ColorString("Active", Qt::white, QColor(Qt::green).darker()) : ColorString("Inactive", Qt::white, QColor(Qt::red).darker())); if (m_UnsavedChanges && this->ActiveTaskIsShown()) { details += QString("%1

").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker())); } else { details += QString("%1

").arg(isDone ? ColorString("Done", Qt::white, QColor(Qt::green).darker()) : ColorString("Not done", Qt::white, QColor(Qt::red).darker())); } if (m_TaskList->HasDescription(current)) details += QString("

Description: %1

").arg(QString::fromStdString(m_TaskList->GetDescription(current))); QStringList stringList; if (m_TaskList->HasImage(current)) stringList << QString::fromStdString("Image: " + m_TaskList->GetImage(current).string()); if (m_TaskList->HasSegmentation(current)) stringList << QString::fromStdString("Segmentation: " + m_TaskList->GetSegmentation(current).string()); if (m_TaskList->HasLabelName(current)) stringList << QString::fromStdString("Label name: " + m_TaskList->GetLabelName(current)); if (m_TaskList->HasLabelNameSuggestions(current)) stringList << QString::fromStdString("Label name suggestions: " + m_TaskList->GetLabelNameSuggestions(current).string()); if (m_TaskList->HasPreset(current)) stringList << QString::fromStdString("Label set preset: " + m_TaskList->GetPreset(current).string()); if (m_TaskList->HasDynamic(current)) stringList << QString("Segmentation type: %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("

%1

").arg(stringList.join(QStringLiteral("
"))); m_Ui->detailsLabel->setText(details); } void QmitkSegmentationTaskListWidget::UpdateStoreAndAcceptButtons() { auto activeTaskIsShown = this->ActiveTaskIsShown(); m_Ui->storeButton->setVisible(activeTaskIsShown); m_Ui->acceptButton->setEnabled(activeTaskIsShown); } /* Load/activate the currently displayed task. Unload all data nodes from * previously active tasks first, but spare and reuse the image if possible. */ void QmitkSegmentationTaskListWidget::OnLoadButtonClicked() { if (!this->HandleUnsavedChanges() || m_UnsavedChanges) return; m_Ui->loadButton->setEnabled(false); QApplication::setOverrideCursor(Qt::BusyCursor); this->LoadTask(this->GetImageDataNode(m_CurrentTaskIndex.value())); QApplication::restoreOverrideCursor(); } /* If present, return the image data node for the task with the specified * index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetImageDataNode(size_t index) const { const auto imagePath = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(index)); auto imageNodes = m_DataStorage->GetDerivations(m_TaskListNode, mitk::NodePredicateFunction::New([imagePath](const mitk::DataNode* node) { return imagePath == GetInputLocation(node->GetData()); })); return !imageNodes->empty() ? imageNodes->front() : nullptr; } /* If present, return the segmentation data node for the task with the * specified index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetSegmentationDataNode(size_t index) const { const auto* imageNode = this->GetImageDataNode(index); if (imageNode != nullptr) { auto segmentations = m_DataStorage->GetDerivations(imageNode, mitk::TNodePredicateDataType::New()); if (!segmentations->empty()) return segmentations->front(); } return nullptr; } /* Unload all task data nodes but spare the passed image data node. */ void QmitkSegmentationTaskListWidget::UnloadTasks(const mitk::DataNode* skip) { this->UnsubscribeFromActiveSegmentation(); if (m_TaskListNode.IsNotNull()) { auto imageNodes = m_DataStorage->GetDerivations(m_TaskListNode, mitk::TNodePredicateDataType::New()); for (auto imageNode : *imageNodes) { m_DataStorage->Remove(m_DataStorage->GetDerivations(imageNode, nullptr, false)); if (imageNode != skip) m_DataStorage->Remove(imageNode); } } this->SetActiveTaskIndex(std::nullopt); } void QmitkSegmentationTaskListWidget::LoadNextUnfinishedTask() { const auto current = m_CurrentTaskIndex.value(); const auto numTasks = m_TaskList->GetNumberOfTasks(); for (size_t unboundNext = current; unboundNext < current + numTasks; ++unboundNext) { auto next = unboundNext % numTasks; if (!m_TaskList->IsDone(next)) { this->SetCurrentTaskIndex(next); this->OnLoadButtonClicked(); break; } } } /* Load/activate the currently displayed task. The task must specify * an image. The segmentation is either created from scratch with an optional * name for the first label, possibly based on a label set preset specified by * the task, or loaded as specified by the task. If a result file does * exist, it is chosen as segmentation instead. */ void QmitkSegmentationTaskListWidget::LoadTask(mitk::DataNode::Pointer imageNode) { this->UnloadTasks(imageNode); const auto current = m_CurrentTaskIndex.value(); mitk::Image::Pointer image; mitk::LabelSetImage::Pointer segmentation; try { if (imageNode.IsNull()) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(current)); image = mitk::IOUtil::Load(path.string()); } const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetResult(current)); const auto interimPath = m_TaskList->GetInterimPath(path); - if (std::filesystem::exists(path)) + if (fs::exists(path)) { segmentation = mitk::IOUtil::Load(path.string()); } - else if (std::filesystem::exists(interimPath)) + else if (fs::exists(interimPath)) { segmentation = mitk::IOUtil::Load(interimPath.string()); } else if (m_TaskList->HasSegmentation(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current)); segmentation = mitk::IOUtil::Load(path.string()); } } catch (const mitk::Exception&) { return; } if (imageNode.IsNull()) { imageNode = mitk::DataNode::New(); imageNode->SetData(image); m_DataStorage->Add(imageNode, m_TaskListNode); mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry()); } else { image = static_cast(imageNode->GetData()); } auto name = "Task " + std::to_string(current + 1); imageNode->SetName(name); if (segmentation.IsNull()) { mitk::Image::ConstPointer templateImage = image; if (templateImage->GetDimension() > 3) { if (m_TaskList->HasDynamic(current)) { if (!m_TaskList->GetDynamic(current)) templateImage = mitk::SegmentationHelper::GetStaticSegmentationTemplate(image); } else { QmitkStaticDynamicSegmentationDialog dialog(this); dialog.SetReferenceImage(templateImage); dialog.exec(); templateImage = dialog.GetSegmentationTemplate(); } } auto segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, name); segmentation = static_cast(segmentationNode->GetData()); if (m_TaskList->HasPreset(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetPreset(current)); mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(path.string(), segmentation); } else { auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation); if (m_TaskList->HasLabelName(current)) label->SetName(m_TaskList->GetLabelName(current)); segmentation->AddLabel(label, segmentation->GetActiveLayer()); } m_DataStorage->Add(segmentationNode, imageNode); } else { auto segmentationNode = mitk::DataNode::New(); segmentationNode->SetName(name); segmentationNode->SetData(segmentation); m_DataStorage->Add(segmentationNode, imageNode); } // Workaround for T29431. Remove when T26953 is fixed. mitk::DICOMQIPropertyHelper::DeriveDICOMSourceProperties(image, segmentation); auto prefs = GetSegmentationPreferences(); if (prefs != nullptr) { if (m_TaskList->HasLabelNameSuggestions(current)) { auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetLabelNameSuggestions(current)); prefs->PutBool("default label naming", false); prefs->Put("label suggestions", path.string()); prefs->PutBool("replace standard suggestions", true); prefs->PutBool("suggest once", true); } else { prefs->PutBool("default label naming", true); prefs->Put("label suggestions", ""); } } m_UnsavedChanges = false; this->SetActiveTaskIndex(current); this->SubscribeToActiveSegmentation(); this->OnCurrentTaskChanged(); } void QmitkSegmentationTaskListWidget::SubscribeToActiveSegmentation() { if (m_ActiveTaskIndex.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast(segmentationNode->GetData()); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationTaskListWidget::OnSegmentationModified); m_SegmentationModifiedObserverTag = segmentation->AddObserver(itk::ModifiedEvent(), command); } } } void QmitkSegmentationTaskListWidget::UnsubscribeFromActiveSegmentation() { if (m_ActiveTaskIndex.has_value() && m_SegmentationModifiedObserverTag.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast(segmentationNode->GetData()); segmentation->RemoveObserver(m_SegmentationModifiedObserverTag.value()); } m_SegmentationModifiedObserverTag.reset(); } } void QmitkSegmentationTaskListWidget::OnSegmentationModified() { if (!m_UnsavedChanges) { m_UnsavedChanges = true; if (m_ActiveTaskIndex.value() == m_CurrentTaskIndex) this->UpdateDetailsLabel(); } } void QmitkSegmentationTaskListWidget::SetActiveTaskIndex(const std::optional& index) { if (m_ActiveTaskIndex != index) { m_ActiveTaskIndex = index; this->UpdateStoreAndAcceptButtons(); } } void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional& index) { if (m_CurrentTaskIndex != index) { m_CurrentTaskIndex = index; this->OnCurrentTaskChanged(); } } bool QmitkSegmentationTaskListWidget::ActiveTaskIsShown() const { return m_ActiveTaskIndex.has_value() && m_CurrentTaskIndex.has_value() && m_ActiveTaskIndex == m_CurrentTaskIndex; } bool QmitkSegmentationTaskListWidget::HandleUnsavedChanges(const QString& alternativeTitle) { if (m_UnsavedChanges) { const auto active = m_ActiveTaskIndex.value(); const auto current = m_CurrentTaskIndex.value(); QString title; if (alternativeTitle.isEmpty()) { title = QString("Load task %1").arg(current + 1); if (m_TaskList->HasName(current)) title += ": " + QString::fromStdString(m_TaskList->GetName(current)); } else { title = alternativeTitle; } auto text = QString("The currently active task %1 ").arg(active + 1); if (m_TaskList->HasName(active)) text += "(" + QString::fromStdString(m_TaskList->GetName(active)) + ") "; text += "has unsaved changes."; auto reply = QMessageBox::question(this, title, text, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel); switch (reply) { case QMessageBox::Save: - this->SaveActiveTask(!std::filesystem::exists(m_TaskList->GetResult(active))); + this->SaveActiveTask(!fs::exists(m_TaskList->GetResult(active))); break; case QMessageBox::Discard: m_UnsavedChanges = false; break; default: return false; } } return true; } void QmitkSegmentationTaskListWidget::SaveActiveTask(bool saveAsIntermediateResult) { if (!m_ActiveTaskIndex.has_value()) return; QApplication::setOverrideCursor(Qt::BusyCursor); try { const auto active = m_ActiveTaskIndex.value(); m_TaskList->SaveTask(active, this->GetSegmentationDataNode(active)->GetData(), saveAsIntermediateResult); this->OnUnsavedChangesSaved(); } catch (const mitk::Exception& e) { MITK_ERROR << e; } QApplication::restoreOverrideCursor(); } bool QmitkSegmentationTaskListWidget::OnPreShutdown() { return this->HandleUnsavedChanges(QStringLiteral("Application shutdown")); } void QmitkSegmentationTaskListWidget::OnPreviousTaskShortcutActivated() { m_Ui->previousButton->click(); } void QmitkSegmentationTaskListWidget::OnNextTaskShortcutActivated() { m_Ui->nextButton->click(); } void QmitkSegmentationTaskListWidget::OnFindTaskShortcutActivated() { m_Ui->findButton->click(); } void QmitkSegmentationTaskListWidget::OnLoadTaskShortcutActivated() { m_Ui->loadButton->click(); } void QmitkSegmentationTaskListWidget::OnStoreInterimResultShortcutActivated() { m_Ui->storeButton->click(); } void QmitkSegmentationTaskListWidget::OnAcceptSegmentationShortcutActivated() { m_Ui->acceptButton->click(); } void QmitkSegmentationTaskListWidget::OnStoreButtonClicked() { this->SaveActiveTask(true); } void QmitkSegmentationTaskListWidget::OnAcceptButtonClicked() { auto* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); int activeToolId = -1; if (toolManager != nullptr) activeToolId = toolManager->GetActiveToolID(); this->SaveActiveTask(); this->LoadNextUnfinishedTask(); if (toolManager != nullptr) toolManager->ActivateTool(activeToolId); }