diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 88116edc86..3300e43d8b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1171 +1,1301 @@ /*============================================================================ 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 #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector), m_SegmentationNodeDataMTime(0) { 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); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(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 = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } 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(); emit SegmentationChanged(); } } mitk::LabelSetImage* QmitkMultiLabelInspector::GetMultiLabelSegmentation() const { return m_Segmentation; } void QmitkMultiLabelInspector::SetMultiLabelNode(mitk::DataNode* node) { if (node != this->m_SegmentationNode.GetPointer()) { m_SegmentationObserver.Reset(); m_SegmentationNode = node; m_SegmentationNodeDataMTime = 0; mitk::LabelSetImage* seg = nullptr; if (m_SegmentationNode.IsNotNull()) { auto& widget = *this; - auto checkAndSetSeg = [&widget, &node](const itk::EventObject& event) + auto checkAndSetSeg = [&widget, node](const itk::EventObject& event) { mitk::LabelSetImage* newSeg = nullptr; if (widget.m_SegmentationNodeDataMTime < node->GetDataReferenceChangedTime()) { newSeg = dynamic_cast(node->GetData()); if (nullptr == newSeg) mitkThrow() << "Invalid usage. Node set does not contain a segmentation."; widget.m_SegmentationNodeDataMTime = node->GetDataReferenceChangedTime(); widget.SetMultiLabelSegmentation(newSeg); } }; m_SegmentationObserver.Reset(node, itk::ModifiedEvent(), checkAndSetSeg); checkAndSetSeg(itk::ModifiedEvent()); } else { this->SetMultiLabelSegmentation(nullptr); } } } mitk::DataNode* QmitkMultiLabelInspector::GetMultiLabelNode() const { return m_SegmentationNode; } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } 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 return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { - // create new selection by retrieving the corresponding indices of the labels + // create new selection by retrieving the corresponding indexes 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()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } 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 : std::as_const(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; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); m_ModelManipulationOngoing = true; auto newLabel = m_Segmentation->AddLabel(templateLabel, groupID, 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(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; - return this->AddNewLabelInstanceInternal(currentLabel); + auto result = this->AddNewLabelInstanceInternal(currentLabel); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } + + return result; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); m_ModelManipulationOngoing = true; m_Segmentation->AddLabel(newLabel, containingGroup, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) 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(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { 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::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; - return AddNewLabelInternal(groupID); + auto result = AddNewLabelInternal(groupID); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } + + return result; } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) + { this->DeleteLabelInternal({ label->GetValue() }); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } + } } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) + { this->DeleteLabelInternal(relevantLabels); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } + } } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.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; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); 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::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); m_Segmentation->SetActiveLayer(groupID); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } + return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); 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; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } 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; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } const auto* selectedLabel = this->GetFirstSelectedLabelObject(); if (selectedLabel == nullptr) return; const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; 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); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything in group but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); 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); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear 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(); + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnDeleteLabels(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(); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; 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()); this->WaitCursorOff(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear 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(); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); m_Segmentation->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); + + // this is needed as workaround for (T27307). It circumvents the fact that modifications + // of data (here the segmentation) does not directly trigger the modification of the + // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. + // listens to the datastorage for modification would not get notified. + if (m_SegmentationNode.IsNotNull()) + { + m_SegmentationNode->Modified(); + } } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); m_Segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); m_Segmentation->SetAllLabelsVisible(false); currentLabel->SetVisible(true); m_Segmentation->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 a837eeaf49..89c772a0e6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,326 +1,327 @@ /*============================================================================ 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 QmitkMultiLabelInspector_h #define QmitkMultiLabelInspector_h #include #include #include #include #include #include class QmitkMultiLabelTreeModel; class QStyledItemDelegate; class QWidgetAction; namespace Ui { class QmitkMultiLabelInspector; } /* * @brief This is an inspector that offers a tree view on the labels and groups of a MultiLabelSegmentation instance. * It also allows some manipulation operations an the labels/groups accordin to the UI/selection state. */ 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; /** Indicates if the inspector is currently modifiying the model/segmentation. Thus as long as the manipulation is ongoing, one should assume the model to be in an invalid state.*/ bool GetModelManipulationOngoing() 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; /** @brief Returns the label that currently has the focus in the tree view. * * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. * * The current label must not equal the selected label(s). If the mouse is not hovering above a label * (label class or instance item), the method will return nullptr. */ mitk::Label* GetCurrentLabel() const; enum class IndexLevelType { Group, LabelClass, LabelInstance }; /** @brief Returns the level of the index that currently has the focus in the tree view. * * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. */ IndexLevelType GetCurrentLevelType() const; /** @brief Returns all label values that are currently affected. * * Affected means that these labels (including the one returned by GetCurrentLabel) are in the subtree of the tree * view element that currently has the focus (indicated by QTreeView::currentIndex, thus the mouse is over it and * it has a dashed border line. */ LabelValueVectorType GetCurrentlyAffactedLabelInstances() const; /** @brief Returns the values of all label instances that are of the same label (class) like the first selected label instance. * * If no label is selected an empty vector will be returned. */ LabelValueVectorType GetLabelInstancesOfSelectedFirstLabel() 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; /** * @brief A signal that will be emitted if the user has requested to "go to" a certain label. * * Going to a label would be e.g. to focus the renderwindows on the centroid of the label. * @param label The label that should be focused. * @param point in World coordinate that should be focused. */ void GoToLabel(LabelValueType label, const mitk::Point3D& point) const; /** @brief 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; /** @brief Signal that is emitted, if the model was updated (e.g. by a delete or add operation).*/ void ModelUpdated() const; /** @briefe Signal is emitted, if the segmentation is changed that is observed by the inspector.*/ void SegmentationChanged() const; public Q_SLOTS: /** * @brief Transform a list of label values into the new selection of the inspector. * @param selectedLabels 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 and monitored by the widget. * @param segmentation A pointer to the segmentation to set. * @remark You cannot set the segmentation directly if a segmentation node is * also set. Reset the node (nullptr) if you want to change to direct segmentation * setting. * @pre Segmentation node is nullptr. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); mitk::LabelSetImage* GetMultiLabelSegmentation() const; /** * @brief Sets the segmentation node that will be used /monitored by the widget. * * @param node A pointer to the segmentation node. - * @remark If not set some features of the inspectors are not active. - * Currently it is also needed to circumvent the fact that + * @remark If not set some features (e.g. highlighting in render windows) of the inspectors + * are not active. + * @remark Currently it is also needed to circumvent the fact that * modification of data does not directly trigger modification of the * node (see T27307). */ void SetMultiLabelNode(mitk::DataNode* node); mitk::DataNode* GetMultiLabelNode() const; void SetMultiSelectionMode(bool multiMode); void SetAllowVisibilityModification(bool visiblityMod); void SetAllowLockModification(bool lockMod); void SetAllowLabelModification(bool labelMod); void SetDefaultLabelNaming(bool defaultLabelNaming); /** @brief Adds an instance of the same label/class like the first label instance * indicated by GetSelectedLabels() to the segmentation. * * This new label instance is returned by the function. If the inspector has no selected label, * no new instance will be generated and nullptr will be returned. * * @remark The new label instance is a clone of the selected label instance. * Therefore all properties but the LabelValue will be the same. * * @pre AllowLabeModification must be set to true. */ mitk::Label* AddNewLabelInstance(); /** @brief 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. The label * will be added to the same group as the first currently selected label. * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewLabel(); /** @brief Removes the first currently selected label instance of the segmentation. * If no label is selected * nothing will happen. * * @pre AllowLabeModification must be set to true.*/ void DeleteLabelInstance(); /** @brief Delete the first currently selected label and all its instances of the segmentation. * If no label is selected * nothing will happen. * * @pre AllowLabeModification must be set to true.*/ void DeleteLabel(); /** @brief Adds a new group with a new label to segmentation. * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewGroup(); /** @brief 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(); void SetVisibilityOfAffectedLabels(bool visible) const; void SetLockOfAffectedLabels(bool visible) const; 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); /** @brief Helper that returns the label object (if multiple labels are selected the first). */ mitk::Label* GetFirstSelectedLabelObject() const; mitk::Label* AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup); /**@brief Adds an instance of the same label/class like the passed label value */ mitk::Label* AddNewLabelInstanceInternal(mitk::Label* templateLabel); void RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID); void DeleteLabelInternal(const LabelValueVectorType& labelValues); 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 OnAddLabel(); void OnAddLabelInstance(); void OnDeleteGroup(); void OnDeleteAffectedLabel(); void OnDeleteLabels(bool); void OnClearLabels(bool); void OnMergeLabels(bool); void OnRenameLabel(bool); void OnClearLabel(bool); void OnUnlockAffectedLabels(); void OnLockAffectedLabels(); void OnSetAffectedLabelsVisible(); void OnSetAffectedLabelsInvisible(); void OnSetOnlyActiveLabelVisible(bool); void OnItemDoubleClicked(const QModelIndex& index); void WaitCursorOn() const; void WaitCursorOff() const; void RestoreOverrideCursor() const; void PrepareGoToLabel(LabelValueType labelID) const; QWidgetAction* CreateOpacityAction(); private: bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; /** @brief Indicates if the context menu allows changes in visiblity. * * Visiblity includes also color */ bool m_AllowVisibilityModification = true; /** @brief Indicates if the context menu allows changes in lock state. */ bool m_AllowLockModification = true; /** @brief Indicates if the context menu allows label modifications (adding, removing, renaming ...) */ bool m_AllowLabelModification = false; bool m_DefaultLabelNaming = true; bool m_ModelManipulationOngoing = false; mitk::DataNode::Pointer m_SegmentationNode; unsigned long m_SegmentationNodeDataMTime; mitk::ITKEventObserverGuard m_SegmentationObserver; }; #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 08afd9849a..190e081391 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1107 +1,1107 @@ /*============================================================================ 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(); this->RemoveObserversFromWorkingImage(); // 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, &Self::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, &Self::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; this->RemoveObserversFromWorkingImage(); // 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, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->AddObserversToWorkingImage(); } } // 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::OnLabelAdded(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) { this->ValidateSelectionInput(); } mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() { if (m_WorkingNode.IsNull()) return nullptr; return dynamic_cast(m_WorkingNode->GetData()); } void QmitkSegmentationView::AddObserversToWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { auto& widget = *this; m_LabelAddedObserver.Reset(workingImage, mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelAdded(labelEvent->GetLabelValue()); }); m_LabelRemovedObserver.Reset(workingImage, mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelRemoved(labelEvent->GetLabelValue()); }); m_GroupRemovedObserver.Reset(workingImage, mitk::GroupRemovedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupRemoved(groupEvent->GetGroupID()); }); } } void QmitkSegmentationView::RemoveObserversFromWorkingImage() { m_LabelAddedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_GroupRemovedObserver.Reset(); } 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(); auto labels = workingImage->GetLabelValuesByGroup(workingImage->GetActiveLayer()); auto it = std::find(labels.begin(), labels.end(), workingImage->GetActiveLabel()->GetValue()); if (it != labels.end()) ++it; if (it == labels.end()) { it = labels.begin(); } workingImage->SetActiveLabel(*it); 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::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); newLabelSetImage->AddLabel(newLabel, newLabelSetImage->GetActiveLayer()); } 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 segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); if (nullptr == segmentation->GetActiveLabel() || labelValue != segmentation->GetActiveLabel()->GetValue()) { segmentation->SetActiveLabel(labelValue); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } m_Controls->slicesInterpolator->SetActiveLabelValue(labelValue); } 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); return; } QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, 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; } /**********************************************************************/ /* 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, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &Self::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, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::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' 'Segment Anything'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #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()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &Self::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->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &Self::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::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, &Self::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, &Self::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 compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } 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; auto image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } 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()); numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); - m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); + m_Controls->multiLabelWidget->SetMultiLabelNode(workingNode); if (!m_Controls->multiLabelWidget->GetSelectedLabels().empty()) { m_Controls->slicesInterpolator->SetActiveLabelValue(m_Controls->multiLabelWidget->GetSelectedLabels().front()); } } else { - m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); + m_Controls->multiLabelWidget->SetMultiLabelNode(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // 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(); } }