diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp index a683088eb9..8f738de084 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,236 +1,247 @@ /*============================================================================ 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 "QmitkDataGeneratorBase.h" #include "QmitkDataGenerationJobBase.h" #include "mitkDataNode.h" #include "mitkProperties.h" #include -QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent), m_Storage(storage) -{} +QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent) +{ + this->SetDataStorage(storage); +} QmitkDataGeneratorBase::QmitkDataGeneratorBase(QObject* parent): QObject(parent) {} QmitkDataGeneratorBase::~QmitkDataGeneratorBase() { auto dataStorage = m_Storage.Lock(); if (dataStorage.IsNotNull()) { // remove "add node listener" from data storage dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // remove "remove node listener" from data storage dataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } mitk::DataStorage::Pointer QmitkDataGeneratorBase::GetDataStorage() const { return m_Storage.Lock(); } bool QmitkDataGeneratorBase::GetAutoUpdate() const { return m_AutoUpdate; } bool QmitkDataGeneratorBase::IsGenerating() const { return m_WIP; } void QmitkDataGeneratorBase::SetDataStorage(mitk::DataStorage* storage) { if (storage == m_Storage) return; std::shared_lock mutexguard(m_DataMutex); auto oldStorage = m_Storage.Lock(); if (oldStorage.IsNotNull()) { // remove "add node listener" from old data storage oldStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // remove "remove node listener" from old data storage oldStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } m_Storage = storage; auto newStorage = m_Storage.Lock(); if (newStorage.IsNotNull()) { // add "add node listener" for new data storage newStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // add remove node listener for new data storage newStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } void QmitkDataGeneratorBase::SetAutoUpdate(bool autoUpdate) { m_AutoUpdate = autoUpdate; } void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const { emit JobError(error, failedJob); } void QmitkDataGeneratorBase::OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const { auto resultnodes = mitk::DataStorage::SetOfObjects::New(); for (auto pos : results) { resultnodes->push_back(this->PrepareResultForStorage(pos.first, pos.second, job)); } { std::shared_lock mutexguard(m_DataMutex); auto storage = m_Storage.Lock(); if (storage.IsNotNull()) { for (auto pos = resultnodes->Begin(); pos != resultnodes->End(); ++pos) { storage->Add(pos->Value()); } } } emit NewDataAvailable(resultnodes.GetPointer()); - - if (!resultnodes->empty()) - { - this->EnsureRecheckingAndGeneration(); - } } void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) { if (this->NodeChangeIsRelevant(node)) { this->EnsureRecheckingAndGeneration(); } } void QmitkDataGeneratorBase::EnsureRecheckingAndGeneration() const { m_RestartGeneration = true; if (!m_InGenerate) { this->Generate(); } } -void QmitkDataGeneratorBase::Generate() const +bool QmitkDataGeneratorBase::Generate() const { + bool everythingValid = false; if (m_InGenerate) { m_RestartGeneration = true; } else { m_InGenerate = true; m_RestartGeneration = true; while (m_RestartGeneration) { m_RestartGeneration = false; - DoGenerate(); + everythingValid = DoGenerate(); } m_InGenerate = false; } + return everythingValid; } mitk::DataNode::Pointer QmitkDataGeneratorBase::CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName) { if (!dataDummy) { mitkThrow() << "data is nullptr"; } auto interimResultNode = mitk::DataNode::New(); interimResultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); dataDummy->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_PENDING)); interimResultNode->SetVisibility(false); interimResultNode->SetData(dataDummy); if (!nodeName.empty()) { interimResultNode->SetName(nodeName); } return interimResultNode; } -void QmitkDataGeneratorBase::DoGenerate() const +bool QmitkDataGeneratorBase::DoGenerate() const { auto imageSegCombinations = this->GetAllImageROICombinations(); QThreadPool* threadPool = QThreadPool::globalInstance(); bool everythingValid = true; for (const auto& imageAndSeg : imageSegCombinations) { - MITK_INFO << "processing node " << imageAndSeg.first << " and struct " << imageAndSeg.second; + if (imageAndSeg.second.IsNotNull()) + { + MITK_INFO << "checking node " << imageAndSeg.first->GetName() << " and ROI " << imageAndSeg.second->GetName(); + } + else + { + MITK_INFO << "checking node " << imageAndSeg.first->GetName(); + } if (!this->IsValidResultAvailable(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer())) { this->IndicateFutureResults(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); if (everythingValid) { m_WIP = true; - emit GenerationStarted(); everythingValid = false; } - MITK_INFO << "no valid result available. Triggering computation of necessary jobs."; - auto job = this->GetNextMissingGenerationJob(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); + MITK_INFO << "No valid result available. Requesting next necessary job." << imageAndSeg.first->GetName(); + auto nextJob = this->GetNextMissingGenerationJob(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); //other jobs are pending, nothing has to be done - if (!job) { - MITK_INFO << "waiting for other jobs to finish"; + if (nextJob.first==nullptr && nextJob.second.IsNotNull()) + { + MITK_INFO << "Last generation job still running, pass on till job is finished..."; } - else + else if(nextJob.first != nullptr && nextJob.second.IsNotNull()) { - job->setAutoDelete(true); - connect(job, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError, Qt::BlockingQueuedConnection); - connect(job, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable, Qt::BlockingQueuedConnection); - threadPool->start(job); + MITK_INFO << "Next generation job started..."; + nextJob.first->setAutoDelete(true); + nextJob.second->GetData()->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS)); + connect(nextJob.first, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError, Qt::BlockingQueuedConnection); + connect(nextJob.first, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable, Qt::BlockingQueuedConnection); + emit DataGenerationStarted(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer(), nextJob.first); + threadPool->start(nextJob.first); } } else { this->RemoveObsoleteDataNodes(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); } } if (everythingValid && m_WIP) { m_WIP = false; emit GenerationFinished(); } + + return everythingValid; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h index 2324a02838..d7f903e0f9 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -1,131 +1,140 @@ /*============================================================================ 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 __QMITK_DATA_GENERATOR_BASE_H #define __QMITK_DATA_GENERATOR_BASE_H #include //QT #include //MITK #include #include "QmitkDataGenerationJobBase.h" #include /*! \brief QmitkDataGeneratorBase BaseClass that implements the organisation of data generation. Use/see the class QmitkDataGenerator for more details. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGeneratorBase : public QObject { Q_OBJECT public: QmitkDataGeneratorBase(const QmitkDataGeneratorBase& other) = delete; QmitkDataGeneratorBase& operator=(const QmitkDataGeneratorBase& other) = delete; ~QmitkDataGeneratorBase(); using JobResultMapType = QmitkDataGenerationJobBase::ResultMapType; mitk::DataStorage::Pointer GetDataStorage() const; bool GetAutoUpdate() const; /** Indicates if currently there is work in progress, so data generation jobs are running or pending. It is set to true when GenerationStarted is triggered and becomes false as soon as GenerationFinished is triggered. */ bool IsGenerating() const; - void Generate() const; + /** Checks data validity and triggers generation of data, if needed. + The generation itselfs will be done in threads an orchastrated by this class. To learn if the threads are finished and + everything is uptodate, listen to the signal GenerationFinished. + @return indicates if everything is already valid (true) or if the generation of new data was triggerd (false).*/ + bool Generate() const; /** Indicates if for a given image and ROI a valid final result is available.*/ virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; public slots: void SetDataStorage(mitk::DataStorage* storage); void SetAutoUpdate(bool autoUpdate); protected slots: void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; /*! @brief Wraps the resulting BaseData* into DataNode objects */ void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; signals: - /*! @brief Signal that is emitted if new final data is produced. + + /*! @brief Signal that is emitted if a data generation job is started to generat outdated/inexistant data. */ - void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; + void DataGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job) const; - /*! @brief Signal that is emitted if the generator emits new jobs. + /*! @brief Signal that is emitted if new final data is produced. */ - void GenerationStarted() const; + void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; /*! @brief Signal that is emitted if all jobs are finished. */ void GenerationFinished() const; /*! @brief Signal that is emitted in case of job errors. */ void JobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; protected: /*! @brief Constructor @param storage the data storage where all produced data should be stored */ QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr); QmitkDataGeneratorBase(QObject* parent = nullptr); using InputPairVectorType = std::vector>; virtual bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const = 0; virtual InputPairVectorType GetAllImageROICombinations() const = 0; /** Indicates if there is already an valid and up-to-date result for the given node pair.*/ virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; - /*! @brief Returns the next job that needs to be done in order to complete the workflow. - If no job instance is passed back, it either indicated that there is nothing to do (IsValidResultAvailable == true) - or that currently a interim result is still missing and therefore the next job cannot be triggered.*/ - virtual QmitkDataGenerationJobBase* GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; + /*! @brief Is called to generate the next job instance that needs to be done and its associated dummy node + in order to progress the data generation workflow. + @remark The method assumes that the caller takes care of the job instance deletion. + @return std::pair of job pointer and placeholder node associated with the job. Current combinations are possible: + - 1) Both are null: nothing to do; + - 2) Both are set: there is something to do for a pending dumme node; + - 3) job null and node set: a job for this node is already work in progress.*/ + virtual std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; /** Remove all obsolete data nodes for the given image and seg node from the data storage. Obsolete nodes are (interim) result nodes that are not the most recent any more.*/ virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; /** Prepares result to be added to the storage in an appropriate way and returns the data node for that.*/ virtual mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const = 0; /*! @brief Creates a data node for interim results @details It's the jobs responsibility to write the final results to the data of the DataNode. */ static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); - void DoGenerate() const; + bool DoGenerate() const; mitk::WeakPointer m_Storage; bool m_AutoUpdate = false; mutable std::shared_mutex m_DataMutex; mutable bool m_InGenerate = false; mutable bool m_RestartGeneration = false; mutable bool m_WIP = false; /**Member is called when a node is added to the storage.*/ void NodeAddedOrModified(const mitk::DataNode* node); void EnsureRecheckingAndGeneration() const; unsigned long m_DataStorageDeletedTag; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp index 15d42bb90b..1675fe6847 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -1,314 +1,328 @@ /*============================================================================ 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; - this->EnsureRecheckingAndGeneration(); + + 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; - this->EnsureRecheckingAndGeneration(); + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } } } unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const { return this->m_HistogramNBins; } bool QmitkImageStatisticsDataGenerator::NodeChangeIsRelevant(const mitk::DataNode* changedNode) const { auto result = QmitkImageAndRoiDataGeneratorBase::NodeChangeIsRelevant(changedNode); if (!result) { std::string status; if (!changedNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status)) { auto stats = dynamic_cast(changedNode->GetData()); - return stats != nullptr; + result = stats != nullptr; } } - return false; + 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 { mitk::DataNode::Pointer result; auto storage = m_Storage.Lock(); if (storage) { if (!imageNode || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode) { mask = roiNode->GetData(); } mitk::NodePredicateBase::ConstPointer predicate = mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(image, mask); if (predicate) { 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)); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate); if (noWIP) { auto noWIPPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); predicate = mitk::NodePredicateAnd::New(predicate, noWIPPredicate); } std::shared_lock mutexguard(m_DataMutex); auto statisticContainerCandidateNodes = storage->GetSubset(predicate); auto statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); for (const auto& node : *statisticContainerCandidateNodes) { auto isUpToDate = image->GetMTime() < node->GetData()->GetMTime() && (mask == nullptr || mask->GetMTime() < node->GetData()->GetMTime()); if (!onlyIfUpToDate || isUpToDate) { statisticContainerCandidateNodesFiltered->push_back(node); } } if (!statisticContainerCandidateNodesFiltered->empty()) { auto newestElement = statisticContainerCandidateNodesFiltered->front(); if (statisticContainerCandidateNodesFiltered->size() > 1) { //in case of multiple found statistics, return only newest one auto newestIter = std::max_element(std::begin(*statisticContainerCandidateNodesFiltered), std::end(*statisticContainerCandidateNodesFiltered), [](mitk::DataNode::Pointer a, mitk::DataNode::Pointer b) { return a->GetData()->GetMTime() < b->GetData()->GetMTime(); }); newestElement = *newestIter; - MITK_WARN << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; + MITK_DEBUG << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; for (const auto& node : *statisticContainerCandidateNodesFiltered) { MITK_DEBUG << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); } } result = newestElement; } } } return result; } void QmitkImageStatisticsDataGenerator::IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { if (!imageNode || !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) { mask = roiNode->GetData(); } - if (!this->IsValidResultAvailable(imageNode, roiNode)) + auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); + if (resultDataNode.IsNull()) { auto dummyStats = mitk::ImageStatisticsContainer::New(); 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) { std::shared_lock mutexguard(m_DataMutex); storage->Add(dummyNode); } } } -QmitkDataGenerationJobBase* QmitkImageStatisticsDataGenerator::GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +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 || !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::Image* mask = nullptr; const mitk::PlanarFigure* planar = nullptr; if (roiNode) { 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 newJob; + return std::pair(newJob, resultDataNode.GetPointer()); } - return nullptr; + 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 || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode) { 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); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate); auto storage = m_Storage.Lock(); if (storage) { std::shared_lock 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) { 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) { 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) { mitkThrow() << "Image is nullptr"; } statisticsNodeName << image->GetUID(); if (roi) { statisticsNodeName << "_" + roi->GetUID(); } return statisticsNodeName.str(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h index 7175b19d7a..d43610d526 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h @@ -1,63 +1,63 @@ /*============================================================================ 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 __QMITK_IMAGE_STATISTICS_DATA_GENERATOR_H #define __QMITK_IMAGE_STATISTICS_DATA_GENERATOR_H #include "QmitkImageAndRoiDataGeneratorBase.h" #include /*! \brief QmitkImageStatisticsDataGenerator */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsDataGenerator : public QmitkImageAndRoiDataGeneratorBase { public: QmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(storage, parent) {}; QmitkImageStatisticsDataGenerator(QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(parent) {}; bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; /** Returns the latest result for a given image and ROI and the current settings of the generator. @param onlyIfUpToDate Indicates if results should only be returned if the are up to date, thus not older then image and ROI. @param noWIP If noWIP is true, the function only returns valid final result and not just its placeholder (WIP). If noWIP equals false it might also return a WIP, thus the valid result is currently processed/ordered but might not be ready yet.*/ mitk::DataNode::Pointer GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate = false, bool noWIP = true) const; std::string GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const; /*! /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; protected: bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const final; void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; - QmitkDataGenerationJobBase* GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; + std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const final; QmitkImageStatisticsDataGenerator(const QmitkImageStatisticsDataGenerator&) = delete; QmitkImageStatisticsDataGenerator& operator = (const QmitkImageStatisticsDataGenerator&) = delete; bool m_IgnoreZeroValueVoxel = false; unsigned int m_HistogramNBins = 100; }; #endif diff --git a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp index 419e861f45..0efbc4b05a 100644 --- a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp +++ b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp @@ -1,223 +1,518 @@ /*============================================================================ 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 #include #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkIOUtil.h" +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkProperties.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + #include #include class TestQmitkImageStatisticsDataGenerator : public QmitkImageStatisticsDataGenerator { public: TestQmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageStatisticsDataGenerator(storage, parent) { connect(this, &QmitkDataGeneratorBase::NewDataAvailable, this, &TestQmitkImageStatisticsDataGenerator::NewDataAvailableEmited); - connect(this, &QmitkDataGeneratorBase::GenerationStarted, this, &TestQmitkImageStatisticsDataGenerator::GenerationStartedEmited); + connect(this, &QmitkDataGeneratorBase::DataGenerationStarted, this, &TestQmitkImageStatisticsDataGenerator::DataGenerationStartedEmited); connect(this, &QmitkDataGeneratorBase::GenerationFinished, this, &TestQmitkImageStatisticsDataGenerator::GenerationFinishedEmited); connect(this, &QmitkDataGeneratorBase::JobError, this, &TestQmitkImageStatisticsDataGenerator::JobErrorEmited); }; mutable std::vector m_NewDataAvailable; void NewDataAvailableEmited(mitk::DataStorage::SetOfObjects::ConstPointer data) const { m_NewDataAvailable.emplace_back(data); }; - mutable int m_GenerationStartedEmited = 0; - void GenerationStartedEmited() const + mutable int m_DataGenerationStartedEmited = 0; + void DataGenerationStartedEmited(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) const { - m_GenerationStartedEmited++; + m_DataGenerationStartedEmited++; } mutable int m_GenerationFinishedEmited = 0; void GenerationFinishedEmited() const { m_GenerationFinishedEmited++; QCoreApplication::instance()->quit(); } mutable std::vector m_JobErrorEmited_error; void JobErrorEmited(QString error, const QmitkDataGenerationJobBase* failedJob) const { m_JobErrorEmited_error.emplace_back(error); } }; class QmitkImageStatisticsDataGeneratorTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(QmitkImageStatisticsDataGeneratorTestSuite); MITK_TEST(GetterSetterTest); MITK_TEST(NullTest); MITK_TEST(OneImageTest); MITK_TEST(MultiImageTest); MITK_TEST(ImageAndROITest); MITK_TEST(ImageAndMultiROITest); MITK_TEST(MultiMultiTest); MITK_TEST(InputChangedTest); MITK_TEST(SettingsChangedTest); MITK_TEST(DataStorageModificationTest); CPPUNIT_TEST_SUITE_END(); mitk::DataStorage::Pointer m_DataStorage; mitk::DataNode::Pointer m_ImageNode1; mitk::DataNode::Pointer m_ImageNode2; mitk::DataNode::Pointer m_MaskImageNode; mitk::DataNode::Pointer m_PFNode; mitk::Image::Pointer m_Image1; mitk::Image::Pointer m_Image2; mitk::Image::Pointer m_Mask; mitk::PlanarFigure::Pointer m_PF; QCoreApplication* m_TestApp; public: void setUp() override { m_DataStorage = mitk::StandaloneDataStorage::New(); m_ImageNode1 = mitk::DataNode::New(); m_ImageNode1->SetName("Image_1"); auto pic3DCroppedFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_cropped.nrrd"); m_Image1 = mitk::IOUtil::Load(pic3DCroppedFile); CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image1.IsNotNull()); m_ImageNode1->SetData(m_Image1); + m_DataStorage->Add(m_ImageNode1); m_ImageNode2 = mitk::DataNode::New(); m_ImageNode2->SetName("Image_2"); m_Image2 = mitk::IOUtil::Load(pic3DCroppedFile); CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image2.IsNotNull()); m_ImageNode2->SetData(m_Image2); + m_DataStorage->Add(m_ImageNode2); m_MaskImageNode = mitk::DataNode::New(); m_MaskImageNode->SetName("Mask"); auto pic3DCroppedBinMaskFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedBinMask.nrrd"); m_Mask = mitk::IOUtil::Load(pic3DCroppedBinMaskFile); CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D binary mask", m_Mask.IsNotNull()); m_MaskImageNode->SetData(m_Mask); + m_DataStorage->Add(m_MaskImageNode); m_PFNode = mitk::DataNode::New(); m_PFNode->SetName("PF"); auto pic3DCroppedPlanarFigureFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedPF.pf"); m_PF = mitk::IOUtil::Load(pic3DCroppedPlanarFigureFile); CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D planar figure", m_PF.IsNotNull()); m_PFNode->SetData(m_PF); + m_DataStorage->Add(m_PFNode); int argc = 0; char** argv = nullptr; m_TestApp = new QCoreApplication(argc, argv); } void tearDown() override { delete m_TestApp; } + bool CheckResultNode(const std::vector resultNodes, const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, unsigned int histBin = 100, bool noZero = false) + { + for (auto& resultNode : resultNodes) + { + bool result = false; + + if (resultNode && resultNode->GetData() && imageNode && imageNode->GetData()) + { + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + result = !imageRule->GetRelationUIDs(resultNode, imageNode).empty(); + + if (roiNode) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + result = result && !maskRule->GetRelationUIDs(resultNode, roiNode).empty(); + } + + auto prop = resultNode->GetData()->GetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str()); + auto binProp = dynamic_cast(prop.GetPointer()); + result = result && binProp->GetValue() == histBin; + + prop = resultNode->GetData()->GetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str()); + auto zeroProp = dynamic_cast(prop.GetPointer()); + result = result && zeroProp->GetValue() == noZero; + } + + if (result) + { //node was in the result set + return true; + } + + } + + return false; + } + void NullTest() { TestQmitkImageStatisticsDataGenerator generator(nullptr); generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); - CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationStartedEmited); CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); generator.SetDataStorage(m_DataStorage); generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); - CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationStartedEmited); CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); } void GetterSetterTest() { TestQmitkImageStatisticsDataGenerator generator(nullptr); CPPUNIT_ASSERT(nullptr == generator.GetDataStorage()); generator.SetDataStorage(m_DataStorage); CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); TestQmitkImageStatisticsDataGenerator generator2(m_DataStorage); CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); CPPUNIT_ASSERT_EQUAL(100u, generator.GetHistogramNBins()); CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); generator.SetHistogramNBins(3); CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); generator.SetIgnoreZeroValueVoxel(true); CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); generator.SetAutoUpdate(true); CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); CPPUNIT_ASSERT_EQUAL(true, generator.GetAutoUpdate()); } void OneImageTest() { TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); generator.SetImageNodes({ m_ImageNode1.GetPointer() }); generator.Generate(); m_TestApp->exec(); + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); - CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationStartedEmited); CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); - CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable.front()->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); } void MultiImageTest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2u == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); } void ImageAndROITest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, m_MaskImageNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, m_PFNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); } void ImageAndMultiROITest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front()}, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); } void MultiMultiTest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(6, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(6 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); } void InputChangedTest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode2.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(0 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 2 == generator.m_NewDataAvailable.size()); } void SettingsChangedTest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, nullptr)); + + generator.SetHistogramNBins(50); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr, 50)); + + generator.SetIgnoreZeroValueVoxel(true); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr, 50, true)); + + //now check auto update feature + generator.SetAutoUpdate(true); + generator.SetHistogramNBins(5); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 4 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[3]->front() }, m_ImageNode1, nullptr, 5, true)); + + generator.SetHistogramNBins(5); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4 == generator.m_NewDataAvailable.size()); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 5 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[4]->front() }, m_ImageNode1, nullptr, 5, false)); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5 == generator.m_NewDataAvailable.size()); } void DataStorageModificationTest() { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + + //now check auto update feature + generator.SetAutoUpdate(true); + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 3 == generator.m_NewDataAvailable.size()); + + + m_DataStorage->Add(mitk::DataNode::New()); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); + + m_Image2->Modified(); + m_ImageNode2->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); } }; MITK_TEST_SUITE_REGISTRATION(QmitkImageStatisticsDataGenerator) diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index 1dff59e495..03ec9afee3 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,374 +1,374 @@ /*============================================================================ 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 "QmitkImageStatisticsView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkPlanarFigureMaskGenerator.h" #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); m_Controls.sliderWidget_histogram->setPrefix("Time: "); m_Controls.sliderWidget_histogram->setDecimals(0); m_Controls.sliderWidget_histogram->setVisible(false); m_Controls.sliderWidget_intensityProfile->setPrefix("Time: "); m_Controls.sliderWidget_intensityProfile->setDecimals(0); m_Controls.sliderWidget_intensityProfile->setVisible(false); ResetGUI(); m_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); m_DataGenerator->SetDataStorage(this->GetDataStorage()); m_DataGenerator->SetAutoUpdate(true); m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); CreateConnections(); } void QmitkImageStatisticsView::CreateConnections() { connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); - connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationStarted, + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::DataGenerationStarted, this, &QmitkImageStatisticsView::OnGenerationStarted); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, this, &QmitkImageStatisticsView::OnGenerationFinished); connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, this, &QmitkImageStatisticsView::OnJobError); } void QmitkImageStatisticsView::UpdateIntensityProfile() { m_Controls.groupBox_intensityProfile->setVisible(false); if (m_selectedImageNodes.size()==1) { //only supported for one image and roi currently auto image = dynamic_cast(m_selectedImageNodes.front()->GetData()); if (m_selectedPlanarFigure.IsNotNull()) { if (!m_selectedPlanarFigure->IsClosed()) { mitk::Image::Pointer inputImage; if (image->GetDimension() == 4) { m_Controls.sliderWidget_intensityProfile->setVisible(true); unsigned int maxTimestep = image->GetTimeSteps(); m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); // Intensity profile can only be calculated on 3D, so extract if 4D mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); timeSelector->SetInput(image); timeSelector->SetTimeNr(currentTimestep); timeSelector->Update(); inputImage = timeSelector->GetOutput(); } else { m_Controls.sliderWidget_intensityProfile->setVisible(false); inputImage = image; } auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, m_selectedPlanarFigure); m_Controls.groupBox_intensityProfile->setVisible(true); m_Controls.widget_intensityProfile->Reset(); m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), "Intensity Profile of " + m_selectedImageNodes.front()->GetName()); } } } } void QmitkImageStatisticsView::UpdateHistogramWidget() { m_Controls.groupBox_histogram->setVisible(false); if (m_selectedImageNodes.size() == 1 || m_selectedMaskNodes.size()<=1) { //currently only supported for one image and roi due to histogram widget limitations. auto imageNode = m_selectedImageNodes.front(); const mitk::DataNode* roiNode = nullptr; if (!m_selectedMaskNodes.empty()) { roiNode = m_selectedMaskNodes.front(); } auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode); if (statisticsNode.IsNotNull()) { auto statistics = dynamic_cast(statisticsNode->GetData()); if (statistics) { std::stringstream label; label << "Histogram " << imageNode->GetName(); if (imageNode->GetData()->GetTimeSteps() > 1) { label << "[0]"; } if (roiNode) { label << " with " << roiNode->GetName(); } m_Controls.groupBox_histogram->setVisible(true); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_histogram->SetHistogram(statistics->GetTimeStepHistogram(0), label.str()); } } } } QmitkChartWidget::ColorTheme QmitkImageStatisticsView::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); } void QmitkImageStatisticsView::ResetGUIDefault() { m_Controls.widget_histogram->ResetDefault(); m_Controls.checkBox_ignoreZero->setChecked(false); m_IgnoreZeroValueVoxel = false; } -void QmitkImageStatisticsView::OnGenerationStarted() +void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { m_Controls.label_currentlyComputingStatistics->setVisible(true); } void QmitkImageStatisticsView::OnGenerationFinished() { m_Controls.label_currentlyComputingStatistics->setVisible(false); mitk::StatusBar::GetInstance()->Clear(); this->UpdateIntensityProfile(); this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) { mitk::StatusBar::GetInstance()->DisplayErrorText(error.toStdString().c_str()); MITK_WARN << "Error when calculating statistics: " << error; } void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int nbins) { m_DataGenerator->SetHistogramNBins(nbins); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { m_IgnoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; m_DataGenerator->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); } void QmitkImageStatisticsView::OnButtonSelectionPressed() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(nullptr, "Select input for the statistic","You may select images and ROIs to compute their statistic. ROIs may be segmentations or planar figures."); dialog->SetDataStorage(GetDataStorage()); dialog->SetSelectionCheckFunction(CheckForSameGeometry()); // set predicates auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); auto isImageOrMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isMaskOrPlanarFigurePredicate, isImagePredicate); dialog->SetNodePredicate(isImageOrMaskOrPlanarFigurePredicate); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetCurrentSelection(m_SelectedNodeList); if (dialog->exec()) { std::vector imageNodes; std::vector maskNodes; m_SelectedNodeList = dialog->GetSelectedNodes(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); for (const auto& node : m_SelectedNodeList) { if (isImagePredicate->CheckNode(node)) { imageNodes.push_back(node.GetPointer()); } else { maskNodes.push_back(node.GetPointer()); } } m_selectedImageNodes = imageNodes; m_selectedMaskNodes = maskNodes; if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); m_selectedPlanarFigure = nullptr; } mitk::PlanarFigure* maskPlanarFigure = nullptr; if (m_selectedMaskNodes.size() == 1) { //currently we support this only with one ROI selected maskPlanarFigure = dynamic_cast(m_selectedMaskNodes.front()->GetData()); } if (nullptr != maskPlanarFigure) { m_selectedPlanarFigure = maskPlanarFigure; ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::UpdateIntensityProfile); m_PlanarFigureObserverTag = m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); this->UpdateIntensityProfile(); } m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); m_DataGenerator->SetImageNodes(m_selectedImageNodes); m_DataGenerator->SetROINodes(m_selectedMaskNodes); } delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto lambda = [](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { imageNodeData = dynamic_cast(node->GetData()); if (imageNodeData) { break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

"; return ss.str(); } auto imageGeoPredicate = mitk::NodePredicateGeometry::New(imageNodeData->GetGeometry()); for (auto& rightNode : nodes) { if (imageNodeData != rightNode->GetData()) { bool sameGeometry = true; if (dynamic_cast(rightNode->GetData())) { sameGeometry = imageGeoPredicate->CheckNode(rightNode); } else { const mitk::PlanarFigure* planar2 = dynamic_cast(rightNode->GetData()); if (planar2) { sameGeometry = mitk::PlanarFigureMaskGenerator::CheckPlanarFigureIsNotTilted(planar2->GetPlaneGeometry(), imageNodeData->GetGeometry()); } } if (!sameGeometry) { std::stringstream ss; ss << "

Invalid selection: All selected nodes must have the same geometry.

Differing node i.a.: \""; ss << rightNode->GetName() <<"\"

"; return ss.str(); } } } return std::string(); }; return lambda; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h index 339dceb170..b88a4c9f21 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,91 +1,91 @@ /*============================================================================ 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 QMITKIMAGESTATISTICSVIEW_H #define QMITKIMAGESTATISTICSVIEW_H #include "ui_QmitkImageStatisticsViewControls.h" #include #include #include #include class QmitkImageStatisticsDataGenerator; class QmitkDataGenerationJobBase; /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a separate thread to keep the gui accessible during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default destructor */ ~QmitkImageStatisticsView() override; /*! \brief Creates the widget containing the application controls, like sliders, buttons etc.*/ void CreateQtPartControl(QWidget *parent) override; protected: using HistogramType = mitk::ImageStatisticsContainer::HistogramType; void SetFocus() override { }; virtual void CreateConnections(); void UpdateIntensityProfile(); void UpdateHistogramWidget(); QmitkChartWidget::ColorTheme GetColorTheme() const; void ResetGUI(); void ResetGUIDefault(); - void OnGenerationStarted(); + void OnGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job); void OnGenerationFinished(); void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob); void OnRequestHistogramUpdate(unsigned int); void OnCheckBoxIgnoreZeroStateChanged(int state); void OnButtonSelectionPressed(); // member variable Ui::QmitkImageStatisticsViewControls m_Controls; private: QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry() const; typedef itk::SimpleMemberCommand ITKCommandType; mitk::PlanarFigure::Pointer m_selectedPlanarFigure = nullptr; long m_PlanarFigureObserverTag; bool m_IgnoreZeroValueVoxel = false; std::vector m_selectedMaskNodes; std::vector m_selectedImageNodes; QmitkNodeSelectionDialog::NodeList m_SelectedNodeList; std::vector m_StatisticsForSelection; QmitkImageStatisticsDataGenerator* m_DataGenerator = nullptr; }; #endif // QMITKIMAGESTATISTICSVIEW_H