diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp index 70a8da970b..38f4484430 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -1,273 +1,274 @@ /*============================================================================ 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 "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainer.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateDataProperty.h" #include "mitkProperties.h" #include "mitkImageStatisticsContainerManager.h" #include "QmitkImageStatisticsCalculationRunnable.h" void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) { if (m_IgnoreZeroValueVoxel != _arg) { m_IgnoreZeroValueVoxel = _arg; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeroValueVoxel; } void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) { if (m_HistogramNBins != nbins) { m_HistogramNBins = nbins; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const { return this->m_HistogramNBins; } bool QmitkImageStatisticsDataGenerator::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const { auto result = QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(changedNode); if (!result) { if (changedNode->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()) == nullptr) { auto stats = dynamic_cast(changedNode->GetData()); result = stats != nullptr; } } return result; } bool QmitkImageStatisticsDataGenerator::IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { auto resultNode = this->GetLatestResult(imageNode, roiNode, true, true); return resultNode.IsNotNull(); } mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate, bool noWIP) const { auto storage = m_Storage.Lock(); if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode) { mask = roiNode->GetData(); } std::lock_guard mutexguard(m_DataMutex); return mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(storage, image, mask, m_IgnoreZeroValueVoxel, m_HistogramNBins, onlyIfUpToDate, noWIP); } void QmitkImageStatisticsDataGenerator::IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image node is nullptr"; } auto image = dynamic_cast(imageNode->GetData()); if (!image) { mitkThrow() << "Image node date is nullptr or no image."; } const mitk::BaseData* mask = nullptr; if (roiNode != nullptr) { mask = roiNode->GetData(); } auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); if (resultDataNode.IsNull()) { auto dummyStats = mitk::ImageStatisticsContainer::New(); + dummyStats->SetTimeGeometry(image->GetTimeGeometry()->Clone()); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(dummyStats, image); if (nullptr != mask) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(dummyStats, mask); } dummyStats->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); dummyStats->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); auto dummyNode = CreateWIPDataNode(dummyStats, "WIP_"+GenerateStatisticsNodeName(image, mask)); auto storage = m_Storage.Lock(); if (storage != nullptr) { std::lock_guard mutexguard(m_DataMutex); storage->Add(dummyNode); } } } std::pair QmitkImageStatisticsDataGenerator::GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); std::string status; if (resultDataNode.IsNull() || (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_PENDING)) { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image node is nullptr"; } auto image = dynamic_cast(imageNode->GetData()); if (image == nullptr) { mitkThrow() << "Image node date is nullptr or no image."; } const mitk::Image* mask = nullptr; const mitk::PlanarFigure* planar = nullptr; if (roiNode != nullptr) { mask = dynamic_cast(roiNode->GetData()); planar = dynamic_cast(roiNode->GetData()); } auto newJob = new QmitkImageStatisticsCalculationRunnable; newJob->Initialize(image, mask, planar); newJob->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); newJob->SetHistogramNBins(m_HistogramNBins); return std::pair(newJob, resultDataNode.GetPointer()); } else if (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS) { return std::pair(nullptr, resultDataNode.GetPointer()); } return std::pair(nullptr, nullptr); } void QmitkImageStatisticsDataGenerator::RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode != nullptr) { mask = roiNode->GetData(); } auto lastResult = this->GetLatestResult(imageNode, roiNode, false, false); auto rulePredicate = mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(image, mask); auto notLatestPredicate = mitk::NodePredicateFunction::New([lastResult](const mitk::DataNode* node) { return node != lastResult; }); auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(rulePredicate, notLatestPredicate).GetPointer(); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); auto storage = m_Storage.Lock(); if (storage != nullptr) { std::lock_guard mutexguard(m_DataMutex); auto oldStatisticContainerNodes = storage->GetSubset(predicate); storage->Remove(oldStatisticContainerNodes); } } mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::PrepareResultForStorage(const std::string& /*label*/, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const { auto statsJob = dynamic_cast(job); if (statsJob != nullptr) { auto resultNode = mitk::DataNode::New(); resultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); resultNode->SetVisibility(false); resultNode->SetData(result); const mitk::BaseData* roi = statsJob->GetMaskImage(); if (roi == nullptr) { roi = statsJob->GetPlanarFigure(); } resultNode->SetName(this->GenerateStatisticsNodeName(statsJob->GetStatisticsImage(), roi)); return resultNode; } return nullptr; } std::string QmitkImageStatisticsDataGenerator::GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const { std::stringstream statisticsNodeName; statisticsNodeName << "statistics_bins-" << m_HistogramNBins <<"_"; if (m_IgnoreZeroValueVoxel) { statisticsNodeName << "noZeros_"; } if (image == nullptr) { mitkThrow() << "Image is nullptr"; } statisticsNodeName << image->GetUID(); if (roi != nullptr) { statisticsNodeName << "_" + roi->GetUID(); } return statisticsNodeName.str(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp index 3ee475c4e8..87c477439b 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp @@ -1,132 +1,132 @@ /*============================================================================ 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 "QmitkImageStatisticsTreeItem.h" QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem( ImageStatisticsObject statisticsData, StatisticNameVector statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parent) : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent), m_IsWIP(isWIP) { } - QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, + QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem(const StatisticNameVector& statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem) : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, isWIP, parentItem ) { } QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), false, nullptr ) {} QmitkImageStatisticsTreeItem::~QmitkImageStatisticsTreeItem() { qDeleteAll(m_childItems); } void QmitkImageStatisticsTreeItem::appendChild(QmitkImageStatisticsTreeItem *item) { m_childItems.append(item); } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::child(int row) { return m_childItems.value(row); } int QmitkImageStatisticsTreeItem::childCount() const { return m_childItems.count(); } int QmitkImageStatisticsTreeItem::columnCount() const { return m_statisticNames.size() + 1; } struct StatValueVisitor : boost::static_visitor { QVariant operator()(const mitk::ImageStatisticsContainer::RealType& val) const { return QVariant(val); } QVariant operator()(const mitk::ImageStatisticsContainer::VoxelCountType& val) const { return QVariant::fromValue(val); } QVariant operator()(const mitk::ImageStatisticsContainer::IndexType& val) const { std::stringstream ss; ss << val; return QVariant(QString::fromStdString(ss.str())); } }; QVariant QmitkImageStatisticsTreeItem::data(int column) const { QVariant result; if (column > 0 && !m_statisticNames.empty()) { if (column - 1 < static_cast(m_statisticNames.size())) { if (m_IsWIP) { result = QVariant(QString("...")); } else { auto statisticKey = m_statisticNames.at(column - 1); if (m_statistics.HasStatistic(statisticKey)) { return boost::apply_visitor(StatValueVisitor(), m_statistics.GetValueNonConverted(statisticKey)); } else { return QVariant(); } } } else { return QVariant(); } } else if (column == 0) { result = m_label; } return result; } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::parentItem() { return m_parentItem; } int QmitkImageStatisticsTreeItem::row() const { if (m_parentItem) return m_parentItem->m_childItems.indexOf(const_cast(this)); return 0; } bool QmitkImageStatisticsTreeItem::isWIP() const { return m_IsWIP; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h index 69236fe49e..40e0ca3b87 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h @@ -1,59 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeItem_h #define QmitkImageStatisticsTreeItem_h #include #include #include "mitkImageStatisticsContainer.h" /*! \class QmitkImageStatisticsTreeItem An item that represents an entry (usually ImageStatisticsObject) for the QmitkImageStatisticsTreeModel */ class QmitkImageStatisticsTreeItem { public: using ImageStatisticsObject = mitk::ImageStatisticsContainer::ImageStatisticsObject; using StatisticNameVector = mitk::ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector; QmitkImageStatisticsTreeItem(); explicit QmitkImageStatisticsTreeItem(ImageStatisticsObject statisticsData, StatisticNameVector statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); - explicit QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, + explicit QmitkImageStatisticsTreeItem(const StatisticNameVector& statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); ~QmitkImageStatisticsTreeItem(); void appendChild(QmitkImageStatisticsTreeItem *child); QmitkImageStatisticsTreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; int row() const; QmitkImageStatisticsTreeItem *parentItem(); /**indicates that the statistic container owned by this instance is only a dummy - WIP containter and the calculation of the up-to-date statistic is not yet finished.**/ + WIP container and the calculation of the up-to-date statistic is not yet finished.**/ bool isWIP() const; private: ImageStatisticsObject m_statistics; StatisticNameVector m_statisticNames; QVariant m_label; QmitkImageStatisticsTreeItem *m_parentItem = nullptr; QList m_childItems; bool m_IsWIP; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp index 65c86e2024..82dbe49401 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp @@ -1,424 +1,497 @@ /*============================================================================ 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 = new QmitkImageStatisticsTreeItem(); + m_RootItem = std::make_unique(); } QmitkImageStatisticsTreeModel ::~QmitkImageStatisticsTreeModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); - delete m_RootItem; }; 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; + 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 && item->isWIP() && item->childCount()==0) { return QVariant(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/hourglass-half-solid.svg"))); } 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; + 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) + 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 timestep of an 4D image + // 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(); } +} - m_StatisticNames = mitk::GetAllStatisticNames(m_Statistics); - BuildHierarchicalModel(); +void AddTimeStepTreeItems(const mitk::ImageStatisticsContainer* statistic, 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); + parentItem->appendChild(statisticsItem); + } + else + { + auto statisticsItem = new QmitkImageStatisticsTreeItem(statisticNames, QStringLiteral("N/A"), isWIP, parentItem); + } + } + } + hasMultipleTimesteps = hasMultipleTimesteps || (statistic->GetTimeSteps() > 1); +} + +void AddLabelTreeItems(const mitk::ImageStatisticsContainer* statistic, 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()); + if (nullptr != multiLabelSeg) + { + auto labelInstance = multiLabelSeg->GetLabel(labelValue); + labelLabel = QString::fromStdString(labelInstance->GetName() + " (" + labelInstance->GetTrackingID() + ")"); + } + QmitkImageStatisticsTreeItem* labelItem = nullptr; + + 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); + } + else + { + labelItem = new QmitkImageStatisticsTreeItem(statisticNames, labelLabel, isWIP, parentItem); + AddTimeStepTreeItems(statistic, labelValue, statisticNames, isWIP, labelItem, hasMultipleTimesteps); + } + + parentItem->appendChild(labelItem); + } + } } void QmitkImageStatisticsTreeModel::BuildHierarchicalModel() { // reset old model - delete m_RootItem; - m_RootItem = new QmitkImageStatisticsTreeItem(); + m_RootItem.reset(new QmitkImageStatisticsTreeItem()); bool hasMask = false; bool hasMultipleTimesteps = false; std::map dataNodeToTreeItem; for (const auto &statistic : m_Statistics) { bool isWIP = statistic->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()).IsNotNull(); // 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); - // the tree item was created previously if (search != dataNodeToTreeItem.end()) { + // the tree item was created previously imageItem = search->second; } - // create the tree item else { QString imageLabel = QString::fromStdString(image->GetName()); if (statistic->GetTimeSteps() == 1 && maskFinding == m_MaskNodes.end()) { - auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem); + // create the final statistics tree item + auto statisticsObject = isWIP ? mitk::ImageStatisticsContainer::ImageStatisticsObject() : statistic->GetStatistics(mitk::ImageStatisticsContainer::NO_MASK_LABEL_VALUE, 0); + imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem.get()); } else { - imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, m_RootItem.get()); } m_RootItem->appendChild(imageItem); dataNodeToTreeItem.emplace(image, imageItem); } - // mask: 2. hierarchy level (optional, only if mask exists) - QmitkImageStatisticsTreeItem *lastParent = nullptr; + const auto labelValues = statistic->GetExistingLabelValues(true); //currently we not support showing the statistics for unlabeled pixels if a mask exist if (maskFinding != m_MaskNodes.end()) { + // mask: 2. hierarchy level exists auto& mask = *maskFinding; QString maskLabel = QString::fromStdString(mask->GetName()); - QmitkImageStatisticsTreeItem *maskItem; - // add statistical values directly in this hierarchy level - if (statistic->GetTimeSteps() == 1) + QmitkImageStatisticsTreeItem* maskItem; + + if (statistic->GetTimeSteps() == 1 && labelValues.size() == 1) { - auto statisticsObject = statistic->GetStatisticsForTimeStep(0); + // 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); } else { maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, imageItem); } imageItem->appendChild(maskItem); - lastParent = maskItem; hasMask = true; + + // 3. hierarchy level: labels (optional, only if more then one label in statistic) + if (labelValues.size() > 1) + { + AddLabelTreeItems(statistic, mask, labelValues, m_StatisticNames, isWIP, maskItem, hasMultipleTimesteps); + } + else + { + mitk::Label::PixelType labelValue = isWIP ? 0 : labelValues.front(); + AddTimeStepTreeItems(statistic, labelValue, m_StatisticNames, isWIP, maskItem, hasMultipleTimesteps); + } } else { - lastParent = imageItem; - } - // 3. hierarchy level (optional, only if >1 timestep) - 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->TimeStepExists(i)) - { - auto statisticsItem = new QmitkImageStatisticsTreeItem( - statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, isWIP, lastParent); - lastParent->appendChild(statisticsItem); - } - } - hasMultipleTimesteps = true; + //no mask -> but multi time step + AddTimeStepTreeItems(statistic, mitk::ImageStatisticsContainer::NO_MASK_LABEL_VALUE, 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 *) +void QmitkImageStatisticsTreeModel::NodeRemoved(const mitk::DataNode* changedNode) { - emit beginResetModel(); - UpdateByDataStorage(); - emit endResetModel(); - emit modelChanged(); + bool isRelevantNode = (nullptr != dynamic_cast(changedNode->GetData())); + + if (isRelevantNode) + { + emit beginResetModel(); + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } } -void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode *) +void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode * changedNode) { - emit beginResetModel(); - UpdateByDataStorage(); - emit endResetModel(); - emit modelChanged(); + bool isRelevantNode = (nullptr != dynamic_cast(changedNode->GetData())); + + if (isRelevantNode) + { + emit beginResetModel(); + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } } -void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode *) +void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode * changedNode) { - emit beginResetModel(); - UpdateByDataStorage(); - emit endResetModel(); - emit modelChanged(); + 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/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h index a32ecf4d94..7797c328cb 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h @@ -1,127 +1,129 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeModel_h #define QmitkImageStatisticsTreeModel_h #include "QmitkAbstractDataStorageModel.h" //MITK #include #include "mitkImageStatisticsContainer.h" #include class QmitkImageStatisticsTreeItem; /*! \class QmitkImageStatisticsTreeModel The class is used to represent the information of mitk::ImageStatisticsContainer in the set datastorage in the context of the QT view-model-concept. The represented ImageStatisticContainer are specified by setting the image and mask nodes that should be regarded. In addition you may specified the statistic computation property HistorgramNBins and IgnoreZeroValueVoxel to select the correct statistics. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkImageStatisticsTreeModel(QObject *parent = nullptr); ~QmitkImageStatisticsTreeModel() override; void SetImageNodes(const std::vector& nodes); void SetMaskNodes(const std::vector& nodes); void Clear(); /*! /brief Set flag to ignore zero valued voxels */ void SetIgnoreZeroValueVoxel(bool _arg); /*! /brief Get status of zero value voxel ignoring. */ bool GetIgnoreZeroValueVoxel() const; /*! /brief Set bin size for histogram resolution.*/ void SetHistogramNBins(unsigned int nbins); /*! /brief Get bin size for histogram resolution.*/ unsigned int GetHistogramNBins() const; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; signals: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: /* * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode *node) override; private: void UpdateByDataStorage(); using StatisticsContainerVector = std::vector; /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] - --> 3. Level: Timestep [if >1 exist] */ + --> 3. Level: Label instances [if Mask has more then one label] + --> 4. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); StatisticsContainerVector m_Statistics; /** Relevant images set by the user.*/ std::vector m_ImageNodes; /** Helper that is constructed when m_ImageNodes is set. It has the same order like m_ImageNodes, but each image is represented n times, while n is the number of time steps the respective image has. This structure makes the business logic to select the correct image given a QIndex much simpler and therefore easy to understand/maintain. */ std::vector > m_TimeStepResolvedImageNodes; /** relevant masks set by the user.*/ std::vector m_MaskNodes; /** @sa m_TimeStepResolvedImageNodes */ std::vector> m_TimeStepResolvedMaskNodes; std::vector m_StatisticNames; std::mutex m_Mutex; - QmitkImageStatisticsTreeItem *m_RootItem; + std::unique_ptr m_RootItem; QVariant m_HeaderFirstColumn; + itk::TimeStamp m_BuildTime; bool m_IgnoreZeroValueVoxel = false; unsigned int m_HistogramNBins = 100; }; #endif diff --git a/Modules/Multilabel/mitkLabel.cpp b/Modules/Multilabel/mitkLabel.cpp index 6972b9f126..e71e6c3b7a 100644 --- a/Modules/Multilabel/mitkLabel.cpp +++ b/Modules/Multilabel/mitkLabel.cpp @@ -1,304 +1,309 @@ /*============================================================================ 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 "mitkLabel.h" #include "itkProcessObject.h" #include #include #include #include const mitk::Label::PixelType mitk::Label::MAX_LABEL_VALUE = std::numeric_limits::max(); mitk::Label::Label() : PropertyList() { if (GetProperty("locked") == nullptr) SetLocked(true); if (GetProperty("visible") == nullptr) SetVisible(true); if (GetProperty("opacity") == nullptr) SetOpacity(0.6); if (GetProperty("center.coordinates") == nullptr) { mitk::Point3D pnt; pnt.SetElement(0, 0); pnt.SetElement(1, 0); pnt.SetElement(2, 0); SetCenterOfMassCoordinates(pnt); } if (GetProperty("center.index") == nullptr) { mitk::Point3D pnt; pnt.SetElement(0, 0); pnt.SetElement(1, 0); pnt.SetElement(2, 0); SetCenterOfMassIndex(pnt); } if (GetProperty("color") == nullptr) { mitk::Color col; col.Set(0, 0, 0); SetColor(col); } if (GetProperty("name") == nullptr) SetName("noName!"); if (GetProperty("value") == nullptr) SetValue(0); if (GetProperty("layer") == nullptr) SetLayer(0); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(this); } mitk::Label::Label(PixelType value, const std::string& name) : Label() { this->SetValue(value); this->SetName(name); } mitk::Label::Label(const Label &other) : PropertyList(other) // copyconstructer of property List handles the coping action { auto *map = this->GetMap(); auto it = map->begin(); auto end = map->end(); for (; it != end; ++it) { itk::SimpleMemberCommand