diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index 1c9a0c9725..aa00e18d04 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,375 +1,377 @@ /*============================================================================ 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 "mitkLabelSet.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); m_ReservedLabelValuesFunctor = nullptr; } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.m_ActiveLabelValue), m_Layer(other.GetLayer()) { // clone Labels auto otherIt = other.IteratorConstBegin(); for (; otherIt != other.IteratorConstEnd(); ++otherIt) { m_LabelContainer[otherIt->first] = otherIt->second->Clone(); itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); } m_ReservedLabelValuesFunctor = other.m_ReservedLabelValuesFunctor; } std::vector mitk::LabelSet::GetUsedLabelValues() const { std::vector result = { 0 }; if (m_ReservedLabelValuesFunctor != nullptr) { result = m_ReservedLabelValuesFunctor(); } else { for (auto [value, label] : this->m_LabelContainer) { result.emplace_back(value); } } return result; } void mitk::LabelSet::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; ModifyLabelEvent.Send(label->GetValue()); Superclass::Modified(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const { return m_LabelContainer.begin(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() { return m_LabelContainer.begin(); } unsigned int mitk::LabelSet::GetNumberOfLabels() const { return m_LabelContainer.size(); } void mitk::LabelSet::SetLayer(unsigned int layer) { m_Layer = layer; Modified(); } void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) { m_ActiveLabelValue = pixelValue; ActiveLabelEvent.Send(pixelValue); Modified(); } bool mitk::LabelSet::ExistLabel(PixelType pixelValue) { return m_LabelContainer.count(pixelValue) > 0 ? true : false; } mitk::Label* mitk::LabelSet::AddLabel(mitk::Label *label, bool addAsClone) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LabelContainer.size() >= max_size) return nullptr; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); PixelType pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { pixelValue = usedValues.back()+1; MITK_DEBUG << "LabelSet label collision. Tried to add a label with a value already in use. Value will be adapted. Old value: " << newLabel->GetValue() << "; new value: " << pixelValue; newLabel->SetValue(pixelValue); } // new map entry m_LabelContainer[pixelValue] = newLabel; UpdateLookupTable(pixelValue); // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); newLabel->AddObserver(itk::ModifiedEvent(), command); AddLabelEvent.Send(newLabel->GetValue()); SetActiveLabel(newLabel->GetValue()); Modified(); return newLabel; } mitk::Label* mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel); } void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) { mitk::Label *label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) { m_LookupTable = lut; Modified(); } void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const { } void mitk::LabelSet::RemoveLabel(PixelType pixelValue) { if (LabelSetImage::UnlabeledLabelValue == pixelValue) return; auto it = m_LabelContainer.rbegin(); PixelType nextActivePixelValue = it->first; for (; it != m_LabelContainer.rend(); ++it) { if (it->first == pixelValue) { it->second->RemoveAllObservers(); m_LabelContainer.erase(pixelValue); break; } nextActivePixelValue = it->first; } if (m_ActiveLabelValue == pixelValue) { if (ExistLabel(nextActivePixelValue)) SetActiveLabel(nextActivePixelValue); - else + else if (!m_LabelContainer.empty()) SetActiveLabel(m_LabelContainer.rbegin()->first); + else + SetActiveLabel(0); } RemoveLabelEvent.Send(pixelValue); Modified(); } void mitk::LabelSet::RemoveAllLabels() { auto _it = IteratorBegin(); for (; _it != IteratorConstEnd();) { auto labelValue = _it->first; m_LabelContainer.erase(_it++); RemoveLabelEvent.Send(labelValue); } AllLabelsModifiedEvent.Send(); } void mitk::LabelSet::SetNextActiveLabel() { auto it = m_LabelContainer.find(m_ActiveLabelValue); if (it != m_LabelContainer.end()) ++it; if (it == m_LabelContainer.end()) { it = m_LabelContainer.begin(); if (m_LabelContainer.size() > 1) ++it; // ...skip background label! } SetActiveLabel(it->first); } void mitk::LabelSet::SetAllLabelsLocked(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) _it->second->SetLocked(value); AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::SetAllLabelsVisible(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) { _it->second->SetVisible(value); UpdateLookupTable(_it->first); } AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) { const mitk::Color &color = GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) { if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) return nullptr; return m_LabelContainer[pixelValue]; } const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const { auto it = m_LabelContainer.find(pixelValue); if (it == m_LabelContainer.end()) return nullptr; return it->second.GetPointer(); } bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // LabelSetmembers MITK_INFO(verbose) << "--- LabelSet Equal ---"; // m_LookupTable; const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // m_ActiveLabel; if (leftHandSide.GetActiveLabel() != rightHandSide.GetActiveLabel()) { returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Active label not equal."; return returnValue; ; } } // m_Layer; returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); if (!returnValue) { MITK_INFO(verbose) << "Layer index not equal."; return returnValue; ; } // container size; returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // Label container (map) // m_LabelContainer; auto lhsit = leftHandSide.IteratorConstBegin(); auto rhsit = rightHandSide.IteratorConstBegin(); for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) { returnValue = rhsit->first == lhsit->first; if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 534ed8cfbe..5d92fb78f7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,793 +1,1101 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include "ui_QmitkMultiLabelInspectorControls.h" QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); this->m_Controls->view->setItemDelegateForColumn(1, m_LockItemDelegate); this->m_Controls->view->setItemDelegateForColumn(2, m_ColorItemDelegate); this->m_Controls->view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); this->m_Controls->view->header()->setSectionResizeMode(0,QHeaderView::Stretch); this->m_Controls->view->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); this->m_Controls->view->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); this->m_Controls->view->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); this->m_Controls->view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(m_Controls->view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(m_Controls->view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(m_Controls->view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0 ) { //in singel selection mode, if at least one label exist select the first label of the mode. auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); + m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { if (multiMode) { m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); } else { m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); } } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; this->m_Model->SetAllowVisibilityModification(vmod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; this->m_Model->SetAllowLockModification(lmod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists auto lambda = [](mitk::LabelSetImage::LabelValueType lhs, mitk::LabelSetImage::LabelValueType rhs) { return lhs == rhs; }; return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { bool equal = EqualLabelSelections(this->GetSelectedLabels(), selectedLabels); if (equal) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { - auto f = matched.front(); - auto d = f.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); newCurrentSelection.select(matched.front(), matched.front()); } } - m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect); + 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 : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } -mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() +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 = this->m_Controls->view->currentIndex(); + return this->m_Model->GetLabelsInSubTree(currentIndex); +} + +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."; - auto currentLabel = this->GetFirstSelectedLabelObject(); - if (nullptr == currentLabel) - return nullptr; + 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(currentLabel->GetValue()); + auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; - auto newLabel = group->AddLabel(currentLabel, true); + auto newLabel = group->AddLabel(templateLabel, 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::AddNewLabelInstance() +{ + auto currentLabel = this->GetFirstSelectedLabelObject(); + if (nullptr == currentLabel) + return nullptr; + + return this->AddNewLabelInstanceInternal(currentLabel); +} + mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::SpatialGroupIndexType& containingGroup) { mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) { emit LabelRenameRequested(newLabel, false); } auto group = m_Segmentation->GetLabelSet(containingGroup); m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { 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->DeleteLabelInternal({ currentLabel->GetValue() }); + } +} - this->WaitCursorOn(); - m_ModelManipulationOngoing = true; - m_Segmentation->RemoveLabel(currentLabel->GetValue()); - m_ModelManipulationOngoing = false; - this->WaitCursorOff(); +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 (labelVariant.isValid()) - { - auto newLabelValue = labelVariant.value(); - this->SetSelectedLabel(newLabelValue); + if (m_Segmentation.IsNull()) + { + return; + } - 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; - } + 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); } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + 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; + } + } + + 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 group", "Could not add a new group. See error log for details.\n"); } m_ModelManipulationOngoing = false; return newLabel; } -void QmitkMultiLabelInspector::RemoveGroup() +void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::SpatialGroupIndexType& groupID) { 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 group with all labels?"; - QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Delete group", 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 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->RemoveSpatialGroup(currentGroup); + m_Segmentation->RemoveSpatialGroup(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.\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*/) +void QmitkMultiLabelInspector::RemoveGroup() { - auto selectedLabelValues = this->GetSelectedLabels(); - if (m_Segmentation.IsNull() || !this->isEnabled() || selectedLabelValues.empty()) + 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 group of the selected label with all labels?"; + QMessageBox::StandardButton answerButton = QMessageBox::question( + this, "Delete group", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - if (nullptr == this->GetCurrentLabel()) - return; //for now we only have context menues if we realy click on an instance - //context menues for group level and label class level will be added later. + if (answerButton != QMessageBox::Yes) + { + return; + } + + auto currentLabel = GetFirstSelectedLabelObject(); + const auto currentGroup = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); + + this->RemoveGroupInternal(currentGroup); +} + +void QmitkMultiLabelInspector::OnDeleteGroup() +{ + if (!m_AllowLabelModification) + mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; - QMenu* menu = new QMenu(this); + if (m_Segmentation.IsNull()) + { + return; + } + auto currentIndex = this->m_Controls->view->currentIndex(); + auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); - if (this->GetMultiSelectionMode() && selectedLabelValues.size()>1) + if (groupIDVariant.isValid()) { - 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); + auto groupID = groupIDVariant.value(); + + QString question = "Do you really want to delete the current group with all labels?"; + QMessageBox::StandardButton answerButton = QMessageBox::question( + this, QString("Delete group ") + QString::number(groupID), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - 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); + if (answerButton != QMessageBox::Yes) + { + return; + } - 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); + this->RemoveGroupInternal(groupID); } - else +}; + + +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(QIcon(":/Qmitk/RenameLabel.png"), "Add label instance...", this); + QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label", this); addInstanceAction->setEnabled(true); - QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::AddNewLabelInstance); + QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); 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); + QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Delete group", this); removeAction->setEnabled(true); - QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::RemoveLabel); + QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); 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) { + menu->addSeparator(); QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock group", this); lockAllAction->setEnabled(true); - QObject::connect(lockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnLockAllLabels(bool))); + QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock group", this); unlockAllAction->setEnabled(true); - QObject::connect(unlockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnUnlockAllLabels(bool))); + QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); 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); + menu->addSeparator(); QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View group", this); viewAllAction->setEnabled(true); - QObject::connect(viewAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsVisible(bool))); + QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide group", this); hideAllAction->setEnabled(true); - QObject::connect(hideAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsInvisible(bool))); + QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); 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) + 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(QIcon(":/Qmitk/RenameLabel.png"), "Add label instance", this); + addInstanceAction->setEnabled(true); + QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); + menu->addAction(addInstanceAction); + + QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label class", 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"), "&Delete label class", this); + removeAction->setEnabled(true); + QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); + menu->addAction(removeAction); + } + + if (m_AllowLockModification) + { + menu->addSeparator(); + QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock label instances", this); + lockAllAction->setEnabled(true); + QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); + menu->addAction(lockAllAction); + + QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock label instances", this); + unlockAllAction->setEnabled(true); + QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); + menu->addAction(unlockAllAction); + } + + if (m_AllowVisibilityModification) + { + menu->addSeparator(); + + QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View label instances", this); + viewAllAction->setEnabled(true); + QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); + menu->addAction(viewAllAction); + + QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide label instances", this); + hideAllAction->setEnabled(true); + QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); + menu->addAction(hideAllAction); + + 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); + + 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); + mergeAction->setEnabled(true); + QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); + menu->addAction(mergeAction); + + QAction* removeLabelsAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete selected labels", this); + removeLabelsAction->setEnabled(true); + QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabels(bool))); + menu->addAction(removeLabelsAction); + + QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); + clearLabelsAction->setEnabled(true); + QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); + menu->addAction(clearLabelsAction); + } + else + { + if (m_AllowLabelModification) { - 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()); + QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label instance...", this); + addInstanceAction->setEnabled(true); + QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); + 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"), "&Delete", this); + removeAction->setEnabled(true); + QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::RemoveLabel); + menu->addAction(removeAction); + + QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); + clearAction->setEnabled(true); + QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); + menu->addAction(clearAction); + } + + if (m_AllowVisibilityModification) + { + menu->addSeparator(); + 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); + + menu->addSeparator(); + + auto opacityAction = this->CreateOpacityAction(); + if (nullptr != opacityAction) menu->addAction(opacityAction); } - ); - 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()); + } +} +QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() +{ + auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); + std::vector relevantLabels; + if (!relevantLabelValues.empty()) + { + //we assume here that all affacted label belong to one group. + auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); + auto group = m_Segmentation->GetLabelSet(groupID); + + 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); + } + + QSlider* 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, group](const int value) + { + float opacity = static_cast(value) / 100.0f; + for (auto label : relevantLabels) + { + label->SetOpacity(opacity); + group->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; } - menu->popup(QCursor::pos()); + + return nullptr; } -void QmitkMultiLabelInspector::OnEraseLabels(bool /*value*/) +void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { - QString question = "Do you really want to erase the selected labels?"; + QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Erase selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + 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(); } } +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 label instances of class \""; + question.append( + QString::fromStdString(currentLabel->GetName())); + question.append("\"?"); + + QMessageBox::StandardButton answerButton = + QMessageBox::question(this, "Delete label class", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + + if (answerButton == QMessageBox::Yes) + { + this->DeleteLabelInternal(affectedLabels); + } +} + void QmitkMultiLabelInspector::OnRemoveLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \""; question.append( QString::fromStdString(currentLabel->GetName())); question.append("\"?"); QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } -void QmitkMultiLabelInspector::OnAddLabelInstance(bool /*value*/) +void QmitkMultiLabelInspector::OnAddLabel() { - this->AddNewLabelInstance(); + auto currentIndex = this->m_Controls->view->currentIndex(); + auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); + + if (groupIDVariant.isValid()) + { + auto groupID = groupIDVariant.value(); + this->AddNewLabelInternal(groupID); + } } -void QmitkMultiLabelInspector::OnEraseLabel(bool /*value*/) +void QmitkMultiLabelInspector::OnAddLabelInstance() +{ + auto currentLabel = this->GetCurrentLabel(); + if (nullptr == currentLabel) + return; + + this->AddNewLabelInstanceInternal(currentLabel); +} + +void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); - QString question = "Do you really want to erase the contents of label \""; + QString question = "Do you really want to clear 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); + 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(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { - auto currentLabel = GetFirstSelectedLabelObject(); + auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); + auto currentLabel = this->GetCurrentLabel(); + emit LabelRenameRequested(currentLabel, true); + + //we assume here that all affacted label belong to one group. + auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); + auto group = m_Segmentation->GetLabelSet(groupID); + + 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()); + group->UpdateLookupTable(label->GetValue()); + } + } } -void QmitkMultiLabelInspector::OnUnlockAllLabels(bool /*value*/) + +void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { - auto currentLabel = GetFirstSelectedLabelObject(); + auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - group->SetAllLabelsLocked(false); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + if (!relevantLabelValues.empty()) + { + //we assume here that all affacted label belong to one group. + auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); + auto group = m_Segmentation->GetLabelSet(groupID); + + 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::OnLockAllLabels(bool /*value*/) +void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { - auto currentLabel = GetFirstSelectedLabelObject(); + this->SetLockOfAffectedLabels(false); +} - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - group->SetAllLabelsLocked(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +void QmitkMultiLabelInspector::OnLockAffectedLabels() +{ + this->SetLockOfAffectedLabels(true); } -void QmitkMultiLabelInspector::OnSetAllLabelsVisible(bool /*value*/) +void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { - auto currentLabel = GetFirstSelectedLabelObject(); + auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - group->SetAllLabelsVisible(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + if (!relevantLabelValues.empty()) + { + //we assume here that all affacted label belong to one group. + auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); + auto group = m_Segmentation->GetLabelSet(groupID); + + 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); + group->UpdateLookupTable(label->GetValue()); + } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } } -void QmitkMultiLabelInspector::OnSetAllLabelsInvisible(bool /*value*/) +void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { - auto currentLabel = GetFirstSelectedLabelObject(); + this->SetVisibilityOfAffectedLabels(true); +} - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - group->SetAllLabelsVisible(false); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() +{ + this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h index 4cd2189862..867be63e7f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,218 +1,250 @@ /*============================================================================ 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; +class QWidgetAction; 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.*/ + is not over a label (label class or instance item), the method will return a null pointer.*/ mitk::Label* GetCurrentLabel() const; + enum class IndexLevelType + { + Group, + LabelClass, + LabelInstance + }; + + /** Returns the level of the index 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).*/ + IndexLevelType GetCurrentLevelType() const; + + /** + * @brief Returns the all labels values that are currently affacted. + 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; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels) const; void GoToLabel(LabelValueType label, const mitk::Point3D&) const; /** Signal that is emitted, if a label should be (re) named and default label naming is deactivated. The instance for which a new name is requested is passed with the signal. @param label Pointer to the instance that needs a (new) name. @param rename Indicates if it is a renaming or naming of a new label.*/ void LabelRenameRequested(mitk::Label* label, bool rename) const; public Q_SLOTS: /** * @brief Transform a list label values into the new selection of the inspector. * @param selectedNodes A list of selected label values. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief The passed label will be used as new selection in the widget * @param selectedLabel Value of the selected label. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetMultiSelectionMode(bool multiMode); void SetAllowVisibilityModification(bool vmod); void SetAllowLockModification(bool lmod); void SetAllowLabelModification(bool labelMod); void SetDefaultLabelNaming(bool defaultLabelNaming); - /** Adds an instance of the same label/class like the label instance - * returned by GetCurrentLabel() to the segmentation. This new label - * instance is returned by the function. If the inspector has no current label, + /** 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 current label instance. Therefore + * @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(); /** 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 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(); /** 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(); + 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); /** 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); + /** Adds an instance of the same label/class like the passed label value*/ + mitk::Label* AddNewLabelInstanceInternal(mitk::Label* templateLabel); + + void RemoveGroupInternal(const mitk::LabelSetImage::SpatialGroupIndexType& 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 OnAddLabelInstance(bool); + void OnAddLabel(); + void OnAddLabelInstance(); + void OnDeleteGroup(); + void OnDeleteAffectedLabel(); void OnRemoveLabels(bool); - void OnEraseLabels(bool); + void OnClearLabels(bool); void OnMergeLabels(bool); void OnRenameLabel(bool); - void OnEraseLabel(bool); + void OnClearLabel(bool); - void OnUnlockAllLabels(bool); - void OnLockAllLabels(bool); + void OnUnlockAffectedLabels(); + void OnLockAffectedLabels(); - void OnSetAllLabelsVisible(bool); - void OnSetAllLabelsInvisible(bool); + 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; /** 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 2454a7837f..bf8a5518ba 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,931 +1,969 @@ /*============================================================================ 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 "mitkRenderingManager.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_parentItem(parentItem), m_ItemType(type), m_Label(label), 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 std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type 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 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 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 currentItem."; } return label->GetValue(); }; + /** returns a vector containing all label values of referenced by this item or its child items.*/ + std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const + { + if (this->m_ItemType == ItemType::Instance) + { + return { this->GetLabelValue() }; + } + + std::vector< mitk::LabelSetImage::LabelValueType> result; + for (const auto child : this->m_childItems) + { + auto childresult = child->GetLabelsInSubTree(); + result.reserve(result.size() + childresult.size()); + result.insert(result.end(), childresult.begin(), childresult.end()); + } + + return result; + } + std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; 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 currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) { name = name + QString(" (") + QString::number(item->m_childItems.size()) + QString(" instances)"); } return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" #") + 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::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } + else if (role == ItemModelRole::GroupIDRole) + { + return QVariant(item->GetGroupID()); + } 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()); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } 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 QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); 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."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); if (nullptr != resultItem) break; //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(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); 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."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** 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; +std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const +{ + const QmitkMultiLabelSegTreeItem* currentItem = nullptr; + + if (!currentIndex.isValid()) + { + currentItem = this->m_RootItem.get(); + } + else + { + currentItem = static_cast(currentIndex.internalPointer()); + } + + if (!currentItem) return {}; + + return currentItem->GetLabelsInSubTree(); +} 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() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } 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) 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 (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) 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 { 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); this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); this->endInsertRows(); } 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 == mitk::LabelSetImage::UnlabeledLabelValue) 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 == mitk::LabelSetImage::UnlabeledLabelValue) 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() > 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 ad3f42370c..b1d76ede55 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h @@ -1,153 +1,158 @@ /*============================================================================ 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 first child node (or itself) 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. If an invalid index is passed into the methods, the search starts at the root; thus the whole tree is search for the first label instance.*/ QModelIndex FirstLabelInstanceIndex(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; + /** Returns a vector containing all label values of the passed currentIndex or its child items.*/ + std::vector GetLabelsInSubTree(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, /**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 + LabelInstanceValueRole = 67, + /**This role returns the group ID the item/index belongs to.*/ + GroupIDRole = 68 }; 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