diff --git a/Modules/Multilabel/mitkSegmentationTask.cpp b/Modules/Multilabel/mitkSegmentationTask.cpp
index f9c58ebf32..9f5789aea0 100644
--- a/Modules/Multilabel/mitkSegmentationTask.cpp
+++ b/Modules/Multilabel/mitkSegmentationTask.cpp
@@ -1,163 +1,174 @@
/*============================================================================
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
#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;
}
+void mitk::SegmentationTask::SaveSubtask(size_t index, const BaseData* segmentation)
+{
+ if (segmentation == nullptr)
+ return;
+
+ auto path = this->GetAbsolutePath(this->GetResult(index));
+ IOUtil::Save(segmentation, path.string());
+}
+
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 b726aec8d8..126eee3b82 100644
--- a/Modules/Multilabel/mitkSegmentationTask.h
+++ b/Modules/Multilabel/mitkSegmentationTask.h
@@ -1,102 +1,104 @@
/*============================================================================
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);
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)
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;
+ void SaveSubtask(size_t index, const BaseData* segmentation);
+
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/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp
index d1afb073ff..eb830a1cde 100644
--- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp
@@ -1,187 +1,183 @@
/*============================================================================
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"
// Blueberry
#include
#include
#include
//MITK
#include
#include
#include
#include
#include
#include
#include
// Qmitk
#include "QmitkSegmentationFlowControlView.h"
// Qt
#include
#include
#include
const std::string QmitkSegmentationFlowControlView::VIEW_ID = "org.mitk.views.flow.control";
QmitkSegmentationFlowControlView::QmitkSegmentationFlowControlView()
: m_Parent(nullptr)
{
auto notHelperObject = mitk::NodePredicateNot::New(
mitk::NodePredicateProperty::New("helper object"));
m_SegmentationPredicate = mitk::NodePredicateAnd::New(
mitk::TNodePredicateDataType::New(),
notHelperObject);
m_SegmentationTaskPredicate = mitk::NodePredicateAnd::New(
mitk::TNodePredicateDataType::New(),
notHelperObject);
}
void QmitkSegmentationFlowControlView::SetFocus()
{
m_Controls.btnStoreAndAccept->setFocus();
}
void QmitkSegmentationFlowControlView::CreateQtPartControl(QWidget* parent)
{
// create GUI widgets from the Qt Designer's .ui file
m_Controls.setupUi(parent);
m_Parent = parent;
using Self = QmitkSegmentationFlowControlView;
connect(m_Controls.btnStoreAndAccept, &QPushButton::clicked, this, &Self::OnAcceptButtonPushed);
connect(m_Controls.segmentationTaskWidget, &QmitkSegmentationTaskWidget::ActiveSubtaskChanged, this, &Self::OnActiveSubtaskChanged);
connect(m_Controls.segmentationTaskWidget, &QmitkSegmentationTaskWidget::CurrentSubtaskChanged, this, &Self::OnCurrentSubtaskChanged);
m_Controls.segmentationTaskWidget->setVisible(false);
m_Controls.labelStored->setVisible(false);
UpdateControls();
m_OutputDir = QString::fromStdString(mitk::BaseApplication::instance().config().getString("flow.outputdir", itksys::SystemTools::GetCurrentWorkingDirectory()));
m_OutputDir = QDir::fromNativeSeparators(m_OutputDir);
m_FileExtension = QString::fromStdString(mitk::BaseApplication::instance().config().getString("flow.outputextension", "nrrd"));
}
void QmitkSegmentationFlowControlView::OnAcceptButtonPushed()
{
if (m_Controls.segmentationTaskWidget->isVisible())
{
auto* task = m_Controls.segmentationTaskWidget->GetTask();
if (task != nullptr)
{
auto activeSubtaskIndex = m_Controls.segmentationTaskWidget->GetActiveSubtaskIndex();
if (activeSubtaskIndex.has_value())
{
auto segmentationNode = m_Controls.segmentationTaskWidget->GetSegmentationDataNode(activeSubtaskIndex.value());
if (segmentationNode != nullptr)
{
- auto path = task->GetAbsolutePath(task->GetResult(activeSubtaskIndex.value()));
+ QApplication::setOverrideCursor(Qt::BusyCursor);
- if (!path.empty())
+ try
{
- QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
-
- try
- {
- mitk::IOUtil::Save(segmentationNode->GetData(), path.string());
- // TODO: Give temporarily displayed feedback to user
- }
- catch (const mitk::Exception&)
- {
- }
-
- QApplication::restoreOverrideCursor();
+ task->SaveSubtask(activeSubtaskIndex.value(), segmentationNode->GetData());
+ m_Controls.segmentationTaskWidget->OnUnsavedChangesSaved();
}
+ catch (const mitk::Exception& e)
+ {
+ MITK_ERROR << e;
+ }
+
+ QApplication::restoreOverrideCursor();
}
}
}
}
else
{
auto nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate);
for (auto node : *nodes)
{
QString outputpath = m_OutputDir + "/" + QString::fromStdString(node->GetName()) + "." + m_FileExtension;
outputpath = QDir::toNativeSeparators(QDir::cleanPath(outputpath));
mitk::IOUtil::Save(node->GetData(), outputpath.toStdString());
}
m_Controls.labelStored->setVisible(true);
}
}
void QmitkSegmentationFlowControlView::OnActiveSubtaskChanged(const std::optional&)
{
this->UpdateControls();
}
void QmitkSegmentationFlowControlView::OnCurrentSubtaskChanged(size_t)
{
this->UpdateControls();
}
void QmitkSegmentationFlowControlView::UpdateControls()
{
auto dataStorage = this->GetDataStorage();
auto hasTask = !dataStorage->GetSubset(m_SegmentationTaskPredicate)->empty();
m_Controls.segmentationTaskWidget->setVisible(hasTask);
if (hasTask)
{
auto activeSubtaskIndex = m_Controls.segmentationTaskWidget->GetActiveSubtaskIndex();
auto hasActiveSubtask = activeSubtaskIndex.has_value();
auto isCurrentSubtask = hasActiveSubtask
? m_Controls.segmentationTaskWidget->GetCurrentSubtaskIndex() == activeSubtaskIndex.value()
: false;
m_Controls.btnStoreAndAccept->setEnabled(hasActiveSubtask && isCurrentSubtask);
}
else
{
auto hasSegmentation = !dataStorage->GetSubset(m_SegmentationPredicate)->empty();
m_Controls.btnStoreAndAccept->setEnabled(hasSegmentation);
}
}
void QmitkSegmentationFlowControlView::NodeAdded(const mitk::DataNode* node)
{
if (dynamic_cast(node->GetData()) != nullptr)
this->UpdateControls();
}
void QmitkSegmentationFlowControlView::NodeChanged(const mitk::DataNode* node)
{
if (dynamic_cast(node->GetData()) != nullptr)
this->UpdateControls();
}
void QmitkSegmentationFlowControlView::NodeRemoved(const mitk::DataNode* node)
{
if (dynamic_cast(node->GetData()) != nullptr)
this->UpdateControls();
}
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 e167262b0e..8802b51838 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,586 +1,689 @@
/*============================================================================
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
#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_CurrentSubtaskIndex(0),
+ m_UnsavedChanges(false)
{
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;
}
+void QmitkSegmentationTaskWidget::OnUnsavedChangesSaved()
+{
+ if (m_UnsavedChanges)
+ {
+ m_UnsavedChanges = false;
+
+ if (m_ActiveSubtaskIndex.value() == m_CurrentSubtaskIndex)
+ this->UpdateDetailsLabel();
+ }
+}
+
/* 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();
+ auto paths = m_FileSystemWatcher->directories();
- if (!paths.empty())
- m_FileSystemWatcher->removePaths(paths);
- }
+ 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&)
{
+ // 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 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()
{
const auto i = m_CurrentSubtaskIndex;
- bool active = m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex.value() == i;
- auto text = !active
+ auto text = !this->ActivateSubtaskIsShown()
? QStringLiteral("Load subtask")
: QStringLiteral("Subtask");
if (m_Task.IsNotNull())
{
text += QString(" %1/%2").arg(i + 1).arg(m_Task->GetNumberOfSubtasks());
if (m_Task->HasName(i))
text += QStringLiteral(":\n") + QString::fromStdString(m_Task->GetName(i));
}
- m_Ui->loadButton->setDisabled(active);
+ m_Ui->loadButton->setDisabled(this->ActivateSubtaskIsShown());
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()
{
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
+ auto details = QString("
Status: %1 / ").arg(this->ActivateSubtaskIsShown()
? 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()));
+ if (m_UnsavedChanges && this->ActivateSubtaskIsShown())
+ {
+ details += QString("%1
").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker()));
+ }
+ else
+ {
+ details += QString("%1").arg(isDone
+ ? ColorString("Done", Qt::white, QColor(Qt::green).darker())
+ : ColorString("Not done", Qt::white, QColor(Qt::red).darker()));
+ }
if (m_Task->HasDescription(i))
details += QString("Description: %1
").arg(QString::fromStdString(m_Task->GetDescription(i)));
QStringList stringList;
if (m_Task->HasImage(i))
stringList << QString("Image: %1").arg(QString::fromStdString(m_Task->GetImage(i)));
if (m_Task->HasSegmentation(i))
stringList << QString("Segmentation: %1").arg(QString::fromStdString(m_Task->GetSegmentation(i)));
if (m_Task->HasLabelName(i))
stringList << QString("Label name: %1").arg(QString::fromStdString(m_Task->GetLabelName(i)));
if (m_Task->HasPreset(i))
stringList << QString("Label set preset: %1").arg(QString::fromStdString(m_Task->GetPreset(i)));
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()
{
+ if (m_UnsavedChanges)
+ {
+ const auto i = m_ActiveSubtaskIndex.value();
+
+ const auto title = QString("Load subtask %1").arg(m_CurrentSubtaskIndex + 1);
+ const auto text = QString("The currently active subtask %1 has unsaved changes.").arg(i + 1);
+ const auto reply = QMessageBox::question(this, title, text, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
+
+ if (reply == QMessageBox::Cancel)
+ return;
+
+ if (reply == QMessageBox::Save)
+ {
+ QApplication::setOverrideCursor(Qt::BusyCursor);
+
+ try
+ {
+ m_Task->SaveSubtask(i, this->GetSegmentationDataNode(i)->GetData());
+ this->OnUnsavedChangesSaved();
+ }
+ catch (const mitk::Exception& e)
+ {
+ MITK_ERROR << e;
+ }
+
+ QApplication::restoreOverrideCursor();
+ }
+ }
+
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();
+
+ m_UnsavedChanges = false;
}
-/* If present, return the image data node for the currently displayed subtask.
- * Otherwise, return nullptr.
+/* If present, return the image data node for the subtask with the specified
+ * index. 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.
+/* If present, return the segmentation data node for the subtask with the
+ * specified index. 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)
{
+ this->UnsubscribeFromActiveSegmentation();
+
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)
{
const auto i = m_CurrentSubtaskIndex;
mitk::Image::Pointer image;
mitk::LabelSetImage::Pointer segmentation;
try
{
if (imageNode.IsNull())
{
const auto path = m_Task->GetAbsolutePath(m_Task->GetImage(i));
image = mitk::IOUtil::Load(path.string());
}
const auto path = m_Task->GetAbsolutePath(m_Task->GetResult(i));
if (std::filesystem::exists(path))
{
segmentation = mitk::IOUtil::Load(path.string());
}
else if (m_Task->HasSegmentation(i))
{
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 name = "Subtask " + std::to_string(i + 1);
imageNode->SetName(name);
if (segmentation.IsNull())
{
mitk::Image::ConstPointer templateImage = image;
if (templateImage->GetDimension() > 3)
{
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();
}
}
auto segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, name);
segmentation = static_cast(segmentationNode->GetData());
if (m_Task->HasPreset(i))
{
const auto path = m_Task->GetAbsolutePath(m_Task->GetPreset(i));
mitk::LabelSetIOHelper::LoadLabelSetImagePreset(path.string(), segmentation);
}
else
{
auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation);
if (m_Task->HasLabelName(i))
label->SetName(m_Task->GetLabelName(i));
segmentation->GetActiveLabelSet()->AddLabel(label);
}
dataStorage->Add(segmentationNode, imageNode);
}
else
{
auto segmentationNode = mitk::DataNode::New();
segmentationNode->SetName(name);
segmentationNode->SetData(segmentation);
dataStorage->Add(segmentationNode, imageNode);
}
+ m_UnsavedChanges = false;
+
this->SetActiveSubtaskIndex(i);
+ this->SubscribeToActiveSegmentation();
+}
+
+void QmitkSegmentationTaskWidget::SubscribeToActiveSegmentation()
+{
+ if (m_ActiveSubtaskIndex.has_value())
+ {
+ auto segmentationNode = this->GetSegmentationDataNode(m_ActiveSubtaskIndex.value());
+
+ if (segmentationNode != nullptr)
+ {
+ auto segmentation = static_cast(segmentationNode->GetData());
+
+ auto command = itk::SimpleMemberCommand::New();
+ command->SetCallbackFunction(this, &QmitkSegmentationTaskWidget::OnSegmentationModified);
+
+ m_SegmentationModifiedObserverTag = segmentation->AddObserver(itk::ModifiedEvent(), command);
+ }
+ }
+}
+
+void QmitkSegmentationTaskWidget::UnsubscribeFromActiveSegmentation()
+{
+ if (m_ActiveSubtaskIndex.has_value() && m_SegmentationModifiedObserverTag.has_value())
+ {
+ auto segmentationNode = this->GetSegmentationDataNode(m_ActiveSubtaskIndex.value());
+
+ if (segmentationNode != nullptr)
+ {
+ auto segmentation = static_cast(segmentationNode->GetData());
+ segmentation->RemoveObserver(m_SegmentationModifiedObserverTag.value());
+ }
+
+ m_SegmentationModifiedObserverTag.reset();
+ }
+}
+
+void QmitkSegmentationTaskWidget::OnSegmentationModified()
+{
+ if (!m_UnsavedChanges)
+ {
+ m_UnsavedChanges = true;
+
+ if (m_ActiveSubtaskIndex.value() == m_CurrentSubtaskIndex)
+ this->UpdateDetailsLabel();
+ }
}
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);
}
}
+
+bool QmitkSegmentationTaskWidget::ActivateSubtaskIsShown() const
+{
+ return m_ActiveSubtaskIndex.has_value() && m_ActiveSubtaskIndex == 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
index eb33b57b63..794f8b7cd8 100644
--- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.h
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.h
@@ -1,78 +1,85 @@
/*============================================================================
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;
+ void OnUnsavedChangesSaved();
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 SubscribeToActiveSegmentation();
+ void UnsubscribeFromActiveSegmentation();
+ void OnSegmentationModified();
void SetActiveSubtaskIndex(const std::optional& index);
void SetCurrentSubtaskIndex(size_t index);
+ bool ActivateSubtaskIsShown() const;
Ui::QmitkSegmentationTaskWidget* m_Ui;
QFileSystemWatcher* m_FileSystemWatcher;
mitk::SegmentationTask::Pointer m_Task;
mitk::DataNode::Pointer m_TaskNode;
size_t m_CurrentSubtaskIndex;
std::optional m_ActiveSubtaskIndex;
+ std::optional m_SegmentationModifiedObserverTag;
+ bool m_UnsavedChanges;
};
#endif