diff --git a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp index bb5c3f0ef3..19ce017be3 100644 --- a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskIO.cpp @@ -1,238 +1,259 @@ /*============================================================================ 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 (subtask.HasName()) + json["Name"] = subtask.GetName(); - if (!name.empty()) - json["Name"] = name; + if (subtask.HasDescription()) + json["Description"] = subtask.GetDescription(); - const auto description = subtask.GetDescription(); + if (subtask.HasImage()) + json["Image"] = subtask.GetImage(); - if (!description.empty()) - json["Description"] = description; + if (subtask.HasSegmentation()) + json["Segmentation"] = subtask.GetSegmentation(); - const auto image = subtask.GetImage(); + if (subtask.HasLabelName()) + json["LabelName"] = subtask.GetLabelName(); - if (!image.empty()) - json["Image"] = image; + if (subtask.HasPreset()) + json["Preset"] = subtask.GetPreset(); - const auto segmentation = subtask.GetSegmentation(); + if (subtask.HasResult()) + json["Result"] = subtask.GetResult(); - if (!segmentation.empty()) - json["Segmentation"] = segmentation; + if (subtask.HasDynamic()) + json["Dynamic"] = subtask.GetDynamic(); + } - const auto labelName = subtask.GetLabelName(); + void from_json(const nlohmann::json& json, SegmentationTask::Subtask& subtask) + { + auto iter = json.find("Name"); - if (!labelName.empty()) - json["LabelName"] = labelName; + if (iter != json.end()) + subtask.SetName(json["Name"].get()); - const auto preset = subtask.GetPreset(); + iter = json.find("Description"); - if (!preset.empty()) - json["Preset"] = preset; + if (iter != json.end()) + subtask.SetDescription(json["Description"].get()); - const auto result = subtask.GetResult(); + iter = json.find("Image"); - if (!result.empty()) - json["Result"] = result; - } + if (iter != json.end()) + subtask.SetImage(json["Image"].get()); - 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", "")); + iter = json.find("Segmentation"); + + if (iter != json.end()) + subtask.SetSegmentation(json["Segmentation"].get()); + + iter = json.find("LabelName"); + + if (iter != json.end()) + subtask.SetLabelName(json["LabelName"].get()); + + iter = json.find("Preset"); + + if (iter != json.end()) + subtask.SetPreset(json["Preset"].get()); + + iter = json.find("Result"); + + if (iter != json.end()) + subtask.SetResult(json["Result"].get()); + + iter = json.find("Dynamic"); + + if (iter != json.end()) + subtask.SetDynamic(json["Dynamic"].get()); } } 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; } 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" != 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(); 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()) + if (segmentationTask->GetDefaults().HasResult()) 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()) + if (!segmentationTask->HasImage(i)) mitkThrow() << "Subtask " << i << " must contain \"Image\"!"; + std::filesystem::path imagePath(segmentationTask->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; } if (!std::filesystem::exists(imagePath)) mitkThrow() << "Referenced image \"" << imagePath << "\" in subtask " << i << " does not exist!"; - if (segmentationTask->GetResult(i).empty()) + if (!segmentationTask->HasResult(i)) 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::ordered_json json = { { "FileFormat", "MITK Segmentation Task" }, { "Version", 1 }, { "Name", segmentationTask->GetProperty("name")->GetValueAsString() } }; 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.h b/Modules/Multilabel/mitkSegmentationTask.h index 5178269662..b726aec8d8 100644 --- a/Modules/Multilabel/mitkSegmentationTask.h +++ b/Modules/Multilabel/mitkSegmentationTask.h @@ -1,99 +1,102 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegmentationTask_h #define mitkSegmentationTask_h #include #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) + mitkSegmentationSubtaskValueMacro(std::string, Name) + mitkSegmentationSubtaskValueMacro(std::string, Description) + mitkSegmentationSubtaskValueMacro(std::string, Image) + mitkSegmentationSubtaskValueMacro(std::string, Segmentation) + mitkSegmentationSubtaskValueMacro(std::string, LabelName) + mitkSegmentationSubtaskValueMacro(std::string, Preset) + mitkSegmentationSubtaskValueMacro(std::string, Result) + mitkSegmentationSubtaskValueMacro(bool, Dynamic) 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) + mitkSegmentationTaskValueMacro(std::string, Name) + mitkSegmentationTaskValueMacro(std::string, Description) + mitkSegmentationTaskValueMacro(std::string, Image) + mitkSegmentationTaskValueMacro(std::string, Segmentation) + mitkSegmentationTaskValueMacro(std::string, LabelName) + mitkSegmentationTaskValueMacro(std::string, Preset) + mitkSegmentationTaskValueMacro(std::string, Result) + mitkSegmentationTaskValueMacro(bool, Dynamic) 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 index 87f445bc85..1d4f456b27 100644 --- a/Modules/Multilabel/mitkSegmentationTaskMacros.h +++ b/Modules/Multilabel/mitkSegmentationTaskMacros.h @@ -1,49 +1,61 @@ /*============================================================================ 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 mitkSegmentationSubtaskHasValueMacro(name) \ + bool Has##name() const { \ + return m_##name.has_value(); \ } -#define mitkSubtaskSetMacro(x) \ - void Set##x(const std::string& value) { \ - m_##x = value; \ +#define mitkSegmentationSubtaskGetValueMacro(type, name) \ + type Get##name() const { \ + if (m_##name.has_value()) return m_##name.value(); \ + if (m_Defaults != nullptr && m_Defaults->m_##name.has_value()) return m_Defaults->m_##name.value(); \ + return type(); \ } -#define mitkSubtaskValueMacro(x) \ +#define mitkSegmentationSubtaskSetValueMacro(type, name) \ + void Set##name(const type& value) { \ + m_##name = value; \ + } + +#define mitkSegmentationSubtaskValueMacro(type, name) \ public: \ - mitkSubtaskGetMacro(x) \ - mitkSubtaskSetMacro(x) \ + mitkSegmentationSubtaskHasValueMacro(name) \ + mitkSegmentationSubtaskGetValueMacro(type, name) \ + mitkSegmentationSubtaskSetValueMacro(type, name) \ private: \ - std::string m_##x; + std::optional m_##name; + +#define mitkSegmentationTaskGetValueMacro(type, name) \ + type Get##name(size_t index) const { \ + return index < m_Subtasks.size() ? m_Subtasks[index].Get##name() : type(); \ + } -#define mitkTaskGetMacro(x) \ - std::string Get##x(size_t index) const { \ - return index < m_Subtasks.size() ? m_Subtasks[index].Get##x() : ""; \ +#define mitkSegmentationTaskHasValueMacro(name) \ + bool Has##name(size_t index) const { \ + return index < m_Subtasks.size() && (m_Subtasks[index].Has##name() || m_Defaults.Has##name()); \ } -#define mitkTaskSetDefaultMacro(x) \ - void SetDefault##x(const std::string& value) { \ - m_Defaults.Set##x(value); \ +#define mitkSegmentationTaskSetDefaultMacro(type, name) \ + void SetDefault##name(const type& value) { \ + m_Defaults.Set##name(value); \ } -#define mitkTaskValueMacro(x) \ - mitkTaskGetMacro(x) \ - mitkTaskSetDefaultMacro(x) +#define mitkSegmentationTaskValueMacro(type, name) \ + mitkSegmentationTaskHasValueMacro(name) \ + mitkSegmentationTaskGetValueMacro(type, name) \ + mitkSegmentationTaskSetDefaultMacro(type, name) #endif diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.cpp index 494d806a1d..2006af2521 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.cpp +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.cpp @@ -1,587 +1,587 @@ /*============================================================================ 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 "QmitkSegmentationTaskWidget.h" #include "org_mitk_gui_qt_flow_segmentation_Activator.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include namespace { mitk::DataStorage* GetDataStorage() { auto* pluginContext = org_mitk_gui_qt_flow_segmentation_Activator::GetContext(); auto dataStorageServiceReference = pluginContext->getServiceReference(); if (dataStorageServiceReference) { auto* dataStorageService = pluginContext->getService(dataStorageServiceReference); if (dataStorageService != nullptr) { auto dataStorageReference = dataStorageService->GetDataStorage(); pluginContext->ungetService(dataStorageServiceReference); return dataStorageReference->GetDataStorage(); } } return nullptr; } std::filesystem::path GetInputLocation(const mitk::BaseData* data) { std::string result; if (data != nullptr) data->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result); return result; } QString ColorString(const QString& string, const QColor& color, const QColor& backgroundColor = QColor::Invalid) { if (!color.isValid() && !backgroundColor.isValid()) return string; auto result = QStringLiteral("%1").arg(string); return result; } } /* 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 */ QmitkSegmentationTaskWidget::QmitkSegmentationTaskWidget(QWidget* parent) : QWidget(parent), m_Ui(new Ui::QmitkSegmentationTaskWidget), m_FileSystemWatcher(new QFileSystemWatcher(this)), m_CurrentSubtaskIndex(0) { m_Ui->setupUi(this); m_Ui->selectionWidget->SetDataStorage(GetDataStorage()); m_Ui->selectionWidget->SetSelectionIsOptional(true); m_Ui->selectionWidget->SetEmptyInfo(QStringLiteral("Select a segmentation task")); m_Ui->selectionWidget->SetAutoSelectNewNodes(true); m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType::New()); m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor())); using Self = QmitkSegmentationTaskWidget; 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->loadButton, &QPushButton::clicked, this, &Self::OnLoadButtonClicked); connect(m_FileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &Self::OnResultDirectoryChanged); this->OnSelectionChanged(m_Ui->selectionWidget->GetSelectedNodes()); } QmitkSegmentationTaskWidget::~QmitkSegmentationTaskWidget() { } mitk::SegmentationTask* QmitkSegmentationTaskWidget::GetTask() const { return m_Task; } std::optional QmitkSegmentationTaskWidget::GetActiveSubtaskIndex() const { return m_ActiveSubtaskIndex; } size_t QmitkSegmentationTaskWidget::GetCurrentSubtaskIndex() const { return m_CurrentSubtaskIndex; } /* Make sure that the widget transitions into a valid state whenever the * selection changes. */ void QmitkSegmentationTaskWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { this->UnloadSubtasks(); this->ResetControls(); if (!nodes.empty()) { m_TaskNode = nodes.front(); auto task = dynamic_cast(m_TaskNode->GetData()); if (task != nullptr) { this->OnTaskChanged(task); return; } } this->SetTask(nullptr); m_TaskNode = nullptr; } /* Reset all controls to a default state as a common basis for further * adjustments. */ void QmitkSegmentationTaskWidget::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->loadButton->setEnabled(false); m_Ui->loadButton->setText(QStringLiteral("Load subtask")); m_Ui->nextButton->setEnabled(false); m_Ui->detailsLabel->clear(); } /* If the segmentation task changed, reset all member variables to expected * default values and reset the file system watcher. */ void QmitkSegmentationTaskWidget::SetTask(mitk::SegmentationTask* task) { if (m_Task != task) { m_Task = task; this->SetCurrentSubtaskIndex(0); this->ResetFileSystemWatcher(); } } void QmitkSegmentationTaskWidget::ResetFileSystemWatcher() { { auto paths = m_FileSystemWatcher->directories(); if (!paths.empty()) m_FileSystemWatcher->removePaths(paths); } if (m_Task.IsNotNull()) { for (const auto& subtask : *m_Task) { auto resultPath = m_Task->GetAbsolutePath(subtask.GetResult()).remove_filename(); if (!std::filesystem::exists(resultPath)) { try { std::filesystem::create_directories(resultPath); } catch (const std::filesystem::filesystem_error& e) { MITK_ERROR << e.what(); } } if (std::filesystem::exists(resultPath)) m_FileSystemWatcher->addPath(QString::fromStdString(resultPath.string())); } } } void QmitkSegmentationTaskWidget::OnResultDirectoryChanged(const QString&) { this->UpdateProgressBar(); this->UpdateDetailsLabel(); } void QmitkSegmentationTaskWidget::UpdateProgressBar() { int progress = 0; for (size_t i = 0; i < m_Task->GetNumberOfSubtasks(); ++i) { if (m_Task->IsDone(i)) ++progress; } m_Ui->progressBar->setValue(progress); } /* Provided that a valid segmentation task is currently selected and the * widget is in its default state, update all controls accordingly. */ void QmitkSegmentationTaskWidget::OnTaskChanged(mitk::SegmentationTask* task) { this->SetTask(task); const auto numSubtasks = task->GetNumberOfSubtasks(); m_Ui->progressBar->setMaximum(numSubtasks); m_Ui->progressBar->setFormat(QStringLiteral("%v/%m Subtask(s) done")); m_Ui->progressBar->setEnabled(true); this->UpdateProgressBar(); m_Ui->loadButton->setEnabled(true); if (numSubtasks > 1) m_Ui->nextButton->setEnabled(true); this->OnSubtaskChanged(); } /* If possible, change the currently displayed subtask to the previous subtask. * Enable/disable navigation buttons according to the subtask's position. */ void QmitkSegmentationTaskWidget::OnPreviousButtonClicked() { const auto maxIndex = m_Task->GetNumberOfSubtasks() - 1; if (m_CurrentSubtaskIndex != 0) { this->SetCurrentSubtaskIndex(m_CurrentSubtaskIndex - 1); this->OnSubtaskChanged(); } if (m_CurrentSubtaskIndex == 0) m_Ui->previousButton->setEnabled(false); if (m_CurrentSubtaskIndex < maxIndex) m_Ui->nextButton->setEnabled(true); } /* If possible, change the currently displayed subtask to the next subtask. * Enable/disable navigation buttons according to the subtask's position. */ void QmitkSegmentationTaskWidget::OnNextButtonClicked() { const auto maxIndex = m_Task->GetNumberOfSubtasks() - 1; if (m_CurrentSubtaskIndex < maxIndex) { this->SetCurrentSubtaskIndex(m_CurrentSubtaskIndex + 1); this->OnSubtaskChanged(); } if (m_CurrentSubtaskIndex != 0) m_Ui->previousButton->setEnabled(true); if (m_CurrentSubtaskIndex >= maxIndex) m_Ui->nextButton->setEnabled(false); } /* Update affected controls when the currently displayed subtask changed. */ void QmitkSegmentationTaskWidget::OnSubtaskChanged() { this->UpdateLoadButton(); this->UpdateDetailsLabel(); } /* Update the load button according to the currently displayed subtask. */ void QmitkSegmentationTaskWidget::UpdateLoadButton() { - bool active = m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex.value() == m_CurrentSubtaskIndex; + const auto i = m_CurrentSubtaskIndex; + bool active = m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex.value() == i; auto text = !active ? QStringLiteral("Load subtask") : QStringLiteral("Subtask"); if (m_Task.IsNotNull()) { - text += QString(" %1/%2").arg(m_CurrentSubtaskIndex + 1).arg(m_Task->GetNumberOfSubtasks()); + text += QString(" %1/%2").arg(i + 1).arg(m_Task->GetNumberOfSubtasks()); - const auto subtaskName = m_Task->GetName(m_CurrentSubtaskIndex); - - if (!subtaskName.empty()) - text += QStringLiteral(":\n") + QString::fromStdString(subtaskName); + if (m_Task->HasName(i)) + text += QStringLiteral(":\n") + QString::fromStdString(m_Task->GetName(i)); } m_Ui->loadButton->setDisabled(active); m_Ui->loadButton->setText(text); } /* Update the details label according to the currently display subtask. * The text is composed of the status of the subtask and a variable number * of text blocks according to the optional values provided by the subtask. */ void QmitkSegmentationTaskWidget::UpdateDetailsLabel() { - bool active = m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex.value() == m_CurrentSubtaskIndex; - bool isDone = m_Task->IsDone(m_CurrentSubtaskIndex); + const auto i = m_CurrentSubtaskIndex; + bool active = m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex.value() == i; + bool isDone = m_Task->IsDone(i); auto details = QString("

Status: %1 / ").arg(active ? ColorString("Active", Qt::white, QColor(Qt::green).darker()) : ColorString("Inactive", Qt::white, QColor(Qt::red).darker())); details += QString("%1

").arg(isDone ? ColorString("Done", Qt::white, QColor(Qt::green).darker()) : ColorString("Undone", Qt::white, QColor(Qt::red).darker())); - const auto description = m_Task->GetDescription(m_CurrentSubtaskIndex); - - if (!description.empty()) - details += QString("

Description: %1

").arg(QString::fromStdString(description)); + if (m_Task->HasDescription(i)) + details += QString("

Description: %1

").arg(QString::fromStdString(m_Task->GetDescription(i))); QStringList stringList; - const auto image = m_Task->GetImage(m_CurrentSubtaskIndex); - - if (!image.empty()) - stringList << QString("Image: %1").arg(QString::fromStdString(image)); - - const auto segmentation = m_Task->GetSegmentation(m_CurrentSubtaskIndex); - - if (!segmentation.empty()) - stringList << QString("Segmentation: %1").arg(QString::fromStdString(segmentation)); + if (m_Task->HasImage(i)) + stringList << QString("Image: %1").arg(QString::fromStdString(m_Task->GetImage(i))); - const auto labelName = m_Task->GetLabelName(m_CurrentSubtaskIndex); + if (m_Task->HasSegmentation(i)) + stringList << QString("Segmentation: %1").arg(QString::fromStdString(m_Task->GetSegmentation(i))); - if (!labelName.empty()) - stringList << QString("Label name: %1").arg(QString::fromStdString(labelName)); + if (m_Task->HasLabelName(i)) + stringList << QString("Label name: %1").arg(QString::fromStdString(m_Task->GetLabelName(i))); - const auto preset = m_Task->GetPreset(m_CurrentSubtaskIndex); + if (m_Task->HasPreset(i)) + stringList << QString("Label set preset: %1").arg(QString::fromStdString(m_Task->GetPreset(i))); - if (!preset.empty()) - stringList << QString("Label set preset: %1").arg(QString::fromStdString(preset)); + if (m_Task->HasDynamic(i)) + stringList << QString("Segmentation type: %1").arg(m_Task->GetDynamic(i) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("

%1

").arg(stringList.join(QStringLiteral("
"))); m_Ui->detailsLabel->setText(details); } /* Load/activate the currently displayed subtask. Unload all data nodes from * previously active subtasks first, but spare and reuse the image if possible. */ void QmitkSegmentationTaskWidget::OnLoadButtonClicked() { m_Ui->loadButton->setEnabled(false); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); auto* imageNode = this->GetImageDataNode(m_CurrentSubtaskIndex); this->UnloadSubtasks(imageNode); this->LoadSubtask(imageNode); this->OnSubtaskChanged(); QApplication::restoreOverrideCursor(); } /* If present, return the image data node for the currently displayed subtask. * Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskWidget::GetImageDataNode(size_t index) const { const auto imagePath = m_Task->GetAbsolutePath(m_Task->GetImage(index)); auto imageNodes = GetDataStorage()->GetDerivations(m_TaskNode, 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 currently displayed * subtask. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskWidget::GetSegmentationDataNode(size_t index) const { const auto* imageNode = this->GetImageDataNode(index); if (imageNode != nullptr) { auto segmentations = GetDataStorage()->GetDerivations(imageNode, mitk::TNodePredicateDataType::New()); if (!segmentations->empty()) return segmentations->front(); } return nullptr; } /* Unload all subtask data nodes but spare the passed image data node. */ void QmitkSegmentationTaskWidget::UnloadSubtasks(const mitk::DataNode* skip) { if (m_TaskNode.IsNotNull()) { mitk::DataStorage::Pointer dataStorage = GetDataStorage(); auto imageNodes = dataStorage->GetDerivations(m_TaskNode, mitk::TNodePredicateDataType::New()); for (auto imageNode : *imageNodes) { dataStorage->Remove(dataStorage->GetDerivations(imageNode, nullptr, false)); if (imageNode != skip) dataStorage->Remove(imageNode); } } this->SetActiveSubtaskIndex(std::nullopt); } /* Load/activate the currently displayed subtask. The subtask 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 subtask, or loaded as specified by the subtask. If a result file does * exist, it is chosen as segmentation instead. */ void QmitkSegmentationTaskWidget::LoadSubtask(mitk::DataNode::Pointer imageNode) { - mitk::DataStorage::Pointer dataStorage = GetDataStorage(); + const auto i = m_CurrentSubtaskIndex; - const auto imagePath = m_Task->GetAbsolutePath(m_Task->GetImage(m_CurrentSubtaskIndex)); mitk::Image::Pointer image; - - const auto resultPath = m_Task->GetAbsolutePath(m_Task->GetResult(m_CurrentSubtaskIndex)); - - const auto segmentationPath = m_Task->GetAbsolutePath(m_Task->GetSegmentation(m_CurrentSubtaskIndex)); mitk::LabelSetImage::Pointer segmentation; - mitk::DataNode::Pointer segmentationNode; - - const auto labelName = m_Task->GetLabelName(m_CurrentSubtaskIndex); - - const auto presetPath = m_Task->GetAbsolutePath(m_Task->GetPreset(m_CurrentSubtaskIndex)); try { if (imageNode.IsNull()) - image = mitk::IOUtil::Load(imagePath.string()); + { + const auto path = m_Task->GetAbsolutePath(m_Task->GetImage(i)); + image = mitk::IOUtil::Load(path.string()); + } - if (std::filesystem::exists(resultPath)) + if (m_Task->HasResult(i)) { - segmentation = mitk::IOUtil::Load(resultPath.string()); + const auto path = m_Task->GetAbsolutePath(m_Task->GetResult(i)); + + if (std::filesystem::exists(path)) + segmentation = mitk::IOUtil::Load(path.string()); } - else if (!segmentationPath.empty()) + else if (m_Task->HasSegmentation(i)) { - segmentation = mitk::IOUtil::Load(segmentationPath.string()); + const auto path = m_Task->GetAbsolutePath(m_Task->GetSegmentation(i)); + segmentation = mitk::IOUtil::Load(path.string()); } } catch (const mitk::Exception&) { return; } + auto dataStorage = GetDataStorage(); + if (imageNode.IsNull()) { imageNode = mitk::DataNode::New(); imageNode->SetData(image); dataStorage->Add(imageNode, m_TaskNode); mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry()); } else { image = static_cast(imageNode->GetData()); } - auto imageName = "Subtask " + std::to_string(m_CurrentSubtaskIndex + 1); - imageNode->SetName(imageName); - - auto segmentationName = imageName; + auto name = "Subtask " + std::to_string(i + 1); + imageNode->SetName(name); if (segmentation.IsNull()) { mitk::Image::ConstPointer templateImage = image; if (templateImage->GetDimension() > 3) { - QmitkStaticDynamicSegmentationDialog dialog(this); - dialog.SetReferenceImage(templateImage); - dialog.exec(); + if (m_Task->HasDynamic(i)) + { + if (!m_Task->GetDynamic(i)) + templateImage = mitk::SegmentationHelper::GetStaticSegmentationTemplate(image); + } + else + { + QmitkStaticDynamicSegmentationDialog dialog(this); + dialog.SetReferenceImage(templateImage); + dialog.exec(); - templateImage = dialog.GetSegmentationTemplate(); + templateImage = dialog.GetSegmentationTemplate(); + } } - segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, segmentationName); + auto segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, name); segmentation = static_cast(segmentationNode->GetData()); - if (!presetPath.empty()) + if (m_Task->HasPreset(i)) { - mitk::LabelSetIOHelper::LoadLabelSetImagePreset(presetPath.string(), segmentation); + const auto path = m_Task->GetAbsolutePath(m_Task->GetPreset(i)); + mitk::LabelSetIOHelper::LoadLabelSetImagePreset(path.string(), segmentation); } else { auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation); - if (!labelName.empty()) - label->SetName(labelName); + if (m_Task->HasLabelName(i)) + label->SetName(m_Task->GetLabelName(i)); segmentation->GetActiveLabelSet()->AddLabel(label); } dataStorage->Add(segmentationNode, imageNode); } else { - segmentationNode = mitk::DataNode::New(); - segmentationNode->SetName(segmentationName); + auto segmentationNode = mitk::DataNode::New(); + segmentationNode->SetName(name); segmentationNode->SetData(segmentation); dataStorage->Add(segmentationNode, imageNode); } - this->SetActiveSubtaskIndex(m_CurrentSubtaskIndex); + this->SetActiveSubtaskIndex(i); } void QmitkSegmentationTaskWidget::SetActiveSubtaskIndex(const std::optional& index) { if (m_ActiveSubtaskIndex != index) { m_ActiveSubtaskIndex = index; emit ActiveSubtaskChanged(m_ActiveSubtaskIndex); } } void QmitkSegmentationTaskWidget::SetCurrentSubtaskIndex(size_t index) { if (m_CurrentSubtaskIndex != index) { m_CurrentSubtaskIndex = index; emit CurrentSubtaskChanged(m_CurrentSubtaskIndex); } }