(nodes.front()->GetData());
+
+ if (task != nullptr)
+ {
+ this->OnTaskChanged(task);
+ return;
+ }
+ }
+
+ this->SetTask(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;
+
+ auto text = !active
+ ? QStringLiteral("Load subtask")
+ : QStringLiteral("Subtask");
+
+ if (m_Task.IsNotNull())
+ {
+ text += QString(" %1/%2").arg(m_CurrentSubtaskIndex + 1).arg(m_Task->GetNumberOfSubtasks());
+
+ const auto subtaskName = m_Task->GetName(m_CurrentSubtaskIndex);
+
+ if (!subtaskName.empty())
+ text += QStringLiteral(":\n") + QString::fromStdString(subtaskName);
+ }
+
+ 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);
+
+ 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));
+
+ 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));
+
+ const auto labelName = m_Task->GetLabelName(m_CurrentSubtaskIndex);
+
+ if (!labelName.empty())
+ stringList << QString("Label name: %1").arg(QString::fromStdString(labelName));
+
+ const auto preset = m_Task->GetPreset(m_CurrentSubtaskIndex);
+
+ if (!preset.empty())
+ stringList << QString("Label set preset: %1").arg(QString::fromStdString(preset));
+
+ 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
+{
+ auto taskNode = m_Ui->selectionWidget->GetSelectedNode();
+ const auto imagePath = m_Task->GetAbsolutePath(m_Task->GetImage(index));
+
+ auto imageNodes = GetDataStorage()->GetDerivations(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)
+{
+ mitk::DataStorage::Pointer dataStorage = GetDataStorage();
+ auto taskNode = m_Ui->selectionWidget->GetSelectedNode();
+
+ auto imageNodes = dataStorage->GetDerivations(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();
+ auto taskNode = m_Ui->selectionWidget->GetSelectedNode();
+
+ 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());
+
+ if (std::filesystem::exists(resultPath))
+ {
+ segmentation = mitk::IOUtil::Load(resultPath.string());
+ }
+ else if (!segmentationPath.empty())
+ {
+ segmentation = mitk::IOUtil::Load(segmentationPath.string());
+ }
+ }
+ catch (const mitk::Exception&)
+ {
+ return;
+ }
+
+ if (imageNode.IsNull())
+ {
+ imageNode = mitk::DataNode::New();
+ imageNode->SetData(image);
+
+ dataStorage->Add(imageNode, 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;
+
+ if (segmentation.IsNull())
+ {
+ mitk::Image::ConstPointer templateImage = image;
+
+ if (templateImage->GetDimension() > 3)
+ {
+ QmitkStaticDynamicSegmentationDialog dialog(this);
+ dialog.SetReferenceImage(templateImage);
+ dialog.exec();
+
+ templateImage = dialog.GetSegmentationTemplate();
+ }
+
+ segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, segmentationName);
+ segmentation = static_cast(segmentationNode->GetData());
+
+ if (!presetPath.empty())
+ {
+ mitk::LabelSetIOHelper::LoadLabelSetImagePreset(presetPath.string(), segmentation);
+ }
+ else
+ {
+ auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation);
+
+ if (!labelName.empty())
+ label->SetName(labelName);
+
+ segmentation->GetActiveLabelSet()->AddLabel(label);
+ }
+
+ dataStorage->Add(segmentationNode, imageNode);
+ }
+ else
+ {
+ segmentationNode = mitk::DataNode::New();
+ segmentationNode->SetName(segmentationName);
+ segmentationNode->SetData(segmentation);
+
+ dataStorage->Add(segmentationNode, imageNode);
+ }
+
+ this->SetActiveSubtaskIndex(m_CurrentSubtaskIndex);
+}
+
+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);
+ }
+}
diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.h b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.h
new file mode 100644
index 0000000000..b41fafc54c
--- /dev/null
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.h
@@ -0,0 +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.
+
+============================================================================*/
+
+#ifndef QmitkSegmentationTaskWidget_h
+#define QmitkSegmentationTaskWidget_h
+
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+
+class QFileSystemWatcher;
+
+namespace Ui
+{
+ class QmitkSegmentationTaskWidget;
+}
+
+class QmitkSegmentationTaskWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit QmitkSegmentationTaskWidget(QWidget* parent = nullptr);
+ ~QmitkSegmentationTaskWidget() override;
+
+ mitk::SegmentationTask* GetTask() const;
+ std::optional GetActiveSubtaskIndex() const;
+ size_t GetCurrentSubtaskIndex() const;
+ mitk::DataNode* GetSegmentationDataNode(size_t index) const;
+
+signals:
+ void ActiveSubtaskChanged(const std::optional& index);
+ void CurrentSubtaskChanged(size_t index);
+
+private:
+ void OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes);
+ void ResetControls();
+ void SetTask(mitk::SegmentationTask* task);
+ void ResetFileSystemWatcher();
+ void OnResultDirectoryChanged(const QString&);
+ void UpdateProgressBar();
+ void OnTaskChanged(mitk::SegmentationTask* task);
+ void OnPreviousButtonClicked();
+ void OnNextButtonClicked();
+ void OnSubtaskChanged();
+ void UpdateLoadButton();
+ void UpdateDetailsLabel();
+ void OnLoadButtonClicked();
+ mitk::DataNode* GetImageDataNode(size_t index) const;
+ void UnloadSubtasks(const mitk::DataNode* skip = nullptr);
+ void LoadSubtask(mitk::DataNode::Pointer imageNode = nullptr);
+ void SetActiveSubtaskIndex(const std::optional& index);
+ void SetCurrentSubtaskIndex(size_t index);
+
+ Ui::QmitkSegmentationTaskWidget* m_Ui;
+ QFileSystemWatcher* m_FileSystemWatcher;
+ mitk::SegmentationTask::Pointer m_Task;
+ size_t m_CurrentSubtaskIndex;
+ std::optional m_ActiveSubtaskIndex;
+};
+
+#endif
diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.ui b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.ui
new file mode 100644
index 0000000000..49af7e838f
--- /dev/null
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.ui
@@ -0,0 +1,150 @@
+
+
+ QmitkSegmentationTaskWidget
+
+
+
+ 0
+ 0
+ 299
+ 329
+
+
+
+ Segmentation Task
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Segmentation Task
+
+
+
-
+
+
+
+ 0
+ 40
+
+
+
+
+ -
+
+
+
+ 0
+ 40
+
+
+
+ 0
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 40
+ 40
+
+
+
+ Previous subtask
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+
+ 0
+ 40
+
+
+
+ Load subtask
+
+
+
+ -
+
+
+
+ 40
+ 40
+
+
+
+ Next subtask
+
+
+ Qt::RightArrow
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ Qt::RichText
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ QmitkSingleNodeSelectionWidget
+ QWidget
+ QmitkSingleNodeSelectionWidget.h
+ 1
+
+
+
+
+
diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/org_mitk_gui_qt_flow_segmentation_Activator.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/org_mitk_gui_qt_flow_segmentation_Activator.cpp
index 692c89b6b7..1663f96836 100644
--- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/org_mitk_gui_qt_flow_segmentation_Activator.cpp
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/org_mitk_gui_qt_flow_segmentation_Activator.cpp
@@ -1,38 +1,55 @@
/*============================================================================
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 "org_mitk_gui_qt_flow_segmentation_Activator.h"
#include "QmitkSegmentationFlowControlView.h"
#include "perspectives/QmitkFlowSegmentationPerspective.h"
+#include
+#include
+
+#include
+#include
+
ctkPluginContext* org_mitk_gui_qt_flow_segmentation_Activator::m_Context = nullptr;
void org_mitk_gui_qt_flow_segmentation_Activator::start(ctkPluginContext* context)
{
BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationFlowControlView, context)
BERRY_REGISTER_EXTENSION_CLASS(QmitkFlowSegmentationPerspective, context);
+ auto* descriptorManager = QmitkNodeDescriptorManager::GetInstance();
+
+ if (descriptorManager != nullptr)
+ {
+ auto icon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/SegmentationTask/Icon.svg"));
+ auto predicate = mitk::TNodePredicateDataType::New();
+ auto* descriptor = new QmitkNodeDescriptor("SegmentationTask", icon, predicate, descriptorManager);
+
+ descriptorManager->AddDescriptor(descriptor);
+ }
+
m_Context = context;
}
void org_mitk_gui_qt_flow_segmentation_Activator::stop(ctkPluginContext* context)
{
Q_UNUSED(context)
m_Context = nullptr;
}
ctkPluginContext* org_mitk_gui_qt_flow_segmentation_Activator::GetContext()
{
return m_Context;
}