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..b23d69df0a --- /dev/null +++ b/Modules/Core/include/mitkFileSystem.h @@ -0,0 +1,24 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkFileSystem_h +#define mitkFileSystem_h + +#if __has_include(<filesystem>) + #include <filesystem> + namespace fs = std::filesystem; +#elif __has_include(<experimental/filesystem>) + #include <experimental/filesystem> + 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 <mitkServiceInterface.h> #include <MitkCoreExports.h> -#include <filesystem> +#include <mitkFileSystem.h> 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 <mitkIPreferences.h> #include <MitkCoreExports.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <memory> 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<IPreferences> 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 <filesystem> +#include <mitkFileSystem.h> #include <memory> #include <string> #include <MitkCoreExports.h> 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<Impl> 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 <mitkIPreferencesStorage.h> -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 <mitkExceptionMacro.h> 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<XMLPreferencesStorage>(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 <mitkIPreferencesService.h> #include <memory> 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<IPreferencesStorage> 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 <mitkLog.h> #include <tinyxml2.h> 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>(Preferences::Properties(), "", nullptr, this); } mitk::XMLPreferencesStorage::~XMLPreferencesStorage() { } void mitk::XMLPreferencesStorage::Flush() { tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); Serialize(static_cast<const mitk::Preferences*>(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 <mitkIPreferencesStorage.h> 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 <mitkVideoRecorder.h> #include <mitkBaseRenderer.h> #include <mitkCoreServices.h> #include <mitkExceptionMacro.h> #include <mitkIOUtil.h> #include <mitkIPreferences.h> #include <mitkIPreferencesService.h> #include <mitkLog.h> #include <vtkImageResize.h> #include <vtkNew.h> #include <vtkPNGWriter.h> #include <vtkRenderWindow.h> #include <vtkWindowToImageFilter.h> #include <itksys/Process.h> #include <iomanip> #include <optional> #include <sstream> 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<vtkWindowToImageFilter> m_WindowToImageFilter; vtkNew<vtkImageResize> m_ImageResize; vtkNew<vtkPNGWriter> 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<OutputFormat>(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<RecordingSession>(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<std::filesystem::path> m_FFmpegPath; - std::filesystem::path m_OutputPath; + std::optional<fs::path> m_FFmpegPath; + fs::path m_OutputPath; std::optional<OutputFormat> m_OutputFormat; std::string m_RenderWindowName; unsigned int m_FrameRate; std::unique_ptr<RecordingSession> m_RecordingSession; }; } mitk::VideoRecorder::VideoRecorder() : m_Impl(std::make_unique<Impl>()) { } 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 <mitkCoreServices.h> #include <mitkIPreferencesService.h> #include <mitkIPreferences.h> #include <mitkTestFixture.h> #include <mitkTestingMacros.h> #include <mitkIOUtil.h> 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<int>(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<int>(nodeNames.size())); CPPUNIT_ASSERT(std::find(nodeNames.begin(), nodeNames.end(), "aa") != nodeNames.end()); nodeNames = bPrefs->ChildrenNames(); CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(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<int>(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<mitkPreferencesTestSuite, const mitk::IPreferences*>(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<mitkPreferencesTestSuite, const mitk::IPreferences::ChangeEvent&>(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 <mitkCommandLineParser.h> #include <mitkIOUtil.h> #include <mitkDICOMEnums.h> #include <mitkDICOMFilesHelper.h> #include <mitkDICOMFileReaderSelector.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <nlohmann/json.hpp> 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<std::string>(args["input"]); auto outputFilename = args.count("output")==0 ? std::string() : us::any_cast<std::string>(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<decltype(nrOfOutputs)> 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/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 <mitkIOMimeTypes.h> #include <mitkLog.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <fstream> #include <nlohmann/json.hpp> #include <itkNrrdImageIO.h> #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<int>("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<std::string>(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::CustomMimeType*> mitk::MitkMultilabelIOMimeTypes::Get() { std::vector<CustomMimeType*> 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 <mitkSegmentationTaskList.h> #include <nlohmann/json.hpp> -#include <filesystem> +#include <mitkFileSystem.h> #include <fstream> 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<std::string>()); iter = json.find("Description"); if (iter != json.end()) task.SetDescription(json["Description"].get<std::string>()); iter = json.find("Image"); if (iter != json.end()) task.SetImage(json["Image"].get<std::string>()); iter = json.find("Segmentation"); if (iter != json.end()) task.SetSegmentation(json["Segmentation"].get<std::string>()); iter = json.find("LabelName"); if (iter != json.end()) task.SetLabelName(json["LabelName"].get<std::string>()); iter = json.find("LabelNameSuggestions"); if (iter != json.end()) task.SetLabelNameSuggestions(json["LabelNameSuggestions"].get<std::string>()); iter = json.find("Preset"); if (iter != json.end()) task.SetPreset(json["Preset"].get<std::string>()); iter = json.find("Result"); if (iter != json.end()) task.SetResult(json["Result"].get<std::string>()); iter = json.find("Dynamic"); if (iter != json.end()) task.SetDynamic(json["Dynamic"].get<bool>()); } } mitk::SegmentationTaskListIO::SegmentationTaskListIO() : AbstractFileIO(SegmentationTaskList::GetStaticNameOfClass(), MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE(), "MITK Segmentation Task List") { this->RegisterService(); } std::vector<mitk::BaseData::Pointer> 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<int>("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<std::string>())); try { if (json.contains("Defaults")) { segmentationTaskList->SetDefaults(json["Defaults"].get<SegmentationTaskList::Task>()); if (segmentationTaskList->GetDefaults().HasResult()) mitkThrow() << "Defaults must not contain \"Result\"!"; } for (const auto& task : json["Tasks"]) { auto i = segmentationTaskList->AddTask(task.get<SegmentationTaskList::Task>()); 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<BaseData::Pointer> result; result.push_back(segmentationTaskList.GetPointer()); return result; } void mitk::SegmentationTaskListIO::Write() { auto segmentationTaskList = dynamic_cast<const SegmentationTaskList*>(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/mitkSegmentationTaskList.cpp b/Modules/Multilabel/mitkSegmentationTaskList.cpp index 33f130a6e5..1467922e7a 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.cpp +++ b/Modules/Multilabel/mitkSegmentationTaskList.cpp @@ -1,196 +1,196 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentationTaskList.h" #include <mitkIOUtil.h> #include <mitkProperties.h> 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(); + ? fs::path(inputLocation).lexically_normal() + : 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; auto normalizedPath = path.lexically_normal(); 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<mitk::SegmentationTaskList::Task>::const_iterator mitk::SegmentationTaskList::begin() const { return m_Tasks.begin(); } std::vector<mitk::SegmentationTaskList::Task>::const_iterator mitk::SegmentationTaskList::end() const { return m_Tasks.end(); } std::vector<mitk::SegmentationTaskList::Task>::iterator mitk::SegmentationTaskList::begin() { return m_Tasks.begin(); } std::vector<mitk::SegmentationTaskList::Task>::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 <mitkBaseData.h> #include <mitkSegmentationTaskListMacros.h> #include <MitkMultilabelExports.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <optional> 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<Task>::const_iterator begin() const; std::vector<Task>::const_iterator end() const; std::vector<Task>::iterator begin(); std::vector<Task>::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<Task> 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 <mitkProportionalTimeGeometry.h> #include <mitkROI.h> #include <mitkROIIOMimeTypes.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <fstream> namespace { int CheckFileFormat(const nlohmann::json& json) { if ("MITK ROI" != json["FileFormat"].get<std::string>()) mitkThrow() << "Unknown file format (expected \"MITK ROI\")!"; auto version = json["Version"].get<int>(); 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<mitk::Point3D>()); } if (jGeometry.contains("Spacing")) { if (hasTransform) mitkThrow() << "Spacing is already defined by Transform."; geometry->SetSpacing(jGeometry["Spacing"].get<mitk::Vector3D>()); } if (jGeometry.contains("Size")) SetSize(geometry, jGeometry["Size"].get<mitk::Vector3D>()); auto timeSteps = jGeometry.contains("TimeSteps") ? jGeometry["TimeSteps"].get<mitk::TimeStepType>() : 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::BaseData::Pointer> 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<std::string>())); if (j.contains("Caption")) result->SetProperty("caption", mitk::StringProperty::New(j["Caption"].get<std::string>())); for (const auto& roi : j["ROIs"]) result->AddElement(roi.get<ROI::Element>()); } catch (const nlohmann::json::exception &e) { mitkThrow() << e.what(); } return { result }; } void mitk::ROIIO::Write() { auto input = dynamic_cast<const ROI*>(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 <mitkROIIOMimeTypes.h> #include <mitkIOMimeTypes.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <fstream> #include <nlohmann/json.hpp> 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<int>("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::CustomMimeType*> mitk::MitkROIIOMimeTypes::Get() { std::vector<CustomMimeType*> mimeTypes; mimeTypes.push_back(ROI_MIMETYPE().Clone()); return mimeTypes; } 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 <mitkUIDManipulator.h> #include <mitkRenderingModeProperty.h> #include <tinyxml2.h> -#include <filesystem> +#include <mitkFileSystem.h> #include <map> MITK_REGISTER_SERIALIZER(SceneReaderV1) namespace { typedef std::pair<mitk::DataNode::Pointer, std::list<std::string>> 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<mitk::ProportionalTimeGeometry*>(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<float>::infinity()) value = std::numeric_limits<float>::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 <data type="..." file="..."> 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<mitk::DataNode::Pointer> 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<std::string, PropertyList::Pointer> 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 <node> 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 <properties> 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<std::string>())); // 3. if there are <source> 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 <node> // 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 <data> 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<Image *>(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 <filesystem> +#include <mitkFileSystem.h> #include <httplib.h> #include <mitkIOUtil.h> #include <mitkLabelSetImageHelper.h> #include <mitkPointSetShapeProperty.h> #include <mitkProperties.h> #include <mitkToolManager.h> #include <itkIntensityWindowingImageFilter.h> #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<mitk::InteractionPositionEvent *>(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<mitk::InteractionPositionEvent *>(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<std::map<std::string, nlohmann::json>>(); 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<std::unordered_map<std::string, int>>(); modelInfo.labels = labels; } catch (const std::exception &) { auto labels = _jsonObj["labels"].get<std::vector<std::string>>(); 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<std::string> GetPartsBetweenBoundary(const std::string &body, const std::string &boundary) { std::vector<std::string> 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<std::string, mitk::Label::PixelType> &labelMap) { if (labelMap.empty()) { auto label = mitk::LabelSetImageHelper::CreateNewLabel(dest, "object"); label->SetValue(1); dest->AddLabel(label, false); return; } std::map<mitk::Label::PixelType, std::string> 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<double, 3> 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 <typename TPixel, unsigned int VImageDimension> void ITKWindowing(const itk::Image<TPixel, VImageDimension> *inputImage, mitk::Image *mitkImage, mitk::ScalarType min, mitk::ScalarType max) { typedef itk::Image<TPixel, VImageDimension> ImageType; typedef itk::IntensityWindowingImageFilter<ImageType, ImageType> 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<Image>(outputImagePath); auto outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); std::map<std::string, mitk::Label::PixelType> 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<mitk::MonaiAppMetadata>(); m_InfoParameters = std::make_unique<mitk::MonaiAppMetadata>(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<std::string> 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::MonaiModelInfo> mitk::MonaiLabelTool::GetAutoSegmentationModels(const int dim) const { std::vector<mitk::MonaiModelInfo> 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::MonaiModelInfo> mitk::MonaiLabelTool::GetInteractiveSegmentationModels(const int dim) const { std::vector<mitk::MonaiModelInfo> 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::MonaiModelInfo> mitk::MonaiLabelTool::GetScribbleSegmentationModels(const int dim) const { std::vector<mitk::MonaiModelInfo> 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<std::string, std::string> 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<int>(index3D[0]) << COMMA << static_cast<int>(index3D[1]) << COMMA; if (is3DMode) { pointsAndLabels << static_cast<int>(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 <mitkSegmentAnythingProcessExecutor.h> #include <itksys/SystemTools.hxx> #include <chrono> #include <thread> -#include <filesystem> +#include <mitkFileSystem.h> #include <itkImageFileWriter.h> #include "mitkImageAccessByItk.h" #include <mitkLocaleSwitch.h> 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<const mitk::ExternalProcessStdOutEvent *>(&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<const mitk::ExternalProcessStdErrEvent *>(&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<std::chrono::seconds>(sys_clock::now() - start).count() > timeOut) { CurrentStatus = Status::OFF; m_DaemonExec->SetStop(true); mitkThrow() << SIGNALCONSTANTS::TIMEOUT_ERROR; } } LabelSetImage::Pointer outputBuffer = mitk::IOUtil::Load<LabelSetImage>(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 <typename TPixel, unsigned int VImageDimension> void mitk::SegmentAnythingPythonService::ITKWriter(const itk::Image<TPixel, VImageDimension> *image, std::string& outputFilename) const { typedef itk::Image<TPixel, VImageDimension> ImageType; typedef itk::ImageFileWriter<ImageType> 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 <mitkIOUtil.h> #include <mitkImageReadAccessor.h> #include <algorithm> -#include <filesystem> +#include <mitkFileSystem.h> #include <itksys/SystemTools.hxx> #include <regex> // us #include <usGetModuleContext.h> #include <usModule.h> #include <usModuleContext.h> #include <usModuleResource.h> #include <usServiceReference.h> 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<const mitk::ExternalProcessStdOutEvent *>(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast<const mitk::ExternalProcessStdErrEvent *>(&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<std::string> 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<Image>(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<std::string> 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<std::string> &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<mitk::Label::PixelType>(), 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<Image>(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<mitk::Label::PixelType, std::string> &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<double, 3> 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 <itksys/SystemTools.hxx> #include <usGetModuleContext.h> #include <usModule.h> #include <usModuleContext.h> #include <usModuleResource.h> -#include <filesystem> +#include <mitkFileSystem.h> 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<const mitk::ExternalProcessStdOutEvent *>(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast<const mitk::ExternalProcessStdErrEvent *>(&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<Image>(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 <mitkCommandLineParser.h> #include <mitkContourModelSet.h> #include <mitkContourModelSetToImageFilter.h> #include <mitkDataStorage.h> #include <mitkImageReadAccessor.h> #include <mitkImageWriteAccessor.h> #include <mitkIOUtil.h> #include <mitkLabelSetImage.h> #include <mitkLabelSetImageHelper.h> #include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/replace.hpp> -#include <filesystem> +#include <mitkFileSystem.h> 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<const mitk::StringProperty*>(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<const mitk::ColorProperty*>(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<std::string>(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<mitk::ContourModelSet::Pointer> FilterValidInputs(const std::vector<mitk::BaseData::Pointer>& inputs) { std::vector<mitk::ContourModelSet::Pointer> validInputs; for (auto input : inputs) { if (input.IsNull()) { MITK_WARN << "Skipping null input."; continue; } auto* validInput = dynamic_cast<mitk::ContourModelSet*>(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<std::string>(args["input"]); auto referenceFilename = us::any_cast<std::string>(args["reference"]); auto outputFilename = us::any_cast<std::string>(args["output"]); auto format = ParseOutputFormat(args); auto referenceImage = mitk::IOUtil::Load<mitk::Image>(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 <mitkCoreServices.h> #include <mitkIPreferencesService.h> #include <mitkIPreferences.h> #include <mitkDICOMQIPropertyHelper.h> #include <mitkIOUtil.h> #include <mitkMultiLabelIOHelper.h> #include <mitkLabelSetImageHelper.h> #include <mitkNodePredicateDataType.h> #include <mitkNodePredicateFunction.h> #include <mitkRenderingManager.h> #include <mitkSegmentationHelper.h> #include <mitkToolManagerProvider.h> #include <QmitkFindSegmentationTaskDialog.h> #include <QmitkStaticDynamicSegmentationDialog.h> #include <QmitkStyleManager.h> #include <ui_QmitkSegmentationTaskListWidget.h> #include <QFileSystemWatcher> #include <QMessageBox> #include <QShortcut> -#include <filesystem> +#include <mitkFileSystem.h> 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("<span style=\""); QStringList strings; if (color.isValid()) strings << QString("color: %1;").arg(color.name()); if (backgroundColor.isValid()) strings << QString("background-color: %1;").arg(backgroundColor.name()); result += strings.join(' ') + QString("\">%1</span>").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<mitk::SegmentationTaskList>::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( "<h3>Developer warning</h3><p>Call <code>SetDataStorage()</code> to fully initialize " "this instance of <code>QmitkSegmentationTaskListWidget</code>.</p>"); } else { auto isTaskList = mitk::TNodePredicateDataType<mitk::SegmentationTaskList>::New(); auto taskListNodes = GetSubset(m_DataStorage, isTaskList, removedNode); if (taskListNodes->empty()) { warning = QStringLiteral( "<h3>No segmentation task list found</h3><p>Load a segmentation task list to use " "this plugin.</p>"); } else if (taskListNodes->Size() > 1) { warning = QStringLiteral( "<h3>More than one segmentation task list found</h3><p>Unload everything but a " "single segmentation task list to use this plugin.</p>"); } 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( "<h3>Unrelated data found</h3><p>Unload everything but a single segmentation task " "list to use this plugin.</p>"); } } } m_Ui->label->setText("<span style=\"color: " + QmitkStyleManager::GetIconAccentColor() + "\">" + warning + "</span>"); 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<mitk::SegmentationTaskList*>(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<decltype(numTasks)> 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("<p><b>Status: %1</b> / <b>").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</b></p>").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker())); } else { details += QString("%1</b></p>").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("<p><b>Description:</b> %1</p>").arg(QString::fromStdString(m_TaskList->GetDescription(current))); QStringList stringList; if (m_TaskList->HasImage(current)) stringList << QString::fromStdString("<b>Image:</b> " + m_TaskList->GetImage(current).string()); if (m_TaskList->HasSegmentation(current)) stringList << QString::fromStdString("<b>Segmentation:</b> " + m_TaskList->GetSegmentation(current).string()); if (m_TaskList->HasLabelName(current)) stringList << QString::fromStdString("<b>Label name:</b> " + m_TaskList->GetLabelName(current)); if (m_TaskList->HasLabelNameSuggestions(current)) stringList << QString::fromStdString("<b>Label name suggestions:</b> " + m_TaskList->GetLabelNameSuggestions(current).string()); if (m_TaskList->HasPreset(current)) stringList << QString::fromStdString("<b>Label set preset:</b> " + m_TaskList->GetPreset(current).string()); if (m_TaskList->HasDynamic(current)) stringList << QString("<b>Segmentation type:</b> %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("<p>%1</p>").arg(stringList.join(QStringLiteral("<br>"))); 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<mitk::LabelSetImage>::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<mitk::Image>::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<mitk::Image>(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<mitk::LabelSetImage>(path.string()); } - else if (std::filesystem::exists(interimPath)) + else if (fs::exists(interimPath)) { segmentation = mitk::IOUtil::Load<mitk::LabelSetImage>(interimPath.string()); } else if (m_TaskList->HasSegmentation(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current)); segmentation = mitk::IOUtil::Load<mitk::LabelSetImage>(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<mitk::Image*>(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<mitk::LabelSetImage*>(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<mitk::LabelSetImage*>(segmentationNode->GetData()); auto command = itk::SimpleMemberCommand<QmitkSegmentationTaskListWidget>::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<mitk::LabelSetImage*>(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<size_t>& index) { if (m_ActiveTaskIndex != index) { m_ActiveTaskIndex = index; this->UpdateStoreAndAcceptButtons(); } } void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional<size_t>& 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); }