diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp index e2ebdbae6d..da141699e5 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -1,130 +1,130 @@ /*============================================================================ 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 "mitkImageStatisticsContainerManager.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateDataProperty.h" #include "mitkProperties.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" mitk::ImageStatisticsContainer::Pointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) { auto node = GetImageStatisticsNode(dataStorage, image, mask, ignoreZeroVoxel, histogramNBins, onlyIfUpToDate, noWIP); mitk::ImageStatisticsContainer::Pointer result; if (node.IsNotNull()) { result = dynamic_cast(node->GetData()); } return result; } mitk::DataNode::Pointer mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) { mitk::DataNode::Pointer result; if (!dataStorage) { mitkThrow() << "data storage is nullptr!"; } if (!image) { mitkThrow() << "Image is nullptr"; } mitk::NodePredicateBase::ConstPointer predicate = GetStatisticsPredicateForSources(image, mask); if (predicate) { auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(histogramNBins)); auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(ignoreZeroVoxel)); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); if (noWIP) { auto noWIPPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); predicate = mitk::NodePredicateAnd::New(predicate, noWIPPredicate).GetPointer(); } auto statisticContainerCandidateNodes = dataStorage->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_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; } mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask) { if (!image) { mitkThrow() << "Image is nullptr"; } auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New(ImageStatisticsContainer::GetStaticNameOfClass()); auto imageRule = mitk::StatisticsToImageRelationRule::New(); auto imagePredicate = imageRule->GetSourcesDetector(image); - mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(nodePredicateImageStatisticsContainer, imagePredicate); + mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(nodePredicateImageStatisticsContainer, imagePredicate).GetPointer(); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); if (mask) { auto maskPredicate = maskRule->GetSourcesDetector(mask); predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } else { auto maskPredicate = mitk::NodePredicateNot::New(maskRule->GetConnectedSourcesDetector()); predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } return predicate; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp index 3aafcfd7cc..5f2a8df39e 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,275 +1,275 @@ /*============================================================================ 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 "mitkImageStatisticsContainerManager.h" #include 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 "change 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); + std::lock_guard mutexguard(m_DataMutex); auto oldStorage = m_Storage.Lock(); if (oldStorage.IsNotNull()) { // remove "change 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 change 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); + std::lock_guard mutexguard(m_DataMutex); auto storage = m_Storage.Lock(); if (storage.IsNotNull()) { m_AddingToStorage = true; for (auto pos = resultnodes->Begin(); pos != resultnodes->End(); ++pos) { storage->Add(pos->Value()); } m_AddingToStorage = false; } } emit NewDataAvailable(resultnodes.GetPointer()); if (!resultnodes->empty()) { this->EnsureRecheckingAndGeneration(); } } void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) { if (!m_AddingToStorage) { if (this->ChangedNodeIsRelevant(node)) { this->EnsureRecheckingAndGeneration(); } } } void QmitkDataGeneratorBase::EnsureRecheckingAndGeneration() const { m_RestartGeneration = true; if (!m_InGenerate) { this->Generate(); } } 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; 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; } QmitkDataGeneratorBase::InputPairVectorType QmitkDataGeneratorBase::FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const { - std::shared_lock mutexguard(m_DataMutex); + std::lock_guard mutexguard(m_DataMutex); InputPairVectorType filteredImageROICombinations; auto storage = m_Storage.Lock(); if (storage.IsNotNull()) { for (auto inputPair : imageROICombinations) { if (storage->Exists(inputPair.first) && (inputPair.second.IsNull() || storage->Exists(inputPair.second))) { filteredImageROICombinations.emplace_back(inputPair); } else { MITK_DEBUG << "Ignor pair because at least one of the nodes is not in storage. Pair: " << GetPairDescription(inputPair); } } } return filteredImageROICombinations; } std::string QmitkDataGeneratorBase::GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const { if (imageAndSeg.second.IsNotNull()) { return imageAndSeg.first->GetName() + " and ROI " + imageAndSeg.second->GetName(); } else { return imageAndSeg.first->GetName(); } } bool QmitkDataGeneratorBase::DoGenerate() const { auto filteredImageROICombinations = FilterImageROICombinations(this->GetAllImageROICombinations()); QThreadPool* threadPool = QThreadPool::globalInstance(); bool everythingValid = true; for (const auto& imageAndSeg : filteredImageROICombinations) { MITK_DEBUG << "checking node " << GetPairDescription(imageAndSeg); if (!this->IsValidResultAvailable(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer())) { this->IndicateFutureResults(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); if (everythingValid) { m_WIP = true; everythingValid = false; } MITK_DEBUG << "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 (nextJob.first==nullptr && nextJob.second.IsNotNull()) { MITK_DEBUG << "Last generation job still running, pass on till job is finished..."; } else if(nextJob.first != nullptr && nextJob.second.IsNotNull()) { MITK_DEBUG << "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 19a841cb4b..bd2c61ba2f 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -1,174 +1,174 @@ /*============================================================================ 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 +#include //QT #include //MITK #include #include "QmitkDataGenerationJobBase.h" #include /*! \brief QmitkDataGeneratorBase BaseClass that implements the organisation of (statistic) data generation for pairs of images and ROIs. The key idea is that this class ensures that for vector of given image ROI pairs (defined by derived classes) a result instance (e.g ImageStatisticsContainer) will be calculated, if needed (e.g. because it is missing or not uptodate anymore), and stored in the data storage passed to a generator instance. While derived classes i.a. specify how to generate the image ROI pairs, how to detect latest results, what the next generation step is and how to remove obsolete data from the storage, the base class takes core of the observation of the data storage and orchestrat the whole checking and generation workflow. In all the generation/orchestration process the data storage passed to the generator serves 1) as place where the final results are stored and searched and it resembles the state of the genertion process with these final results and WIP place holder nodes that indicate planned or currently processed generation steps. */ 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; /** Indicates the generator may triggers the update automatically (true). Reasons for an update are: - Input data has been changed or modified - Generation relevant settings in derived classes have been changed (must be implemented in derived classes) */ bool GetAutoUpdate() const; /** Indicates if there is currently work in progress, thus 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; /** Checks data validity and triggers generation of data, if needed. The generation itselfs will be done with a thread pool and is 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: /** Sets the data storage the generator should monitor and where WIP placeholder nodes and final result nodes should be stored.*/ void SetDataStorage(mitk::DataStorage* storage); void SetAutoUpdate(bool autoUpdate); protected slots: /** Used by QmitkDataGenerationJobBase to signal the generator that an error occured. */ void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; /** Used by QmitkDataGenerationJobBase to signal and communicate the results of there computation. */ void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; signals: /*! @brief Signal that is emitted if a data generation job is started to generat outdated/inexistant data. */ void DataGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job) const; /*! @brief Signal that is emitted if new final data is produced. */ void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; /*! @brief Signal that is emitted if all jobs are finished and everything is up to date. */ 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>; /** This method must be implemented by derived to indicate if a changed node is relevant and therefore if an update must be triggered.*/ virtual bool ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const = 0; /** This method must be impemented by derived classes to return the pairs of images and ROIs (ROI may be null if no ROI is needed) for which data are needed.*/ virtual InputPairVectorType GetAllImageROICombinations() const = 0; /** This method should indicate all missing and outdated (interim) results in the data storage, with new placeholder nodes and WIP dummy data added to the storage. The placeholder nodes will be replaced by the real results as soon as they are ready. The strategy how to detact which placeholder node is need and how the dummy data should look like must be implemented by derived classes.*/ virtual void IndicateFutureResults(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 is associated dummy node in order to progress the data generation workflow. @remark The method can assume that the caller takes care of the job instance deletion. @return std::pair of job pointer and placeholder node associated with the job. Following combinations are possible: - Both are null: nothing to do; - Both are set: there is something to do for a pending dumme node -> trigger computation; - Job null and node set: a job for this node is already work in progress -> pass on till its finished.*/ virtual std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; /** Remove all obsolete data nodes for the given image and ROI 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; /*! Creates a data node for WIP place holder results. It can be used by IndicateFutureResults().*/ static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); /** Filters a passed pair vector. The returned pair vector only contains pair of nodes that exist in the data storage.*/ InputPairVectorType FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const; /** Return a descriptive label of a passed pair. Used e.g. for some debug log messages.*/ std::string GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const; /** Internal part of the generation strategy. Here is where the heavy lifting is done.*/ bool DoGenerate() const; /** Methods either directly calls generation or if its allready onging flags to restart the generation.*/ void EnsureRecheckingAndGeneration() const; mitk::WeakPointer m_Storage; bool m_AutoUpdate = false; - mutable std::shared_mutex m_DataMutex; + mutable std::mutex m_DataMutex; private: /** Indicates if we are currently in the Generation() verification and generation of pending jobs triggering loop. Only needed for the internal logic.*/ mutable bool m_InGenerate = false; /** Internal flag that is set if a generation was requested, while one generation loop was already ongoing.*/ mutable bool m_RestartGeneration = false; /** Indicates if there are still jobs pending or computing (true) or if everything is valid (false).*/ mutable bool m_WIP = false; /** Internal flag that indicates that generator is currently in the process of adding results to the storage*/ mutable bool m_AddingToStorage = false; /**Member is called when a node is added to the storage.*/ void NodeAddedOrModified(const mitk::DataNode* node); unsigned long m_DataStorageDeletedTag; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp index 94510df181..38e50a02c9 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp @@ -1,103 +1,103 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageAndRoiDataGeneratorBase.h" QmitkImageAndRoiDataGeneratorBase::NodeVectorType QmitkImageAndRoiDataGeneratorBase::GetROINodes() const { return m_ROINodes; } QmitkImageAndRoiDataGeneratorBase::NodeVectorType QmitkImageAndRoiDataGeneratorBase::GetImageNodes() const { return m_ImageNodes; } void QmitkImageAndRoiDataGeneratorBase::SetImageNodes(const NodeVectorType& imageNodes) { if (m_ImageNodes != imageNodes) { { - std::shared_lock mutexguard(m_DataMutex); + std::lock_guard mutexguard(m_DataMutex); m_ImageNodes = imageNodes; } if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } void QmitkImageAndRoiDataGeneratorBase::SetROINodes(const NodeVectorType& roiNodes) { if (m_ROINodes != roiNodes) { { - std::shared_lock mutexguard(m_DataMutex); + std::lock_guard mutexguard(m_DataMutex); m_ROINodes = roiNodes; } if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } bool QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const { if (m_AutoUpdate) { auto finding = std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); if (finding != m_ImageNodes.end()) { return true; } finding = std::find(m_ROINodes.begin(), m_ROINodes.end(), changedNode); if (finding != m_ROINodes.end()) { return true; } } return false; } QmitkImageAndRoiDataGeneratorBase::InputPairVectorType QmitkImageAndRoiDataGeneratorBase::GetAllImageROICombinations() const { - std::shared_lock mutexguard(m_DataMutex); + std::lock_guard mutexguard(m_DataMutex); InputPairVectorType allCombinations; for (auto& imageNode : m_ImageNodes) { if (m_ROINodes.empty()) { allCombinations.emplace_back(imageNode, nullptr); } else { for (auto& roiNode : m_ROINodes) { allCombinations.emplace_back(imageNode, roiNode); } } } return allCombinations; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp index 8cb149f74c..5cb2cfbd81 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -1,273 +1,273 @@ /*============================================================================ 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 || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode) { mask = roiNode->GetData(); } - std::shared_lock mutexguard(m_DataMutex); + 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 || !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(); } 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); + 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 || !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 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 || !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).GetPointer(); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); auto storage = m_Storage.Lock(); if (storage) { - std::shared_lock mutexguard(m_DataMutex); + 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 +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/test/QmitkImageStatisticsDataGeneratorTest.cpp b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp index 7f1ab831d3..a64ab03339 100644 --- a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp +++ b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp @@ -1,519 +1,519 @@ /*============================================================================ 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 "mitkImageStatisticsContainerManager.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::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_DataGenerationStartedEmited = 0; void DataGenerationStartedEmited(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) const { 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 + 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(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(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(generator.m_JobErrorEmited_error.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/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp index 2b0baa06e0..0caa074875 100644 --- a/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp @@ -1,233 +1,233 @@ /*============================================================================ 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. ============================================================================*/ // semantic relations UI module #include "QmitkStatisticsCalculator.h" // semantic relations module #include #include #include #include #include // mitk image statistics module #include #include #include #include QmitkStatisticsCalculator::QmitkStatisticsCalculator() : m_CalculationJob(nullptr) , m_DataStorage(nullptr) , m_MaskVolume(0.0) { m_CalculationJob = new QmitkImageStatisticsCalculationJob(); connect(m_CalculationJob, &QmitkImageStatisticsCalculationJob::finished, this, &QmitkStatisticsCalculator::OnStatisticsCalculationEnds, Qt::QueuedConnection); } QmitkStatisticsCalculator::~QmitkStatisticsCalculator() { if (!m_CalculationJob->isFinished()) { m_CalculationJob->terminate(); m_CalculationJob->wait(); } m_CalculationJob->deleteLater(); } void QmitkStatisticsCalculator::ComputeLesionVolume(mitk::LesionData& lesionData, const mitk::SemanticTypes::CaseID& caseID) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); std::vector lesionVolume; mitk::SemanticTypes::Lesion lesion = lesionData.GetLesion(); double volume = 0.0; mitk::SemanticTypes::ControlPointVector controlPoints = mitk::RelationStorage::GetAllControlPointsOfCase(caseID); // sort the vector of control points for the timeline std::sort(controlPoints.begin(), controlPoints.end()); mitk::SemanticTypes::InformationTypeVector informationTypes = mitk::RelationStorage::GetAllInformationTypesOfCase(caseID); for (const auto& informationType : informationTypes) { for (const auto& controlPoint : controlPoints) { mitk::SemanticRelationsDataStorageAccess semanticRelationsDataStorageAccess(dataStorage); mitk::DataNode::Pointer specificImage; mitk::DataNode::Pointer specificSegmentation; try { specificSegmentation = semanticRelationsDataStorageAccess.GetSpecificSegmentation(caseID, controlPoint, informationType, lesion); if (nullptr == specificSegmentation) { volume = 0.0; } else { // get parent node of the specific segmentation node with the node predicate auto parentNodes = dataStorage->GetSources(specificSegmentation, mitk::NodePredicates::GetImagePredicate(), false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { specificImage = it->Value(); } volume = GetSegmentationMaskVolume(specificImage, specificSegmentation); } } catch (mitk::SemanticRelationException&) { volume = 0.0; } lesionVolume.push_back(volume); } } lesionData.SetLesionVolume(lesionVolume); } double QmitkStatisticsCalculator::GetSegmentationMaskVolume(mitk::DataNode::Pointer imageNode, mitk::DataNode::Pointer segmentationNode) { m_MaskVolume = 0.0; if (m_DataStorage.IsExpired()) { return m_MaskVolume; } auto dataStorage = m_DataStorage.Lock(); if (imageNode.IsNull() || segmentationNode.IsNull()) { return m_MaskVolume; } m_ImageNode = imageNode; m_SegmentationNode = segmentationNode; auto image = dynamic_cast(m_ImageNode->GetData()); auto segmentation = dynamic_cast(m_SegmentationNode->GetData()); if (nullptr == image || nullptr == segmentation) { return m_MaskVolume; } // all nodes and images are valid, retrieve statistics - mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation); + mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation).GetPointer(); bool imageStatisticsOlderThanInputs = false; if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || (imageStatistics->GetMTime() < segmentation->GetMTime()))) { imageStatisticsOlderThanInputs = true; } // statistics need to be (re)computed if (!imageStatistics || imageStatisticsOlderThanInputs) { m_CalculationJob->Initialize(image, segmentation, nullptr); try { m_CalculationJob->start(); return m_MaskVolume; } catch (const std::exception&) { return m_MaskVolume; } } // use a valid statistics object to get the volume of the image-segmentation pair mitk::ImageStatisticsContainer::ImageStatisticsObject statisticsObject; try { statisticsObject = imageStatistics->GetStatisticsForTimeStep(0); } catch (mitk::Exception&) { return m_MaskVolume; } try { if (statisticsObject.HasStatistic(mitk::ImageStatisticsConstants::VOLUME())) { auto valueVariant = statisticsObject.GetValueNonConverted(mitk::ImageStatisticsConstants::VOLUME()); m_MaskVolume = boost::get(valueVariant); } } catch (mitk::Exception&) { return m_MaskVolume; } return m_MaskVolume; } void QmitkStatisticsCalculator::OnStatisticsCalculationEnds() { // taken from 'QmitkImageStatisticsView' (see measurementtoolbox plugin) if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); if (m_CalculationJob->GetStatisticsUpdateSuccessFlag()) { auto statistic = m_CalculationJob->GetStatisticsData(); auto image = m_CalculationJob->GetStatisticsImage(); mitk::BaseData::ConstPointer mask = nullptr; auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statistic, image); if (m_CalculationJob->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); mask = m_CalculationJob->GetMaskImage(); maskRule->Connect(statistic, mask); } auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, mask); // if statistics base data already exist: add to existing node if (nullptr != imageStatistics) { auto allDataNodes = dataStorage->GetAll()->CastToSTLConstContainer(); for (auto node : allDataNodes) { auto nodeData = node->GetData(); if (nullptr != nodeData && nodeData->GetUID() == imageStatistics->GetUID()) { node->SetData(statistic); } } } // statistics base data does not exist: add new node else { auto statisticsNodeName = m_ImageNode->GetName(); if (m_SegmentationNode) { statisticsNodeName += "_" + m_SegmentationNode->GetName(); } statisticsNodeName += "_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statistic, statisticsNodeName); dataStorage->Add(statisticsNode); } } }