diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 9a79275109..0e9a4a23a8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,522 +1,775 @@ /*============================================================================ 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) + : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { - m_Controls.setupUi(this); + m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); - m_Controls.view->setModel(m_Model); + 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->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); + 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(ChangeModelSelection(const QItemSelection&, const QItemSelection&))); - connect(m_Controls.view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); - connect(m_Controls.view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); + 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_Controls->view->expandAll(); } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { if (multiMode) { - m_Controls.view->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); + m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); } else { - m_Controls.view->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); } } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { - return QAbstractItemView::SelectionMode::MultiSelection == m_Controls.view->selectionMode(); + 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::LabelValueRole, QVariant(labelID), 1, Qt::MatchRecursive); + 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); + 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(); + QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { - QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelValueRole); + 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; } -void QmitkMultiLabelInspector::ChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected) +mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { - 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 + 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) { - m_LastValidSelectedLabels = internalSelection; - emit CurrentSelectionChanged(GetSelectedLabels()); + 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 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 + } + + 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(); - auto currentLabel = this->GetCurrentLabel(); - if (nullptr== currentLabel) + if (m_Segmentation.IsNull() || !this->isEnabled() || 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); 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, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabel(bool))); + 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); 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); 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); 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); 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(), m_Segmentation->GetActiveLayer()); + 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 = GetCurrentLabel(); + 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::OnRemoveLabel(bool /*value*/) -{ - auto currentLabel = GetCurrentLabel(); - 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) - { - this->WaitCursorOn(); - m_Segmentation->RemoveLabel(currentLabel->GetValue(), m_Segmentation->GetActiveLayer()); - this->WaitCursorOff(); - } -} - void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { //TODO } void QmitkMultiLabelInspector::OnUnlockAllLabels(bool /*value*/) { - auto currentLabel = GetCurrentLabel(); + 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 = GetCurrentLabel(); + 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 = GetCurrentLabel(); + auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(true); } void QmitkMultiLabelInspector::OnSetAllLabelsInvisible(bool /*value*/) { - auto currentLabel = GetCurrentLabel(); + auto currentLabel = GetFirstSelectedLabelObject(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { - auto currentLabel = GetCurrentLabel(); + 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); group->UpdateLookupTable(labelID); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; + if (index.column() > 0) return; - auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelValueRole); + auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { - this->OnRemoveLabel(false); -// t this->OnRenameLabelShortcutActivated(); + 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 10d9e1fc0a..ddf55e1f08 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,153 +1,211 @@ /*============================================================================ 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(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&) const; + void GoToLabel(LabelValueType label, const mitk::Point3D&) 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. + * @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; + Ui::QmitkMultiLabelInspector* m_Controls; LabelValueVectorType GetSelectedLabelsFromSelectionModel() const; void UpdateSelectionModel(const LabelValueVectorType& selectedLabels); - bool m_ShowVisibility = true; - bool m_ShowLock = true; - bool m_ShowOther = false; + /** 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); - /** 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; 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 ChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); + 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 OnRemoveLabel(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(mitk::Label::PixelType labelID) 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/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 36c36d230b..c008040ffe 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,733 +1,896 @@ /*============================================================================ 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 "QmitkMultiLabelTreeModel.h" #include "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "QmitkStyleManager.h" class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_ItemType(type), m_Label(label), m_parentItem(parentItem), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; + const QmitkMultiLabelSegTreeItem* ParentItem() const + { + return m_parentItem; + }; + + const QmitkMultiLabelSegTreeItem* NextSibblingItem() const + { + if (m_parentItem) + { + const auto row = this->Row()+1; + if (row < m_parentItem->m_childItems.size()) + return m_parentItem->m_childItems[row]; + } + + return nullptr; + }; + + const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const + { + if (m_parentItem) + { + const auto row = this->Row(); + if (row > 0) + return m_parentItem->m_childItems[row-1]; + } + + return nullptr; + }; + const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); - if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an item as child that has root as parent."; + if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { - if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label item has no instance item."; + if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { - mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group item."; + mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; std::vector m_childItems; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; + +QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) +{ + QModelIndex parentIndex = QModelIndex(); + if (nullptr != start->m_parentItem) + { + parentIndex = GetIndexByItem(start->m_parentItem, model); + } + else + { + return parentIndex; + } + + return model->index(start->Row(), 0, parentIndex); +} + +QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::SpatialGroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) +{ + if (nullptr != root && groupIndex < root->m_childItems.size()) + { + return root->m_childItems[groupIndex]; + } + + return nullptr; +} + +QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) +{ + QmitkMultiLabelSegTreeItem* result = nullptr; + + for (auto item : root->m_childItems) + { + result = GetInstanceItem(labelValue, item); + if (nullptr != result) return result; + } + + if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) + { + return root; + } + + return nullptr; +} + +const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) +{ + const QmitkMultiLabelSegTreeItem* result = nullptr; + + if (nullptr != startItem) + { + if (startItem->HandleAsInstance()) + { + result = startItem; + } + else if (!startItem->m_childItems.empty()) + { + result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); + } + } + + return result; +} + +QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) +{ + if (nullptr != group) + { + auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; + auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); + if (group->m_childItems.end() != finding) + { + return *finding; + } + } + + return nullptr; +} + QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) , m_Observed(false) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group ")+QString::number(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); - if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel item is refering to a label that does not exist."; + if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName())); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: return QVariant(QString("Instance #") + QString::number(item->GetLabelValue())); } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } else { } } } else if (role == ItemModelRole::LabelDataRole) + { + auto label = item->GetLabel(); + if (nullptr!=label) return QVariant::fromValue(label); + } + else if (role == ItemModelRole::LabelValueRole) + { + auto label = item->GetLabel(); + if (nullptr != label) return QVariant(label->GetValue()); + } + else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } - else if (role == ItemModelRole::LabelValueRole) + else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } + return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } +QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const +{ + if (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) return QModelIndex(); + auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); + + if (nullptr == relevantItem) QModelIndex(); + + auto labelItem = relevantItem->ParentItem(); + + if (labelItem->m_childItems.size() == 1) + { //was the only instance of the label, therefor return the label item instat. + relevantItem = labelItem; + } + + return GetIndexByItem(relevantItem, this); +} + +QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::SpatialGroupIndexType groupIndex) const +{ + auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); + + if (nullptr == relevantItem) QModelIndex(); + + return GetIndexByItem(relevantItem, this); +} + QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } -QModelIndex GetIndexByItem(QmitkMultiLabelSegTreeItem* start, QmitkMultiLabelTreeModel* model) +QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { - QModelIndex parentIndex = QModelIndex(); - if (nullptr != start->m_parentItem) - { - parentIndex = GetIndexByItem(start->m_parentItem, model); - } - else - { - return parentIndex; - } + if (!currentIndex.isValid()) return QModelIndex(); - return model->index(start->Row(), 0, parentIndex); -} - -QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::SpatialGroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) -{ - if (nullptr != root && groupIndex < root->m_childItems.size()) - { - return root->m_childItems[groupIndex]; - } + auto currentItem = static_cast(currentIndex.internalPointer()); + if (!currentItem) return QModelIndex(); - return nullptr; -} + if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; -QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) -{ - QmitkMultiLabelSegTreeItem* result = nullptr; + const QmitkMultiLabelSegTreeItem* resultItem = nullptr; + auto searchItem = currentItem; + const auto rootItem = currentItem->RootItem(); - for (auto item : root->m_childItems) + while (searchItem != rootItem) { - result = GetInstanceItem(labelValue, item); - if (nullptr != result) return result; - } + resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); + if (nullptr != resultItem) break; - if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) - { - return root; + //no next closest label instance on this level -> check for closest before + resultItem = GetFirstInstanceLikeItem(searchItem->PrevSibblingItem()); + if (nullptr != resultItem) break; + + //no closest label instance before current on this level -> moeve one level up + searchItem = searchItem->ParentItem(); } - return nullptr; + if (nullptr == resultItem) + return QModelIndex(); + + return GetIndexByItem(resultItem, this); } -QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem * group) -{ - if (nullptr != group) - { - auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; - auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); - if (group->m_childItems.end() != finding) - { - return *finding; - } - } +///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance +//or instance node). If current index is at the end, an invalid index is returned.*/ +//QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; - return nullptr; -} Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { - if (item->HandleAsInstance()) + if (item->HandleAsInstance() && + ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || + (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } return true; } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } return true; } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { this->RemoveObserver(); this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { auto labelSet = m_Segmentation->GetLabelSet(groupID); for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) { if (lIter->first== mitk::LabelSetImage::UnlabeledLabelValue || lIter->second == m_Segmentation->GetExteriorLabel()) continue; bool newItemCreated = false; AddLabelToGroupTree(lIter->second, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } void QmitkMultiLabelTreeModel::AddObserver() { if (this->m_Segmentation.IsNotNull()) { if (m_Observed) { MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); m_Observed = true; } } void QmitkMultiLabelTreeModel::RemoveObserver() { if (this->m_Segmentation.IsNotNull()) { this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); } m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { SpatialGroupIndexType groupIndex = 0; if (m_Segmentation->IsLabeInGroup(labelValue, groupIndex)) { auto label = m_Segmentation->GetLabel(labelValue); if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; if (label == m_Segmentation->GetExteriorLabel()) return; auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); bool newLabelCreated = false; auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); if (newLabelCreated) { if (groupItem->m_childItems.size() == 1) { //first label added auto groupIndex = GetIndexByItem(groupItem, this); emit dataChanged(groupIndex, groupIndex); } else { //whole new label level added to group item auto groupIndex = GetIndexByItem(groupItem, this); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } } else - { // instance item was added to existing label item - auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); - this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); - this->endInsertRows(); + { + if (instanceItem->ParentItem()->m_childItems.size() < 3) + { //second instance item was added, so label item will now able to colapse + // -> the whole label node has to be updated. + auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); + emit dataChanged(labelIndex, labelIndex); + } + else + { + // instance item was added to existing label item with multiple instances + //-> just notify the row insertion + auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); + this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); + this->endInsertRows(); + } } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == m_Segmentation->GetExteriorLabel()->GetValue()) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { if (labelValue == m_Segmentation->GetExteriorLabel()->GetValue()) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); - if (labelItem->m_childItems.size() > 1) + if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } + else if (labelItem->m_childItems.size() == 2) + { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). + auto labelIndex = GetIndexByItem(labelItem, this); + this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); + labelItem->RemoveChild(instanceItem->Row()); + this->endRemoveRows(); + emit dataChanged(labelIndex, labelIndex); + } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::OnGroupAdded(SpatialGroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } void QmitkMultiLabelTreeModel::OnGroupModified(SpatialGroupIndexType groupIndex) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(SpatialGroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } +void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) +{ + m_AllowVisibilityModification = vmod; +} + +bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const +{ + return m_AllowVisibilityModification; +} + +void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) +{ + m_AllowLockModification = lmod; +} + +bool QmitkMultiLabelTreeModel::GetAllowLockModification() const +{ + return m_AllowLockModification; +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h index 1cef2abe4d..bafdf0ce68 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h @@ -1,108 +1,149 @@ /*============================================================================ 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 QmitkMultiLabelTreeModel_h #define QmitkMultiLabelTreeModel_h #include "mitkLabelSetImage.h" // qt #include #include "MitkSegmentationUIExports.h" class QmitkMultiLabelSegTreeItem; /*! \class QmitkMultiLabelTreeModel The class is used to represent the information of an MITK MultiLabel segmentation instance (labels, spacial groups...). */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelTreeModel : public QAbstractItemModel { Q_OBJECT public: using LabelValueType = mitk::LabelSetImage::LabelValueType; using SpatialGroupIndexType = mitk::LabelSetImage::SpatialGroupIndexType; QmitkMultiLabelTreeModel(QObject *parent = nullptr); ~QmitkMultiLabelTreeModel() override; void SetSegmentation(mitk::LabelSetImage* segmentation); const mitk::LabelSetImage* GetSegmentation() const; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; + /** returns the index of a passed label value (always first column). If label value does not exist in + segmentation or segmentation is not set an invalid index will be returned.*/ + QModelIndex indexOfLabel(mitk::Label::PixelType labelValue) const; + QModelIndex indexOfGroup(mitk::LabelSetImage::SpatialGroupIndexType groupIndex) const; + /** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance + or instance node). If current index is at the end, an invalid index is returned.*/ + QModelIndex ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const; + + ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance + //or instance node). If current index is at the end, an invalid index is returned.*/ + //QModelIndex PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; + enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; enum ItemModelRole { + /**This role returns the label object that is associated with an index. + - On group level it always returns an invalid QVariant + - On label level (with multiple instances) it returns the first label instance). + - On instance level it returns the label instance object.*/ LabelDataRole = 64, - LabelValueRole = 65, //this role returns the value of the label instance. - //If index is in a row that does not represent a a Label instance - //an invalid QVarient will be returned. + /**This role returns only the label value of the label that would be returned by + LabelDataRole.*/ + LabelValueRole = 65, + /**Simelar to LabelDataRole, but only returns a valid QVariant if index points only to + a specific instance (so either instance level or label level with only one instance). + You can use that role if you want to assure that only one specific label instance is + referenced by the index.*/ + LabelInstanceDataRole = 66, + /**Simelar to LabelValueRole, but like LabelInstanceDataRole only returns a valid QVariant + if index points only to a specific instance (so either instance level or label + level with only one instance). + You can use that role if you want to assure that only one specific label instance is + referenced by the index.*/ + LabelInstanceValueRole = 67 }; -signals: + bool GetAllowVisibilityModification() const; + bool GetAllowLockModification() const; + +public Q_SLOTS: + void SetAllowVisibilityModification(bool vmod); + void SetAllowLockModification(bool lmod); + +Q_SIGNALS: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: void OnLabelAdded(LabelValueType labelValue); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(SpatialGroupIndexType groupIndex); void OnGroupModified(SpatialGroupIndexType groupIndex); void OnGroupRemoved(SpatialGroupIndexType groupIndex); private: void AddObserver(); void RemoveObserver(); void UpdateInternalTree(); void GenerateInternalGroupTree(unsigned int layerID, QmitkMultiLabelSegTreeItem* layerItem); QmitkMultiLabelSegTreeItem* GenerateInternalTree(); /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] --> 3. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); mitk::LabelSetImage::Pointer m_Segmentation; std::mutex m_Mutex; std::unique_ptr m_RootItem; bool m_Observed; bool m_ShowGroups = true; + + bool m_ShowVisibility = true; + bool m_ShowLock = true; + bool m_ShowOther = false; + + bool m_AllowVisibilityModification = true; + bool m_AllowLockModification = true; }; #endif // mitkQmitkMultiLabelTreeModel_h diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp index b6cba705c8..e6ed2c7835 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp @@ -1,31 +1,31 @@ /*============================================================================ 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 #include QmitkMultiLabelTreeView::QmitkMultiLabelTreeView(QWidget* parent) : QTreeView(parent) { } QItemSelectionModel::SelectionFlags QmitkMultiLabelTreeView::selectionCommand(const QModelIndex& index, const QEvent* event) const { - auto value = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelValueRole); + auto value = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (index.column()!=0 || !value.isValid()) { return QItemSelectionModel::NoUpdate; } return QAbstractItemView::selectionCommand(index, event); }