diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp index 641ecc7031..5e8ae834da 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -1,92 +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::ConstPointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask) +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);; + mitk::NodePredicateBase::ConstPointer predicate = GetStatisticsPredicateForSources(image, mask); if (predicate) { - auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); - mitk::DataStorage::SetOfObjects::Pointer statisticContainerCandidateNodesFiltered; + 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); - statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); - for (const auto& node : *statisticContainerCandidateNodes) { - statisticContainerCandidateNodesFiltered->push_back(node); + if (noWIP) + { + auto noWIPPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); + predicate = mitk::NodePredicateAnd::New(predicate, noWIPPredicate); } - if (statisticContainerCandidateNodesFiltered->empty()) { - return nullptr; + 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); + } } - auto newestElement = statisticContainerCandidateNodesFiltered->front(); - if (statisticContainerCandidateNodesFiltered->size() > 1) { + 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."; - for (const auto& node : *statisticContainerCandidateNodesFiltered) { + 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(); } } - return dynamic_cast(newestElement->GetData()); - } - else { - return nullptr; + 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); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); if (mask) { auto maskPredicate = maskRule->GetSourcesDetector(mask); predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); } else { auto maskPredicate = mitk::NodePredicateNot::New(maskRule->GetConnectedSourcesDetector()); predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); } return predicate; } diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h index 458eb7aa35..e75d241c08 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h @@ -1,46 +1,60 @@ /*============================================================================ 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 QmitkImageStatisticsContainerManager_H__INCLUDED #define QmitkImageStatisticsContainerManager_H__INCLUDED #include "MitkImageStatisticsExports.h" #include #include #include #include #include #include namespace mitk { + + static const std::string STATS_HISTOGRAM_BIN_PROPERTY_NAME = "MITK.statistic.histogram_bins"; + static const std::string STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME = "MITK.statistic.ignore_zero_voxel"; + static const std::string STATS_GENERATION_STATUS_PROPERTY_NAME = "MITK.statistic.generation.status"; + static const std::string STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS = "workInProgress"; + static const std::string STATS_GENERATION_STATUS_VALUE_PENDING = "pending"; + static const std::string STATS_GENERATION_STATUS_VALUE_BASE_DATA_FAILED = "failed"; + /** \brief Returns the StatisticsContainer that was computed on given input (image/mask/planar figure) and is added as DataNode in a DataStorage */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainerManager { public: /**Documentation - @brief Returns the StatisticContainer for the given image and mask + @brief Returns the StatisticContainer for the given image and mask from the storage- @return a valid StatisticsContainer or nullptr if no StatisticContainer is found. @details if more than one StatisticsContainer is found, only the newest (ModifiedTime) is returned @pre Datastorage must point to a valid instance. @pre image must Point to a valid instance. + @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. + @param ignoreZeroVoxel indicates the wanted statistics are calculated with or w/o zero voxels. + @param histogramNBins Number of bins the statistics should have that are searched for. */ - static mitk::ImageStatisticsContainer::ConstPointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr); + static mitk::ImageStatisticsContainer::Pointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); + static mitk::DataNode::Pointer GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask = nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); /** Returns the predicate that can be used to search for statistic containers of the given image (and mask) in the passed data storage.*/ static mitk::NodePredicateBase::ConstPointer GetStatisticsPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask = nullptr); }; } #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h index 9c970e5c63..5fb4382aa4 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -1,93 +1,85 @@ /*============================================================================ 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_GENERATION_JOB_BASE_H #define __QMITK_DATA_GENERATION_JOB_BASE_H //QT #include #include #include //MITK #include #include -namespace mitk -{ - static const std::string STATS_GENERATION_STATUS_PROPERTY_NAME = "MITK.statistic.generation.status"; - static const std::string STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS = "workInProgress"; - static const std::string STATS_GENERATION_STATUS_VALUE_PENDING = "pending"; - static const std::string STATS_GENERATION_STATUS_VALUE_BASE_DATA_FAILED = "failed"; -} - /*! \brief QmitkDataGenerationJobBase Base class for all jobs. Each job wraps an implementation of DataGenerationComputationInterface to compute a computation in a multi threaded environment with Qt \details the signal ResultsAvailable is emitted when the job is finished the signal Error is emitted in case of an error \example QThreadPool* threadPool = QThreadPool::globalInstance(); auto voxelizationJob = new QmitkVoxelizationJob(doseImage, structContourModelSet, voxelizationNode); connect(job, SIGNAL(ResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkDataGenerationJobBase*)), this, SLOT(OnFinalResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkDataGenerationJobBase*))); threadPool->start(job); */ class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGenerationJobBase : public QObject, public QRunnable { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: QmitkDataGenerationJobBase(const QmitkDataGenerationJobBase& other) = delete; QmitkDataGenerationJobBase& operator=(const QmitkDataGenerationJobBase& other) = delete; /** Result map that indicates all results generated by the job. The key is a job specific label for the results.*/ using ResultMapType = std::map; virtual ResultMapType GetResults() const = 0; void run() final; /*! /brief Returns a flag the indicates if the jop computation was successfull.*/ bool GetComputationSuccessFlag() const; std::string GetLastErrorMessage() const; signals: void Error(QString err, const QmitkDataGenerationJobBase* job); /*! @brief Signal is emitted when results are available. @param results a of base date objects produces by the job and ready tu use, put into storage. @param the job that produced the data */ void ResultsAvailable(ResultMapType results, const QmitkDataGenerationJobBase* job); protected: QmitkDataGenerationJobBase() = default; virtual ~QmitkDataGenerationJobBase() = default; /**Does the real computation. Returns true if there where results produced.*/ virtual bool RunComputation() = 0; std::string m_LastErrorMessage; private: bool m_ComputationSuccessful = false; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp index 8f738de084..a6b0b7d933 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,247 +1,248 @@ /*============================================================================ 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 "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()); } 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(); } } 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; } bool QmitkDataGeneratorBase::DoGenerate() const { auto imageSegCombinations = this->GetAllImageROICombinations(); QThreadPool* threadPool = QThreadPool::globalInstance(); bool everythingValid = true; for (const auto& imageAndSeg : imageSegCombinations) { 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; everythingValid = false; } 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 (nextJob.first==nullptr && nextJob.second.IsNotNull()) { MITK_INFO << "Last generation job still running, pass on till job is finished..."; } else if(nextJob.first != nullptr && nextJob.second.IsNotNull()) { 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/QmitkImageStatisticsCalculationRunnable.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp index d315ecbdfe..e2f309b183 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp @@ -1,215 +1,216 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsCalculationRunnable.h" #include "mitkImageStatisticsCalculator.h" #include #include #include #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" +#include "mitkImageStatisticsContainerManager.h" #include "mitkProperties.h" QmitkImageStatisticsCalculationRunnable::QmitkImageStatisticsCalculationRunnable() : QmitkDataGenerationJobBase() , m_StatisticsImage(nullptr) , m_BinaryMask(nullptr) , m_PlanarFigureMask(nullptr) , m_IgnoreZeros(false) , m_HistogramNBins(100) { } QmitkImageStatisticsCalculationRunnable::~QmitkImageStatisticsCalculationRunnable() { } void QmitkImageStatisticsCalculationRunnable::Initialize(const mitk::Image *image, const mitk::Image *binaryImage, const mitk::PlanarFigure *planarFig) { this->m_StatisticsImage = image; this->m_BinaryMask = binaryImage; this->m_PlanarFigureMask = planarFig; } mitk::ImageStatisticsContainer* QmitkImageStatisticsCalculationRunnable::GetStatisticsData() const { return this->m_StatisticsContainer.GetPointer(); } const mitk::Image* QmitkImageStatisticsCalculationRunnable::GetStatisticsImage() const { return this->m_StatisticsImage.GetPointer(); } const mitk::Image* QmitkImageStatisticsCalculationRunnable::GetMaskImage() const { return this->m_BinaryMask.GetPointer(); } const mitk::PlanarFigure* QmitkImageStatisticsCalculationRunnable::GetPlanarFigure() const { return this->m_PlanarFigureMask.GetPointer(); } void QmitkImageStatisticsCalculationRunnable::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationRunnable::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationRunnable::SetHistogramNBins(unsigned int nbins) { this->m_HistogramNBins = nbins; } unsigned int QmitkImageStatisticsCalculationRunnable::GetHistogramNBins() const { return this->m_HistogramNBins; } QmitkDataGenerationJobBase::ResultMapType QmitkImageStatisticsCalculationRunnable::GetResults() const { ResultMapType result; result.emplace("statistics", this->GetStatisticsData()); return result; } bool QmitkImageStatisticsCalculationRunnable::RunComputation() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if (this->m_StatisticsImage.IsNotNull()) { calculator->SetInputImage(m_StatisticsImage); } else { statisticCalculationSuccessful = false; } // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality // of the masked and input image differ, we need to catch them and mark the calculation as failed // the same holds for the ::SetPlanarFigure() try { if (this->m_BinaryMask.IsNotNull()) { mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); imgMask->SetImageMask(m_BinaryMask->Clone()); calculator->SetMask(imgMask.GetPointer()); } if (this->m_PlanarFigureMask.IsNotNull()) { mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); pfMaskGen->SetInputImage(m_StatisticsImage); pfMaskGen->SetPlanarFigure(m_PlanarFigureMask->Clone()); calculator->SetMask(pfMaskGen.GetPointer()); } } catch (const mitk::Exception& e) { MITK_ERROR << "MITK Exception: " << e.what(); m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const std::runtime_error &e) { MITK_ERROR << "Runtime Exception: " << e.what(); m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const std::exception &e) { MITK_ERROR << "Standard Exception: " << e.what(); m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } if (this->m_IgnoreZeros) { mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); ignorePixelValueMaskGen->SetIgnoredPixelValue(0); ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); } else { calculator->SetSecondaryMask(nullptr); } calculator->SetNBinsForHistogramStatistics(m_HistogramNBins); try { calculator->GetStatistics(); } catch (mitk::Exception& e) { m_LastErrorMessage = e.GetDescription(); MITK_ERROR << "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch (const std::runtime_error &e) { m_LastErrorMessage = "Failure: " + std::string(e.what()); MITK_ERROR << "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch (const std::exception &e) { m_LastErrorMessage = "Failure: " + std::string(e.what()); MITK_ERROR << "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } if (statisticCalculationSuccessful) { m_StatisticsContainer = calculator->GetStatistics(); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(m_StatisticsContainer, m_StatisticsImage); if (nullptr != m_PlanarFigureMask) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(m_StatisticsContainer, m_PlanarFigureMask); } if (nullptr != m_BinaryMask) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(m_StatisticsContainer, m_BinaryMask); } m_StatisticsContainer->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); m_StatisticsContainer->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeros)); } return statisticCalculationSuccessful; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h index 42769bded3..f253838398 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h @@ -1,92 +1,86 @@ /*============================================================================ 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 QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED #define QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsContainer.h" #include "QmitkDataGenerationJobBase.h" // itk headers #ifndef __itkHistogram_h #include #endif #include -namespace mitk -{ - static const std::string STATS_HISTOGRAM_BIN_PROPERTY_NAME = "MITK.statistic.histogram_bins"; - static const std::string STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME = "MITK.statistic.ignore_zero_voxel"; -} - /** * /brief This class is executed as background thread for image statistics calculation. * * This class is derived from QRunnable and is intended to be used by QmitkImageStatisticsView * to run the image statistics calculation in a background thread keeping the GUI usable. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsCalculationRunnable : public QmitkDataGenerationJobBase { Q_OBJECT public: typedef itk::Statistics::Histogram HistogramType; /*! /brief standard constructor. */ QmitkImageStatisticsCalculationRunnable(); /*! /brief standard destructor. */ ~QmitkImageStatisticsCalculationRunnable(); /*! /brief Initializes the object with necessary data. */ void Initialize(const mitk::Image* image, const mitk::Image* binaryImage, const mitk::PlanarFigure* planarFig); /*! /brief returns the calculated image statistics. */ mitk::ImageStatisticsContainer* GetStatisticsData() const; const mitk::Image* GetStatisticsImage() const; const mitk::Image* GetMaskImage() const; const mitk::PlanarFigure* GetPlanarFigure() 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; ResultMapType GetResults() const override; protected: bool RunComputation() override; private: mitk::Image::ConstPointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. mitk::Image::ConstPointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. mitk::PlanarFigure::ConstPointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. mitk::ImageStatisticsContainer::Pointer m_StatisticsContainer; bool m_IgnoreZeros; ///< member variable holds flag to indicate if zero valued voxel should be suppressed unsigned int m_HistogramNBins; ///< member variable holds the bin size for histogram resolution. }; #endif // QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp index 1675fe6847..2203157144 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -1,328 +1,274 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainer.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateDataProperty.h" #include "mitkProperties.h" #include "mitkImageStatisticsContainerManager.h" #include "QmitkImageStatisticsCalculationRunnable.h" void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) { if (m_IgnoreZeroValueVoxel != _arg) { m_IgnoreZeroValueVoxel = _arg; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeroValueVoxel; } void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) { if (m_HistogramNBins != nbins) { m_HistogramNBins = nbins; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const { return this->m_HistogramNBins; } bool QmitkImageStatisticsDataGenerator::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()); 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 { - 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_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; - } - } + if (!imageNode || !imageNode->GetData()) + { + mitkThrow() << "Image is nullptr"; } - return result; + const auto image = imageNode->GetData(); + const mitk::BaseData* mask = nullptr; + if (roiNode) + { + mask = roiNode->GetData(); + } + + std::shared_lock 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); 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); 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/QmitkImageStatisticsTreeItem.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp index 43ec8cf2f6..57cafb348d 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp @@ -1,100 +1,113 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsTreeItem.h" QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem( ImageStatisticsObject statisticsData, StatisticNameVector statisticNames, - QVariant label, + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parent) - : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent) + : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent), m_IsWIP(isWIP) { } QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, QVariant label, + bool isWIP, QmitkImageStatisticsTreeItem *parentItem) - : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, parentItem ) + : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, isWIP, parentItem ) { } - QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), nullptr ) {} + QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), false, nullptr ) {} QmitkImageStatisticsTreeItem::~QmitkImageStatisticsTreeItem() { qDeleteAll(m_childItems); } void QmitkImageStatisticsTreeItem::appendChild(QmitkImageStatisticsTreeItem *item) { m_childItems.append(item); } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::child(int row) { return m_childItems.value(row); } int QmitkImageStatisticsTreeItem::childCount() const { return m_childItems.count(); } int QmitkImageStatisticsTreeItem::columnCount() const { return m_statisticNames.size() + 1; } QVariant QmitkImageStatisticsTreeItem::data(int column) const { QVariant result; if (column > 0 && !m_statisticNames.empty()) { - if (column - 1 < static_cast(m_statisticNames.size())) + if (m_IsWIP) { - auto statisticKey = m_statisticNames.at(column - 1); - std::stringstream ss; - if (m_statistics.HasStatistic(statisticKey)) + result = QVariant(QString("...")); + } + else + { + if (column - 1 < static_cast(m_statisticNames.size())) { - ss << m_statistics.GetValueNonConverted(statisticKey); + auto statisticKey = m_statisticNames.at(column - 1); + std::stringstream ss; + if (m_statistics.HasStatistic(statisticKey)) + { + ss << m_statistics.GetValueNonConverted(statisticKey); + } + else + { + return QVariant(); + } + result = QVariant(QString::fromStdString(ss.str())); } else { return QVariant(); } - result = QVariant(QString::fromStdString(ss.str())); - } - else - { - return QVariant(); } } else if (column == 0) { result = m_label; } return result; } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::parentItem() { return m_parentItem; } int QmitkImageStatisticsTreeItem::row() const { if (m_parentItem) return m_parentItem->m_childItems.indexOf(const_cast(this)); return 0; } + +bool QmitkImageStatisticsTreeItem::isWIP() const +{ + return m_IsWIP; +}; diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h index 055cb54991..76c982b934 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h @@ -1,54 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeItem_h #define QmitkImageStatisticsTreeItem_h #include #include #include "mitkImageStatisticsContainer.h" /*! \class QmitkImageStatisticsTreeItem An item that represents an entry (usually ImageStatisticsObject) for the QmitkImageStatisticsTreeModel */ class QmitkImageStatisticsTreeItem { public: using ImageStatisticsObject = mitk::ImageStatisticsContainer::ImageStatisticsObject; using StatisticNameVector = mitk::ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector; QmitkImageStatisticsTreeItem(); explicit QmitkImageStatisticsTreeItem(ImageStatisticsObject statisticsData, - StatisticNameVector statisticNames, QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + StatisticNameVector statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); explicit QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, - QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); ~QmitkImageStatisticsTreeItem(); void appendChild(QmitkImageStatisticsTreeItem *child); QmitkImageStatisticsTreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; int row() const; QmitkImageStatisticsTreeItem *parentItem(); + /**indicates that the statistic container owned by this instance is only a dummy + WIP containter and the calculation of the up-to-date statistic is not yet finished.**/ + bool isWIP() const; + private: ImageStatisticsObject m_statistics; StatisticNameVector m_statisticNames; QVariant m_label; QmitkImageStatisticsTreeItem *m_parentItem = nullptr; QList m_childItems; + bool m_IsWIP; }; #endif // QmitkImageStatisticsTreeItem_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp index 033598353c..d7f77cdc96 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp @@ -1,383 +1,425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsTreeModel.h" #include "QmitkImageStatisticsTreeItem.h" #include "itkMutexLockHolder.h" #include "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" +#include "QmitkStyleManager.h" + QmitkImageStatisticsTreeModel::QmitkImageStatisticsTreeModel(QObject *parent) : QmitkAbstractDataStorageModel(parent) { m_RootItem = new QmitkImageStatisticsTreeItem(); } QmitkImageStatisticsTreeModel ::~QmitkImageStatisticsTreeModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); delete m_RootItem; }; void QmitkImageStatisticsTreeModel::DataStorageChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodePredicateChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } int QmitkImageStatisticsTreeModel::columnCount(const QModelIndex& /*parent*/) const { int columns = m_StatisticNames.size() + 1; return columns; } int QmitkImageStatisticsTreeModel::rowCount(const QModelIndex &parent) const { QmitkImageStatisticsTreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } QVariant QmitkImageStatisticsTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - if (role != Qt::DisplayRole) - return QVariant(); - - QmitkImageStatisticsTreeItem *item = static_cast(index.internalPointer()); + QmitkImageStatisticsTreeItem* item = static_cast(index.internalPointer()); - return item->data(index.column()); + if (role == Qt::DisplayRole) + { + return item->data(index.column()); + } + else if (role == Qt::DecorationRole && index.column() == 0 && item->isWIP()) + { + return QVariant(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/hourglass-half-solid.svg"))); + } + return QVariant(); } QModelIndex QmitkImageStatisticsTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); QmitkImageStatisticsTreeItem *parentItem; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); QmitkImageStatisticsTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkImageStatisticsTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkImageStatisticsTreeItem *childItem = static_cast(child.internalPointer()); QmitkImageStatisticsTreeItem *parentItem = childItem->parentItem(); if (parentItem == m_RootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } Qt::ItemFlags QmitkImageStatisticsTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return QAbstractItemModel::flags(index); } QVariant QmitkImageStatisticsTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (section == 0) { return m_HeaderFirstColumn; } else { return QVariant(m_StatisticNames.at(section - 1).c_str()); } } return QVariant(); } void QmitkImageStatisticsTreeModel::SetImageNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedImageNodes = std::move(tempNodes); m_ImageNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::SetMaskNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); // special case: apply one mask to each timestep of an 4D image if (timeSteps == 1 && m_TimeStepResolvedImageNodes.size() > 1) { timeSteps = m_TimeStepResolvedImageNodes.size(); } for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedMaskNodes = std::move(tempNodes); m_MaskNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::Clear() { emit beginResetModel(); m_Statistics.clear(); m_ImageNodes.clear(); m_TimeStepResolvedImageNodes.clear(); m_MaskNodes.clear(); m_StatisticNames.clear(); emit endResetModel(); emit modelChanged(); } +void QmitkImageStatisticsTreeModel::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + emit beginResetModel(); + m_IgnoreZeroValueVoxel = _arg; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +bool QmitkImageStatisticsTreeModel::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsTreeModel::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + emit beginResetModel(); + m_HistogramNBins = nbins; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +unsigned int QmitkImageStatisticsTreeModel::GetHistogramNBins() const +{ + return this->m_HistogramNBins; +} + void QmitkImageStatisticsTreeModel::UpdateByDataStorage() { StatisticsContainerVector newStatistics; auto datamanager = m_DataStorage.Lock(); if (datamanager.IsNotNull()) { for (const auto &image : m_ImageNodes) { if (m_MaskNodes.empty()) { - auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData()); + auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), nullptr, m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } else { for (const auto &mask : m_MaskNodes) { auto stats = - mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData()); + mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData(), m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } } } if (!newStatistics.empty()) { emit dataAvailable(); } } { itk::MutexLockHolder locked(m_Mutex); m_Statistics = newStatistics; } m_StatisticNames = mitk::GetAllStatisticNames(m_Statistics); BuildHierarchicalModel(); } void QmitkImageStatisticsTreeModel::BuildHierarchicalModel() { // reset old model delete m_RootItem; m_RootItem = new QmitkImageStatisticsTreeItem(); bool hasMask = false; bool hasMultipleTimesteps = false; std::map dataNodeToTreeItem; for (auto statistic : m_Statistics) { + bool isWIP = statistic->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()).IsNotNull(); // get the connected image data node/mask data node auto imageRule = mitk::StatisticsToImageRelationRule::New(); auto imageOfStatisticsPredicate = imageRule->GetDestinationsDetector(statistic); auto imageFinding = std::find_if(m_ImageNodes.begin(), m_ImageNodes.end(), [&imageOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return imageOfStatisticsPredicate->CheckNode(testNode); }); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); auto maskOfStatisticsPredicate = maskRule->GetDestinationsDetector(statistic); auto maskFinding = std::find_if(m_MaskNodes.begin(), m_MaskNodes.end(), [&maskOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return maskOfStatisticsPredicate->CheckNode(testNode); }); if (imageFinding == m_ImageNodes.end()) { mitkThrow() << "no image found connected to statistic" << statistic << " Aborting."; } auto& image = *imageFinding; // image: 1. hierarchy level QmitkImageStatisticsTreeItem *imageItem = nullptr; auto search = dataNodeToTreeItem.find(image); // the tree item was created previously if (search != dataNodeToTreeItem.end()) { imageItem = search->second; } // create the tree item else { QString imageLabel = QString::fromStdString(image->GetName()); if (statistic->GetTimeSteps() == 1 && maskFinding == m_MaskNodes.end()) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem); } else { - imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, m_RootItem); } m_RootItem->appendChild(imageItem); dataNodeToTreeItem.emplace(image, imageItem); } // mask: 2. hierarchy level (optional, only if mask exists) QmitkImageStatisticsTreeItem *lastParent = nullptr; if (maskFinding != m_MaskNodes.end()) { auto& mask = *maskFinding; QString maskLabel = QString::fromStdString(mask->GetName()); QmitkImageStatisticsTreeItem *maskItem; // add statistical values directly in this hierarchy level if (statistic->GetTimeSteps() == 1) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, isWIP, imageItem); } else { - maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, imageItem); } imageItem->appendChild(maskItem); lastParent = maskItem; hasMask = true; } else { lastParent = imageItem; } // 3. hierarchy level (optional, only if >1 timestep) if (statistic->GetTimeSteps() > 1) { for (unsigned int i = 0; i < statistic->GetTimeSteps(); i++) { QString timeStepLabel = "[" + QString::number(i) + "] " + QString::number(statistic->GetTimeGeometry()->TimeStepToTimePoint(i)) + " ms"; if (statistic->TimeStepExists(i)) { auto statisticsItem = new QmitkImageStatisticsTreeItem( statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, lastParent); lastParent->appendChild(statisticsItem); } } hasMultipleTimesteps = true; } } QString headerString = "Images"; if (hasMask) { headerString += "/Masks"; } if (hasMultipleTimesteps) { headerString += "/Timesteps"; } m_HeaderFirstColumn = headerString; } void QmitkImageStatisticsTreeModel::NodeRemoved(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h index 4797719702..800959cba8 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h @@ -1,111 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeModel_h #define QmitkImageStatisticsTreeModel_h #include "itkSimpleFastMutexLock.h" #include "QmitkAbstractDataStorageModel.h" //MITK #include #include "mitkImageStatisticsContainer.h" class QmitkImageStatisticsTreeItem; /*! \class QmitkImageStatisticsTreeModel -Model that takes a mitk::ImageStatisticsContainer and represents it as model in context of the QT view-model-concept. +The class is used to represent the information of mitk::ImageStatisticsContainer in the set datastorage in the context of the QT view-model-concept. +The represented ImageStatisticContainer are specified by setting the image and mask nodes that should be regarded. +In addition you may specified the statistic computation property HistorgramNBins and IgnoreZeroValueVoxel to select the correct +statistics. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkImageStatisticsTreeModel(QObject *parent = nullptr); ~QmitkImageStatisticsTreeModel() override; void SetImageNodes(const std::vector& nodes); void SetMaskNodes(const std::vector& nodes); void Clear(); + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; signals: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: /* * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode *node) override; private: void UpdateByDataStorage(); using StatisticsContainerVector = std::vector; /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] --> 3. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); StatisticsContainerVector m_Statistics; /** Relevant images set by the user.*/ std::vector m_ImageNodes; /** Helper that is constructed when m_ImageNodes is set. It has the same order like m_ImageNodes, but each image is represented n times, while n is the number of time steps the respective image has. This structure makes the business logic to select the correct image given a QIndex much simpler and therefore easy to understand/maintain. */ std::vector > m_TimeStepResolvedImageNodes; /** relevant masks set by the user.*/ std::vector m_MaskNodes; /** @sa m_TimeStepResolvedImageNodes */ std::vector> m_TimeStepResolvedMaskNodes; std::vector m_StatisticNames; itk::SimpleFastMutexLock m_Mutex; QmitkImageStatisticsTreeItem *m_RootItem; QVariant m_HeaderFirstColumn; + + bool m_IgnoreZeroValueVoxel = false; + unsigned int m_HistogramNBins = 100; }; #endif // mitkQmitkImageStatisticsTreeModel_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp index 23e9c0cc99..f6202041a2 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp @@ -1,79 +1,100 @@ /*============================================================================ 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 "QmitkImageStatisticsWidget.h" #include "QmitkStatisticsModelToStringConverter.h" #include "QmitkImageStatisticsTreeModel.h" #include #include QmitkImageStatisticsWidget::QmitkImageStatisticsWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_imageStatisticsModel = new QmitkImageStatisticsTreeModel(parent); CreateConnections(); m_ProxyModel = new QSortFilterProxyModel(this); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.treeViewStatistics->setModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_imageStatisticsModel); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::dataAvailable, this, &QmitkImageStatisticsWidget::OnDataAvailable); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::modelChanged, m_Controls.treeViewStatistics, &QTreeView::expandAll); } void QmitkImageStatisticsWidget::SetDataStorage(mitk::DataStorage* newDataStorage) { m_imageStatisticsModel->SetDataStorage(newDataStorage); } void QmitkImageStatisticsWidget::SetImageNodes(const std::vector& nodes) { m_imageStatisticsModel->SetImageNodes(nodes); } void QmitkImageStatisticsWidget::SetMaskNodes(const std::vector& nodes) { m_imageStatisticsModel->SetMaskNodes(nodes); } void QmitkImageStatisticsWidget::Reset() { m_imageStatisticsModel->Clear(); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(false); } + +void QmitkImageStatisticsWidget::SetIgnoreZeroValueVoxel(bool _arg) +{ + m_imageStatisticsModel->SetIgnoreZeroValueVoxel(_arg); +} + +bool QmitkImageStatisticsWidget::GetIgnoreZeroValueVoxel() const +{ + return this->m_imageStatisticsModel->GetIgnoreZeroValueVoxel(); +} + +void QmitkImageStatisticsWidget::SetHistogramNBins(unsigned int nbins) +{ + m_imageStatisticsModel->SetHistogramNBins(nbins); +} + +unsigned int QmitkImageStatisticsWidget::GetHistogramNBins() const +{ + return this->m_imageStatisticsModel->GetHistogramNBins(); +} + void QmitkImageStatisticsWidget::CreateConnections() { connect(m_Controls.buttonCopyImageStatisticsToClipboard, &QPushButton::clicked, this, &QmitkImageStatisticsWidget::OnClipboardButtonClicked); } void QmitkImageStatisticsWidget::OnDataAvailable() { m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(true); m_Controls.treeViewStatistics->setEnabled(true); } void QmitkImageStatisticsWidget::OnClipboardButtonClicked() { QmitkStatisticsModelToStringConverter converter; converter.SetTableModel(m_imageStatisticsModel); converter.SetRootIndex(m_Controls.treeViewStatistics->rootIndex()); converter.SetIncludeHeaderData(true); QString clipboardAsString = converter.GetString(); QApplication::clipboard()->setText(clipboardAsString, QClipboard::Clipboard); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h index 2635b6fd29..13e4fef97d 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h @@ -1,53 +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 QmitkImageStatisticsWidget_H__INCLUDED #define QmitkImageStatisticsWidget_H__INCLUDED #include #include #include #include class QSortFilterProxyModel; class QmitkImageStatisticsTreeModel; class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsWidget : public QWidget { Q_OBJECT public: QmitkImageStatisticsWidget(QWidget *parent = nullptr); /**Documentation Set the data storage the model should fetch its statistic objects from. @pre data storage must be valid */ void SetDataStorage(mitk::DataStorage *newDataStorage); void SetImageNodes(const std::vector &nodes); void SetMaskNodes(const std::vector &nodes); void Reset(); + /*! /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; + private: void CreateConnections(); void OnDataAvailable(); /** \brief Saves the image statistics to the clipboard */ void OnClipboardButtonClicked(); private: Ui::QmitkImageStatisticsControls m_Controls; QmitkImageStatisticsTreeModel *m_imageStatisticsModel; QSortFilterProxyModel *m_ProxyModel; }; #endif // QmitkImageStatisticsWidget_H__INCLUDED diff --git a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp index 0efbc4b05a..7f1ab831d3 100644 --- a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp +++ b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp @@ -1,518 +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 { 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/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index 4f12f499d8..788d6fc8cf 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,26 +1,27 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg + hourglass-half-solid.svg diff --git a/Modules/QtWidgets/resource/hourglass-half-solid.svg b/Modules/QtWidgets/resource/hourglass-half-solid.svg new file mode 100644 index 0000000000..6045f39bb7 --- /dev/null +++ b/Modules/QtWidgets/resource/hourglass-half-solid.svg @@ -0,0 +1,59 @@ + + diff --git a/Modules/QtWidgets/resource/icon-license.txt b/Modules/QtWidgets/resource/icon-license.txt index 662c801d5f..df5504436b 100644 --- a/Modules/QtWidgets/resource/icon-license.txt +++ b/Modules/QtWidgets/resource/icon-license.txt @@ -1,6 +1,7 @@ See [Font Awsome 4] in Licenses/ICONS.md for: - history-solid.svg based on Font Awsome's history-solid.svg - list-solid.svg based on Font Awsome's list-solid.svg - star-solid.svg based on Font Awsome's star-solid.svg - favorite_add.svg based on Font Awsome's star-solid.svg - favorite_remove.svg based on Font Awsome's star-solid.svg +- hourglass-half-solid.svg based on Font Awsome's hourglass-half-solid.svg