diff --git a/Modules/Multilabel/mitkSegmentationTaskList.cpp b/Modules/Multilabel/mitkSegmentationTaskList.cpp index 71e17397f8..fb6cae54ae 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.cpp +++ b/Modules/Multilabel/mitkSegmentationTaskList.cpp @@ -1,174 +1,190 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegmentationTaskList.h" #include <mitkIOUtil.h> #include <mitkProperties.h> mitk::SegmentationTaskList::Task::Task() : m_Defaults(nullptr) { } mitk::SegmentationTaskList::Task::~Task() { } void mitk::SegmentationTaskList::Task::SetDefaults(const Task* defaults) { m_Defaults = defaults; } mitk::SegmentationTaskList::SegmentationTaskList() { // A base data cannot be serialized if empty. To be not considered empty its // geometry must consist of at least one time step. However, a segmentation // task would then appear as invisible 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::SegmentationTaskList::SegmentationTaskList(const Self& other) : BaseData(other) { } mitk::SegmentationTaskList::~SegmentationTaskList() { } size_t mitk::SegmentationTaskList::GetNumberOfTasks() const { return m_Tasks.size(); } size_t mitk::SegmentationTaskList::AddTask(const Task& subtask) { m_Tasks.push_back(subtask); m_Tasks.back().SetDefaults(&m_Defaults); return m_Tasks.size() - 1; } const mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) const { return &m_Tasks.at(index); } mitk::SegmentationTaskList::Task* mitk::SegmentationTaskList::GetTask(size_t index) { return &m_Tasks.at(index); } const mitk::SegmentationTaskList::Task& mitk::SegmentationTaskList::GetDefaults() const { return m_Defaults; } void mitk::SegmentationTaskList::SetDefaults(const Task& defaults) { m_Defaults = defaults; for (auto& subtask : m_Tasks) subtask.SetDefaults(&m_Defaults); } bool mitk::SegmentationTaskList::IsDone() const { for (size_t i = 0; i < m_Tasks.size(); ++i) { if (!this->IsDone(i)) return false; } return true; } bool mitk::SegmentationTaskList::IsDone(size_t index) const { return fs::exists(this->GetAbsolutePath(m_Tasks.at(index).GetResult())); } fs::path mitk::SegmentationTaskList::GetInputLocation() const { std::string inputLocation; this->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", inputLocation); return !inputLocation.empty() ? fs::path(inputLocation)/*.lexically_normal()*/ // See T29246 : fs::path(); } fs::path mitk::SegmentationTaskList::GetBasePath() const { return this->GetInputLocation().remove_filename(); } fs::path mitk::SegmentationTaskList::GetAbsolutePath(const fs::path& path) const { if (path.empty()) return path; auto normalizedPath = path/*.lexically_normal()*/; // See T29246 return !normalizedPath.is_absolute() ? this->GetBasePath() / normalizedPath : normalizedPath; } -void mitk::SegmentationTaskList::SaveTask(size_t index, const BaseData* segmentation) +fs::path mitk::SegmentationTaskList::GetIntermediatePath(const fs::path& path) const +{ + auto intermediatePath = path; + return intermediatePath.replace_extension(".intermediate" + path.extension().string()); +} + +void mitk::SegmentationTaskList::SaveTask(size_t index, const BaseData* segmentation, bool saveAsIntermediateResult) { if (segmentation == nullptr) return; auto path = this->GetAbsolutePath(this->GetResult(index)); - IOUtil::Save(segmentation, path.string()); + auto intermediatePath = this->GetIntermediatePath(path); + + if (fs::exists(path)) + saveAsIntermediateResult = false; + + IOUtil::Save(segmentation, saveAsIntermediateResult + ? intermediatePath.string() + : path.string()); + + if (!saveAsIntermediateResult && fs::exists(intermediatePath)) + fs::remove(intermediatePath, std::error_code()); } std::vector<mitk::SegmentationTaskList::Task>::const_iterator mitk::SegmentationTaskList::begin() const { return m_Tasks.begin(); } std::vector<mitk::SegmentationTaskList::Task>::const_iterator mitk::SegmentationTaskList::end() const { return m_Tasks.end(); } std::vector<mitk::SegmentationTaskList::Task>::iterator mitk::SegmentationTaskList::begin() { return m_Tasks.begin(); } std::vector<mitk::SegmentationTaskList::Task>::iterator mitk::SegmentationTaskList::end() { return m_Tasks.end(); } void mitk::SegmentationTaskList::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::SegmentationTaskList::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::SegmentationTaskList::VerifyRequestedRegion() { return true; } void mitk::SegmentationTaskList::SetRequestedRegion(const itk::DataObject*) { } diff --git a/Modules/Multilabel/mitkSegmentationTaskList.h b/Modules/Multilabel/mitkSegmentationTaskList.h index bc72f6e72c..001c6e64df 100644 --- a/Modules/Multilabel/mitkSegmentationTaskList.h +++ b/Modules/Multilabel/mitkSegmentationTaskList.h @@ -1,110 +1,111 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegmentationTaskList_h #define mitkSegmentationTaskList_h #include <mitkBaseData.h> #include <mitkSegmentationTaskListMacros.h> #include <MitkMultilabelExports.h> #include <mitkFileSystem.h> #include <optional> namespace mitk { /** \brief A list of segmentation tasks. * * See \ref MITKSegmentationTaskListsPage for more information. */ class MITKMULTILABEL_EXPORT SegmentationTaskList : public BaseData { public: class MITKMULTILABEL_EXPORT Task { public: Task(); ~Task(); void SetDefaults(const Task* defaults); mitkSegmentationTaskValueMacro(std::string, Name) mitkSegmentationTaskValueMacro(std::string, Description) mitkSegmentationTaskValueMacro(fs::path, Image) mitkSegmentationTaskValueMacro(fs::path, Segmentation) mitkSegmentationTaskValueMacro(std::string, LabelName) mitkSegmentationTaskValueMacro(fs::path, LabelNameSuggestions) mitkSegmentationTaskValueMacro(fs::path, Preset) mitkSegmentationTaskValueMacro(fs::path, Result) mitkSegmentationTaskValueMacro(bool, Dynamic) private: const Task* m_Defaults; }; mitkClassMacro(SegmentationTaskList, BaseData) itkFactorylessNewMacro(Self) itkCloneMacro(Self) mitkSegmentationTaskListValueMacro(std::string, Name) mitkSegmentationTaskListValueMacro(std::string, Description) mitkSegmentationTaskListValueMacro(fs::path, Image) mitkSegmentationTaskListValueMacro(fs::path, Segmentation) mitkSegmentationTaskListValueMacro(std::string, LabelName) mitkSegmentationTaskListValueMacro(fs::path, LabelNameSuggestions) mitkSegmentationTaskListValueMacro(fs::path, Preset) mitkSegmentationTaskListValueMacro(fs::path, Result) mitkSegmentationTaskListValueMacro(bool, Dynamic) size_t GetNumberOfTasks() const; size_t AddTask(const Task& subtask); const Task* GetTask(size_t index) const; Task* GetTask(size_t index); const Task& GetDefaults() const; void SetDefaults(const Task& defaults); bool IsDone() const; bool IsDone(size_t index) const; fs::path GetInputLocation() const; fs::path GetBasePath() const; fs::path GetAbsolutePath(const fs::path& path) const; + fs::path GetIntermediatePath(const fs::path& path) const; - void SaveTask(size_t index, const BaseData* segmentation); + void SaveTask(size_t index, const BaseData* segmentation, bool saveAsIntermediateResult = false); std::vector<Task>::const_iterator begin() const; std::vector<Task>::const_iterator end() const; std::vector<Task>::iterator begin(); std::vector<Task>::iterator end(); void SetRequestedRegionToLargestPossibleRegion() override; bool RequestedRegionIsOutsideOfTheBufferedRegion() override; bool VerifyRequestedRegion() override; void SetRequestedRegion(const itk::DataObject*) override; protected: mitkCloneMacro(Self) SegmentationTaskList(); SegmentationTaskList(const Self& other); ~SegmentationTaskList() override; private: Task m_Defaults; std::vector<Task> m_Tasks; }; } #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 5f2f575629..3a238fb710 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,180 +1,199 @@ /*============================================================================ 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 <berryISelectionService.h> #include <berryIWorkbenchWindow.h> +#include <berryQtStyleManager.h> #include <itksys/SystemTools.hxx> //MITK #include <mitkBaseApplication.h> #include <mitkLabelSetImage.h> #include <mitkNodePredicateAnd.h> #include <mitkNodePredicateNot.h> #include <mitkNodePredicateProperty.h> #include <mitkNodePredicateDataType.h> #include <mitkIOUtil.h> // Qmitk #include "QmitkSegmentationFlowControlView.h" #include <ui_QmitkSegmentationFlowControlView.h> // Qt #include <QMessageBox> #include <QDir> #include <mitkFileSystem.h> const std::string QmitkSegmentationFlowControlView::VIEW_ID = "org.mitk.views.flow.control"; QmitkSegmentationFlowControlView::QmitkSegmentationFlowControlView() : m_Controls(new Ui::SegmentationFlowControlView), m_Parent(nullptr) { auto notHelperObject = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("helper object")); m_SegmentationPredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType<mitk::LabelSetImage>::New(), notHelperObject); m_SegmentationTaskListPredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType<mitk::SegmentationTaskList>::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->btnStore, &QPushButton::clicked, this, &Self::OnStoreButtonClicked); + connect(m_Controls->btnStoreAndAccept, &QPushButton::clicked, this, &Self::OnAcceptButtonClicked); connect(m_Controls->segmentationTaskListWidget, &QmitkSegmentationTaskListWidget::ActiveTaskChanged, this, &Self::OnActiveTaskChanged); connect(m_Controls->segmentationTaskListWidget, &QmitkSegmentationTaskListWidget::CurrentTaskChanged, this, &Self::OnCurrentTaskChanged); + m_Controls->btnStore->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); + m_Controls->segmentationTaskListWidget->setVisible(false); + m_Controls->btnStore->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() +void QmitkSegmentationFlowControlView::OnStoreButtonClicked() +{ + this->SaveActiveTask(true); +} + +void QmitkSegmentationFlowControlView::OnAcceptButtonClicked() { if (m_Controls->segmentationTaskListWidget->isVisible()) { - auto* taskList = m_Controls->segmentationTaskListWidget->GetTaskList(); - - if (taskList != nullptr) - { - auto activeTaskIndex = m_Controls->segmentationTaskListWidget->GetActiveTaskIndex(); - - if (activeTaskIndex.has_value()) - { - auto segmentationNode = m_Controls->segmentationTaskListWidget->GetSegmentationDataNode(activeTaskIndex.value()); - - if (segmentationNode != nullptr) - { - QApplication::setOverrideCursor(Qt::BusyCursor); - - try - { - taskList->SaveTask(activeTaskIndex.value(), segmentationNode->GetData()); - m_Controls->segmentationTaskListWidget->OnUnsavedChangesSaved(); - } - catch (const mitk::Exception& e) - { - MITK_ERROR << e; - } - - QApplication::restoreOverrideCursor(); - } - } - } + this->SaveActiveTask(); } 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::SaveActiveTask(bool saveAsIntermediateResult) +{ + auto* taskList = m_Controls->segmentationTaskListWidget->GetTaskList(); + + if (taskList != nullptr) + { + auto activeTaskIndex = m_Controls->segmentationTaskListWidget->GetActiveTaskIndex(); + + if (activeTaskIndex.has_value()) + { + auto segmentationNode = m_Controls->segmentationTaskListWidget->GetSegmentationDataNode(activeTaskIndex.value()); + + if (segmentationNode != nullptr) + { + QApplication::setOverrideCursor(Qt::BusyCursor); + + try + { + taskList->SaveTask(activeTaskIndex.value(), segmentationNode->GetData(), saveAsIntermediateResult); + m_Controls->segmentationTaskListWidget->OnUnsavedChangesSaved(); + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e; + } + + QApplication::restoreOverrideCursor(); + } + } + } +} + void QmitkSegmentationFlowControlView::OnActiveTaskChanged(const std::optional<size_t>&) { this->UpdateControls(); } void QmitkSegmentationFlowControlView::OnCurrentTaskChanged(const std::optional<size_t>&) { this->UpdateControls(); } void QmitkSegmentationFlowControlView::UpdateControls() { auto dataStorage = this->GetDataStorage(); - auto hasTaskList = !dataStorage->GetSubset(m_SegmentationTaskListPredicate)->empty(); + m_Controls->segmentationTaskListWidget->setVisible(hasTaskList); if (hasTaskList) { auto activeTaskIsShown = m_Controls->segmentationTaskListWidget->ActiveTaskIsShown(); + + m_Controls->btnStore->setVisible(activeTaskIsShown); m_Controls->btnStoreAndAccept->setEnabled(activeTaskIsShown); } else { auto hasSegmentation = !dataStorage->GetSubset(m_SegmentationPredicate)->empty(); + + m_Controls->btnStore->setVisible(false); m_Controls->btnStoreAndAccept->setEnabled(hasSegmentation); } } void QmitkSegmentationFlowControlView::NodeAdded(const mitk::DataNode* node) { if (dynamic_cast<const mitk::LabelSetImage*>(node->GetData()) != nullptr) this->UpdateControls(); } void QmitkSegmentationFlowControlView::NodeChanged(const mitk::DataNode* node) { if (dynamic_cast<const mitk::LabelSetImage*>(node->GetData()) != nullptr) this->UpdateControls(); } void QmitkSegmentationFlowControlView::NodeRemoved(const mitk::DataNode* node) { if (dynamic_cast<const mitk::LabelSetImage*>(node->GetData()) != nullptr) this->UpdateControls(); } diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.h b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.h index 49335b6def..9a8fdb94d0 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.h +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.h @@ -1,82 +1,84 @@ /*============================================================================ 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 __Q_MITK_MATCHPOINT_MAPPER_H #define __Q_MITK_MATCHPOINT_MAPPER_H #include <berryISelectionListener.h> #include <QmitkAbstractView.h> #include "mitkNodePredicateBase.h" #include <optional> namespace Ui { class SegmentationFlowControlView; } /*! \brief QmitkSegmentationFlowControlView Class that "controls" the segmentation view. It offers the possibility to accept a segmentation. Accepting the segmentation stores the segmentation to the given working directory. The working directory is specified by command line arguments. If no commandline flag is set the current working directory will be used. */ class QmitkSegmentationFlowControlView : public QmitkAbstractView { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; /** * Creates smartpointer typedefs */ berryObjectMacro(QmitkSegmentationFlowControlView) QmitkSegmentationFlowControlView(); void CreateQtPartControl(QWidget *parent) override; protected slots: - void OnAcceptButtonPushed(); + void OnStoreButtonClicked(); + void OnAcceptButtonClicked(); void OnActiveTaskChanged(const std::optional<size_t>& index); void OnCurrentTaskChanged(const std::optional<size_t>& index); protected: void SetFocus() override; void NodeAdded(const mitk::DataNode* node) override; void NodeChanged(const mitk::DataNode* node) override; void NodeRemoved(const mitk::DataNode* node) override; void UpdateControls(); + void SaveActiveTask(bool saveAsIntermediateResult = false); Ui::SegmentationFlowControlView* m_Controls; private: QWidget *m_Parent; mitk::NodePredicateBase::Pointer m_SegmentationPredicate; mitk::NodePredicateBase::Pointer m_SegmentationTaskListPredicate; QString m_OutputDir; QString m_FileExtension; }; #endif // MatchPoint_h diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.ui b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.ui index e404561a09..c4f289c8ab 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.ui +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.ui @@ -1,110 +1,133 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>SegmentationFlowControlView</class> <widget class="QWidget" name="SegmentationFlowControlView"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>392</width> + <width>268</width> <height>324</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> - <property name="spacing"> - <number>20</number> - </property> - <property name="leftMargin"> - <number>5</number> - </property> - <property name="topMargin"> - <number>5</number> - </property> - <property name="rightMargin"> - <number>5</number> - </property> - <property name="bottomMargin"> - <number>5</number> - </property> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>40</height> </size> </property> </spacer> </item> <item> <widget class="QmitkSegmentationTaskListWidget" name="segmentationTaskListWidget" native="true"/> </item> <item> - <widget class="QPushButton" name="btnStoreAndAccept"> - <property name="minimumSize"> - <size> - <width>0</width> - <height>50</height> - </size> - </property> - <property name="text"> - <string>Accept segmentation</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="btnStore"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>50</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>50</width> + <height>50</height> + </size> + </property> + <property name="toolTip"> + <string>Save intermediate result</string> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnStoreAndAccept"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>50</height> + </size> + </property> + <property name="toolTip"> + <string>Save and accept segmentation</string> + </property> + <property name="text"> + <string>Accept segmentation</string> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QLabel" name="labelStored"> <property name="text"> - <string><html><head/><body><p><span style=" font-size:12pt;">Segmentation stored! Flow on...</span></p></body></html></string> + <string><html><head/><body><p><span style=" font-size:12pt;">Segmentation accepted! Flow on...</span></p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> </widget> </item> <item> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>40</height> </size> </property> </spacer> </item> </layout> </widget> <customwidgets> <customwidget> <class>QmitkSegmentationTaskListWidget</class> <extends>QWidget</extends> <header location="global">internal/QmitkSegmentationTaskListWidget.h</header> <container>1</container> </customwidget> </customwidgets> <resources/> <connections/> <designerdata> <property name="gridDeltaX"> <number>5</number> </property> <property name="gridDeltaY"> <number>5</number> </property> <property name="gridSnapX"> <bool>true</bool> </property> <property name="gridSnapY"> <bool>true</bool> </property> <property name="gridVisible"> <bool>true</bool> </property> </designerdata> </ui> diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp index 38e2942b19..ae24fa2f65 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp @@ -1,769 +1,774 @@ /*============================================================================ 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 "QmitkSegmentationTaskListWidget.h" #include "org_mitk_gui_qt_flow_segmentation_Activator.h" #include <berryIPreferences.h> #include <berryIPreferencesService.h> #include <berryPlatform.h> #include <mitkIDataStorageService.h> #include <mitkIOUtil.h> #include <mitkLabelSetIOHelper.h> #include <mitkLabelSetImageHelper.h> #include <mitkNodePredicateDataType.h> #include <mitkNodePredicateFunction.h> #include <mitkRenderingManager.h> #include <mitkSegmentationHelper.h> #include <QmitkStaticDynamicSegmentationDialog.h> #include <QmitkStyleManager.h> #include <ui_QmitkSegmentationTaskListWidget.h> #include <QFileSystemWatcher> #include <QMessageBox> #include <mitkFileSystem.h> namespace { berry::IPreferences::Pointer GetSegmentationPreferences() { return berry::Platform::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); } mitk::DataStorage* GetDataStorage() { auto* pluginContext = org_mitk_gui_qt_flow_segmentation_Activator::GetContext(); auto dataStorageServiceReference = pluginContext->getServiceReference<mitk::IDataStorageService>(); if (dataStorageServiceReference) { auto* dataStorageService = pluginContext->getService<mitk::IDataStorageService>(dataStorageServiceReference); if (dataStorageService != nullptr) { auto dataStorageReference = dataStorageService->GetDataStorage(); pluginContext->ungetService(dataStorageServiceReference); return dataStorageReference->GetDataStorage(); } } return nullptr; } fs::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("<span style=\""); QStringList strings; if (color.isValid()) strings << QString("color: %1;").arg(color.name()); if (backgroundColor.isValid()) strings << QString("background-color: %1;").arg(backgroundColor.name()); result += strings.join(' ') + QString("\">%1</span>").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 */ QmitkSegmentationTaskListWidget::QmitkSegmentationTaskListWidget(QWidget* parent) : QWidget(parent), m_Ui(new Ui::QmitkSegmentationTaskListWidget), m_FileSystemWatcher(new QFileSystemWatcher(this)), 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 list")); m_Ui->selectionWidget->SetAutoSelectNewNodes(true); m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType<mitk::SegmentationTaskList>::New()); m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor())); using Self = QmitkSegmentationTaskListWidget; 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()); } QmitkSegmentationTaskListWidget::~QmitkSegmentationTaskListWidget() { } mitk::SegmentationTaskList* QmitkSegmentationTaskListWidget::GetTaskList() const { return m_TaskList; } std::optional<size_t> QmitkSegmentationTaskListWidget::GetActiveTaskIndex() const { return m_ActiveTaskIndex; } std::optional<size_t> QmitkSegmentationTaskListWidget::GetCurrentTaskIndex() const { return m_CurrentTaskIndex; } void QmitkSegmentationTaskListWidget::OnUnsavedChangesSaved() { if (m_UnsavedChanges) { m_UnsavedChanges = false; if (this->ActiveTaskIsShown()) this->UpdateDetailsLabel(); } } /* Make sure that the widget transitions into a valid state whenever the * selection changes. */ void QmitkSegmentationTaskListWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { this->UnloadTasks(); this->ResetControls(); if (!nodes.empty()) { m_TaskListNode = nodes.front(); auto taskList = dynamic_cast<mitk::SegmentationTaskList*>(m_TaskListNode->GetData()); if (taskList != nullptr) { this->OnTaskListChanged(taskList); return; } } this->SetTaskList(nullptr); m_TaskListNode = nullptr; } /* Reset all controls to a default state as a common basis for further * adjustments. */ void QmitkSegmentationTaskListWidget::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->nextButton->setEnabled(false); this->UpdateLoadButton(); this->UpdateDetailsLabel(); } /* If the segmentation task changed, reset all member variables to expected * default values and reset the file system watcher. */ void QmitkSegmentationTaskListWidget::SetTaskList(mitk::SegmentationTaskList* taskList) { if (m_TaskList != taskList) { m_TaskList = taskList; if (taskList != nullptr) { this->SetCurrentTaskIndex(0); } else { this->SetCurrentTaskIndex(std::nullopt); } this->ResetFileSystemWatcher(); } } void QmitkSegmentationTaskListWidget::ResetFileSystemWatcher() { auto paths = m_FileSystemWatcher->directories(); if (!paths.empty()) m_FileSystemWatcher->removePaths(paths); if (m_TaskList.IsNotNull()) { for (const auto& task : *m_TaskList) { auto resultPath = m_TaskList->GetAbsolutePath(task.GetResult()).remove_filename(); if (!fs::exists(resultPath)) { try { fs::create_directories(resultPath); } catch (const fs::filesystem_error& e) { MITK_ERROR << e.what(); } } if (fs::exists(resultPath)) m_FileSystemWatcher->addPath(QString::fromStdString(resultPath.string())); } } } void QmitkSegmentationTaskListWidget::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 QmitkSegmentationTaskListWidget::UpdateProgressBar() { int progress = 0; for (size_t i = 0; i < m_TaskList->GetNumberOfTasks(); ++i) { if (m_TaskList->IsDone(i)) ++progress; } m_Ui->progressBar->setValue(progress); } /* Provided that a valid segmentation task list is currently selected and the * widget is in its default state, update all controls accordingly. */ void QmitkSegmentationTaskListWidget::OnTaskListChanged(mitk::SegmentationTaskList* taskList) { this->SetTaskList(taskList); const auto numTasks = taskList->GetNumberOfTasks(); m_Ui->progressBar->setMaximum(numTasks); m_Ui->progressBar->setFormat(QStringLiteral("%v/%m Task(s) done")); m_Ui->progressBar->setEnabled(true); this->UpdateProgressBar(); m_Ui->loadButton->setEnabled(true); if (numTasks > 1) m_Ui->nextButton->setEnabled(true); } /* If possible, change the currently displayed task to the previous task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnPreviousButtonClicked() { const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1; auto current = m_CurrentTaskIndex.value(); if (current != 0) this->SetCurrentTaskIndex(current - 1); current = m_CurrentTaskIndex.value(); if (current == 0) m_Ui->previousButton->setEnabled(false); if (current < maxIndex) m_Ui->nextButton->setEnabled(true); } /* If possible, change the currently displayed task to the next task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnNextButtonClicked() { const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1; auto current = m_CurrentTaskIndex.value(); if (current < maxIndex) this->SetCurrentTaskIndex(current + 1); current = m_CurrentTaskIndex.value(); if (current != 0) m_Ui->previousButton->setEnabled(true); if (current >= maxIndex) m_Ui->nextButton->setEnabled(false); } /* Update affected controls when the currently displayed task changed. */ void QmitkSegmentationTaskListWidget::OnCurrentTaskChanged() { this->UpdateLoadButton(); this->UpdateDetailsLabel(); } /* Update the load button according to the currently displayed task. */ void QmitkSegmentationTaskListWidget::UpdateLoadButton() { auto text = !this->ActiveTaskIsShown() ? QStringLiteral("Load task") : QStringLiteral("Task"); if (m_CurrentTaskIndex.has_value()) { const auto current = m_CurrentTaskIndex.value(); if (m_TaskList.IsNotNull()) { text += QString(" %1/%2").arg(current + 1).arg(m_TaskList->GetNumberOfTasks()); if (m_TaskList->HasName(current)) text += QStringLiteral(":\n") + QString::fromStdString(m_TaskList->GetName(current)); } m_Ui->loadButton->setDisabled(this->ActiveTaskIsShown()); } else { m_Ui->loadButton->setEnabled(false); } m_Ui->loadButton->setText(text); } /* Update the details label according to the currently display task. * The text is composed of the status of the task and a variable number * of text blocks according to the optional values provided by the task. */ void QmitkSegmentationTaskListWidget::UpdateDetailsLabel() { if (!m_CurrentTaskIndex.has_value()) { m_Ui->detailsLabel->clear(); return; } const auto current = m_CurrentTaskIndex.value(); bool isDone = m_TaskList->IsDone(current); auto details = QString("<p><b>Status: %1</b> / <b>").arg(this->ActiveTaskIsShown() ? ColorString("Active", Qt::white, QColor(Qt::green).darker()) : ColorString("Inactive", Qt::white, QColor(Qt::red).darker())); if (m_UnsavedChanges && this->ActiveTaskIsShown()) { details += QString("%1</b></p>").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker())); } else { details += QString("%1</b></p>").arg(isDone ? ColorString("Done", Qt::white, QColor(Qt::green).darker()) : ColorString("Not done", Qt::white, QColor(Qt::red).darker())); } if (m_TaskList->HasDescription(current)) details += QString("<p><b>Description:</b> %1</p>").arg(QString::fromStdString(m_TaskList->GetDescription(current))); QStringList stringList; if (m_TaskList->HasImage(current)) stringList << QString::fromStdString("<b>Image:</b> " + m_TaskList->GetImage(current).string()); if (m_TaskList->HasSegmentation(current)) stringList << QString::fromStdString("<b>Segmentation:</b> " + m_TaskList->GetSegmentation(current).string()); if (m_TaskList->HasLabelName(current)) stringList << QString::fromStdString("<b>Label name:</b> " + m_TaskList->GetLabelName(current)); if (m_TaskList->HasLabelNameSuggestions(current)) stringList << QString::fromStdString("<b>Label name suggestions:</b> " + m_TaskList->GetLabelNameSuggestions(current).string()); if (m_TaskList->HasPreset(current)) stringList << QString::fromStdString("<b>Label set preset:</b> " + m_TaskList->GetPreset(current).string()); if (m_TaskList->HasDynamic(current)) stringList << QString("<b>Segmentation type:</b> %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("<p>%1</p>").arg(stringList.join(QStringLiteral("<br>"))); m_Ui->detailsLabel->setText(details); } /* Load/activate the currently displayed task. Unload all data nodes from * previously active tasks first, but spare and reuse the image if possible. */ void QmitkSegmentationTaskListWidget::OnLoadButtonClicked() { if (!this->HandleUnsavedChanges() || m_UnsavedChanges) return; m_Ui->loadButton->setEnabled(false); QApplication::setOverrideCursor(Qt::BusyCursor); this->LoadTask(this->GetImageDataNode(m_CurrentTaskIndex.value())); QApplication::restoreOverrideCursor(); } /* If present, return the image data node for the task with the specified * index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetImageDataNode(size_t index) const { const auto imagePath = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(index)); auto imageNodes = GetDataStorage()->GetDerivations(m_TaskListNode, 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 task with the * specified index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetSegmentationDataNode(size_t index) const { const auto* imageNode = this->GetImageDataNode(index); if (imageNode != nullptr) { auto segmentations = GetDataStorage()->GetDerivations(imageNode, mitk::TNodePredicateDataType<mitk::LabelSetImage>::New()); if (!segmentations->empty()) return segmentations->front(); } return nullptr; } /* Unload all task data nodes but spare the passed image data node. */ void QmitkSegmentationTaskListWidget::UnloadTasks(const mitk::DataNode* skip) { this->UnsubscribeFromActiveSegmentation(); if (m_TaskListNode.IsNotNull()) { mitk::DataStorage::Pointer dataStorage = GetDataStorage(); auto imageNodes = dataStorage->GetDerivations(m_TaskListNode, mitk::TNodePredicateDataType<mitk::Image>::New()); for (auto imageNode : *imageNodes) { dataStorage->Remove(dataStorage->GetDerivations(imageNode, nullptr, false)); if (imageNode != skip) dataStorage->Remove(imageNode); } } this->SetActiveTaskIndex(std::nullopt); } /* Load/activate the currently displayed task. The task 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 task, or loaded as specified by the task. If a result file does * exist, it is chosen as segmentation instead. */ void QmitkSegmentationTaskListWidget::LoadTask(mitk::DataNode::Pointer imageNode) { this->UnloadTasks(imageNode); const auto current = m_CurrentTaskIndex.value(); mitk::Image::Pointer image; mitk::LabelSetImage::Pointer segmentation; try { if (imageNode.IsNull()) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(current)); image = mitk::IOUtil::Load<mitk::Image>(path.string()); } const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetResult(current)); + const auto intermediatePath = m_TaskList->GetIntermediatePath(path); if (fs::exists(path)) { segmentation = mitk::IOUtil::Load<mitk::LabelSetImage>(path.string()); } + else if (fs::exists(intermediatePath)) + { + segmentation = mitk::IOUtil::Load<mitk::LabelSetImage>(intermediatePath.string()); + } else if (m_TaskList->HasSegmentation(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current)); segmentation = mitk::IOUtil::Load<mitk::LabelSetImage>(path.string()); } } catch (const mitk::Exception&) { return; } auto dataStorage = GetDataStorage(); if (imageNode.IsNull()) { imageNode = mitk::DataNode::New(); imageNode->SetData(image); dataStorage->Add(imageNode, m_TaskListNode); mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry()); } else { image = static_cast<mitk::Image*>(imageNode->GetData()); } auto name = "Task " + std::to_string(current + 1); imageNode->SetName(name); if (segmentation.IsNull()) { mitk::Image::ConstPointer templateImage = image; if (templateImage->GetDimension() > 3) { if (m_TaskList->HasDynamic(current)) { if (!m_TaskList->GetDynamic(current)) 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<mitk::LabelSetImage*>(segmentationNode->GetData()); if (m_TaskList->HasPreset(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetPreset(current)); mitk::LabelSetIOHelper::LoadLabelSetImagePreset(path.string(), segmentation); } else { auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation); if (m_TaskList->HasLabelName(current)) label->SetName(m_TaskList->GetLabelName(current)); segmentation->GetActiveLabelSet()->AddLabel(label); } dataStorage->Add(segmentationNode, imageNode); } else { auto segmentationNode = mitk::DataNode::New(); segmentationNode->SetName(name); segmentationNode->SetData(segmentation); dataStorage->Add(segmentationNode, imageNode); } auto prefs = GetSegmentationPreferences(); if (prefs.IsNotNull()) { if (m_TaskList->HasLabelNameSuggestions(current)) { auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetLabelNameSuggestions(current)); prefs->PutBool("default label naming", false); prefs->Put("label suggestions", QString::fromStdString(path.string())); prefs->PutBool("replace standard suggestions", true); prefs->PutBool("suggest once", true); } else { prefs->PutBool("default label naming", true); prefs->Put("label suggestions", ""); } } m_UnsavedChanges = false; this->SetActiveTaskIndex(current); this->SubscribeToActiveSegmentation(); this->OnCurrentTaskChanged(); } void QmitkSegmentationTaskListWidget::SubscribeToActiveSegmentation() { if (m_ActiveTaskIndex.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast<mitk::LabelSetImage*>(segmentationNode->GetData()); auto command = itk::SimpleMemberCommand<QmitkSegmentationTaskListWidget>::New(); command->SetCallbackFunction(this, &QmitkSegmentationTaskListWidget::OnSegmentationModified); m_SegmentationModifiedObserverTag = segmentation->AddObserver(itk::ModifiedEvent(), command); } } } void QmitkSegmentationTaskListWidget::UnsubscribeFromActiveSegmentation() { if (m_ActiveTaskIndex.has_value() && m_SegmentationModifiedObserverTag.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast<mitk::LabelSetImage*>(segmentationNode->GetData()); segmentation->RemoveObserver(m_SegmentationModifiedObserverTag.value()); } m_SegmentationModifiedObserverTag.reset(); } } void QmitkSegmentationTaskListWidget::OnSegmentationModified() { if (!m_UnsavedChanges) { m_UnsavedChanges = true; if (m_ActiveTaskIndex.value() == m_CurrentTaskIndex) this->UpdateDetailsLabel(); } } void QmitkSegmentationTaskListWidget::SetActiveTaskIndex(const std::optional<size_t>& index) { if (m_ActiveTaskIndex != index) { m_ActiveTaskIndex = index; emit ActiveTaskChanged(m_ActiveTaskIndex); } } void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional<size_t>& index) { if (m_CurrentTaskIndex != index) { m_CurrentTaskIndex = index; this->OnCurrentTaskChanged(); emit CurrentTaskChanged(m_CurrentTaskIndex); } } bool QmitkSegmentationTaskListWidget::ActiveTaskIsShown() const { return m_ActiveTaskIndex.has_value() && m_CurrentTaskIndex.has_value() && m_ActiveTaskIndex == m_CurrentTaskIndex; } bool QmitkSegmentationTaskListWidget::HandleUnsavedChanges() { if (m_UnsavedChanges) { const auto active = m_ActiveTaskIndex.value(); const auto current = m_CurrentTaskIndex.value(); auto title = QString("Load task %1").arg(current + 1); if (m_TaskList->HasName(current)) title += ": " + QString::fromStdString(m_TaskList->GetName(current)); auto text = QString("The currently active task %1 ").arg(active + 1); if (m_TaskList->HasName(active)) text += "(" + QString::fromStdString(m_TaskList->GetName(active)) + ") "; text += "has unsaved changes."; auto reply = QMessageBox::question(this, title, text, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel); switch (reply) { case QMessageBox::Save: - this->SaveActiveTask(); + this->SaveActiveTask(!fs::exists(m_TaskList->GetResult(active))); break; case QMessageBox::Discard: m_UnsavedChanges = false; break; default: return false; } } return true; } -void QmitkSegmentationTaskListWidget::SaveActiveTask() +void QmitkSegmentationTaskListWidget::SaveActiveTask(bool saveAsIntermediateResult) { if (!m_ActiveTaskIndex.has_value()) return; QApplication::setOverrideCursor(Qt::BusyCursor); try { const auto active = m_ActiveTaskIndex.value(); - m_TaskList->SaveTask(active, this->GetSegmentationDataNode(active)->GetData()); + m_TaskList->SaveTask(active, this->GetSegmentationDataNode(active)->GetData(), saveAsIntermediateResult); this->OnUnsavedChangesSaved(); } catch (const mitk::Exception& e) { MITK_ERROR << e; } QApplication::restoreOverrideCursor(); } diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h index f1cf781021..8c25d315e7 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h @@ -1,87 +1,87 @@ /*============================================================================ 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 QmitkSegmentationTaskListWidget_h #define QmitkSegmentationTaskListWidget_h #include <mitkSegmentationTaskList.h> #include <MitkSegmentationUIExports.h> #include <QmitkSingleNodeSelectionWidget.h> #include <QWidget> #include <optional> class QFileSystemWatcher; namespace Ui { class QmitkSegmentationTaskListWidget; } class QmitkSegmentationTaskListWidget : public QWidget { Q_OBJECT public: explicit QmitkSegmentationTaskListWidget(QWidget* parent = nullptr); ~QmitkSegmentationTaskListWidget() override; mitk::SegmentationTaskList* GetTaskList() const; std::optional<size_t> GetActiveTaskIndex() const; std::optional<size_t> GetCurrentTaskIndex() const; mitk::DataNode* GetSegmentationDataNode(size_t index) const; void OnUnsavedChangesSaved(); bool ActiveTaskIsShown() const; signals: void ActiveTaskChanged(const std::optional<size_t>& index); void CurrentTaskChanged(const std::optional<size_t>& index); private: void OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes); void ResetControls(); void SetTaskList(mitk::SegmentationTaskList* task); void ResetFileSystemWatcher(); void OnResultDirectoryChanged(const QString&); void UpdateProgressBar(); void OnTaskListChanged(mitk::SegmentationTaskList* task); void OnPreviousButtonClicked(); void OnNextButtonClicked(); void OnCurrentTaskChanged(); void UpdateLoadButton(); void UpdateDetailsLabel(); void OnLoadButtonClicked(); mitk::DataNode* GetImageDataNode(size_t index) const; void UnloadTasks(const mitk::DataNode* skip = nullptr); void LoadTask(mitk::DataNode::Pointer imageNode = nullptr); void SubscribeToActiveSegmentation(); void UnsubscribeFromActiveSegmentation(); void OnSegmentationModified(); void SetActiveTaskIndex(const std::optional<size_t>& index); void SetCurrentTaskIndex(const std::optional<size_t>& index); bool HandleUnsavedChanges(); - void SaveActiveTask(); + void SaveActiveTask(bool saveAsIntermediateResult = false); Ui::QmitkSegmentationTaskListWidget* m_Ui; QFileSystemWatcher* m_FileSystemWatcher; mitk::SegmentationTaskList::Pointer m_TaskList; mitk::DataNode::Pointer m_TaskListNode; std::optional<size_t> m_CurrentTaskIndex; std::optional<size_t> m_ActiveTaskIndex; std::optional<unsigned long> m_SegmentationModifiedObserverTag; bool m_UnsavedChanges; }; #endif