diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 95b6d2ceaa..32990081f6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,787 +1,796 @@ /*============================================================================ 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 // mitk #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include "ui_QmitkMultiLabelInspectorControls.h" QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); this->m_Controls->view->setItemDelegateForColumn(1, m_LockItemDelegate); this->m_Controls->view->setItemDelegateForColumn(2, m_ColorItemDelegate); this->m_Controls->view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); this->m_Controls->view->header()->setSectionResizeMode(0,QHeaderView::Stretch); this->m_Controls->view->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); this->m_Controls->view->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); this->m_Controls->view->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); this->m_Controls->view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(m_Controls->view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(m_Controls->view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(m_Controls->view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; if (m_Segmentation->GetTotalNumberOfLabels() > 0 && !this->GetMultiSelectionMode()) { //in singel selection mode, if at least one label exist select the first label of the mode. auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { if (multiMode) { m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); } else { m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); } } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; this->m_Model->SetAllowVisibilityModification(vmod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; this->m_Model->SetAllowLockModification(lmod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists auto lambda = [](mitk::LabelSetImage::LabelValueType lhs, mitk::LabelSetImage::LabelValueType rhs) { return lhs == rhs; }; return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { bool equal = EqualLabelSelections(this->GetSelectedLabels(), selectedLabels); if (equal) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { auto f = matched.front(); auto d = f.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; auto newLabel = group->AddLabel(currentLabel, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::SpatialGroupIndexType& containingGroup) { mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) { - // TODO + emit LabelRenameRequested(newLabel, false); } auto group = m_Segmentation->GetLabelSet(containingGroup); m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { //todo das sollte eigentlich weg gelassen werden können. //Wenn beim Testen kein problem auffällt, dann kann die Zeile wirklich raus. //m_ToolManager->ActivateTool(-1); if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::SpatialGroupIndexType groupID = nullptr == currentLabel ? 0 : m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::RemoveLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to remove label \""; question.append( QString::fromStdString(currentLabel->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Remove label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { auto currentIndex = m_Model->indexOfLabel(currentLabel->GetValue()); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveLabel(currentLabel->GetValue()); m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::SpatialGroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); this->WaitCursorOff(); newLabel = AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( this, "Add layer", "Could not add a new layer. See error log for details.\n"); } m_ModelManipulationOngoing = false; return newLabel; } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } QString question = "Do you really want to delete the current layer with all labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } auto currentLabel = GetFirstSelectedLabelObject(); const auto currentGroup = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto currentIndex = m_Model->indexOfGroup(currentGroup); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveSpatialGroup(currentGroup); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( this, "Delete layer", "Could not delete the currently active layer. See error log for details.\n"); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& pos) { auto selectedLabelValues = this->GetSelectedLabels(); if (m_Segmentation.IsNull() || !this->isEnabled() || selectedLabelValues.empty()) return; + if (nullptr == this->GetCurrentLabel()) + return; //for now we only have context menues if we realy click on an instance + //context menues for group level and label class level will be added later. + QMenu* menu = new QMenu(this); + if (this->GetMultiSelectionMode() && selectedLabelValues.size()>1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); mergeAction->setEnabled(true); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove selected labels", this); removeLabelsAction->setEnabled(true); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabels(bool))); menu->addAction(removeLabelsAction); QAction* eraseLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase selected labels", this); eraseLabelsAction->setEnabled(true); QObject::connect(eraseLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabels(bool))); menu->addAction(eraseLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "Add label instance...", this); addInstanceAction->setEnabled(true); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::AddNewLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "Rename...", this); renameAction->setEnabled(true); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove...", this); removeAction->setEnabled(true); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::RemoveLabel); menu->addAction(removeAction); QAction* eraseAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase...", this); eraseAction->setEnabled(true); QObject::connect(eraseAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabel(bool))); menu->addAction(eraseAction); } if (m_AllowLockModification) { - QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock all", this); + QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock group", this); lockAllAction->setEnabled(true); QObject::connect(lockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnLockAllLabels(bool))); menu->addAction(lockAllAction); - QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock all", this); + QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock group", this); unlockAllAction->setEnabled(true); QObject::connect(unlockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnUnlockAllLabels(bool))); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { QAction* viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", this); viewOnlyAction->setEnabled(true); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); - QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View all", this); + QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View group", this); viewAllAction->setEnabled(true); QObject::connect(viewAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsVisible(bool))); menu->addAction(viewAllAction); - QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide all", this); + QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide group", this); hideAllAction->setEnabled(true); QObject::connect(hideAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsInvisible(bool))); menu->addAction(hideAllAction); QSlider* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto currentLabel = this->GetFirstSelectedLabelObject(); auto opacity = currentLabel->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, currentLabel](const int value) { float opacity = static_cast(value) / 100.0f; currentLabel->SetOpacity(opacity); auto groupID = segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = segmentation->GetLabelSet(groupID); group->UpdateLookupTable(currentLabel->GetValue()); } ); QLabel* _OpacityLabel = new QLabel("Opacity: "); QVBoxLayout* _OpacityWidgetLayout = new QVBoxLayout; _OpacityWidgetLayout->setContentsMargins(4, 4, 4, 4); _OpacityWidgetLayout->addWidget(_OpacityLabel); _OpacityWidgetLayout->addWidget(opacitySlider); QWidget* _OpacityWidget = new QWidget; _OpacityWidget->setLayout(_OpacityWidgetLayout); QWidgetAction* OpacityAction = new QWidgetAction(this); OpacityAction->setDefaultWidget(_OpacityWidget); menu->addAction(OpacityAction); } } menu->popup(QCursor::pos()); } void QmitkMultiLabelInspector::OnEraseLabels(bool /*value*/) { QString question = "Do you really want to erase the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Erase selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRemoveLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \""; question.append( QString::fromStdString(currentLabel->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabelInstance(bool /*value*/) { this->AddNewLabelInstance(); } void QmitkMultiLabelInspector::OnEraseLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to erase the contents of label \""; question.append( QString::fromStdString(currentLabel->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Erase label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { - //TODO + auto currentLabel = GetFirstSelectedLabelObject(); + emit LabelRenameRequested(currentLabel, true); } void QmitkMultiLabelInspector::OnUnlockAllLabels(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsLocked(false); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::OnLockAllLabels(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsLocked(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::OnSetAllLabelsVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(true); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::OnSetAllLabelsInvisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); - currentLabel->SetVisible(true); + currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h index ddf55e1f08..4cd2189862 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,211 +1,218 @@ /*============================================================================ 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 QMITKMULTILABELSEGMENTATIONINSPECTOR_H #define QMITKMULTILABELSEGMENTATIONINSPECTOR_H #include #include #include #include #include "ui_QmitkMultiLabelInspectorControls.h" class QmitkMultiLabelTreeModel; class QStyledItemDelegate; namespace Ui { class QmitkMultiLabelInspector; } /* * @brief This is an inspector that offers a simple list view on a data storage. */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelInspector : public QWidget { Q_OBJECT public: QmitkMultiLabelInspector(QWidget* parent = nullptr); ~QmitkMultiLabelInspector(); bool GetMultiSelectionMode() const; bool GetAllowVisibilityModification() const; bool GetAllowLockModification() const; bool GetAllowLabelModification() const; using LabelValueType = mitk::LabelSetImage::LabelValueType; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; /** Returns the label that currently has the focus in the tree view (indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line). The current label must not equal the seleted label(s). If the mouse is not over a label (instance item), the method will return a null pointer.*/ mitk::Label* GetCurrentLabel() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels) const; void GoToLabel(LabelValueType label, const mitk::Point3D&) const; + /** Signal that is emitted, if a label should be (re) named and default + label naming is deactivated. The instance for which a new name is requested + is passed with the signal. + @param label Pointer to the instance that needs a (new) name. + @param rename Indicates if it is a renaming or naming of a new label.*/ + void LabelRenameRequested(mitk::Label* label, bool rename) const; + public Q_SLOTS: /** * @brief Transform a list label values into the new selection of the inspector. * @param selectedNodes A list of selected label values. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief The passed label will be used as new selection in the widget * @param selectedLabel Value of the selected label. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetMultiSelectionMode(bool multiMode); void SetAllowVisibilityModification(bool vmod); void SetAllowLockModification(bool lmod); void SetAllowLabelModification(bool labelMod); void SetDefaultLabelNaming(bool defaultLabelNaming); /** Adds an instance of the same label/class like the label instance * returned by GetCurrentLabel() to the segmentation. This new label * instance is returned by the function. If the inspector has no current label, * no new instance will be generated and nullptr will be returned. * @remark The new label instance is a clone of current label instance. Therefore * all properties but the LabelValue will be the same. * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewLabelInstance(); /** Adds a new label to the segmentation. Depending on the settings the name of * the label will be either default generated or the rename delegate will be used. * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewLabel(); /** Removes the first currently selected label of the segmentation. If no label is selected * nothing will happen. * @pre AllowLabeModification must be set to true.*/ void RemoveLabel(); /** Adds a new group with a new label to segmentation. * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewGroup(); /** Removes the group of the first currently selected label of the segmentation. If no label is selected * nothing will happen. * @pre AllowLabeModification must be set to true.*/ void RemoveGroup(); protected: void Initialize(); void OnModelReset(); QmitkMultiLabelTreeModel* m_Model; mitk::LabelSetImage::Pointer m_Segmentation; LabelValueVectorType m_LastValidSelectedLabels; QStyledItemDelegate* m_LockItemDelegate; QStyledItemDelegate* m_ColorItemDelegate; QStyledItemDelegate* m_VisibilityItemDelegate; Ui::QmitkMultiLabelInspector* m_Controls; LabelValueVectorType GetSelectedLabelsFromSelectionModel() const; void UpdateSelectionModel(const LabelValueVectorType& selectedLabels); /** Helper that returns the label object (if multiple labels are selected the first).*/ mitk::Label* GetFirstSelectedLabelObject() const; mitk::Label* AddNewLabelInternal(const mitk::LabelSetImage::SpatialGroupIndexType& containingGroup); private Q_SLOTS: /** * @brief Transform a labels selection into a data node list and emit the 'CurrentSelectionChanged'-signal. * * The function adds the selected nodes from the original selection that could not be modified, if * 'm_SelectOnlyVisibleNodes' is false. * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member item view. * * @param selected The newly selected items. * @param deselected The newly deselected items. */ void OnChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); void OnContextMenuRequested(const QPoint&); void OnAddLabelInstance(bool); void OnRemoveLabels(bool); void OnEraseLabels(bool); void OnMergeLabels(bool); void OnRenameLabel(bool); void OnEraseLabel(bool); void OnUnlockAllLabels(bool); void OnLockAllLabels(bool); void OnSetAllLabelsVisible(bool); void OnSetAllLabelsInvisible(bool); void OnSetOnlyActiveLabelVisible(bool); void OnItemDoubleClicked(const QModelIndex& index); void WaitCursorOn() const; void WaitCursorOff() const; void RestoreOverrideCursor() const; void PrepareGoToLabel(LabelValueType labelID) const; private: bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; /** Indicates if the context menu allows changes in visiblity. Visiblity includes also color*/ bool m_AllowVisibilityModification = true; bool m_AllowLockModification = true; bool m_AllowLabelModification = false; bool m_DefaultLabelNaming = true; bool m_ModelManipulationOngoing = false; }; #endif // QMITKDATASTORAGELISTINSPECTOR_H diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp index 703c0b5bf0..90493c7ea0 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp @@ -1,448 +1,457 @@ /*============================================================================ 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 "QmitkMultiLabelManager.h" // mitk #include #include #include #include #include #include #include #include #include // Qmitk #include // Qt #include #include #include #include #include #include #include #include #include #include #include // itk #include #include "ui_QmitkMultiLabelManagerControls.h" QmitkMultiLabelManager::QmitkMultiLabelManager(QWidget *parent) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelManagerControls), m_Completer(nullptr), m_ProcessingManualSelection(false) { m_Controls->setupUi(this); m_Controls->labelSearchBox->setAlwaysShowClearIcon(true); m_Controls->labelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls->labelSearchBox->setCompleter(m_Completer); m_Controls->labelInspector->SetAllowLabelModification(true); connect(m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::UpdateControls); connect(m_Controls->labelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); m_Controls->btnSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); connect(m_Controls->btnAddLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); connect(m_Controls->btnAddInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); connect(m_Controls->btnRemoveLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveLabel); connect(m_Controls->btnAddGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewGroup); connect(m_Controls->btnRemoveGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveGroup); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnSavePreset); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnLoadPreset); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::GoToLabel, this, &QmitkMultiLabelManager::OnGoToLabel); + connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::LabelRenameRequested, this, &QmitkMultiLabelManager::OnLabelRenameRequested); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::CurrentSelectionChanged); auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkMultiLabelManager::OnRenameLabelShortcutActivated); auto* newLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_N), this); connect(newLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); this->UpdateControls(); } QmitkMultiLabelManager::~QmitkMultiLabelManager() { delete m_Controls; } QmitkMultiLabelManager::LabelValueVectorType QmitkMultiLabelManager::GetSelectedLabels() const { return m_Controls->labelInspector->GetSelectedLabels(); } void QmitkMultiLabelManager::OnRenameLabelShortcutActivated() { - if (this->GetSelectedLabels().size() == 1) + auto selectedLabels = this->GetSelectedLabels(); + + for (auto labelValue : selectedLabels) { - //TODO delegate like inspector -> will call QmitkNewSegmentationDialog::RenameLabel() + auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); + emit LabelRenameRequested(currentLabel, true); } } void QmitkMultiLabelManager::OnSelectedLabelChanged(LabelValueVectorType labels) { if (labels.empty() || labels.size() > 1) return; emit CurrentSelectionChanged(labels); } QStringList &QmitkMultiLabelManager::GetLabelStringList() { return m_LabelStringList; } void QmitkMultiLabelManager::SetDefaultLabelNaming(bool defaultLabelNaming) { this->m_Controls->labelInspector->SetDefaultLabelNaming(defaultLabelNaming); } void QmitkMultiLabelManager::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { this->m_Controls->labelInspector->SetSelectedLabels(selectedLabels); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->m_Controls->labelInspector->SetSelectedLabel(selectedLabel); UpdateControls(); } void QmitkMultiLabelManager::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != this->m_Segmentation.GetPointer()) { m_Segmentation = segmentation; this->m_Controls->labelInspector->SetMultiLabelSegmentation(segmentation); UpdateControls(); } } void QmitkMultiLabelManager::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkMultiLabelManager::OnSearchLabel() { //std::string text = m_Controls->labelSearchBox->text().toStdString(); //int pixelValue = -1; //int row = -1; //for (int i = 0; i < m_Controls->m_LabelSetTableWidget->rowCount(); ++i) //{ // if (m_Controls->m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) // { // pixelValue = m_Controls->m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); // row = i; // break; // } //} //if (pixelValue == -1) //{ // return; //} //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //QTableWidgetItem *nameItem = m_Controls->m_LabelSetTableWidget->item(row, NAME_COL); //if (!nameItem) //{ // return; //} //m_Controls->m_LabelSetTableWidget->clearSelection(); //m_Controls->m_LabelSetTableWidget->selectRow(row); //m_Controls->m_LabelSetTableWidget->scrollToItem(nameItem); //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //this->WaitCursorOn(); //mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); //m_ToolManager->WorkingDataChanged(); //if (pos.GetVnlVector().max_value() > 0.0) //{ // emit goToLabel(pos); //} //else //{ // GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); // mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); // emit goToLabel(pos); //} //this->WaitCursorOff(); } void QmitkMultiLabelManager::UpdateControls() { bool hasWorkingData = m_Segmentation.IsNotNull(); auto labels = this->m_Controls->labelInspector->GetSelectedLabels(); m_Controls->labelSearchBox->setEnabled(hasWorkingData); m_Controls->btnAddGroup->setEnabled(hasWorkingData); m_Controls->btnAddInstance->setEnabled(hasWorkingData && labels.size()==1); m_Controls->btnAddLabel->setEnabled(hasWorkingData); m_Controls->btnLoadPreset->setEnabled(hasWorkingData); m_Controls->btnRemoveGroup->setEnabled(hasWorkingData && !labels.empty() && m_Segmentation->GetNumberOfLayers()>1); m_Controls->btnRemoveLabel->setEnabled(hasWorkingData && !labels.empty()); m_Controls->btnSavePreset->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = dynamic_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkMultiLabelManager::OnCreateCroppedMask(bool) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); mitk::Image::Pointer maskImage; auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); try { this->WaitCursorOn(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(this->m_Segmentation->CreateLabelMask(pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateMask(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::Image::Pointer maskImage; try { this->WaitCursorOn(); maskImage = m_Segmentation->CreateLabelMask(pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateSmoothedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", true); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnCreateDetailedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", false); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnSavePreset() { } void QmitkMultiLabelManager::OnLoadPreset() { } -void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) +void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const { emit GoToLabel(label, position); } +void QmitkMultiLabelManager::OnLabelRenameRequested(mitk::Label* label, bool rename) const +{ + emit LabelRenameRequested(label, rename); +} + void QmitkMultiLabelManager::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelManager::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkMultiLabelManager::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkMultiLabelManager::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h index beecf2168b..c32d2f9c6f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h @@ -1,147 +1,155 @@ /*============================================================================ 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 QmitkMultiLabelManager_h #define QmitkMultiLabelManager_h #include #include #include #include #include class QmitkDataStorageComboBox; class QCompleter; namespace Ui { class QmitkMultiLabelManagerControls; } namespace mitk { class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelManager : public QWidget { Q_OBJECT public: explicit QmitkMultiLabelManager(QWidget *parent = nullptr); ~QmitkMultiLabelManager() override; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels); - void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&); + void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&) const; + + /** Signal that is emitted, if a label should be (re) named and default + label naming is deactivated. The instance for which a new name is requested + is passed with the signal. + @param label Pointer to the instance that needs a (new) name. + @param rename Indicates if it is a renaming or naming of a new label.*/ + void LabelRenameRequested(mitk::Label* label, bool rename) const; public Q_SLOTS: /** * @brief Transform a list label values into a model selection and set this as a new selection of the view * * @param selectedNodes A list of data nodes that should be newly selected. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetDataStorage(mitk::DataStorage *storage); void UpdateControls(); virtual void setEnabled(bool enabled); QStringList &GetLabelStringList(); void SetDefaultLabelNaming(bool defaultLabelNaming); private Q_SLOTS: // LabelSet dependent void OnRenameLabelShortcutActivated(); // reaction to "returnPressed" signal from ... void OnSearchLabel(); // reaction to the change of labels. If multiple labels are selected, it is ignored. void OnSelectedLabelChanged(LabelValueVectorType labels); // LabelSetImage Dependet void OnCreateDetailedSurface(bool); void OnCreateSmoothedSurface(bool); // reaction to the signal "createMask" from QmitkLabelSetTableWidget void OnCreateMask(bool); // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget void OnCreateCroppedMask(bool); void OnSavePreset(); void OnLoadPreset(); - void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position); + void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const; + void OnLabelRenameRequested(mitk::Label* label, bool rename) const; private: enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); void OnThreadedCalculationDone(); Ui::QmitkMultiLabelManagerControls* m_Controls; QCompleter *m_Completer; QStringList m_OrganColors; QStringList m_LabelStringList; bool m_ProcessingManualSelection; mitk::LabelSetImage::Pointer m_Segmentation; mitk::DataNode::Pointer m_SegmentationNode; mitk::DataStorage* m_DataStorage; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp index 20c7d0ff32..b7e607cbda 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp @@ -1,399 +1,401 @@ /*============================================================================ 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 "QmitkNewSegmentationDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Get standard label name and color suggestions from embedded XML preset file for anatomical structures. QmitkNewSegmentationDialog::SuggestionsType GetStandardSuggestions() { vtkNew presets; presets->LoadPreset(); auto colorPresets = presets->GetColorPresets(); QmitkNewSegmentationDialog::SuggestionsType standardSuggestions; standardSuggestions.reserve(colorPresets.size()); for (const auto& preset : colorPresets) { auto name = QString::fromStdString(preset.first); auto color = QColor::fromRgbF(preset.second.GetRed(), preset.second.GetGreen(), preset.second.GetBlue()); standardSuggestions.emplace_back(name, color); } std::sort(begin(standardSuggestions), end(standardSuggestions), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); return standardSuggestions; } // Parse label name and color suggestions from a JSON file. An array of objects is expected, each consisting // of a "name" string and an optional "color" string. If present, the "color" string must follow the conventions // of QColor::setNamedColor(), i.e., #rrggbb or any SVG color keyword name like CornflowerBlue. Everything else // in the JSON file is simply ignored. In case of any error, an empty map is returned. QmitkNewSegmentationDialog::SuggestionsType ParseSuggestions(const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { MITK_ERROR << "Could not open \"" << filename << "\"!"; return {}; } auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_array()) { MITK_ERROR << "Could not parse \"" << filename << "\" as JSON array!"; return {}; } QmitkNewSegmentationDialog::SuggestionsType parsedSuggestions; for (const auto& obj : json) { if (!obj.is_object() || !obj.contains("name")) continue; auto name = QString::fromStdString(obj["name"]); QColor color; if (obj.contains("color")) color.setNamedColor(QString::fromStdString(obj["color"])); auto it = std::find_if(begin(parsedSuggestions), end(parsedSuggestions), [&name](const auto& suggestion) { return name == suggestion.first; }); // Ignore duplicates... if (it != parsedSuggestions.end()) { // unless we can complete the original suggestion with a valid color. if (!it->second.isValid() && color.isValid()) { it->second = color; } else { MITK_WARN << "Ignoring duplicate of suggestion \"" << name.toStdString() << "\"!"; } continue; } parsedSuggestions.emplace_back(name, color); } if (parsedSuggestions.empty()) MITK_WARN << "Could not parse any suggestions from \"" << filename << "\"!"; return parsedSuggestions; } struct Preferences { std::string labelSuggestions; bool replaceStandardSuggestions; bool suggestOnce; std::vector geometry; }; // Get all relevant preferences and consider command-line arguments overrides. Preferences GetPreferences() { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); Preferences prefs; prefs.labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); if (prefs.labelSuggestions.empty()) prefs.labelSuggestions = nodePrefs->Get("label suggestions", ""); prefs.replaceStandardSuggestions = nodePrefs->GetBool("replace standard suggestions", true); prefs.suggestOnce = nodePrefs->GetBool("suggest once", true); prefs.geometry = nodePrefs->GetByteArray("QmitkNewSegmentationDialog geometry", nullptr, 0); return prefs; } void SaveGeometry(const QByteArray& geometry) { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); nodePrefs->PutByteArray("QmitkNewSegmentationDialog geometry", reinterpret_cast(geometry.data()), geometry.size()); } // Get names of all labels in all layers of a LabelSetImage. QStringList GetExistingLabelNames(mitk::LabelSetImage* labelSetImage) { QStringList existingLabelNames; existingLabelNames.reserve(labelSetImage->GetTotalNumberOfLabels()); const auto numLayers = labelSetImage->GetNumberOfLayers(); for (std::remove_const_t layerIndex = 0; layerIndex < numLayers; ++layerIndex) { const auto* labelSet = labelSetImage->GetLabelSet(layerIndex); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { if (0 == labelIter->first) continue; // Ignore background label auto name = QString::fromStdString(labelIter->second->GetName()); if (!name.isEmpty()) // Potential duplicates do not matter for our purpose existingLabelNames.push_back(name); } } return existingLabelNames; } // Remove blacklisted suggestions. QmitkNewSegmentationDialog::SuggestionsType FilterSuggestions(const QmitkNewSegmentationDialog::SuggestionsType& suggestions, const QStringList& blacklist) { QmitkNewSegmentationDialog::SuggestionsType filteredSuggestions; std::remove_copy_if(begin(suggestions), end(suggestions), std::inserter(filteredSuggestions, end(filteredSuggestions)), [&blacklist](const auto& suggestion) { return blacklist.contains(suggestion.first); }); return filteredSuggestions; } } QmitkNewSegmentationDialog::QmitkNewSegmentationDialog(QWidget *parent, mitk::LabelSetImage* labelSetImage, Mode mode) : QDialog(parent), m_Ui(new Ui::QmitkNewSegmentationDialog), m_SuggestOnce(true), m_Color(Qt::red) { m_Ui->setupUi(this); if (RenameLabel == mode) { this->setWindowTitle("Rename Label"); m_Ui->label->setText("New name and color of the label"); m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Rename label"); } else { m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Create label"); } m_Ui->nameLineEdit->setFocus(); connect(this, &QDialog::finished, this, &QmitkNewSegmentationDialog::OnFinished); connect(m_Ui->colorButton, &QToolButton::clicked, this, &QmitkNewSegmentationDialog::OnColorButtonClicked); connect(m_Ui->nameLineEdit, &QLineEdit::textChanged, this, &QmitkNewSegmentationDialog::OnTextChanged); connect(m_Ui->nameList, &QListWidget::itemSelectionChanged, this, &QmitkNewSegmentationDialog::OnSuggestionSelected); connect(m_Ui->nameList, &QListWidget::itemDoubleClicked, this, &QmitkNewSegmentationDialog::OnAccept); connect(m_Ui->buttonBox, &QDialogButtonBox::accepted, this, &QmitkNewSegmentationDialog::OnAccept); this->UpdateColorButtonBackground(); auto prefs = GetPreferences(); if (!prefs.labelSuggestions.empty()) { auto suggestions = ParseSuggestions(prefs.labelSuggestions); this->SetSuggestions(suggestions, prefs.replaceStandardSuggestions && !suggestions.empty()); } else { this->SetSuggestions(GetStandardSuggestions(), true); } if (nullptr != labelSetImage && prefs.suggestOnce) { auto existingLabelNames = GetExistingLabelNames(labelSetImage); m_Suggestions = FilterSuggestions(m_Suggestions, existingLabelNames); this->UpdateNameList(); } if (!(prefs.geometry.empty())) this->restoreGeometry(QByteArray(reinterpret_cast(prefs.geometry.data()), prefs.geometry.size())); } QmitkNewSegmentationDialog::~QmitkNewSegmentationDialog() { } void QmitkNewSegmentationDialog::OnFinished(int) { SaveGeometry(this->saveGeometry()); } void QmitkNewSegmentationDialog::UpdateColorButtonBackground() { m_Ui->colorButton->setStyleSheet("background-color:" + m_Color.name()); } QString QmitkNewSegmentationDialog::GetName() const { return m_Name; } mitk::Color QmitkNewSegmentationDialog::GetColor() const { mitk::Color color; if (m_Color.isValid()) { color.SetRed(m_Color.redF()); color.SetGreen(m_Color.greenF()); color.SetBlue(m_Color.blueF()); } else { color.Set(1.0f, 0.0f, 0.0f); } return color; } void QmitkNewSegmentationDialog::SetName(const QString& name) { m_Ui->nameLineEdit->setText(name); } void QmitkNewSegmentationDialog::SetColor(const mitk::Color& color) { m_Color.setRgbF(color.GetRed(), color.GetGreen(), color.GetBlue()); this->UpdateColorButtonBackground(); } void QmitkNewSegmentationDialog::SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions) { m_Suggestions = suggestions; if (!replaceStandardSuggestions) { auto standardSuggestions = GetStandardSuggestions(); // Append all standard suggestions with unique names to the list of custom suggestions std::remove_copy_if(begin(standardSuggestions), end(standardSuggestions), std::inserter(m_Suggestions, end(m_Suggestions)), [this](const auto& suggestion) { return m_Suggestions.end() != std::find_if(begin(m_Suggestions), end(m_Suggestions), [&suggestion](const auto& otherSuggestion) { return suggestion.first == otherSuggestion.first; }); }); } this->UpdateNameList(); } void QmitkNewSegmentationDialog::UpdateNameList() { QStringList names; for (const auto& suggestion : m_Suggestions) names << suggestion.first; m_Ui->nameList->clear(); m_Ui->nameList->addItems(names); } void QmitkNewSegmentationDialog::OnAccept() { m_Name = m_Ui->nameLineEdit->text(); this->accept(); } void QmitkNewSegmentationDialog::OnTextChanged(const QString& text) { auto finding = m_Ui->nameList->findItems(text, Qt::MatchFlag::MatchExactly); if (!finding.isEmpty()) m_Ui->nameList->setCurrentItem(finding.first()); } void QmitkNewSegmentationDialog::OnColorButtonClicked() { auto color = QColorDialog::getColor(m_Color); if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } void QmitkNewSegmentationDialog::OnSuggestionSelected() { const auto* currentItem = m_Ui->nameList->currentItem(); if (currentItem == nullptr) return; auto row = m_Ui->nameList->selectionModel()->selectedIndexes().first().row(); m_Ui->nameLineEdit->setText(m_Suggestions[row].first); auto color = m_Suggestions[row].second; if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } -bool QmitkNewSegmentationDialog::DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent) +bool QmitkNewSegmentationDialog::DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent, Mode mode) { if (nullptr == currentLabel) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label is null."; - if (nullptr == segmentation) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed segmentation is null."; - if (segmentation->IsLabeInGroup(currentLabel->GetValue())) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label value does not exist in segmentation."; - QmitkNewSegmentationDialog dialog(parent, segmentation, QmitkNewSegmentationDialog::RenameLabel); + if (nullptr != segmentation && !segmentation->IsLabeInGroup(currentLabel->GetValue())) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label value does not exist in segmentation."; + QmitkNewSegmentationDialog dialog(parent, segmentation, mode); dialog.SetColor(currentLabel->GetColor()); dialog.SetName(QString::fromStdString(currentLabel->GetName())); if (dialog.exec() == QDialog::Rejected) { return false; } QString segmentationName = dialog.GetName(); if (segmentationName.isEmpty()) { segmentationName = "Unnamed"; } - auto groupID = segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = segmentation->GetLabelSet(groupID); - group->RenameLabel(currentLabel->GetValue(), segmentationName.toStdString(), dialog.GetColor()); - group->UpdateLookupTable(currentLabel->GetValue()); + if (nullptr != segmentation) + { + auto groupID = segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); + auto group = segmentation->GetLabelSet(groupID); + group->RenameLabel(currentLabel->GetValue(), segmentationName.toStdString(), dialog.GetColor()); + group->UpdateLookupTable(currentLabel->GetValue()); + } return true; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h index e6acbe45eb..e7c51b335f 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h @@ -1,86 +1,88 @@ /*============================================================================ 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 QmitkNewSegmentationDialog_h #define QmitkNewSegmentationDialog_h #include #include #include #include #include #include #include namespace mitk { class LabelSetImage; class Label; } namespace Ui { class QmitkNewSegmentationDialog; } /** \brief Dialog for naming labels. */ class MITK_QT_SEGMENTATION QmitkNewSegmentationDialog : public QDialog { Q_OBJECT public: using SuggestionsType = std::vector>; enum Mode { NewLabel, RenameLabel }; explicit QmitkNewSegmentationDialog(QWidget *parent = nullptr, mitk::LabelSetImage* labelSetImage = nullptr, Mode mode = NewLabel); ~QmitkNewSegmentationDialog() override; QString GetName() const; mitk::Color GetColor() const; void SetName(const QString& name); void SetColor(const mitk::Color& color); - static bool DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent = nullptr); + /**Static helper function to do (re)naming of a label via the dialog class. If mode == NewLabel, the function assumes that the label is not yet + added, thus e.g. no lookup table update is done. In rename mode, if a segmentation is provided, the segmentation will directly be updated.*/ + static bool DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent = nullptr, Mode mode = NewLabel); private: void OnAccept(); void OnFinished(int result); void OnSuggestionSelected(); void OnColorButtonClicked(); void OnTextChanged(const QString& text); void SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions = false); void UpdateColorButtonBackground(); void UpdateNameList(); Ui::QmitkNewSegmentationDialog* m_Ui; bool m_SuggestOnce; QString m_Name; QColor m_Color; SuggestionsType m_Suggestions; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 6712fcbbda..233bbd0926 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1030 +1,1040 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include namespace { QList Get2DWindows(const QList allWindows) { QList all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { - QmitkNewSegmentationDialog dialog(m_Parent); - dialog.SetName(QString::fromStdString(newLabel->GetName())); - dialog.SetColor(newLabel->GetColor()); - - if (QDialog::Rejected == dialog.exec()) - return; - - auto name = dialog.GetName(); - - if (!name.isEmpty()) - newLabel->SetName(name.toStdString()); - - newLabel->SetColor(dialog.GetColor()); + QmitkNewSegmentationDialog::DoRenameLabel(newLabel,nullptr,m_Parent); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { - auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); - if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; - - auto segmentation = dynamic_cast(workingNode->GetData()); - if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; + auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } +void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const +{ + auto segmentation = this->GetCurrentSegmentation(); + + if (rename) + { + QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); + } + else + { + QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); + } +} + +mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const +{ + auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); + if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; + + auto segmentation = dynamic_cast(workingNode->GetData()); + if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; + + return segmentation; +} + void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); //TODO temporary interpolator deactivation //m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); //TODO END temporary interpolator deactivation // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); //TODO temporary interpolator deactivation // connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); //TODO END temporary interpolator deactivation connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &QmitkSegmentationView::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &QmitkSegmentationView::OnGoToLabel); + connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &QmitkSegmentationView::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; //TODO temporary interpolator deactivation //mitk::Image* image = dynamic_cast(node->GetData()); //mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); //TODO END temporary interpolator deactivation } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLayer = labelSetImage->GetActiveLayer(); numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); //TODO temporary interpolator deactivation //if (numberOfLabels > 1) // m_Controls->slicesInterpolator->setEnabled(true); //TODO END temporary interpolator deactivation m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); } toolSelectionBoxesEnabled &= numberOfLabels > 1; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h index ef08c69777..4ae429a6b6 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h @@ -1,183 +1,186 @@ /*============================================================================ 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 QmitkSegmentationView_h #define QmitkSegmentationView_h #include "ui_QmitkSegmentationViewControls.h" #include #include /** * @brief The segmentation view provides a set of tool to use different segmentation algorithms. * It provides two selection widgets to load an image node and a segmentation node * on which to perform the segmentation. Creating new segmentation nodes is also possible. * The available segmentation tools are grouped into "2D"- and "3D"-tools. * * Most segmentation tools / algorithms need some kind of user interaction, where the * user is asked to draw something in the image display or set some seed points / start values. * The tools also often provide additional propeties so that a user can modify the * algorithm's behavior. * * This class additionally provides options to work with different layers (create new layers, * switch between layers). * Moreover, a multilabel widget displays all the existing labels of a multilabel segmentation * for the currently active layer. * The multilabel widget allows to control the labels by creatin new one, removing existing ones, * showing / hiding single labels, merging labels, (re-)naming them etc. * * Additionally the view provides an option to create "2D"- and "3D"-interpolations between * neighboring segmentation masks on unsegmented slices. * Interpolation for multilabel segmentations is currently not implemented. */ class QmitkSegmentationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkSegmentationView(); ~QmitkSegmentationView() override; private Q_SLOTS: // reaction to the selection of a new reference image in the selection widget void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new segmentation image in the selection widget void OnSegmentationSelectionChanged(QList nodes); // reaction to the shortcut ("CTRL+H") for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut ("CTRL+L") for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the button "New segmentation" void OnNewSegmentation(); void OnManualTool2DSelected(int id); void OnShowMarkerNodes(bool); void OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&); + void OnLabelRenameRequested(mitk::Label* label, bool rename) const; void OnLabelSetWidgetReset(); private: void CreateQtPartControl(QWidget* parent) override; void SetFocus() override {} /** * @brief Enable or disable the SegmentationInteractor. * * The active tool is retrieved from the tool manager. * If the active tool is valid, the SegmentationInteractor is enabled * to listen to 'SegmentationInteractionEvent's. */ void ActiveToolChanged(); /** * @brief Test whether the geometry of the reference image * fits the world geometry of the respective renderer. * * The respective renderer is retrieved from the given event, which * needs to be a 'SegmentationInteractionEvent'. * This event provides not only the sending base renderer but also a * bool that indicates whether the mouse cursor entered or left the * base renderer. * This information is used to compare the renderer's world geometry * with the oriented time geometry of the current reference image. * If the geometries align, the renderer is not blocked anymore and the * view's warning message is removed. * If the geometries do not align, 'ShowRenderWindowWarning' is called * and a warning message is added to the top of this plugin view. * * @param event The observed mitk::SegmentationInteractionEvent. */ void ValidateRendererGeometry(const itk::EventObject& event); void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartInputChanged(mitk::IRenderWindowPart* renderWindowPart) override; void OnPreferencesChanged(const mitk::IPreferences* prefs) override; void NodeAdded(const mitk::DataNode* node) override; void NodeRemoved(const mitk::DataNode* node) override; void OnAnySelectionChanged(); // make sure all images / segmentations look according to the user preference settings void ApplyDisplayOptions(); // decorates a DataNode according to the user preference settings void ApplyDisplayOptions(mitk::DataNode* node); void ApplySelectionMode(); void ApplySelectionModeOnReferenceNode(); void ApplySelectionModeOnWorkingNode(); void ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate); // If a contourmarker is selected, the plane in the related widget will be reoriented according to the marker`s geometry void OnContourMarkerSelected(const mitk::DataNode* node); void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) override; void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource&, int hotspotX, int hotspotY); void UpdateGUI(); void ValidateSelectionInput(); void UpdateWarningLabel(QString text); std::string GetDefaultLabelSetPreset() const; + mitk::LabelSetImage* GetCurrentSegmentation() const; + QWidget* m_Parent; Ui::QmitkSegmentationViewControls* m_Controls; mitk::IRenderWindowPart* m_RenderWindowPart; mitk::ToolManager* m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; typedef std::map NodeTagMapType; NodeTagMapType m_WorkingDataObserverTags; NodeTagMapType m_ReferenceDataObserverTags; unsigned int m_RenderingManagerObserverTag; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_DrawOutline; bool m_SelectionMode; bool m_MouseCursorSet; QString m_LabelSetPresetPreference; bool m_DefaultLabelNaming; bool m_SelectionChangeIsAlreadyBeingHandled; }; #endif