diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp index b82ed8585e..8969b03af1 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp @@ -1,521 +1,535 @@ /*============================================================================ 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 "QmitkImageStatisticsTreeModel.h" #include "QmitkImageStatisticsTreeItem.h" #include "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "QmitkStyleManager.h" QmitkImageStatisticsTreeModel::QmitkImageStatisticsTreeModel(QObject *parent) : QmitkAbstractDataStorageModel(parent) { m_RootItem = std::make_unique(); } QmitkImageStatisticsTreeModel ::~QmitkImageStatisticsTreeModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); }; void QmitkImageStatisticsTreeModel::DataStorageChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodePredicateChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } int QmitkImageStatisticsTreeModel::columnCount(const QModelIndex& /*parent*/) const { int columns = m_StatisticNames.size() + 1; return columns; } int QmitkImageStatisticsTreeModel::rowCount(const QModelIndex &parent) const { QmitkImageStatisticsTreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = m_RootItem.get(); else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } QVariant QmitkImageStatisticsTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); QmitkImageStatisticsTreeItem* item = static_cast(index.internalPointer()); if (role == Qt::DisplayRole) { return item->data(index.column()); } else if (role == Qt::DecorationRole && index.column() == 0) { if (item->isWIP() && item->childCount() == 0) return QVariant(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/hourglass-half-solid.svg"))); else if (!item->isWIP()) { auto label = item->GetLabelInstance(); if (label.IsNotNull()) { QPixmap pixmap(QSize(20,20)); QColor color(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255); pixmap.fill(color); return QVariant(QIcon(pixmap)); } } } return QVariant(); } QModelIndex QmitkImageStatisticsTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); QmitkImageStatisticsTreeItem *parentItem; if (!parent.isValid()) parentItem = m_RootItem.get(); else parentItem = static_cast(parent.internalPointer()); QmitkImageStatisticsTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkImageStatisticsTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkImageStatisticsTreeItem *childItem = static_cast(child.internalPointer()); QmitkImageStatisticsTreeItem *parentItem = childItem->parentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } Qt::ItemFlags QmitkImageStatisticsTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return {}; return QAbstractItemModel::flags(index); } QVariant QmitkImageStatisticsTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (section == 0) { return m_HeaderFirstColumn; } else { return QVariant(m_StatisticNames.at(section - 1).c_str()); } } return QVariant(); } void QmitkImageStatisticsTreeModel::SetImageNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedImageNodes = std::move(tempNodes); m_ImageNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::SetMaskNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); // special case: apply one mask to each time step of an 4D image if (timeSteps == 1 && m_TimeStepResolvedImageNodes.size() > 1) { timeSteps = m_TimeStepResolvedImageNodes.size(); } for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedMaskNodes = std::move(tempNodes); m_MaskNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::Clear() { emit beginResetModel(); m_Statistics.clear(); m_ImageNodes.clear(); m_TimeStepResolvedImageNodes.clear(); m_MaskNodes.clear(); m_StatisticNames.clear(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::SetIgnoreZeroValueVoxel(bool _arg) { if (m_IgnoreZeroValueVoxel != _arg) { emit beginResetModel(); m_IgnoreZeroValueVoxel = _arg; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } } bool QmitkImageStatisticsTreeModel::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeroValueVoxel; } void QmitkImageStatisticsTreeModel::SetHistogramNBins(unsigned int nbins) { if (m_HistogramNBins != nbins) { emit beginResetModel(); m_HistogramNBins = nbins; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } } unsigned int QmitkImageStatisticsTreeModel::GetHistogramNBins() const { return this->m_HistogramNBins; } void QmitkImageStatisticsTreeModel::UpdateByDataStorage() { StatisticsContainerVector newStatistics; auto datamanager = m_DataStorage.Lock(); if (datamanager.IsNotNull()) { for (const auto &image : m_ImageNodes) { if (m_MaskNodes.empty()) { auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), nullptr, m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } else { for (const auto &mask : m_MaskNodes) { auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData(), m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } } } if (!newStatistics.empty()) { emit dataAvailable(); } } { std::lock_guard locked(m_Mutex); m_Statistics = newStatistics; m_StatisticNames = mitk::GetAllStatisticNames(m_Statistics); BuildHierarchicalModel(); m_BuildTime.Modified(); } } void AddTimeStepTreeItems(const mitk::ImageStatisticsContainer* statistic, const mitk::DataNode* imageNode, const mitk::DataNode* maskNode, mitk::ImageStatisticsContainer::LabelValueType labelValue, const std::vector& statisticNames, bool isWIP, QmitkImageStatisticsTreeItem* parentItem, bool& hasMultipleTimesteps) { // 4. hierarchy level: time steps (optional, only if >1 time step) if (statistic->GetTimeSteps() > 1) { for (unsigned int i = 0; i < statistic->GetTimeSteps(); i++) { QString timeStepLabel = "[" + QString::number(i) + "] " + QString::number(statistic->GetTimeGeometry()->TimeStepToTimePoint(i)) + " ms"; if (statistic->StatisticsExist(labelValue, i)) { auto statisticsItem = new QmitkImageStatisticsTreeItem( statistic->GetStatistics(labelValue,i), statisticNames, timeStepLabel, isWIP, parentItem, imageNode, maskNode); parentItem->appendChild(statisticsItem); } else { auto statisticsItem = new QmitkImageStatisticsTreeItem(statisticNames, timeStepLabel, isWIP, true, parentItem, imageNode, maskNode); parentItem->appendChild(statisticsItem); } } } hasMultipleTimesteps = hasMultipleTimesteps || (statistic->GetTimeSteps() > 1); } void AddLabelTreeItems(const mitk::ImageStatisticsContainer* statistic, const mitk::DataNode* imageNode, const mitk::DataNode* maskNode, mitk::ImageStatisticsContainer::LabelValueVectorType labelValues, const std::vector& statisticNames, bool isWIP, QmitkImageStatisticsTreeItem* parentItem, bool& hasMultipleTimesteps) { // 3. hierarchy level: labels (optional, only if labels >1) for (const auto labelValue : labelValues) { if (labelValue != mitk::ImageStatisticsContainer::NO_MASK_LABEL_VALUE) { //currently we only show statistics of the labeled pixel if a mask is provided QString labelLabel = QStringLiteral("unnamed label"); const auto multiLabelSeg = dynamic_cast(maskNode->GetData()); - const mitk::Label* labelInstance = nullptr; + mitk::Label::ConstPointer labelInstance; if (nullptr != multiLabelSeg) { labelInstance = multiLabelSeg->GetLabel(labelValue); - labelLabel = QString::fromStdString(labelInstance->GetName() + " [" + labelInstance->GetTrackingID() + "]"); + if (labelInstance.IsNotNull()) + { + labelLabel = QString::fromStdString(labelInstance->GetName() + " [" + labelInstance->GetTrackingID() + "]"); + } + else + { + labelLabel = QString("Unknown label ID ") + QString::number(labelValue); + } } QmitkImageStatisticsTreeItem* labelItem = nullptr; - if (statistic->GetTimeSteps() == 1) + if (labelInstance.IsNotNull()) { - // add statistical values directly in this hierarchy level - auto statisticsObject = statistic->GetStatistics(labelValue, 0); - labelItem = new QmitkImageStatisticsTreeItem(statisticsObject, statisticNames, labelLabel, isWIP, parentItem, imageNode, maskNode, labelInstance); + if (statistic->GetTimeSteps() == 1) + { + // add statistical values directly in this hierarchy level + auto statisticsObject = statistic->GetStatistics(labelValue, 0); + labelItem = new QmitkImageStatisticsTreeItem(statisticsObject, statisticNames, labelLabel, isWIP, parentItem, imageNode, maskNode, labelInstance); + } + else + { + labelItem = new QmitkImageStatisticsTreeItem(statisticNames, labelLabel, isWIP, false, parentItem, imageNode, maskNode, labelInstance); + AddTimeStepTreeItems(statistic, imageNode, maskNode, labelValue, statisticNames, isWIP, labelItem, hasMultipleTimesteps); + } } else { - labelItem = new QmitkImageStatisticsTreeItem(statisticNames, labelLabel, isWIP, false, parentItem, imageNode, maskNode, labelInstance); - AddTimeStepTreeItems(statistic, imageNode, maskNode, labelValue, statisticNames, isWIP, labelItem, hasMultipleTimesteps); + labelItem = new QmitkImageStatisticsTreeItem(statisticNames, labelLabel, isWIP, true, parentItem, imageNode, maskNode); } parentItem->appendChild(labelItem); } } } void QmitkImageStatisticsTreeModel::BuildHierarchicalModel() { // reset old model m_RootItem.reset(new QmitkImageStatisticsTreeItem()); bool hasMask = false; bool hasMultipleTimesteps = false; std::map dataNodeToTreeItem; for (const auto &statistic : m_Statistics) { bool isWIP = statistic->IsWIP(); // get the connected image data node/mask data node auto imageRule = mitk::StatisticsToImageRelationRule::New(); auto imageOfStatisticsPredicate = imageRule->GetDestinationsDetector(statistic); auto imageFinding = std::find_if(m_ImageNodes.begin(), m_ImageNodes.end(), [&imageOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return imageOfStatisticsPredicate->CheckNode(testNode); }); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); auto maskOfStatisticsPredicate = maskRule->GetDestinationsDetector(statistic); auto maskFinding = std::find_if(m_MaskNodes.begin(), m_MaskNodes.end(), [&maskOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return maskOfStatisticsPredicate->CheckNode(testNode); }); if (imageFinding == m_ImageNodes.end()) { mitkThrow() << "no image found connected to statistic" << statistic << " Aborting."; } auto& image = *imageFinding; // image: 1. hierarchy level QmitkImageStatisticsTreeItem *imageItem = nullptr; auto search = dataNodeToTreeItem.find(image); if (search != dataNodeToTreeItem.end()) { // the tree item was created previously imageItem = search->second; } else { QString imageLabel = QString::fromStdString(image->GetName()); if (statistic->GetTimeSteps() == 1 && maskFinding == m_MaskNodes.end()) { auto labelValue = isWIP ? mitk::ImageStatisticsContainer::NO_MASK_LABEL_VALUE : statistic->GetExistingLabelValues().front(); auto statisticsObject = isWIP ? mitk::ImageStatisticsContainer::ImageStatisticsObject() : statistic->GetStatistics(labelValue, 0); // create the final statistics tree item imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem.get(), image); } else { imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, false, m_RootItem.get(), image); } m_RootItem->appendChild(imageItem); dataNodeToTreeItem.emplace(image, imageItem); } if (maskFinding != m_MaskNodes.end()) { const auto labelValues = statistic->GetExistingLabelValues(); //currently we not support showing the statistics for unlabeled pixels if a mask exist // mask: 2. hierarchy level exists auto& mask = *maskFinding; QString maskLabel = QString::fromStdString(mask->GetName()); QmitkImageStatisticsTreeItem* maskItem; if (statistic->GetTimeSteps() == 1 && labelValues.size() == 1) { // add statistical values directly in this hierarchy level auto statisticsObject = isWIP ? mitk::ImageStatisticsContainer::ImageStatisticsObject() : statistic->GetStatistics(labelValues.front(), 0); maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, isWIP, imageItem, image, mask); } else if(labelValues.empty()) { //all labels are empty -> no stats are computed maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, true, imageItem, image, mask); } else { maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, false, imageItem, image, mask); // 3. hierarchy level: labels (optional, only if more then one label in statistic) if (labelValues.size() > 1) { AddLabelTreeItems(statistic, image, mask, labelValues, m_StatisticNames, isWIP, maskItem, hasMultipleTimesteps); } else if (!labelValues.empty()) { mitk::Label::PixelType labelValue = isWIP ? 0 : labelValues.front(); AddTimeStepTreeItems(statistic, image, mask, labelValue, m_StatisticNames, isWIP, maskItem, hasMultipleTimesteps); } } imageItem->appendChild(maskItem); hasMask = true; } else { //no mask -> but multi time step auto labelValue = isWIP ? mitk::ImageStatisticsContainer::NO_MASK_LABEL_VALUE : statistic->GetExistingLabelValues().front(); AddTimeStepTreeItems(statistic, image, nullptr, labelValue, m_StatisticNames, isWIP, imageItem, hasMultipleTimesteps); } } QString headerString = "Images"; if (hasMask) { headerString += "/Masks"; } if (hasMultipleTimesteps) { headerString += "/Timesteps"; } m_HeaderFirstColumn = headerString; } void QmitkImageStatisticsTreeModel::NodeRemoved(const mitk::DataNode* changedNode) { bool isRelevantNode = (nullptr != dynamic_cast(changedNode->GetData())); if (isRelevantNode) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } } void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode * changedNode) { bool isRelevantNode = (nullptr != dynamic_cast(changedNode->GetData())); if (isRelevantNode) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } } void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode * changedNode) { bool isRelevantNode = m_ImageNodes.end() != std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); isRelevantNode = isRelevantNode || (m_MaskNodes.end() != std::find(m_MaskNodes.begin(), m_MaskNodes.end(), changedNode)); isRelevantNode = isRelevantNode || (nullptr != dynamic_cast(changedNode->GetData())); if (isRelevantNode) { if (m_BuildTime.GetMTime() < changedNode->GetData()->GetMTime()) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } } } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 1a37383aa3..da9c223c99 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1743 +1,1750 @@ /*============================================================================ 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 "mitkLabelSetImage.h" #include #include #include #include #include #include #include #include #include #include namespace mitk { template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UNLABELED_VALUE = 0; mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLabelValue(0), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLabelValue(other.m_ActiveLabelValue), m_LookupTable(other.m_LookupTable->Clone()), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { GroupIndexType i = 0; for (auto groupImage : other.m_LayerContainer) { this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); i++; } m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto [value, label] : m_LabelMap) { this->ReleaseLabel(label); } m_LabelMap.clear(); } unsigned int mitk::LabelSetImage::GetActiveLayer() const { if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/subtract one more because we have not removed the layer yet, thus the group count is to 1 high. newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; } } if (activeIndex == indexToDelete) { // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; //copy the image content of the upcoming new active layer; SetActiveLayer(newActiveIndexBeforeDeletion); } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; { std::lock_guard guard(m_LabelNGroupMapsMutex); // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->InvokeEvent(LabelRemovedEvent(labelValue)); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); } //update old indexes in m_GroupToLabelMap to new layer indexes for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->InvokeEvent(LabelsChangedEvent(relevantLabels)); this->InvokeEvent(GroupRemovedEvent(indexToDelete)); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const { LabelValueVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UNLABELED_VALUE }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image* layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); if (nullptr == layerImage) mitkThrow() << "Cannot add group. Passed group image is nullptr."; bool equalGeometries = Equal( *(this->GetTimeGeometry()), *(layerImage->GetTimeGeometry()), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false); if (!equalGeometries) mitkThrow() << "Cannot add group. Passed group image has not the same geometry like segmentation."; if (layerImage->GetPixelType() != MakePixelType()) mitkThrow() << "Cannot add group. Passed group image has incorrect pixel type. Only LabelValueType is supported. Invalid pixel type: "<< layerImage->GetPixelType().GetTypeAsString(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); m_GroupToLabelMap.push_back({}); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); } this->Modified(); this->InvokeEvent(GroupAddedEvent(newGroupID)); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-existing group. Invalid group id: "< guard(m_LabelNGroupMapsMutex); oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->InvokeEvent(LabelRemovedEvent(labelID)); } } this->InvokeEvent(LabelsChangedEvent(oldLabels)); this->InvokeEvent(GroupModifiedEvent(groupID)); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer.at(groupID).GetPointer(); } const mitk::Image* mitk::LabelSetImage::GetGroupImageWorkaround(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; if (groupID == this->GetActiveLayer() && this->GetMTime()> m_LayerContainer[groupID]->GetMTime()) { //we have to transfer the content first into the group image if (4 == this->GetDimension()) { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (groupID)); } else { AccessByItk_1(this, ImageToLayerContainerProcessing, groupID); } } return m_LayerContainer[groupID].GetPointer(); } const std::string& mitk::LabelSetImage::GetGroupName(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group name. Group ID is invalid. Invalid ID: " << groupID; return m_Groups[groupID]; } void mitk::LabelSetImage::SetGroupName(GroupIndexType groupID, const std::string& name) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot set group name. Group ID is invalid. Invalid ID: " << groupID; m_Groups[groupID] = name; this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UNLABELED_VALUE) { auto groupID = this->GetGroupIndexOfLabel(label); if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(sourcePixelValue)); this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ sourcePixelValue, pixelValue })); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->InvokeEvent(LabelModifiedEvent(vectorOfSourcePixelValues[idx])); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(pixelValue)); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->InvokeEvent(LabelsChangedEvent(modifiedValues)); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { GroupIndexType groupID = 0; { std::lock_guard guard(m_LabelNGroupMapsMutex); if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } } this->InvokeEvent(LabelRemovedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of instance. RemoveLabelFromMap was called for unknown label id. invalid label id: "<GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->InvokeEvent(LabelsChangedEvent(vectorOfLabelPixelValues)); } void mitk::LabelSetImage::EraseLabel(LabelValueType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetGroupImage(groupID); if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } groupImage->Modified(); } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); Modified(); } void mitk::LabelSetImage::EraseLabels(const LabelValueVectorType& labelValues) { for (auto labelValue : labelValues) { this->EraseLabel(labelValue); } } mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { auto usedValues = this->GetUsedLabelValues(); return usedValues.back() + 1; } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { if (nullptr == label) mitkThrow() << "Invalid use of AddLabel. label is not valid."; mitk::Label::Pointer newLabel = label; { std::lock_guard guard(m_LabelNGroupMapsMutex); unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; if (addAsClone) newLabel = label->Clone(); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = this->GetUnusedLabelValue(); newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); } this->InvokeEvent(LabelAddedEvent(newLabel->GetValue())); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabelWithContent(Label* label, const Image* labelContent, GroupIndexType groupID, LabelValueType contentLabelValue, bool addAsClone, bool correctLabelValue) { if (nullptr == labelContent) mitkThrow() << "Invalid use of AddLabel. labelContent is not valid."; if (!Equal(*(this->GetTimeGeometry()), *(labelContent->GetTimeGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) mitkThrow() << "Invalid use of AddLabel. labelContent has not the same geometry like the segmentation."; auto newLabel = this->AddLabel(label, groupID, addAsClone, correctLabelValue); mitk::TransferLabelContent(labelContent, this->GetGroupImage(groupID), this->GetConstLabelsByValue(this->GetLabelValuesByGroup(groupID)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, { {contentLabelValue, newLabel->GetValue()}}, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(LabelValueType pixelValue, const std::string& name, const mitk::Color& color) { std::shared_lock guard(m_LabelNGroupMapsMutex); - mitk::Label* label = GetLabel(pixelValue); - if (nullptr == label) mitkThrow() << "Cannot rename label.Unknown label value provided. Unknown label value:" << pixelValue; + auto label = GetLabel(pixelValue); + if (label.IsNull()) mitkThrow() << "Cannot rename label.Unknown label value provided. Unknown label value:" << pixelValue; label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); m_LookupTable->Modified(); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, pixelValue); } } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { - const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); + auto label = this->GetLabel(pixelValue); + if (label.IsNull()) mitkThrow() << "Cannot update lookup table.Unknown label value provided. Unknown label value:" << pixelValue; + + const mitk::Color& color = label->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(); + if (label->GetVisible()) + rgba[3] = label->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { if (layer >= m_Groups.size()) mitkThrow() << "Cannot get number of labels in group. Group is unknown. Invalid index:" << layer; return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (Exception& e) { mitkReThrow(e) << "Could not initialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UNLABELED_VALUE!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UNLABELED_VALUE) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; - this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); - this->GetSlicedGeometry()->IndexToWorld(pos, pos); - this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); + auto label = this->GetLabel(pixelValue); + if (label.IsNotNull()) + { + label->SetCenterOfMassIndex(pos); + this->GetSlicedGeometry()->IndexToWorld(pos, pos); + label->SetCenterOfMassCoordinates(pos); + } } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(const itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } m_LayerContainer[layer]->Modified(); } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unknown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of RegisterLabel with a nullptr."; UpdateLookupTable(label->GetValue()); m_LookupTable->Modified(); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); m_LabelModEventGuardMap.emplace(label->GetValue(), ITKEventObserverGuard(label, itk::ModifiedEvent(), command)); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; m_LabelModEventGuardMap.erase(label->GetValue()); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); this->InvokeEvent(LabelsChangedEvent(values)); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const { auto labels = this->GetConstLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); } void mitk::LabelSetImage::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."; Superclass::Modified(); this->InvokeEvent(LabelModifiedEvent(label->GetValue())); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { return finding->second == groupIndex; } return false; } bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LayerContainer.size(); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } mitk::Label::ConstPointer mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label::Pointer mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UNLABELED_VALUE) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { LabelVectorType result; for (const auto& labelValue : labelValues) { - auto* label = this->GetLabel(labelValue); + Label::Pointer label = this->GetLabel(labelValue); - if (label != nullptr) + if (label.IsNotNull()) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const { ConstLabelVectorType result; for (const auto& labelValue : labelValues) { - const auto* label = this->GetLabel(labelValue); + Label::ConstPointer label = this->GetLabel(labelValue); - if (label != nullptr) + if (label.IsNotNull()) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; return m_GroupToLabelMap[index]; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, const std::string_view name) const { LabelValueVectorType result; auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } std::vector mitk::LabelSetImage::GetLabelClassNames() const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector(names.begin(), names.end()); } std::vector mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector(names.begin(), names.end()); } void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) { auto setVisibility = [visible,this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) { auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, const std::string_view name, bool visible) { auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetAllLabelValues(), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, const std::string_view name, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // m_LookupTable; const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tables not equal."; return returnValue; ; } // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are not equal."; return false; } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // label data auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const ConstLabelMapType& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabels(destinationLabels), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } if (!Equal(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { if (IsSubGeometry(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, true)) { //we have to pad the source image //because ImageToImageFilters always check for origin matching even if //the requested output region is fitting :( auto padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, sourceImageAtTimeStep); padFilter->SetInput(1, destinationImageAtTimeStep); padFilter->SetPadConstant(Label::UNLABELED_VALUE); padFilter->SetBinaryFilter(false); padFilter->Update(); sourceImageAtTimeStep = padFilter->GetOutput(); } else { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; source image has neither the same geometry than destination image nor has the source image a sub geometry."; } } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, timeStep, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp index 4bcf1430d9..6e593a5d32 100644 --- a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp @@ -1,108 +1,109 @@ /*============================================================================ 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 "mitkLabelSetImageSurfaceStampFilter.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include #include #include #include mitk::LabelSetImageSurfaceStampFilter::LabelSetImageSurfaceStampFilter() : m_ForceOverwrite(false) { this->SetNumberOfIndexedInputs(1); this->SetNumberOfRequiredInputs(1); } mitk::LabelSetImageSurfaceStampFilter::~LabelSetImageSurfaceStampFilter() { } void mitk::LabelSetImageSurfaceStampFilter::GenerateData() { // GenerateOutputInformation(); this->SetNthOutput(0, this->GetInput(0)); mitk::Image::Pointer inputImage = this->GetInput(0); if (m_Surface.IsNull()) { MITK_ERROR << "Input surface is nullptr."; return; } mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(inputImage); surfaceToImageFilter->Update(); mitk::Image::Pointer resultImage = surfaceToImageFilter->GetOutput(); AccessByItk_1(inputImage, ItkImageProcessing, resultImage); inputImage->DisconnectPipeline(); } template void mitk::LabelSetImageSurfaceStampFilter::ItkImageProcessing(itk::Image *itkImage, mitk::Image::Pointer resultImage) { typedef itk::Image ImageType; - mitk::LabelSetImage::Pointer LabelSetInputImage = dynamic_cast(GetInput()); + const mitk::LabelSetImage* labelSetInputImage = dynamic_cast(GetInput()); try { typename ImageType::Pointer itkResultImage = ImageType::New(); mitk::CastToItkImage(resultImage, itkResultImage); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkResultImage, itkResultImage->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); - int activeLabel = LabelSetInputImage->GetActiveLabel()->GetValue(); + int activeLabel = labelSetInputImage->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); auto targetValue = static_cast(targetIter.Get()); + auto label = labelSetInputImage->GetLabel(targetValue); if ((sourceValue != LabelSetImage::UNLABELED_VALUE) && (m_ForceOverwrite || - !LabelSetInputImage->GetLabel(targetValue)->GetLocked())) // skip unlabled pixels and locked labels + label.IsNull() || !label->GetLocked())) // skip unlabeled source pixels and locked target labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImageSurfaceStampFilter::GenerateOutputInformation() { mitk::Image::Pointer inputImage = (mitk::Image *)this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if (inputImage.IsNull()) return; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 073c159e4e..16108fedd7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1414 +1,1425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector), m_SegmentationNodeDataMTime(0) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(m_Model, &QAbstractItemModel::dataChanged, this, &QmitkMultiLabelInspector::OnDataChanged); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); connect(view, &QAbstractItemView::entered, this, &QmitkMultiLabelInspector::OnEntered); connect(view, &QmitkMultiLabelTreeView::MouseLeave, this, &QmitkMultiLabelInspector::OnMouseLeave); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); emit SegmentationChanged(); } } mitk::LabelSetImage* QmitkMultiLabelInspector::GetMultiLabelSegmentation() const { return m_Segmentation; } void QmitkMultiLabelInspector::SetMultiLabelNode(mitk::DataNode* node) { if (node != this->m_SegmentationNode.GetPointer()) { m_SegmentationObserver.Reset(); m_SegmentationNode = node; m_LabelHighlightGuard.SetSegmentationNode(m_SegmentationNode); m_SegmentationNodeDataMTime = 0; if (m_SegmentationNode.IsNotNull()) { auto& widget = *this; auto checkAndSetSeg = [&widget, node](const itk::EventObject&) { if (widget.m_SegmentationNodeDataMTime < node->GetDataReferenceChangedTime()) { auto newSeg = dynamic_cast(node->GetData()); if (nullptr == newSeg) mitkThrow() << "Invalid usage. Node set does not contain a segmentation."; widget.m_SegmentationNodeDataMTime = node->GetDataReferenceChangedTime(); widget.SetMultiLabelSegmentation(newSeg); } }; m_SegmentationObserver.Reset(node, itk::ModifiedEvent(), checkAndSetSeg); checkAndSetSeg(itk::ModifiedEvent()); } else { this->SetMultiLabelSegmentation(nullptr); } } } mitk::DataNode* QmitkMultiLabelInspector::GetMultiLabelNode() const { return m_SegmentationNode; } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } void QmitkMultiLabelInspector::OnDataChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/, const QList& /*roles*/) { if (!m_ModelManipulationOngoing && topLeft.isValid()) m_Controls->view->expand(topLeft); } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indexes of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : std::as_const(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); m_ModelManipulationOngoing = true; auto newLabel = m_Segmentation->AddLabel(templateLabel, groupID, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; auto result = this->AddNewLabelInstanceInternal(currentLabel); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return result; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); bool canceled = false; if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false, canceled); if (canceled) return nullptr; m_ModelManipulationOngoing = true; m_Segmentation->AddLabel(newLabel, containingGroup, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; auto result = AddNewLabelInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return result; } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) { this->DeleteLabelInternal({ label->GetValue() }); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) { this->DeleteLabelInternal(relevantLabels); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); m_Segmentation->SetActiveLayer(groupID); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } const auto* selectedLabel = this->GetFirstSelectedLabelObject(); if (selectedLabel == nullptr) return; const auto groupID = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); auto groupName = QString::fromStdString(mitk::LabelSetImageHelper::CreateDisplayGroupName(m_Segmentation, groupID)); auto question = QStringLiteral("Do you really want to delete group \"%1\" including all of its labels?").arg(groupName); auto answer = QMessageBox::question(this, QString("Delete group \"%1\"").arg(groupName), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); auto groupName = QString::fromStdString(mitk::LabelSetImageHelper::CreateDisplayGroupName(m_Segmentation, groupID)); auto question = QStringLiteral("Do you really want to delete group \"%1\" including all of its labels?").arg(groupName); auto answer = QMessageBox::question(this, QString("Delete group \"%1\"").arg(groupName), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); auto currentIndex = this->m_Controls->view->currentIndex(); //this ensures correct highlighting is the context menu is triggered while //another context menu is already open. if (currentIndex.isValid() && this->m_AboutToShowContextMenu) this->OnEntered(this->m_Controls->view->currentIndex()); QMenu* menu = new QMenu(this); if (IndexLevelType::Group == indexLevel) { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } else if (IndexLevelType::LabelClass == indexLevel) { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { if (m_AllowLabelModification) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } if (m_AllowVisibilityModification) { if (m_AllowLabelModification) menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { if (m_AllowLabelModification) menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } } QObject::connect(menu, &QMenu::aboutToHide, this, &QmitkMultiLabelInspector::OnMouseLeave); m_AboutToShowContextMenu = true; menu->popup(QCursor::pos()); } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto selectedLabelValues = this->GetSelectedLabels(); auto relevantLabelValues = !this->GetMultiSelectionMode() || selectedLabelValues.size() <= 1 ? this->GetCurrentlyAffactedLabelInstances() : selectedLabelValues; std::vector relevantLabels; if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); - if (nullptr == label) + if (label.IsNull()) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; auto guard = &m_LabelHighlightGuard; auto onChangeLambda = [segmentation, relevantLabels](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); segmentation->UpdateLookupTable(label->GetValue()); } segmentation->GetLookupTable()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); }; QObject::connect(opacitySlider, &QSlider::valueChanged, this, onChangeLambda); auto onPressedLambda = [guard]() { guard->SetHighlightedLabels({}); }; QObject::connect(opacitySlider, &QSlider::sliderPressed, this, onPressedLambda); auto onReleasedLambda = [relevantLabelValues, guard]() { guard->SetHighlightedLabels(relevantLabelValues); }; QObject::connect(opacitySlider, &QSlider::sliderReleased, this, onReleasedLambda); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); 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()); + + if (currentLabel.IsNull()) + { + MITK_WARN << "Ignore operation. Try to delete in-existing label. Invalid ID: " << affectedLabels.front(); + return; + } + QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); bool canceled = false; emit LabelRenameRequested(currentLabel, true, canceled); if (canceled) return; for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); m_Segmentation->UpdateLookupTable(label->GetValue()); m_Segmentation->GetLookupTable()->Modified(); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); - if (nullptr == label) + if (label.IsNull()) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); - if (nullptr == label) + if (label.IsNull()) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); m_Segmentation->UpdateLookupTable(label->GetValue()); } m_Segmentation->GetLookupTable()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; m_Segmentation->SetAllLabelsVisible(false); for (auto selectedValue : selectedLabelValues) { auto currentLabel = m_Segmentation->GetLabel(selectedValue); currentLabel->SetVisible(true); m_Segmentation->UpdateLookupTable(selectedValue); } m_Segmentation->GetLookupTable()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(selectedLabelValues.front()); } 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); + this->WaitCursorOff(); + const auto currentLabel = m_Segmentation->GetLabel(labelID); + if (currentLabel.IsNull()) + return; + const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); - this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } void QmitkMultiLabelInspector::OnEntered(const QModelIndex& index) { if (m_SegmentationNode.IsNotNull()) { auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); auto highlightedValues = m_Model->GetLabelsInSubTree(index); m_LabelHighlightGuard.SetHighlightedLabels(highlightedValues); } m_AboutToShowContextMenu = false; } void QmitkMultiLabelInspector::OnMouseLeave() { if (m_SegmentationNode.IsNotNull() && !m_AboutToShowContextMenu) { m_LabelHighlightGuard.SetHighlightedLabels({}); } else { m_AboutToShowContextMenu = false; } } void QmitkMultiLabelInspector::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Shift) { m_LabelHighlightGuard.SetHighlightInvisibleLabels(true); } QWidget::keyPressEvent(event); } void QmitkMultiLabelInspector::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Shift) { m_LabelHighlightGuard.SetHighlightInvisibleLabels(false); } QWidget::keyPressEvent(event); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp index 36eac12056..63fbd02b51 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp @@ -1,563 +1,581 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include // itk #include #include QmitkMultiLabelManager::QmitkMultiLabelManager(QWidget *parent) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelManagerControls), m_Completer(nullptr), m_ProcessingManualSelection(false), m_DataStorage(nullptr) { m_Controls->setupUi(this); m_Controls->labelSearchBox->setAlwaysShowClearIcon(true); m_Controls->labelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls->labelSearchBox->setCompleter(m_Completer); m_Controls->labelInspector->SetAllowLabelModification(true); connect(m_Controls->labelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); // See T29549 m_Controls->labelSearchBox->hide(); m_Controls->btnSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); m_Controls->btnAddLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg"))); m_Controls->btnAddInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg"))); m_Controls->btnAddGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_add.svg"))); m_Controls->btnRemoveLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg"))); m_Controls->btnRemoveInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg"))); m_Controls->btnRemoveGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg"))); connect(m_Controls->btnAddLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); connect(m_Controls->btnRemoveLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabel); connect(m_Controls->btnAddInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); connect(m_Controls->btnRemoveInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabelInstance); connect(m_Controls->btnAddGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewGroup); connect(m_Controls->btnRemoveGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveGroup); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnSavePreset); connect(m_Controls->btnLoadPreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnLoadPreset); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::GoToLabel, this, &QmitkMultiLabelManager::OnGoToLabel); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::LabelRenameRequested, this, &QmitkMultiLabelManager::OnLabelRenameRequested); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::OnSelectedLabelChanged); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::ModelUpdated, this, &QmitkMultiLabelManager::OnModelUpdated); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::SegmentationChanged, this, &QmitkMultiLabelManager::OnSegmentationChanged); auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkMultiLabelManager::OnRenameLabelShortcutActivated); auto* addLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_A), this); connect(addLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); auto* addLabelInstanceShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), this); connect(addLabelInstanceShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); auto* deleteLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_D), this); connect(deleteLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabelInstance); this->UpdateControls(); } QmitkMultiLabelManager::~QmitkMultiLabelManager() { this->SetMultiLabelSegmentation(nullptr); delete m_Controls; } QmitkMultiLabelManager::LabelValueVectorType QmitkMultiLabelManager::GetSelectedLabels() const { return m_Controls->labelInspector->GetSelectedLabels(); } void QmitkMultiLabelManager::OnRenameLabelShortcutActivated() { auto selectedLabels = this->GetSelectedLabels(); for (auto labelValue : selectedLabels) { auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(labelValue); + if (currentLabel.IsNull()) + continue; bool canceled = false; emit LabelRenameRequested(currentLabel, true, canceled); } } void QmitkMultiLabelManager::OnSelectedLabelChanged(const LabelValueVectorType& labels) { this->UpdateControls(); if (labels.empty() || labels.size() > 1) return; emit CurrentSelectionChanged(labels); } QStringList &QmitkMultiLabelManager::GetLabelStringList() { return m_LabelStringList; } void QmitkMultiLabelManager::SetDefaultLabelNaming(bool defaultLabelNaming) { this->m_Controls->labelInspector->SetDefaultLabelNaming(defaultLabelNaming); } void QmitkMultiLabelManager::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { this->m_Controls->labelInspector->SetSelectedLabels(selectedLabels); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->m_Controls->labelInspector->SetSelectedLabel(selectedLabel); UpdateControls(); } void QmitkMultiLabelManager::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { this->m_Controls->labelInspector->SetMultiLabelSegmentation(segmentation); //Update of controls and observers is done in OnSegmentationChanged() // which is triggered by the inspector when setting the segmentation or node } mitk::LabelSetImage* QmitkMultiLabelManager::GetMultiLabelSegmentation() const { return this->m_Controls->labelInspector->GetMultiLabelSegmentation(); } void QmitkMultiLabelManager::SetMultiLabelNode(mitk::DataNode* node) { this->m_Controls->labelInspector->SetMultiLabelNode(node); } mitk::DataNode* QmitkMultiLabelManager::GetMultiLabelNode() const { return this->m_Controls->labelInspector->GetMultiLabelNode(); } void QmitkMultiLabelManager::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkMultiLabelManager::OnSearchLabel() { //std::string text = m_Controls->labelSearchBox->text().toStdString(); //int pixelValue = -1; //int row = -1; //for (int i = 0; i < m_Controls->m_LabelSetTableWidget->rowCount(); ++i) //{ // if (m_Controls->m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) // { // pixelValue = m_Controls->m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); // row = i; // break; // } //} //if (pixelValue == -1) //{ // return; //} //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //QTableWidgetItem *nameItem = m_Controls->m_LabelSetTableWidget->item(row, NAME_COL); //if (!nameItem) //{ // return; //} //m_Controls->m_LabelSetTableWidget->clearSelection(); //m_Controls->m_LabelSetTableWidget->selectRow(row); //m_Controls->m_LabelSetTableWidget->scrollToItem(nameItem); //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //this->WaitCursorOn(); //mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); //m_ToolManager->WorkingDataChanged(); //if (pos.GetVnlVector().max_value() > 0.0) //{ // emit goToLabel(pos); //} //else //{ // GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); // mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); // emit goToLabel(pos); //} //this->WaitCursorOff(); } void QmitkMultiLabelManager::UpdateControls() { bool hasWorkingData = this->GetMultiLabelSegmentation() != nullptr; auto labels = this->m_Controls->labelInspector->GetSelectedLabels(); bool hasMultipleInstances = this->m_Controls->labelInspector->GetLabelInstancesOfSelectedFirstLabel().size() > 1; m_Controls->labelSearchBox->setEnabled(hasWorkingData); m_Controls->btnAddGroup->setEnabled(hasWorkingData); m_Controls->btnAddInstance->setEnabled(hasWorkingData && labels.size()==1); m_Controls->btnAddLabel->setEnabled(hasWorkingData); m_Controls->btnLoadPreset->setEnabled(hasWorkingData); m_Controls->btnRemoveGroup->setEnabled(hasWorkingData && !labels.empty() && this->GetMultiLabelSegmentation()->GetNumberOfLayers()>1); m_Controls->btnRemoveLabel->setEnabled(hasWorkingData && !labels.empty()); m_Controls->btnRemoveInstance->setEnabled(hasWorkingData && !labels.empty() && hasMultipleInstances); m_Controls->btnSavePreset->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = dynamic_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkMultiLabelManager::OnCreateCroppedMask(bool) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); mitk::Image::Pointer maskImage; auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); - auto pixelValue = currentLabel->GetValue(); try { this->WaitCursorOn(); + if (currentLabel.IsNull()) + mitkThrow() << "Invalid state context menu action was triggered with an invalid label id. Label id: " << this->GetSelectedLabels().front(); + + auto pixelValue = currentLabel->GetValue(); + mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(mitk::CreateLabelMask(this->GetMultiLabelSegmentation(),pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->GetMultiLabelNode()); } void QmitkMultiLabelManager::OnCreateMask(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); - auto pixelValue = currentLabel->GetValue(); mitk::Image::Pointer maskImage; try { this->WaitCursorOn(); + + if (currentLabel.IsNull()) + mitkThrow() << "Invalid state context menu action was triggered with an invalid label id. Label id: " << this->GetSelectedLabels().front(); + auto pixelValue = currentLabel->GetValue(); + maskImage = mitk::CreateLabelMask(GetMultiLabelSegmentation(),pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->GetMultiLabelNode()); } void QmitkMultiLabelManager::OnCreateSmoothedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); - auto pixelValue = currentLabel->GetValue(); - mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); + try + { + if (currentLabel.IsNull()) + mitkThrow() << "Invalid state context menu action was triggered with an invalid label id. Label id: " << this->GetSelectedLabels().front(); - itk::SimpleMemberCommand::Pointer successCommand = - itk::SimpleMemberCommand::New(); - successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); + auto pixelValue = currentLabel->GetValue(); - itk::SimpleMemberCommand::Pointer errorCommand = - itk::SimpleMemberCommand::New(); - errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); + mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); - mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); - surfaceFilter->SetPointerParameter("Group node", groupNode); - surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); - surfaceFilter->SetParameter("RequestedLabel", pixelValue); - surfaceFilter->SetParameter("Smooth", true); - surfaceFilter->SetDataStorage(*m_DataStorage); + itk::SimpleMemberCommand::Pointer successCommand = + itk::SimpleMemberCommand::New(); + successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); + surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); - mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); + itk::SimpleMemberCommand::Pointer errorCommand = + itk::SimpleMemberCommand::New(); + errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); + surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); + + mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); + surfaceFilter->SetPointerParameter("Group node", groupNode); + surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); + surfaceFilter->SetParameter("RequestedLabel", pixelValue); + surfaceFilter->SetParameter("Smooth", true); + surfaceFilter->SetDataStorage(*m_DataStorage); + + mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); - try - { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnCreateDetailedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); - auto pixelValue = currentLabel->GetValue(); - mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); + try + { + if (currentLabel.IsNull()) + mitkThrow() << "Invalid state context menu action was triggered with an invalid label id. Label id: " << this->GetSelectedLabels().front(); + + auto pixelValue = currentLabel->GetValue(); - itk::SimpleMemberCommand::Pointer successCommand = - itk::SimpleMemberCommand::New(); - successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); + mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); - itk::SimpleMemberCommand::Pointer errorCommand = - itk::SimpleMemberCommand::New(); - errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); + itk::SimpleMemberCommand::Pointer successCommand = + itk::SimpleMemberCommand::New(); + successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); + surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); - mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); - surfaceFilter->SetPointerParameter("Group node", groupNode); - surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); - surfaceFilter->SetParameter("RequestedLabel", pixelValue); - surfaceFilter->SetParameter("Smooth", false); - surfaceFilter->SetDataStorage(*m_DataStorage); + itk::SimpleMemberCommand::Pointer errorCommand = + itk::SimpleMemberCommand::New(); + errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); + surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); - mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); + mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); + surfaceFilter->SetPointerParameter("Group node", groupNode); + surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); + surfaceFilter->SetParameter("RequestedLabel", pixelValue); + surfaceFilter->SetParameter("Smooth", false); + surfaceFilter->SetDataStorage(*m_DataStorage); + + mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); - try - { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnSavePreset() { QmitkSaveMultiLabelPreset(this->GetMultiLabelSegmentation()); } void QmitkMultiLabelManager::OnLoadPreset() { QmitkLoadMultiLabelPreset({ this->GetMultiLabelSegmentation() }); } void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const { emit GoToLabel(label, position); } void QmitkMultiLabelManager::OnLabelRenameRequested(mitk::Label* label, bool rename, bool& canceled) const { emit LabelRenameRequested(label, rename, canceled); } void QmitkMultiLabelManager::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelManager::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkMultiLabelManager::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkMultiLabelManager::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } void QmitkMultiLabelManager::AddSegmentationObserver() { if (this->GetMultiLabelSegmentation() != nullptr) { auto& widget = *this; m_LabelAddedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_LabelModifiedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelModifiedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_LabelRemovedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_GroupAddedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupAddedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); m_GroupModifiedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupModifiedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); m_GroupRemovedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupRemovedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); } } void QmitkMultiLabelManager::RemoveSegmentationObserver() { m_LabelAddedObserver.Reset(); m_LabelModifiedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_GroupAddedObserver.Reset(); m_GroupModifiedObserver.Reset(); m_GroupRemovedObserver.Reset(); } void QmitkMultiLabelManager::OnLabelEvent(mitk::LabelSetImage::LabelValueType /*labelValue*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnGroupEvent(mitk::LabelSetImage::GroupIndexType /*groupIndex*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnModelUpdated() { this->UpdateControls(); } void QmitkMultiLabelManager::OnSegmentationChanged() { this->RemoveSegmentationObserver(); this->AddSegmentationObserver(); UpdateControls(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 23ec29058b..d5ee79265b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,1060 +1,1060 @@ /*============================================================================ 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 #include #include #include 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::GroupIndexType 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_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: { const auto groupID = item->GetGroupID(); if (m_Segmentation->ExistGroup(groupID)) { return QVariant(QString::fromStdString(mitk::LabelSetImageHelper::CreateDisplayGroupName(m_Segmentation, groupID))); } return QVariant(QString("unknown group")); } 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(" (%1 instances)").arg(item->m_childItems.size()); 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(" [%1]").arg(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 if (role == Qt::ToolTipRole) { if (item->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Group) { return QVariant(QString("Group %1").arg(item->GetGroupID())); } else { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is referring to a label that does not exist."; QString name = QString::fromStdString(""+label->GetName()+""); if (!item->HandleAsInstance()) { name = QString("Label class: %1\nContaining %2 instances").arg(name).arg(item->m_childItems.size()); } else { name = QString::fromStdString(label->GetName()) + QString("\nLabel instance ID: %1\nPixel value: %2").arg(item->GetLabelValue()).arg(item->GetLabelValue()); } return QVariant(name); } } 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) { QVariant v; v.setValue(item->GetGroupID()); return v; } 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()); } m_Segmentation->UpdateLookupTable(label->GetValue()); m_Segmentation->GetLookupTable()->Modified(); 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::UNLABELED_VALUE) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) return 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::GroupIndexType 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) { const auto* sibling = searchItem; while (sibling != nullptr) { sibling = sibling->NextSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } if (nullptr != resultItem) break; // No next closest label instance on this level -> check for closest before sibling = searchItem; while (sibling != nullptr) { sibling = sibling->PrevSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } 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(); } std::vector QmitkMultiLabelTreeModel::GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (currentIndex.isValid()) { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Group == currentItem->m_ItemType) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Instance == currentItem->m_ItemType) currentItem = currentItem->ParentItem(); 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->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 labels = m_Segmentation->GetLabelsByValue(m_Segmentation->GetLabelValuesByGroup(groupID)); for (auto& label : labels) { if (label->GetValue()== mitk::LabelSetImage::UNLABELED_VALUE) continue; bool newItemCreated = false; AddLabelToGroupTree(label, 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::ITKEventHandler(const itk::EventObject& e) { if (mitk::LabelAddedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelAdded(labelEvent->GetLabelValue()); } else if (mitk::LabelModifiedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelModified(labelEvent->GetLabelValue()); } else if (mitk::LabelRemovedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelRemoved(labelEvent->GetLabelValue()); } else if (mitk::GroupAddedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupAdded(labelEvent->GetGroupID()); } else if (mitk::GroupModifiedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupModified(labelEvent->GetGroupID()); } else if (mitk::GroupRemovedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupRemoved(labelEvent->GetGroupID()); } } void QmitkMultiLabelTreeModel::AddObserver() { m_LabelAddedObserver.Reset(); m_LabelModifiedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_GroupAddedObserver.Reset(); m_GroupModifiedObserver.Reset(); m_GroupRemovedObserver.Reset(); if (this->m_Segmentation.IsNotNull()) { auto& model = *this; m_LabelAddedObserver.Reset(m_Segmentation, mitk::LabelAddedEvent(), [&model](const itk::EventObject& event){model.ITKEventHandler(event);}); m_LabelModifiedObserver.Reset(m_Segmentation, mitk::LabelModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_LabelRemovedObserver.Reset(m_Segmentation, mitk::LabelRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_GroupAddedObserver.Reset(m_Segmentation, mitk::GroupAddedEvent(), [&model](const itk::EventObject& event) { model.ITKEventHandler(event); }); m_GroupModifiedObserver.Reset(m_Segmentation, mitk::GroupModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_GroupRemovedObserver.Reset(m_Segmentation, mitk::GroupRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); } } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { GroupIndexType groupIndex = m_Segmentation->GetGroupIndexOfLabel(labelValue); auto label = m_Segmentation->GetLabel(labelValue); - if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; + if (label.IsNull()) 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::UNLABELED_VALUE) 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); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } 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 collapse // -> 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(); } } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel received 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 = labelItem->HandleAsInstance() ? GetIndexByItem(labelItem, this) : GetIndexByItem(instanceItem, this); auto rightIndex = index.sibling(index.row(), this->columnCount() - 1); emit dataChanged(index, rightIndex); } 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::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel received 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(GroupIndexType 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(GroupIndexType /*groupIndex*/) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType 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/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp index e3053c2882..b286e6589c 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp @@ -1,259 +1,265 @@ /*============================================================================ 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 "QmitkBooleanOperationsWidget.h" #include #include #include #include #include #include #include #include QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent) : QWidget(parent) { m_Controls = new Ui::QmitkBooleanOperationsWidgetControls; m_Controls->setupUi(this); m_Controls->label1st->setText(""); m_Controls->label1st->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_Controls->label2nd->setText(""); m_Controls->label2nd->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_Controls->segNodeSelector->SetDataStorage(dataStorage); m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); m_Controls->segNodeSelector->SetSelectionIsOptional(false); m_Controls->segNodeSelector->SetInvalidInfo(QStringLiteral("Please select segmentation for boolean operations.")); m_Controls->segNodeSelector->SetPopUpTitel(QStringLiteral("Select segmentation")); m_Controls->segNodeSelector->SetPopUpHint(QStringLiteral("Select the segmentation that should be used as source for boolean operations.")); m_Controls->labelInspector->SetMultiSelectionMode(true); connect(m_Controls->segNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkBooleanOperationsWidget::OnSegSelectionChanged); connect(m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkBooleanOperationsWidget::OnLabelSelectionChanged); connect(m_Controls->differenceButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnDifferenceButtonClicked); connect(m_Controls->intersectionButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnIntersectionButtonClicked); connect(m_Controls->unionButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnUnionButtonClicked); m_Controls->segNodeSelector->SetAutoSelectNewNodes(true); this->ConfigureWidgets(); } QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() { m_Controls->labelInspector->SetMultiLabelNode(nullptr); } void QmitkBooleanOperationsWidget::OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { auto node = m_Controls->segNodeSelector->GetSelectedNode(); m_Controls->labelInspector->SetMultiLabelNode(node); this->ConfigureWidgets(); } void QmitkBooleanOperationsWidget::OnLabelSelectionChanged(mitk::LabelSetImage::LabelValueVectorType /*labels*/) { this->ConfigureWidgets(); } namespace { std::string GenerateLabelHTML(const mitk::Label* label) { std::stringstream stream; auto color = label->GetColor(); stream << "(color.GetRed()*255) << std::setw(2) << static_cast(color.GetGreen()*255) << std::setw(2) << static_cast(color.GetBlue()*255) << "; font-size: 20px '>■" << std::dec; stream << " " << label->GetName()<< ""; return stream.str(); } } void QmitkBooleanOperationsWidget::ConfigureWidgets() { auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); auto styleSheet = qApp->styleSheet(); m_Controls->line1stLabel->document()->setDefaultStyleSheet(styleSheet); m_Controls->lineOtherLabels->document()->setDefaultStyleSheet(styleSheet); if (selectedLabelValues.empty()) { m_Controls->line1stLabel->setHtml(QStringLiteral("Select 1st label to proceed.")); } else { - auto label = seg->GetLabel(selectedLabelValues.front()); - m_Controls->line1stLabel->setText(QString::fromStdString(GenerateLabelHTML(label))); + mitk::Label::ConstPointer label = seg->GetLabel(selectedLabelValues.front()); + if (label.IsNotNull()) + m_Controls->line1stLabel->setText(QString::fromStdString(GenerateLabelHTML(label))); + else + m_Controls->line1stLabel->setText(QString("Unknown label ID ")+QString::number(selectedLabelValues.front())); } if (selectedLabelValues.size() < 2) { m_Controls->lineOtherLabels->setHtml(QStringLiteral("Select secondary label(s) to proceed.")); } else { std::stringstream stream; - for (auto iter = selectedLabelValues.cbegin() + 1; iter != selectedLabelValues.cend(); ++iter) + for (const auto& labelValue : selectedLabelValues) { - auto label = seg->GetLabel(*iter); + mitk::Label::ConstPointer label = seg->GetLabel(labelValue); if (stream.rdbuf()->in_avail() != 0) stream << "; "; - stream << GenerateLabelHTML(label); + if (label.IsNotNull()) + stream << GenerateLabelHTML(label); + else + stream << "Unknown label ID " << labelValue; } m_Controls->lineOtherLabels->setText(QString::fromStdString(stream.str())); } m_Controls->differenceButton->setEnabled(selectedLabelValues.size()>1); m_Controls->intersectionButton->setEnabled(selectedLabelValues.size() > 1); m_Controls->unionButton->setEnabled(selectedLabelValues.size() > 1); } void QmitkBooleanOperationsWidget::OnDifferenceButtonClicked() { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(110); unsigned int currentProgress = 0; auto progressCallback = [¤tProgress](float filterProgress) { auto delta = (filterProgress * 100) - currentProgress; if (delta > 0) { currentProgress += delta; mitk::ProgressBar::GetInstance()->Progress(delta); } }; auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); auto minuend = selectedLabelValues.front(); auto subtrahends = mitk::LabelSetImage::LabelValueVectorType(selectedLabelValues.begin() + 1, selectedLabelValues.end()); auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); auto resultMask = mitk::BooleanOperation::GenerateDifference(seg, minuend, subtrahends, progressCallback); std::stringstream name; name << "Difference " << seg->GetLabel(minuend)->GetName() << " -"; for (auto label : subtrahends) { name << " " << seg->GetLabel(label)->GetName(); } this->SaveResultLabelMask(resultMask, name.str()); mitk::ProgressBar::GetInstance()->Reset(); QApplication::restoreOverrideCursor(); } void QmitkBooleanOperationsWidget::OnIntersectionButtonClicked() { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(110); unsigned int currentProgress = 0; auto progressCallback = [¤tProgress](float filterProgress) { auto delta = (filterProgress * 100) - currentProgress; if (delta > 0) { currentProgress += delta; mitk::ProgressBar::GetInstance()->Progress(delta); } }; auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); auto resultMask = mitk::BooleanOperation::GenerateIntersection(seg, selectedLabelValues, progressCallback); std::stringstream name; name << "Intersection"; for (auto label : selectedLabelValues) { name << " " << seg->GetLabel(label)->GetName(); } this->SaveResultLabelMask(resultMask, name.str()); mitk::ProgressBar::GetInstance()->Reset(); QApplication::restoreOverrideCursor(); } void QmitkBooleanOperationsWidget::OnUnionButtonClicked() { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(110); unsigned int currentProgress = 0; auto progressCallback = [¤tProgress](float filterProgress) { auto delta = (filterProgress * 100) - currentProgress; if (delta > 0) { currentProgress += delta; mitk::ProgressBar::GetInstance()->Progress(delta); } }; auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); auto resultMask = mitk::BooleanOperation::GenerateUnion(seg, selectedLabelValues, progressCallback); std::stringstream name; name << "Union"; for (auto label : selectedLabelValues) { name << " " << seg->GetLabel(label)->GetName(); } this->SaveResultLabelMask(resultMask, name.str()); mitk::ProgressBar::GetInstance()->Reset(); QApplication::restoreOverrideCursor(); } void QmitkBooleanOperationsWidget::SaveResultLabelMask(const mitk::Image* resultMask, const std::string& labelName) const { auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); if (seg == nullptr) mitkThrow() << "Widget is in invalid state. Processing was triggered with no segmentation selected."; auto labels = m_Controls->labelInspector->GetSelectedLabels(); if (labels.empty()) mitkThrow() << "Widget is in invalid state. Processing was triggered with no label selected."; auto groupID = seg->AddLayer(); auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(seg, labelName, true); seg->AddLabelWithContent(newLabel, resultMask, groupID, 1); m_Controls->labelInspector->GetMultiLabelSegmentation()->Modified(); m_Controls->labelInspector->GetMultiLabelNode()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); }