diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake index 2d5b0a93a5..32665859ed 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake @@ -1,46 +1,49 @@ set(SRC_CPP_FILES ) set(INTERNAL_CPP_FILES org_mitk_gui_qt_flow_segmentation_Activator.cpp QmitkSegmentationFlowControlView.cpp + QmitkSegmentationTaskWidget.cpp perspectives/QmitkFlowSegmentationPerspective.cpp ) set(UI_FILES src/internal/QmitkSegmentationFlowControlView.ui + src/internal/QmitkSegmentationTaskWidget.ui ) set(MOC_H_FILES src/internal/org_mitk_gui_qt_flow_segmentation_Activator.h src/internal/QmitkSegmentationFlowControlView.h + src/internal/QmitkSegmentationTaskWidget.h src/internal/perspectives/QmitkFlowSegmentationPerspective.h ) # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench set(CACHED_RESOURCE_FILES resources/icon.svg plugin.xml resources/perspectives/segmentation_icon.svg ) # list of Qt .qrc files which contain additional resources # specific to this plugin set(QRC_FILES - + resources/SegmentationTask.qrc ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTask.qrc b/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTask.qrc new file mode 100644 index 0000000000..271b85ea96 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTask.qrc @@ -0,0 +1,5 @@ + + + SegmentationTaskIcon.svg + + diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTaskIcon.svg b/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTaskIcon.svg new file mode 100644 index 0000000000..1beb5d6b9e --- /dev/null +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/resources/SegmentationTaskIcon.svg @@ -0,0 +1,170 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 bc674b04d4..d1afb073ff 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,104 +1,187 @@ /*============================================================================ 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 nodePredicate = mitk::NodePredicateAnd::New(); - nodePredicate->AddPredicate(mitk::TNodePredicateDataType::New()); - nodePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); - m_SegmentationPredicate = nodePredicate; + 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; - connect(m_Controls.btnStoreAndAccept, SIGNAL(clicked()), this, SLOT(OnAcceptButtonPushed())); + 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() { - auto nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); - - for (auto node : *nodes) + if (m_Controls.segmentationTaskWidget->isVisible()) { - QString outputpath = m_OutputDir + "/" + QString::fromStdString(node->GetName()) + "." + m_FileExtension; - outputpath = QDir::toNativeSeparators(QDir::cleanPath(outputpath)); - mitk::IOUtil::Save(node->GetData(), outputpath.toStdString()); + 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())); + + if (!path.empty()) + { + 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(); + } + } + } + } } + else + { + auto nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); - m_Controls.labelStored->setVisible(true); + 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 nodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); - m_Controls.btnStoreAndAccept->setEnabled(!nodes->empty()); -}; + auto dataStorage = this->GetDataStorage(); + + auto hasTask = !dataStorage->GetSubset(m_SegmentationTaskPredicate)->empty(); + m_Controls.segmentationTaskWidget->setVisible(hasTask); -void QmitkSegmentationFlowControlView::NodeAdded(const mitk::DataNode* /*node*/) + 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) { - UpdateControls(); -}; + if (dynamic_cast(node->GetData()) != nullptr) + this->UpdateControls(); +} -void QmitkSegmentationFlowControlView::NodeChanged(const mitk::DataNode* /*node*/) +void QmitkSegmentationFlowControlView::NodeChanged(const mitk::DataNode* node) { - UpdateControls(); -}; + if (dynamic_cast(node->GetData()) != nullptr) + this->UpdateControls(); +} -void QmitkSegmentationFlowControlView::NodeRemoved(const mitk::DataNode* /*node*/) +void QmitkSegmentationFlowControlView::NodeRemoved(const mitk::DataNode* node) { - UpdateControls(); -}; + if (dynamic_cast(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 1d8cc9f646..41a18a7a9c 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,74 +1,79 @@ /*============================================================================ 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 #include #include "mitkNodePredicateBase.h" #include "ui_QmitkSegmentationFlowControlView.h" +#include + /*! \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 OnActiveSubtaskChanged(const std::optional& index); + void OnCurrentSubtaskChanged(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(); Ui::SegmentationFlowControlView m_Controls; private: QWidget *m_Parent; mitk::NodePredicateBase::Pointer m_SegmentationPredicate; + mitk::NodePredicateBase::Pointer m_SegmentationTaskPredicate; 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 05d90c45ef..cc0c2c8f2d 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,99 +1,110 @@ SegmentationFlowControlView 0 0 392 324 20 5 5 5 5 Qt::Vertical 20 40 + + + 0 50 Accept segmentation <html><head/><body><p><span style=" font-size:12pt;">Segmentation stored! Flow on...</span></p></body></html> Qt::AlignCenter Qt::Vertical 20 40 + + + QmitkSegmentationTaskWidget + QWidget +
QmitkSegmentationTaskWidget.h
+ 1 +
+
5 5 true true true
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 new file mode 100644 index 0000000000..a01e6ac570 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskWidget.cpp @@ -0,0 +1,584 @@ +/*============================================================================ + +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 + +namespace +{ + mitk::DataStorage* GetDataStorage() + { + auto* pluginContext = org_mitk_gui_qt_flow_segmentation_Activator::GetContext(); + auto dataStorageServiceReference = pluginContext->getServiceReference(); + + if (dataStorageServiceReference) + { + auto* dataStorageService = pluginContext->getService(dataStorageServiceReference); + + if (dataStorageService != nullptr) + { + auto dataStorageReference = dataStorageService->GetDataStorage(); + pluginContext->ungetService(dataStorageServiceReference); + + return dataStorageReference->GetDataStorage(); + } + } + + return nullptr; + } + + std::filesystem::path GetInputLocation(const mitk::BaseData* data) + { + std::string result; + + if (data != nullptr) + data->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result); + + return result; + } + + QString ColorString(const QString& string, const QColor& color, const QColor& backgroundColor = QColor::Invalid) + { + if (!color.isValid() && !backgroundColor.isValid()) + return string; + + auto result = QStringLiteral("%1").arg(string); + + return result; + } +} + +/* This constructor has three objectives: + * 1. Do widget initialization that cannot be done in the .ui file + * 2. Connect signals and slots + * 3. Explicitly trigger a reset to a valid initial widget state + */ +QmitkSegmentationTaskWidget::QmitkSegmentationTaskWidget(QWidget* parent) + : QWidget(parent), + m_Ui(new Ui::QmitkSegmentationTaskWidget), + m_FileSystemWatcher(new QFileSystemWatcher(this)), + m_CurrentSubtaskIndex(0) +{ + m_Ui->setupUi(this); + + m_Ui->selectionWidget->SetDataStorage(GetDataStorage()); + m_Ui->selectionWidget->SetSelectionIsOptional(true); + m_Ui->selectionWidget->SetEmptyInfo(QStringLiteral("Select a segmentation task")); + m_Ui->selectionWidget->SetAutoSelectNewNodes(true); + m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType::New()); + + m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor())); + + using Self = QmitkSegmentationTaskWidget; + + connect(m_Ui->selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSelectionChanged); + connect(m_Ui->previousButton, &QToolButton::clicked, this, &Self::OnPreviousButtonClicked); + connect(m_Ui->nextButton, &QToolButton::clicked, this, &Self::OnNextButtonClicked); + connect(m_Ui->loadButton, &QPushButton::clicked, this, &Self::OnLoadButtonClicked); + + connect(m_FileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &Self::OnResultDirectoryChanged); + + this->OnSelectionChanged(m_Ui->selectionWidget->GetSelectedNodes()); +} + +QmitkSegmentationTaskWidget::~QmitkSegmentationTaskWidget() +{ +} + +mitk::SegmentationTask* QmitkSegmentationTaskWidget::GetTask() const +{ + return m_Task; +} + +std::optional QmitkSegmentationTaskWidget::GetActiveSubtaskIndex() const +{ + return m_ActiveSubtaskIndex; +} + +size_t QmitkSegmentationTaskWidget::GetCurrentSubtaskIndex() const +{ + return m_CurrentSubtaskIndex; +} + +/* Make sure that the widget transitions into a valid state whenever the + * selection changes. + */ +void QmitkSegmentationTaskWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) +{ + this->ResetControls(); + + if (!nodes.empty()) + { + auto task = dynamic_cast(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; }