diff --git a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp index e883c20426..66f5e9130c 100644 --- a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp @@ -1,267 +1,273 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentationTaskListIO.h" #include "mitkMultilabelIOMimeTypes.h" #include #include #include #include namespace mitk { void to_json(nlohmann::json& json, const SegmentationTaskList::Task& task) { if (task.HasName()) json["Name"] = task.GetName(); if (task.HasDescription()) json["Description"] = task.GetDescription(); if (task.HasImage()) json["Image"] = task.GetImage().string(); if (task.HasSegmentation()) json["Segmentation"] = task.GetSegmentation().string(); if (task.HasLabelName()) json["LabelName"] = task.GetLabelName(); if (task.HasLabelNameSuggestions()) json["LabelNameSuggestions"] = task.GetLabelNameSuggestions().string(); if (task.HasPreset()) json["Preset"] = task.GetPreset().string(); if (task.HasResult()) json["Result"] = task.GetResult().string(); if (task.HasDynamic()) json["Dynamic"] = task.GetDynamic(); } void from_json(const nlohmann::json& json, SegmentationTaskList::Task& task) { auto iter = json.find("Name"); if (iter != json.end()) task.SetName(json["Name"].get()); iter = json.find("Description"); if (iter != json.end()) task.SetDescription(json["Description"].get()); iter = json.find("Image"); if (iter != json.end()) task.SetImage(json["Image"].get()); iter = json.find("Segmentation"); if (iter != json.end()) task.SetSegmentation(json["Segmentation"].get()); iter = json.find("LabelName"); if (iter != json.end()) task.SetLabelName(json["LabelName"].get()); iter = json.find("LabelNameSuggestions"); if (iter != json.end()) task.SetLabelNameSuggestions(json["LabelNameSuggestions"].get()); iter = json.find("Preset"); if (iter != json.end()) task.SetPreset(json["Preset"].get()); iter = json.find("Result"); if (iter != json.end()) task.SetResult(json["Result"].get()); iter = json.find("Dynamic"); if (iter != json.end()) task.SetDynamic(json["Dynamic"].get()); } } mitk::SegmentationTaskListIO::SegmentationTaskListIO() : AbstractFileIO(SegmentationTaskList::GetStaticNameOfClass(), MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE(), "MITK Segmentation Task List") { this->RegisterService(); } std::vector mitk::SegmentationTaskListIO::DoRead() { auto* stream = this->GetInputStream(); std::ifstream fileStream; if (nullptr == stream) { auto filename = this->GetInputLocation(); if (filename.empty() || !fs::exists(filename)) mitkThrow() << "Invalid or nonexistent filename: \"" << filename << "\"!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for reading!"; stream = &fileStream; } nlohmann::json json; try { json = nlohmann::json::parse(*stream); } catch (const nlohmann::json::exception& e) { mitkThrow() << e.what(); } if (!json.is_object()) mitkThrow() << "Unknown file format (expected JSON object as root)!"; if ("MITK Segmentation Task List" != json.value("FileFormat", "")) mitkThrow() << "Unknown file format (expected \"MITK Segmentation Task List\")!"; if (1 != json.value("Version", 0)) mitkThrow() << "Unknown file format version (expected \"1\")!"; if (!json.contains("Tasks") || !json["Tasks"].is_array()) mitkThrow() << "Tasks array not found!"; auto segmentationTaskList = SegmentationTaskList::New(); - if (json.contains("Name")) - segmentationTaskList->SetProperty("name", StringProperty::New(json["Name"].get())); - try { + if (json.contains("Name")) + segmentationTaskList->SetProperty("name", StringProperty::New(json["Name"].get())); + + if (json.contains("Form")) + segmentationTaskList->SetForm(json["Form"].get()); + if (json.contains("Defaults")) { segmentationTaskList->SetDefaults(json["Defaults"].get()); if (segmentationTaskList->GetDefaults().HasResult()) mitkThrow() << "Defaults must not contain \"Result\"!"; } for (const auto& task : json["Tasks"]) { auto i = segmentationTaskList->AddTask(task.get()); if (!segmentationTaskList->HasImage(i)) mitkThrow() << "Task " << i << " must contain \"Image\"!"; 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 = fs::path(inputLocation).remove_filename() / imagePath; } if (!fs::exists(imagePath)) mitkThrow() << "Referenced image \"" << imagePath << "\" in task " << i << " does not exist!"; if (!segmentationTaskList->HasResult(i)) mitkThrow() << "Task " << i << " must contain \"Result\"!"; } } catch (const nlohmann::json::type_error& e) { mitkThrow() << e.what(); } std::vector result; result.push_back(segmentationTaskList.GetPointer()); return result; } void mitk::SegmentationTaskListIO::Write() { auto segmentationTaskList = dynamic_cast(this->GetInput()); if (nullptr == segmentationTaskList) mitkThrow() << "Invalid input for writing!"; if (segmentationTaskList->GetNumberOfTasks() == 0) mitkThrow() << "No tasks found!"; auto* stream = this->GetOutputStream(); std::ofstream fileStream; if (nullptr == stream) { auto filename = this->GetOutputLocation(); if (filename.empty()) mitkThrow() << "Neither an output stream nor an output filename was specified!"; fileStream.open(filename); if (!fileStream.is_open()) mitkThrow() << "Could not open file \"" << filename << "\" for writing!"; stream = &fileStream; } if (!stream->good()) mitkThrow() << "Stream for writing is not good!"; nlohmann::ordered_json json = { { "FileFormat", "MITK Segmentation Task List" }, { "Version", 1 }, { "Name", segmentationTaskList->GetProperty("name")->GetValueAsString() } }; + if (segmentationTaskList->HasForm()) + json["Form"] = segmentationTaskList->GetForm().string(); + 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 97523730e6..8badfb906a 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.cpp +++ b/Modules/Multilabel/mitkSegmentationTaskList.cpp @@ -1,204 +1,223 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentationTaskList.h" #include #include mitk::SegmentationTaskList::Task::Task() : m_Defaults(nullptr) { } mitk::SegmentationTaskList::Task::~Task() { } void mitk::SegmentationTaskList::Task::SetDefaults(const Task* defaults) { m_Defaults = defaults; } mitk::SegmentationTaskList::SegmentationTaskList() { // A base data cannot be serialized if empty. To be not considered empty its // geometry must consist of at least one time step. However, a segmentation // task would then appear as invisible spatial object in a scene. This can // be prevented by excluding it from the scene's bounding box calculations. this->GetTimeGeometry()->Expand(1); this->SetProperty("includeInBoundingBox", BoolProperty::New(false)); } mitk::SegmentationTaskList::SegmentationTaskList(const Self& other) : BaseData(other) { } mitk::SegmentationTaskList::~SegmentationTaskList() { } size_t mitk::SegmentationTaskList::GetNumberOfTasks() const { return m_Tasks.size(); } size_t mitk::SegmentationTaskList::AddTask(const Task& subtask) { m_Tasks.push_back(subtask); m_Tasks.back().SetDefaults(&m_Defaults); return m_Tasks.size() - 1; } const mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) const { return &m_Tasks.at(index); } mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) { return &m_Tasks.at(index); } const mitk::SegmentationTaskList::Task& mitk::SegmentationTaskList::GetDefaults() const { return m_Defaults; } void mitk::SegmentationTaskList::SetDefaults(const Task& defaults) { m_Defaults = defaults; for (auto& subtask : m_Tasks) subtask.SetDefaults(&m_Defaults); } +bool mitk::SegmentationTaskList::HasForm() const +{ + return m_Form.has_value(); +} + +std::filesystem::path mitk::SegmentationTaskList::GetForm() const +{ + return m_Form.value_or(std::filesystem::path()); +} + +void mitk::SegmentationTaskList::SetForm(const std::filesystem::path& form) +{ + if (this->GetForm() != form) + { + m_Form = form; + this->Modified(); + } +} + 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 fs::exists(this->GetAbsolutePath(m_Tasks.at(index).GetResult())); } fs::path mitk::SegmentationTaskList::GetInputLocation() const { std::string inputLocation; this->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", inputLocation); return !inputLocation.empty() #ifdef MITK_HAS_FILESYSTEM ? fs::path(inputLocation).lexically_normal() #else ? fs::path(inputLocation) #endif : fs::path(); } fs::path mitk::SegmentationTaskList::GetBasePath() const { return this->GetInputLocation().remove_filename(); } fs::path mitk::SegmentationTaskList::GetAbsolutePath(const fs::path& path) const { if (path.empty()) return path; #ifdef MITK_HAS_FILESYSTEM auto normalizedPath = path.lexically_normal(); #else auto normalizedPath = path; #endif return !normalizedPath.is_absolute() ? this->GetBasePath() / normalizedPath : normalizedPath; } 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 (fs::exists(path)) saveAsInterimResult = false; IOUtil::Save(segmentation, saveAsInterimResult ? interimPath.string() : path.string()); if (!saveAsInterimResult && fs::exists(interimPath)) { std::error_code ec; fs::remove(interimPath, ec); } } std::vector::const_iterator mitk::SegmentationTaskList::begin() const { return m_Tasks.begin(); } std::vector::const_iterator mitk::SegmentationTaskList::end() const { return m_Tasks.end(); } std::vector::iterator mitk::SegmentationTaskList::begin() { return m_Tasks.begin(); } std::vector::iterator mitk::SegmentationTaskList::end() { return m_Tasks.end(); } void mitk::SegmentationTaskList::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::SegmentationTaskList::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::SegmentationTaskList::VerifyRequestedRegion() { return true; } void mitk::SegmentationTaskList::SetRequestedRegion(const itk::DataObject*) { } diff --git a/Modules/Multilabel/mitkSegmentationTaskList.h b/Modules/Multilabel/mitkSegmentationTaskList.h index 9a90f1210d..13355776ba 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.h +++ b/Modules/Multilabel/mitkSegmentationTaskList.h @@ -1,111 +1,116 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegmentationTaskList_h #define mitkSegmentationTaskList_h #include #include #include #include #include namespace mitk { /** \brief A list of segmentation tasks. * * See \ref MITKSegmentationTaskListsPage for more information. */ class MITKMULTILABEL_EXPORT SegmentationTaskList : public BaseData { public: class MITKMULTILABEL_EXPORT Task { public: Task(); ~Task(); void SetDefaults(const Task* defaults); mitkSegmentationTaskValueMacro(std::string, Name) mitkSegmentationTaskValueMacro(std::string, Description) mitkSegmentationTaskValueMacro(fs::path, Image) mitkSegmentationTaskValueMacro(fs::path, Segmentation) mitkSegmentationTaskValueMacro(std::string, LabelName) 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(fs::path, Image) mitkSegmentationTaskListValueMacro(fs::path, Segmentation) mitkSegmentationTaskListValueMacro(std::string, LabelName) 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 HasForm() const; + std::filesystem::path GetForm() const; + void SetForm(const std::filesystem::path& form); + bool IsDone() const; bool IsDone(size_t index) const; fs::path GetInputLocation() const; fs::path GetBasePath() const; fs::path GetAbsolutePath(const fs::path& path) const; fs::path GetInterimPath(const fs::path& path) const; void SaveTask(size_t index, const BaseData* segmentation, bool saveAsInterimResult = false); std::vector::const_iterator begin() const; std::vector::const_iterator end() const; std::vector::iterator begin(); std::vector::iterator end(); void SetRequestedRegionToLargestPossibleRegion() override; bool RequestedRegionIsOutsideOfTheBufferedRegion() override; bool VerifyRequestedRegion() override; void SetRequestedRegion(const itk::DataObject*) override; protected: mitkCloneMacro(Self) SegmentationTaskList(); SegmentationTaskList(const Self& other); ~SegmentationTaskList() override; private: Task m_Defaults; std::vector m_Tasks; + std::optional m_Form; }; } #endif