diff --git a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp index 0a8c63f9f8..b4b0bc09ea 100644 --- a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp @@ -1,106 +1,224 @@ /*============================================================================ 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 "mitkSegmentationTaskIO.h" #include "mitkMultilabelIOMimeTypes.h" #include #include #include #include +namespace mitk +{ + void to_json(nlohmann::json& json, const SegmentationTask::Subtask& subtask) + { + const auto name = subtask.GetName(); + + if (!name.empty()) + json["Name"] = name; + + const auto description = subtask.GetDescription(); + + if (!description.empty()) + json["Description"] = description; + + const auto image = subtask.GetImage(); + + if (!image.empty()) + json["Image"] = image; + + const auto segmentation = subtask.GetSegmentation(); + + if (!segmentation.empty()) + json["Segmentation"] = segmentation; + + const auto labelName = subtask.GetLabelName(); + + if (!labelName.empty()) + json["LabelName"] = labelName; + + const auto preset = subtask.GetPreset(); + + if (!preset.empty()) + json["Preset"] = preset; + + const auto result = subtask.GetResult(); + + if (!result.empty()) + json["Result"] = result; + } + + void from_json(const nlohmann::json& json, SegmentationTask::Subtask& subtask) + { + subtask.SetName(json.value("Name", "")); + subtask.SetDescription(json.value("Description", "")); + subtask.SetImage(json.value("Image", "")); + subtask.SetSegmentation(json.value("Segmentation", "")); + subtask.SetLabelName(json.value("LabelName", "")); + subtask.SetPreset(json.value("Preset", "")); + subtask.SetResult(json.value("Result", "")); + } +} + mitk::SegmentationTaskIO::SegmentationTaskIO() : AbstractFileIO(SegmentationTask::GetStaticNameOfClass(), MitkMultilabelIOMimeTypes::SEGMENTATIONTASK_MIMETYPE(), "MITK Segmentation Task") { this->RegisterService(); } std::vector mitk::SegmentationTaskIO::DoRead() { auto* stream = this->GetInputStream(); std::ifstream fileStream; if (nullptr == stream) { auto filename = this->GetInputLocation(); if (filename.empty() || !std::filesystem::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 json = nlohmann::json::parse(*stream, nullptr, false); + nlohmann::json json; + + try + { + json = nlohmann::json::parse(*stream); + } + catch (const nlohmann::json::exception& e) + { + mitkThrow() << e.what(); + } - if (json.is_discarded() || !json.is_object()) - mitkThrow() << "Could not parse JSON!"; + if (!json.is_object()) + mitkThrow() << "Unknown file format (expected JSON object as root)!"; if ("MITK Segmentation Task" != json.value("FileFormat", "")) mitkThrow() << "Unknown file format (expected \"MITK Segmentation Task\")!"; if (1 != json.value("Version", 0)) mitkThrow() << "Unknown file format version (expected \"1\")!"; + if (!json.contains("Subtasks") || !json["Subtasks"].is_array()) + mitkThrow() << "Subtasks array not found!"; + auto segmentationTask = SegmentationTask::New(); - // TODO + if (json.contains("Name")) + segmentationTask->SetProperty("name", StringProperty::New(json["Name"].get())); + + try + { + if (json.contains("Defaults")) + { + segmentationTask->SetDefaults(json["Defaults"].get()); + + if (!segmentationTask->GetDefaults().GetResult().empty()) + mitkThrow() << "Defaults must not contain \"Result\"!"; + } + + for (const auto& subtask : json["Subtasks"]) + { + auto i = segmentationTask->AddSubtask(subtask.get()); + + std::filesystem::path imagePath(segmentationTask->GetImage(i)); + + if (imagePath.empty()) + mitkThrow() << "Subtask " << i << " must contain \"Image\"!"; + + if (imagePath.is_relative()) + imagePath = std::filesystem::path(this->GetInputLocation()).remove_filename() / imagePath; // TODO: Does not work for loading from an MITK scene file since the input location is different! + + if (!std::filesystem::exists(imagePath)) + mitkThrow() << "Referenced image \"" << imagePath << "\" in subtask " << i << " does not exist!"; + + if (segmentationTask->GetResult(i).empty()) + mitkThrow() << "Subtask " << i << " must contain \"Result\"!"; + } + } + catch (const nlohmann::json::type_error& e) + { + mitkThrow() << e.what(); + } std::vector result; result.push_back(segmentationTask.GetPointer()); return result; } void mitk::SegmentationTaskIO::Write() { + auto segmentationTask = dynamic_cast(this->GetInput()); + + if (nullptr == segmentationTask) + mitkThrow() << "Invalid input for writing!"; + + if (segmentationTask->GetNumberOfSubtasks() == 0) + mitkThrow() << "No subtasks 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::json json = { + nlohmann::ordered_json json = { { "FileFormat", "MITK Segmentation Task" }, - { "Version", 1 } + { "Version", 1 }, + { "Name", segmentationTask->GetProperty("name")->GetValueAsString() } }; - // TODO + nlohmann::json defaults = segmentationTask->GetDefaults(); + + if (!defaults.is_null()) + json["Defaults"] = defaults; + + nlohmann::json subtasks; + + for (const auto& subtask : *segmentationTask) + subtasks.push_back(subtask); + + json["Subtasks"] = subtasks; *stream << std::setw(2) << json << std::endl; } mitk::SegmentationTaskIO* mitk::SegmentationTaskIO::IOClone() const { return new SegmentationTaskIO(*this); } diff --git a/Modules/Multilabel/mitkSegmentationTask.cpp b/Modules/Multilabel/mitkSegmentationTask.cpp index a374ac46c0..f9c58ebf32 100644 --- a/Modules/Multilabel/mitkSegmentationTask.cpp +++ b/Modules/Multilabel/mitkSegmentationTask.cpp @@ -1,51 +1,163 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentationTask.h" #include +mitk::SegmentationTask::Subtask::Subtask() + : m_Defaults(nullptr) +{ +} + +mitk::SegmentationTask::Subtask::~Subtask() +{ +} + +void mitk::SegmentationTask::Subtask::SetDefaults(const Subtask* defaults) +{ + m_Defaults = defaults; +} + mitk::SegmentationTask::SegmentationTask() { // 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 spacial 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::SegmentationTask::SegmentationTask(const Self& other) : BaseData(other) { } mitk::SegmentationTask::~SegmentationTask() { } +size_t mitk::SegmentationTask::GetNumberOfSubtasks() const +{ + return m_Subtasks.size(); +} + +size_t mitk::SegmentationTask::AddSubtask(const Subtask& subtask) +{ + m_Subtasks.push_back(subtask); + m_Subtasks.back().SetDefaults(&m_Defaults); + return m_Subtasks.size() - 1; +} + +const mitk::SegmentationTask::Subtask* mitk::SegmentationTask::GetSubtask(size_t index) const +{ + return &m_Subtasks.at(index); +} + +mitk::SegmentationTask::Subtask* mitk::SegmentationTask::GetSubtask(size_t index) +{ + return &m_Subtasks.at(index); +} + +const mitk::SegmentationTask::Subtask& mitk::SegmentationTask::GetDefaults() const +{ + return m_Defaults; +} + +void mitk::SegmentationTask::SetDefaults(const Subtask& defaults) +{ + m_Defaults = defaults; + + for (auto& subtask : m_Subtasks) + subtask.SetDefaults(&m_Defaults); +} + +bool mitk::SegmentationTask::IsDone() const +{ + for (size_t i = 0; i < m_Subtasks.size(); ++i) + { + if (!this->IsDone(i)) + return false; + } + + return true; +} + +bool mitk::SegmentationTask::IsDone(size_t index) const +{ + return std::filesystem::exists(this->GetAbsolutePath(m_Subtasks.at(index).GetResult())); +} + +std::filesystem::path mitk::SegmentationTask::GetInputLocation() const +{ + std::string result; + this->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result); + + return !result.empty() + ? std::filesystem::path(result).lexically_normal() + : result; +} + +std::filesystem::path mitk::SegmentationTask::GetBasePath() const +{ + return this->GetInputLocation().remove_filename(); +} + +std::filesystem::path mitk::SegmentationTask::GetAbsolutePath(const std::filesystem::path& path) const +{ + if (path.empty()) + return path; + + auto normalizedPath = path.lexically_normal(); + + return !normalizedPath.is_absolute() + ? this->GetBasePath() / normalizedPath + : normalizedPath; +} + +std::vector::const_iterator mitk::SegmentationTask::begin() const +{ + return m_Subtasks.begin(); +} + +std::vector::const_iterator mitk::SegmentationTask::end() const +{ + return m_Subtasks.end(); +} + +std::vector::iterator mitk::SegmentationTask::begin() +{ + return m_Subtasks.begin(); +} + +std::vector::iterator mitk::SegmentationTask::end() +{ + return m_Subtasks.end(); +} + void mitk::SegmentationTask::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::SegmentationTask::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::SegmentationTask::VerifyRequestedRegion() { return true; } void mitk::SegmentationTask::SetRequestedRegion(const itk::DataObject*) { } diff --git a/Modules/Multilabel/mitkSegmentationTask.h b/Modules/Multilabel/mitkSegmentationTask.h index 2f2c8d08c5..5178269662 100644 --- a/Modules/Multilabel/mitkSegmentationTask.h +++ b/Modules/Multilabel/mitkSegmentationTask.h @@ -1,42 +1,99 @@ /*============================================================================ 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 mitkSegmentationTask_h #define mitkSegmentationTask_h #include +#include + #include +#include + namespace mitk { class MITKMULTILABEL_EXPORT SegmentationTask : public BaseData { public: + class MITKMULTILABEL_EXPORT Subtask + { + public: + Subtask(); + ~Subtask(); + + void SetDefaults(const Subtask* defaults); + + mitkSubtaskValueMacro(Name) + mitkSubtaskValueMacro(Description) + mitkSubtaskValueMacro(Image) + mitkSubtaskValueMacro(Segmentation) + mitkSubtaskValueMacro(LabelName) + mitkSubtaskValueMacro(Preset) + mitkSubtaskValueMacro(Result) + + private: + const Subtask* m_Defaults; + }; + mitkClassMacro(SegmentationTask, BaseData) itkFactorylessNewMacro(Self) itkCloneMacro(Self) + mitkTaskValueMacro(Name) + mitkTaskValueMacro(Description) + mitkTaskValueMacro(Image) + mitkTaskValueMacro(Segmentation) + mitkTaskValueMacro(LabelName) + mitkTaskValueMacro(Preset) + mitkTaskValueMacro(Result) + + size_t GetNumberOfSubtasks() const; + size_t AddSubtask(const Subtask& subtask); + const Subtask* GetSubtask(size_t index) const; + Subtask* GetSubtask(size_t index); + + const Subtask& GetDefaults() const; + void SetDefaults(const Subtask& 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::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) SegmentationTask(); SegmentationTask(const Self& other); ~SegmentationTask() override; + + private: + Subtask m_Defaults; + std::vector m_Subtasks; }; } #endif diff --git a/Modules/Multilabel/mitkSegmentationTaskMacros.h b/Modules/Multilabel/mitkSegmentationTaskMacros.h new file mode 100644 index 0000000000..87f445bc85 --- /dev/null +++ b/Modules/Multilabel/mitkSegmentationTaskMacros.h @@ -0,0 +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. + +============================================================================*/ + +#ifndef mitkSegmentationTaskMacros_h +#define mitkSegmentationTaskMacros_h + +#define mitkSubtaskGetMacro(x) \ + std::string Get##x() const { \ + if (!m_##x.empty()) return m_##x; \ + if (m_Defaults != nullptr) return m_Defaults->Get##x(); \ + return ""; \ + } + +#define mitkSubtaskSetMacro(x) \ + void Set##x(const std::string& value) { \ + m_##x = value; \ + } + +#define mitkSubtaskValueMacro(x) \ + public: \ + mitkSubtaskGetMacro(x) \ + mitkSubtaskSetMacro(x) \ + private: \ + std::string m_##x; + +#define mitkTaskGetMacro(x) \ + std::string Get##x(size_t index) const { \ + return index < m_Subtasks.size() ? m_Subtasks[index].Get##x() : ""; \ + } + +#define mitkTaskSetDefaultMacro(x) \ + void SetDefault##x(const std::string& value) { \ + m_Defaults.Set##x(value); \ + } + +#define mitkTaskValueMacro(x) \ + mitkTaskGetMacro(x) \ + mitkTaskSetDefaultMacro(x) + +#endif