diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index 9694021070..1219edc8da 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,361 +1,361 @@ /*============================================================================ 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 = [this]() { std::vector result = { 0 }; for (auto [value, label] : this->m_LabelContainer) { result.emplace_back(value); } return result; }; } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.GetActiveLabel()->GetValue()), 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; } 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; } void 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; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); PixelType pixelValue = newLabel->GetValue(); if (!m_LabelContainer.empty()) { auto usedValues = m_ReservedLabelValuesFunctor(); 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); - SetActiveLabel(newLabel->GetValue()); AddLabelEvent.Send(newLabel->GetValue()); + SetActiveLabel(newLabel->GetValue()); Modified(); } void mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); 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) { 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 SetActiveLabel(m_LabelContainer.rbegin()->first); } 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; 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/QmitkMultiLabelSegmentationInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.cpp index 6e7be2c9dd..d5fff800f7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.cpp @@ -1,140 +1,163 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include QmitkMultiLabelSegmentationInspector::QmitkMultiLabelSegmentationInspector(QWidget* parent/* = nullptr*/) : QWidget(parent) { m_Controls.setupUi(this); m_Model = new QmitkMultiLabelSegmentationTreeModel(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); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelSegmentationInspector::OnModelReset); - //connect(m_Controls.view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(ChangeModelSelection(const QItemSelection&, const QItemSelection&))); + connect(m_Controls.view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(ChangeModelSelection(const QItemSelection&, const QItemSelection&))); } void QmitkMultiLabelSegmentationInspector::Initialize() { + m_LastValidSelectedLabels = {}; m_Model->SetSegmentation(m_Segmentation.Lock()); m_Controls.view->expandAll(); } void QmitkMultiLabelSegmentationInspector::SetSelectionMode(SelectionMode mode) { m_Controls.view->setSelectionMode(mode); } QmitkMultiLabelSegmentationInspector::SelectionMode QmitkMultiLabelSegmentationInspector::GetSelectionMode() const { return m_Controls.view->selectionMode(); } void QmitkMultiLabelSegmentationInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation.Lock()) { m_Segmentation = segmentation; this->Initialize(); } } void QmitkMultiLabelSegmentationInspector::OnModelReset() { + m_LastValidSelectedLabels = {}; } bool EqualLabelSelections(const QmitkMultiLabelSegmentationInspector::LabelValueVectorType& selection1, const QmitkMultiLabelSegmentationInspector::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 QmitkMultiLabelSegmentationInspector::SetSelectedLabels(LabelValueVectorType selectedLabels) +void QmitkMultiLabelSegmentationInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { - bool equal = EqualLabelSelections(this->GetSelectedLabels(), selectedLabels); if (equal) { return; } + this->UpdateSelectionModel(selectedLabels); + m_LastValidSelectedLabels = selectedLabels; +} + +void QmitkMultiLabelSegmentationInspector::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), QmitkMultiLabelSegmentationTreeModel::ItemModelRole::LabelValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls.view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect); } void QmitkMultiLabelSegmentationInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } -QmitkMultiLabelSegmentationInspector::LabelValueVectorType QmitkMultiLabelSegmentationInspector::GetSelectedLabels() const +QmitkMultiLabelSegmentationInspector::LabelValueVectorType QmitkMultiLabelSegmentationInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls.view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelSegmentationTreeModel::ItemModelRole::LabelValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } +QmitkMultiLabelSegmentationInspector::LabelValueVectorType QmitkMultiLabelSegmentationInspector::GetSelectedLabels() const +{ + return m_LastValidSelectedLabels; +} + void QmitkMultiLabelSegmentationInspector::ChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected) { - emit CurrentSelectionChanged(GetSelectedLabels()); + auto internalSelection = GetSelectedLabelsFromSelectionModel(); + if (internalSelection.empty()) + { + //empty selections are not allowed by UI interactions, there should always be at least on label selected. + //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) + UpdateSelectionModel(m_LastValidSelectedLabels); + } + else + { + m_LastValidSelectedLabels = internalSelection; + emit CurrentSelectionChanged(GetSelectedLabels()); + } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.h index e1dc0b3f43..5c662eb4b7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegmentationInspector.h @@ -1,100 +1,103 @@ /*============================================================================ 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_QmitkMultiLabelSegmentationInspector.h" class QmitkMultiLabelSegmentationTreeModel; class QStyledItemDelegate; /* * @brief This is an inspector that offers a simple list view on a data storage. */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelSegmentationInspector : public QWidget { Q_OBJECT public: QmitkMultiLabelSegmentationInspector(QWidget* parent = nullptr); using SelectionMode = QAbstractItemView::SelectionMode; void SetSelectionMode(SelectionMode mode); SelectionMode GetSelectionMode() const; /** * @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); using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels); public Q_SLOTS: /** * @brief Transform a list label values into a model selection and set this as a new selection of the view * * @param selectedNodes A list of data nodes that should be newly selected. */ - void SetSelectedLabels(LabelValueVectorType selectedLabels); + void SetSelectedLabels(const LabelValueVectorType& selectedLabels); void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); protected: void Initialize(); void OnModelReset(); QmitkMultiLabelSegmentationTreeModel* m_Model; mitk::WeakPointer m_Segmentation; + LabelValueVectorType m_LastValidSelectedLabels; QStyledItemDelegate* m_LockItemDelegate; QStyledItemDelegate* m_ColorItemDelegate; QStyledItemDelegate* m_VisibilityItemDelegate; Ui_QmitkMultiLabelSegmentationInspector m_Controls; + LabelValueVectorType GetSelectedLabelsFromSelectionModel() const; + void UpdateSelectionModel(const LabelValueVectorType& selectedLabels); private Q_SLOTS: /** * @brief Transform a labels selection into a data node list and emit the 'CurrentSelectionChanged'-signal. * * The function adds the selected nodes from the original selection that could not be modified, if * 'm_SelectOnlyVisibleNodes' is false. * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member item view. * * @param selected The newly selected items. * @param deselected The newly deselected items. */ void ChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); }; #endif // QMITKDATASTORAGELISTINSPECTOR_H