diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp similarity index 93% rename from Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp rename to Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp index bfae7c9f0b..82656b39a2 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp @@ -1,869 +1,845 @@ /*============================================================================ 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 #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IPreferences* GetSegmentationPreferences() { return mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); } - 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 */ 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::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); auto* prevShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_P), this); connect(prevShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* prevUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_P), this); connect(prevUndoneShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* nextShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_N), this); connect(nextShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto* nextUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_N), this); connect(nextUndoneShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto* loadShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_L), this); connect(loadShortcut, &QShortcut::activated, this, &Self::OnLoadTaskShortcutActivated); - - this->OnSelectionChanged(m_Ui->selectionWidget->GetSelectedNodes()); } QmitkSegmentationTaskListWidget::~QmitkSegmentationTaskListWidget() { } +void QmitkSegmentationTaskListWidget::SetDataStorage(mitk::DataStorage* dataStorage) +{ + m_DataStorage = dataStorage; + m_Ui->selectionWidget->SetDataStorage(dataStorage); // Triggers OnSelectionChanged() +} + 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(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 (!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 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. * TODO: Then, load the first unfinished task, if any. */ 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); // TODO: This line should be enough but it is happening too early even before // the RenderingManager has any registered render windows, resulting in mismatching // renderer and data geometries. // this->LoadNextUnfinishedTask(); } /* If possible, change the currently displayed task to the previous task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnPreviousButtonClicked() { auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the previous undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { if (current > 0) { for (decltype(current) i = current; i > 0; --i) { if (!m_TaskList->IsDone(i - 1)) { this->SetCurrentTaskIndex(i - 1); break; } } } } else { if (current != 0) this->SetCurrentTaskIndex(current - 1); } this->UpdateNavigationButtons(); } /* 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 numTasks = m_TaskList->GetNumberOfTasks(); auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the next undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { for (std::remove_const_t i = current + 1; i < numTasks; ++i) { if (!m_TaskList->IsDone(i)) { this->SetCurrentTaskIndex(i); break; } } } else { if (current < numTasks - 1) this->SetCurrentTaskIndex(current + 1); } this->UpdateNavigationButtons(); } void QmitkSegmentationTaskListWidget::UpdateNavigationButtons() { if (m_TaskList.IsNull() || m_TaskList->GetNumberOfTasks() == 0) { m_Ui->previousButton->setEnabled(false); m_Ui->nextButton->setEnabled(false); return; } const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1; const auto current = m_CurrentTaskIndex.value(); m_Ui->previousButton->setEnabled(current != 0); m_Ui->nextButton->setEnabled(current != maxIndex); } /* Update affected controls when the currently displayed task changed. */ void QmitkSegmentationTaskListWidget::OnCurrentTaskChanged() { this->UpdateLoadButton(); this->UpdateNavigationButtons(); 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("

Status: %1 / ").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

").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_TaskList->HasDescription(current)) details += QString("

Description: %1

").arg(QString::fromStdString(m_TaskList->GetDescription(current))); QStringList stringList; if (m_TaskList->HasImage(current)) stringList << QString::fromStdString("Image: " + m_TaskList->GetImage(current).string()); if (m_TaskList->HasSegmentation(current)) stringList << QString::fromStdString("Segmentation: " + m_TaskList->GetSegmentation(current).string()); if (m_TaskList->HasLabelName(current)) stringList << QString::fromStdString("Label name: " + m_TaskList->GetLabelName(current)); if (m_TaskList->HasLabelNameSuggestions(current)) stringList << QString::fromStdString("Label name suggestions: " + m_TaskList->GetLabelNameSuggestions(current).string()); if (m_TaskList->HasPreset(current)) stringList << QString::fromStdString("Label set preset: " + m_TaskList->GetPreset(current).string()); if (m_TaskList->HasDynamic(current)) stringList << QString("Segmentation type: %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("

%1

").arg(stringList.join(QStringLiteral("
"))); 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) { + auto imageNodes = m_DataStorage->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::New()); + auto segmentations = m_DataStorage->GetDerivations(imageNode, mitk::TNodePredicateDataType::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::New()); + auto imageNodes = m_DataStorage->GetDerivations(m_TaskListNode, mitk::TNodePredicateDataType::New()); for (auto imageNode : *imageNodes) { - dataStorage->Remove(dataStorage->GetDerivations(imageNode, nullptr, false)); + m_DataStorage->Remove(m_DataStorage->GetDerivations(imageNode, nullptr, false)); if (imageNode != skip) - dataStorage->Remove(imageNode); + m_DataStorage->Remove(imageNode); } } this->SetActiveTaskIndex(std::nullopt); } void QmitkSegmentationTaskListWidget::LoadNextUnfinishedTask() { const auto current = m_CurrentTaskIndex.value(); const auto numTasks = m_TaskList->GetNumberOfTasks(); for (size_t unboundNext = current; unboundNext < current + numTasks; ++unboundNext) { auto next = unboundNext % numTasks; if (!m_TaskList->IsDone(next)) { this->SetCurrentTaskIndex(next); this->OnLoadButtonClicked(); break; } } } /* 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(path.string()); } const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetResult(current)); const auto interimPath = m_TaskList->GetInterimPath(path); if (std::filesystem::exists(path)) { segmentation = mitk::IOUtil::Load(path.string()); } else if (std::filesystem::exists(interimPath)) { segmentation = mitk::IOUtil::Load(interimPath.string()); } else if (m_TaskList->HasSegmentation(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current)); 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_TaskListNode); + m_DataStorage->Add(imageNode, m_TaskListNode); mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry()); } else { image = static_cast(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(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); + m_DataStorage->Add(segmentationNode, imageNode); } else { auto segmentationNode = mitk::DataNode::New(); segmentationNode->SetName(name); segmentationNode->SetData(segmentation); - dataStorage->Add(segmentationNode, imageNode); + m_DataStorage->Add(segmentationNode, imageNode); } // Workaround for T29431. Remove when T26953 is fixed. mitk::DICOMQIPropertyHelper::DeriveDICOMSourceProperties(image, segmentation); auto prefs = GetSegmentationPreferences(); if (prefs != nullptr) { if (m_TaskList->HasLabelNameSuggestions(current)) { auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetLabelNameSuggestions(current)); prefs->PutBool("default label naming", false); prefs->Put("label suggestions", 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(segmentationNode->GetData()); auto command = itk::SimpleMemberCommand::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(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& index) { if (m_ActiveTaskIndex != index) { m_ActiveTaskIndex = index; emit ActiveTaskChanged(m_ActiveTaskIndex); } } void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional& 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(const QString& alternativeTitle) { if (m_UnsavedChanges) { const auto active = m_ActiveTaskIndex.value(); const auto current = m_CurrentTaskIndex.value(); QString title; if (alternativeTitle.isEmpty()) { title = QString("Load task %1").arg(current + 1); if (m_TaskList->HasName(current)) title += ": " + QString::fromStdString(m_TaskList->GetName(current)); } else { title = alternativeTitle; } 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(!std::filesystem::exists(m_TaskList->GetResult(active))); break; case QMessageBox::Discard: m_UnsavedChanges = false; break; default: return false; } } return true; } 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(), saveAsIntermediateResult); this->OnUnsavedChangesSaved(); } catch (const mitk::Exception& e) { MITK_ERROR << e; } QApplication::restoreOverrideCursor(); } bool QmitkSegmentationTaskListWidget::OnPreShutdown() { return this->HandleUnsavedChanges(QStringLiteral("Application shutdown")); } void QmitkSegmentationTaskListWidget::OnPreviousTaskShortcutActivated() { m_Ui->previousButton->click(); } void QmitkSegmentationTaskListWidget::OnNextTaskShortcutActivated() { m_Ui->nextButton->click(); } void QmitkSegmentationTaskListWidget::OnLoadTaskShortcutActivated() { m_Ui->loadButton->click(); } diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.h similarity index 94% rename from Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h rename to Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.h index 4ce97a712b..d8aebca780 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.h @@ -1,90 +1,93 @@ /*============================================================================ 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 #include #include #include #include class QFileSystemWatcher; namespace Ui { class QmitkSegmentationTaskListWidget; } -class QmitkSegmentationTaskListWidget : public QWidget +class MITKSEGMENTATIONUI_EXPORT QmitkSegmentationTaskListWidget : public QWidget { Q_OBJECT public: explicit QmitkSegmentationTaskListWidget(QWidget* parent = nullptr); ~QmitkSegmentationTaskListWidget() override; + void SetDataStorage(mitk::DataStorage* dataStorage); + bool ActiveTaskIsShown() const; void LoadNextUnfinishedTask(); void SaveActiveTask(bool saveAsIntermediateResult = false); bool OnPreShutdown(); signals: void ActiveTaskChanged(const std::optional& index); void CurrentTaskChanged(const std::optional& 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 UpdateNavigationButtons(); 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& index); void SetCurrentTaskIndex(const std::optional& index); bool HandleUnsavedChanges(const QString& alternativeTitle = QString()); mitk::DataNode* GetSegmentationDataNode(size_t index) const; void OnUnsavedChangesSaved(); void OnPreviousTaskShortcutActivated(); void OnNextTaskShortcutActivated(); void OnLoadTaskShortcutActivated(); Ui::QmitkSegmentationTaskListWidget* m_Ui; QFileSystemWatcher* m_FileSystemWatcher; + mitk::DataStorage* m_DataStorage; mitk::SegmentationTaskList::Pointer m_TaskList; mitk::DataNode::Pointer m_TaskListNode; std::optional m_CurrentTaskIndex; std::optional m_ActiveTaskIndex; std::optional m_SegmentationModifiedObserverTag; bool m_UnsavedChanges; }; #endif diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.ui b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.ui similarity index 100% rename from Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.ui rename to Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.ui diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index adfa9a06ad..08da87a365 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,99 +1,102 @@ set(CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkEditableContourToolGUIBase.cpp Qmitk/QmitkGrowCutToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp + Qmitk/QmitkSegmentationTaskListWidget.cpp SegmentationUtilities/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/QmitkContourModelToImageWidget.cpp SegmentationUtilities/QmitkImageMaskingWidget.cpp SegmentationUtilities/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/QmitkSurfaceToImageWidget.cpp SegmentationUtilities/QmitkSegmentationUtilityWidget.cpp SegmentationUtilities/QmitkDataSelectionWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkEditableContourToolGUIBase.h Qmitk/QmitkGrowCutToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSimpleLabelSetListWidget.h + Qmitk/QmitkSegmentationTaskListWidget.h SegmentationUtilities/QmitkBooleanOperationsWidget.h SegmentationUtilities/QmitkContourModelToImageWidget.h SegmentationUtilities/QmitkImageMaskingWidget.h SegmentationUtilities/QmitkMorphologicalOperationsWidget.h SegmentationUtilities/QmitkSurfaceToImageWidget.h SegmentationUtilities/QmitkSegmentationUtilityWidget.h SegmentationUtilities/QmitkDataSelectionWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkGrowCutToolWidgetControls.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui Qmitk/QmitkEditableContourToolGUIControls.ui + Qmitk/QmitkSegmentationTaskListWidget.ui SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui SegmentationUtilities/QmitkContourModelToImageWidgetControls.ui SegmentationUtilities/QmitkImageMaskingWidgetControls.ui SegmentationUtilities/QmitkMorphologicalOperationsWidgetControls.ui SegmentationUtilities/QmitkSurfaceToImageWidgetControls.ui SegmentationUtilities/QmitkDataSelectionWidgetControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake index 965a81bd30..45e6d49d05 100644 --- a/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.flow.segmentation/files.cmake @@ -1,49 +1,46 @@ set(SRC_CPP_FILES ) set(INTERNAL_CPP_FILES org_mitk_gui_qt_flow_segmentation_Activator.cpp QmitkSegmentationFlowControlView.cpp - QmitkSegmentationTaskListWidget.cpp perspectives/QmitkFlowSegmentationPerspective.cpp ) set(UI_FILES src/internal/QmitkSegmentationFlowControlView.ui - src/internal/QmitkSegmentationTaskListWidget.ui ) set(MOC_H_FILES src/internal/org_mitk_gui_qt_flow_segmentation_Activator.h src/internal/QmitkSegmentationFlowControlView.h - src/internal/QmitkSegmentationTaskListWidget.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/SegmentationTaskList.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/src/internal/QmitkSegmentationFlowControlView.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationFlowControlView.cpp index 5fbb4736b0..71e81f6602 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,208 +1,209 @@ /*============================================================================ 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 #include #include //MITK #include #include #include #include #include #include #include #include // Qmitk #include "QmitkSegmentationFlowControlView.h" #include // Qt #include #include #include 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::New(), notHelperObject); m_SegmentationTaskListPredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), notHelperObject); berry::PlatformUI::GetWorkbench()->AddWorkbenchListener(this); } QmitkSegmentationFlowControlView::~QmitkSegmentationFlowControlView() { berry::PlatformUI::GetWorkbench()->RemoveWorkbenchListener(this); } bool QmitkSegmentationFlowControlView::PreShutdown(berry::IWorkbench*, bool) { return m_Controls->segmentationTaskListWidget != nullptr ? m_Controls->segmentationTaskListWidget->OnPreShutdown() : true; } void QmitkSegmentationFlowControlView::SetFocus() { m_Controls->btnStoreAndAccept->setFocus(); } void QmitkSegmentationFlowControlView::CreateQtPartControl(QWidget* parent) { m_Controls->setupUi(parent); m_Parent = parent; using Self = QmitkSegmentationFlowControlView; 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); auto* prevShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_S), parent); connect(prevShortcut, &QShortcut::activated, this, &Self::OnStoreInterimResultShortcutActivated); auto* nextShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_A), parent); connect(nextShortcut, &QShortcut::activated, this, &Self::OnAcceptSegmentationShortcutActivated); m_Controls->btnStore->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnStore->setVisible(false); + m_Controls->segmentationTaskListWidget->SetDataStorage(this->GetDataStorage()); m_Controls->segmentationTaskListWidget->setVisible(false); m_Controls->labelStored->setVisible(false); this->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::OnStoreButtonClicked() { m_Controls->segmentationTaskListWidget->SaveActiveTask(true); } void QmitkSegmentationFlowControlView::OnAcceptButtonClicked() { if (m_Controls->segmentationTaskListWidget->isVisible()) { auto* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); int activeToolId = -1; if (toolManager != nullptr) activeToolId = toolManager->GetActiveToolID(); m_Controls->segmentationTaskListWidget->SaveActiveTask(); m_Controls->segmentationTaskListWidget->LoadNextUnfinishedTask(); if (toolManager != nullptr) toolManager->ActivateTool(activeToolId); } 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::OnActiveTaskChanged(const std::optional&) { this->UpdateControls(); } void QmitkSegmentationFlowControlView::OnCurrentTaskChanged(const std::optional&) { 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(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(); } void QmitkSegmentationFlowControlView::OnStoreInterimResultShortcutActivated() { m_Controls->btnStore->click(); } void QmitkSegmentationFlowControlView::OnAcceptSegmentationShortcutActivated() { m_Controls->btnStoreAndAccept->click(); } 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 eaebffec27..e2a96650c9 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,133 +1,133 @@ SegmentationFlowControlView 0 0 268 324 Qt::Vertical 20 40 50 50 50 50 Save interim segmentation 32 32 0 50 Save and accept segmentation Accept segmentation 32 32 <html><head/><body><p><span style=" font-size:12pt;">Segmentation accepted! Flow on...</span></p></body></html> Qt::AlignCenter Qt::Vertical 20 40 QmitkSegmentationTaskListWidget QWidget -
internal/QmitkSegmentationTaskListWidget.h
+
QmitkSegmentationTaskListWidget.h
1
5 5 true true true