diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp index 4339973779..519129c007 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp @@ -1,234 +1,240 @@ /*============================================================================ 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 #include namespace mitk { ImageStatisticsContainer::ImageStatisticsContainer() { this->Reset(); } // The order is derived from the old (<2018) image statistics plugin. const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::m_DefaultNames = {ImageStatisticsConstants::MEAN(), ImageStatisticsConstants::MEDIAN(), ImageStatisticsConstants::STANDARDDEVIATION(), ImageStatisticsConstants::RMS(), ImageStatisticsConstants::MAXIMUM(), ImageStatisticsConstants::MAXIMUMPOSITION(), ImageStatisticsConstants::MINIMUM(), ImageStatisticsConstants::MINIMUMPOSITION(), ImageStatisticsConstants::NUMBEROFVOXELS(), ImageStatisticsConstants::VOLUME(), ImageStatisticsConstants::SKEWNESS(), ImageStatisticsConstants::KURTOSIS(), ImageStatisticsConstants::UNIFORMITY(), ImageStatisticsConstants::ENTROPY(), ImageStatisticsConstants::MPP(), ImageStatisticsConstants::UPP()}; ImageStatisticsContainer::ImageStatisticsObject::ImageStatisticsObject() { Reset(); } void ImageStatisticsContainer::ImageStatisticsObject::AddStatistic(const std::string &key, StatisticsVariantType value) { m_Statistics.emplace(key, value); if (std::find(m_DefaultNames.cbegin(), m_DefaultNames.cend(), key) == m_DefaultNames.cend()) { if (std::find(m_CustomNames.cbegin(), m_CustomNames.cend(), key) == m_CustomNames.cend()) { m_CustomNames.emplace_back(key); } } } const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector & ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames() { return m_DefaultNames; } const ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector & ImageStatisticsContainer::ImageStatisticsObject::GetCustomStatisticNames() const { return m_CustomNames; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::GetAllStatisticNames() const { StatisticNameVector names = GetDefaultStatisticNames(); names.insert(names.cend(), m_CustomNames.cbegin(), m_CustomNames.cend()); return names; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector ImageStatisticsContainer::ImageStatisticsObject::GetExistingStatisticNames() const { StatisticNameVector names; std::transform(m_Statistics.begin(), m_Statistics.end(), std::back_inserter(names), [](const auto &pair) { return pair.first; }); return names; } bool ImageStatisticsContainer::ImageStatisticsObject::HasStatistic(const std::string &name) const { return m_Statistics.find(name) != m_Statistics.cend(); } ImageStatisticsContainer::StatisticsVariantType ImageStatisticsContainer::ImageStatisticsObject::GetValueNonConverted( const std::string &name) const { if (HasStatistic(name)) { return m_Statistics.find(name)->second; } else { mitkThrow() << "invalid statistic key, could not find"; } } void ImageStatisticsContainer::ImageStatisticsObject::Reset() { m_Statistics.clear(); m_CustomNames.clear(); } bool ImageStatisticsContainer::TimeStepExists(TimeStepType timeStep) const { return m_TimeStepMap.find(timeStep) != m_TimeStepMap.end(); } + const ImageStatisticsContainer::HistogramType* + ImageStatisticsContainer::GetTimeStepHistogram(TimeStepType timeStep) const + { + return this->GetStatisticsForTimeStep(timeStep).m_Histogram; + } + const ImageStatisticsContainer::ImageStatisticsObject &ImageStatisticsContainer::GetStatisticsForTimeStep( TimeStepType timeStep) const { auto it = m_TimeStepMap.find(timeStep); if (it != m_TimeStepMap.end()) { return it->second; } mitkThrow() << "StatisticsObject for timeStep " << timeStep << " not found!"; } void ImageStatisticsContainer::SetStatisticsForTimeStep(TimeStepType timeStep, ImageStatisticsObject statistics) { if (timeStep < this->GetTimeSteps()) { m_TimeStepMap.emplace(timeStep, statistics); this->Modified(); } else { mitkThrow() << "Given timeStep " << timeStep << " out of timeStep geometry bounds. TimeSteps in geometry: " << this->GetTimeSteps(); } } void ImageStatisticsContainer::PrintSelf(std::ostream &os, itk::Indent indent) const { Superclass::PrintSelf(os, indent); for (unsigned int i = 0; i < this->GetTimeSteps(); i++) { auto statisticsValues = GetStatisticsForTimeStep(i); os << std::endl << indent << "Statistics instance for timeStep " << i << ":"; auto statisticKeys = statisticsValues.GetExistingStatisticNames(); os << std::endl << indent << "Number of entries: " << statisticKeys.size(); for (const auto &aKey : statisticKeys) { os << std::endl << indent.GetNextIndent() << aKey << ": " << statisticsValues.GetValueNonConverted(aKey); } } } unsigned int ImageStatisticsContainer::GetNumberOfTimeSteps() const { return this->GetTimeSteps(); } void ImageStatisticsContainer::Reset() { for (auto iter = m_TimeStepMap.begin(); iter != m_TimeStepMap.end(); iter++) { iter->second.Reset(); } } itk::LightObject::Pointer ImageStatisticsContainer::InternalClone() const { itk::LightObject::Pointer ioPtr = Superclass::InternalClone(); Self::Pointer rval = dynamic_cast(ioPtr.GetPointer()); if (rval.IsNull()) { itkExceptionMacro(<< "downcast to type " << "StatisticsContainer" << " failed."); } rval->SetTimeStepMap(m_TimeStepMap); rval->SetTimeGeometry(this->GetTimeGeometry()->Clone()); return ioPtr; } void ImageStatisticsContainer::SetTimeStepMap(TimeStepMapType map) { m_TimeStepMap = map; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames( const ImageStatisticsContainer *container) { ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector names = ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames(); if (container) { std::set customKeys; for (unsigned int i = 0; i < container->GetTimeSteps(); i++) { auto statisticKeys = container->GetStatisticsForTimeStep(i).GetCustomStatisticNames(); customKeys.insert(statisticKeys.cbegin(), statisticKeys.cend()); } names.insert(names.cend(), customKeys.cbegin(), customKeys.cend()); } return names; } ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames( std::vector containers) { ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector names = ImageStatisticsContainer::ImageStatisticsObject::GetDefaultStatisticNames(); std::set customKeys; for (auto container : containers) { for (unsigned int i = 0; i < container->GetTimeSteps(); i++) { if(container->TimeStepExists(i)) { auto statisticKeys = container->GetStatisticsForTimeStep(i).GetCustomStatisticNames(); customKeys.insert(statisticKeys.cbegin(), statisticKeys.cend()); } } } names.insert(names.end(), customKeys.begin(), customKeys.end()); return names; }; } // namespace mitk diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.h b/Modules/ImageStatistics/mitkImageStatisticsContainer.h index f9a3842d04..26d0fdfa36 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainer.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainer.h @@ -1,162 +1,167 @@ /*============================================================================ 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 MITKIMAGESTATISTICSCONTAINER #define MITKIMAGESTATISTICSCONTAINER #include #include #include #include #include namespace mitk { + /** @brief Container class for storing a StatisticsObject for each timestep. Stored statistics are: - for the defined statistics, see GetAllStatisticNames - Histogram of Pixel Values */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer : public mitk::BaseData { public: mitkClassMacro(ImageStatisticsContainer, mitk::BaseData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); using HistogramType = itk::Statistics::Histogram; using RealType = double; using LabelIndex = unsigned int; using IndexType = vnl_vector; using VoxelCountType = unsigned long; using StatisticsVariantType = boost::variant; using StatisticsMapType = std::map < std::string, StatisticsVariantType>; using StatisticsKeyType = std::string; void SetRequestedRegionToLargestPossibleRegion() override {} bool RequestedRegionIsOutsideOfTheBufferedRegion() override { return false; } bool VerifyRequestedRegion() override { return true; } void SetRequestedRegion(const itk::DataObject*) override {} /** @brief Container class for storing the computed image statistics. @details The statistics are stored in a map with value as boost::variant. The type used to create the boost::variant is important as only this type can be recovered lateron. */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsObject { public: ImageStatisticsObject(); /** @brief Adds a statistic to the statistics object @details if already a statistic with that name is included, it is overwritten */ void AddStatistic(const std::string& key, StatisticsVariantType value); using StatisticNameVector = std::vector; /** @brief Returns the names of the default statistics @details The order is derived from the image statistics plugin. */ static const StatisticNameVector& GetDefaultStatisticNames(); /** @brief Returns the names of all custom statistics (defined at runtime and no default names). */ const StatisticNameVector& GetCustomStatisticNames() const; /** @brief Returns the names of all statistics (default and custom defined) Additional custom keys are added at the end in a sorted order. */ StatisticNameVector GetAllStatisticNames() const; StatisticNameVector GetExistingStatisticNames() const; bool HasStatistic(const std::string& name) const; /** @brief Converts the requested value to the defined type @param name defined string on creation (AddStatistic) @exception if no statistics with key name was found. */ template TType GetValueConverted(const std::string& name) const { auto value = GetValueNonConverted(name); return boost::get(value); } /** @brief Returns the requested value @exception if no statistics with key name was found. */ StatisticsVariantType GetValueNonConverted(const std::string& name) const; void Reset(); HistogramType::ConstPointer m_Histogram=nullptr; private: StatisticsMapType m_Statistics; StatisticNameVector m_CustomNames; static const StatisticNameVector m_DefaultNames; }; using TimeStepMapType = std::map; unsigned int GetNumberOfTimeSteps() const; /** @brief Deletes all stored values*/ void Reset(); /** @brief Returns the statisticObject for the given Timestep @pre timeStep must be valid */ const ImageStatisticsObject& GetStatisticsForTimeStep(TimeStepType timeStep) const; /** @brief Sets the statisticObject for the given Timestep @pre timeStep must be valid */ void SetStatisticsForTimeStep(TimeStepType timeStep, ImageStatisticsObject statistics); /** @brief Checks if the Time step exists @pre timeStep must be valid */ bool TimeStepExists(TimeStepType timeStep) const; + /*! + /brief Returns the histogram of the currently selected time step. + @pre timeStep must be valid*/ + const HistogramType* GetTimeStepHistogram(TimeStepType timeStep) const; + protected: ImageStatisticsContainer(); void PrintSelf(std::ostream &os, itk::Indent indent) const override; private: itk::LightObject::Pointer InternalClone() const override; void SetTimeStepMap(TimeStepMapType map); TimeStepMapType m_TimeStepMap; }; MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames(const ImageStatisticsContainer* container); MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector GetAllStatisticNames(std::vector containers); - } #endif // MITKIMAGESTATISTICSCONTAINER diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp index 2710c49a63..641ecc7031 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -1,90 +1,92 @@ /*============================================================================ 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 "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" mitk::ImageStatisticsContainer::ConstPointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask) { if (!dataStorage) { mitkThrow() << "data storage is nullptr!"; } if (!image) { mitkThrow() << "Image is nullptr"; } - mitk::NodePredicateBase::ConstPointer predicate = GetPredicateForSources(image, mask);; - - if (predicate) { - auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New(ImageStatisticsContainer::GetStaticNameOfClass()); - predicate = mitk::NodePredicateAnd::New(predicate, nodePredicateImageStatisticsContainer); + mitk::NodePredicateBase::ConstPointer predicate = GetStatisticsPredicateForSources(image, mask);; + if (predicate) + { auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); mitk::DataStorage::SetOfObjects::Pointer statisticContainerCandidateNodesFiltered; statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); for (const auto& node : *statisticContainerCandidateNodes) { statisticContainerCandidateNodesFiltered->push_back(node); } if (statisticContainerCandidateNodesFiltered->empty()) { return nullptr; } 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 << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); } } return dynamic_cast(newestElement->GetData()); } else { return nullptr; } } -mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::GetPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask) +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(); - mitk::NodePredicateBase::ConstPointer predicate = imageRule->GetSourcesDetector(image); + 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 403724fbe5..458eb7aa35 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h @@ -1,45 +1,46 @@ /*============================================================================ 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 { /** \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 - @return a valid StatisticsContainer or nullptr if no StatisticContainer is found or no rules have been defined + @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. */ static mitk::ImageStatisticsContainer::ConstPointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr); - protected: - static mitk::NodePredicateBase::ConstPointer GetPredicateForSources(const mitk::BaseData* image, const mitk::BaseData* mask = nullptr); + /** 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.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp index cc368b19cb..c132a2bfc9 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp @@ -1,71 +1,51 @@ /*============================================================================ 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 "QmitkDataGenerationJobBase.h" -mitk::DataStorage::SetOfObjects::ConstPointer QmitkDataGenerationJobBase::GetOutputDataNodes() const +std::string QmitkDataGenerationJobBase::GetLastErrorMessage() const { - return m_OutputDataNodes; + return m_LastErrorMessage; } -QmitkDataGenerationJobBase::QmitkDataGenerationJobBase(const mitk::DataStorage::SetOfObjects* outputDataNodes, const std::vector& inputBaseData) :m_OutputDataNodes(outputDataNodes), m_InputBaseData(inputBaseData) +bool QmitkDataGenerationJobBase::GetComputationSuccessFlag() const { -} - -QmitkDataGenerationJobBase::QmitkDataGenerationJobBase(mitk::DataNode* outputDataNode, const std::vector& inputBaseData): m_InputBaseData(inputBaseData) -{ - if (!outputDataNode) { - mitkThrow() << "No output data node defined."; - } - - auto nodes = mitk::DataStorage::SetOfObjects::New(); - nodes->InsertElement(0, outputDataNode); - m_OutputDataNodes = nodes; -} - -QmitkDataGenerationJobBase::QmitkDataGenerationJobBase(const mitk::DataStorage::SetOfObjects* outputDataNodes) : m_OutputDataNodes(outputDataNodes) -{ -} - -QmitkDataGenerationJobBase::QmitkDataGenerationJobBase(mitk::DataNode* outputDataNode) : QmitkDataGenerationJobBase(outputDataNode, std::vector()) -{ -} - -QmitkDataGenerationJobBase::~QmitkDataGenerationJobBase() -{ -} - -void QmitkDataGenerationJobBase::SetInputBaseData(const std::vector& baseData) -{ - m_InputBaseData = baseData; + return m_ComputationSuccessful; } void QmitkDataGenerationJobBase::run() { try { - if (this->RunComputation()) + m_ComputationSuccessful = this->RunComputation(); + if (m_ComputationSuccessful) { - emit ResultsAvailable(m_OutputDataNodes, this); + emit ResultsAvailable(this->GetResults(), this); + } + else + { + emit Error(QString("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); } } catch (std::exception& e) { - emit Error(QString("Error while running computation. Error description: ") + QString::fromLatin1( - e.what()), this); + m_LastErrorMessage = e.what(); + emit Error(QString("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + } catch (...) { - emit Error(QString("Unknown error while running computation."), this); + m_LastErrorMessage = "Unknown exception"; + emit Error(QString("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); } } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h index fd19abe50a..3bc2d72feb 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -1,91 +1,92 @@ /*============================================================================ 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 -#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_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 -The resulting data should be written to the provided outputDataNodes -\warning it's the responsibility of the job to store the data in the dataNode. \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; - mitk::DataStorage::SetOfObjects::ConstPointer GetOutputDataNodes() const; + /** 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; - void run() override; + /*! + /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 vector of DataNode objects produces by the job and ready tu use, put into storage. + @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(mitk::DataStorage::SetOfObjects::ConstPointer results, const QmitkDataGenerationJobBase* job); + void ResultsAvailable(ResultMapType results, const QmitkDataGenerationJobBase* job); protected: - /*! @brief constructor with inputData and outputNodes - @param inputBaseData the input BaseData that is required for computation - @param outputDataNodes the nodes where the results should be stored - */ - QmitkDataGenerationJobBase(const mitk::DataStorage::SetOfObjects* outputDataNodes, const std::vector& inputBaseData); - QmitkDataGenerationJobBase(mitk::DataNode* outputDataNode, const std::vector& inputBaseData); - - /*! @brief constructor with outputNodes only - @param outputDataNodes the nodes where the results should be stored - */ - QmitkDataGenerationJobBase(const mitk::DataStorage::SetOfObjects* outputDataNodes); - QmitkDataGenerationJobBase(mitk::DataNode* outputDataNode); - - virtual ~QmitkDataGenerationJobBase(); - - void SetInputBaseData(const std::vector& baseData); - + QmitkDataGenerationJobBase() = default; + + virtual ~QmitkDataGenerationJobBase() = default; + /**Does the real computation. Returns true if there where results produced.*/ virtual bool RunComputation() = 0; - mitk::DataStorage::SetOfObjects::ConstPointer m_OutputDataNodes; - std::vector m_InputBaseData; + std::string m_LastErrorMessage; + +private: + bool m_ComputationSuccessful = false; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp deleted file mode 100644 index 2ef7d82621..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/*=================================================================== - -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 "QmitkDataGenerationRuleBase.h" - -#include "mitkProperties.h" -#include "mitkImage.h" - -bool QmitkDataGenerationRuleBase::IsResultAvailable(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const { - auto resultNode = GetLatestResult(storage, doseNode, structNode); - if (!resultNode) { - return false; - } - else { - //use BaseData as DataNode may have different MTime due to visualization changes - auto resultNeedsUpdate = resultNode && (doseNode->GetData()->GetMTime() > resultNode->GetData()->GetMTime() || structNode->GetData()->GetMTime() > resultNode->GetData()->GetMTime()); - return !resultNeedsUpdate; - } -} - -mitk::DataNode::Pointer QmitkDataGenerationRuleBase::CreateDataNodeInterimResults(mitk::BaseData::Pointer data) const { - if (!data) { - mitkThrow() << "data is nullptr"; - } - - auto interimResultsNode = mitk::DataNode::New(); - interimResultsNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - data->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS)); - interimResultsNode->SetVisibility(false); - interimResultsNode->SetData(data); - return interimResultsNode; -} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h deleted file mode 100644 index 9ce1ca1910..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h +++ /dev/null @@ -1,65 +0,0 @@ -/*=================================================================== - -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. - -===================================================================*/ - - -#ifndef __QMITK_DATA_GENERATION_RULE_BASE_H -#define __QMITK_DATA_GENERATION_RULE_BASE_H - -//MITK -#include -#include - -#include "QmitkDataGenerationJobBase.h" - -#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_BASE_DATA_FAILED = "failed"; -} - -/*! -\brief QmitkDataGenerationRuleBase -Base class for a Task generation rule -\details With task generation rule, we denote the process of defining a sequence of jobs (that encapsulate computation classes) to produce a desired output. -We require the following information: -- GetNextMissingJob(): determine the next job that has to be computed to compute the final result -- GetResultTypePredicate() and GetResultTypeBaseData(): information about the to-be-produced output data -\example to compute a DVH, dose and voxelization are required. If the voxelization is not available, a job has to be triggered to compute it. -*/ - -class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGenerationRuleBase -{ -public: - /*! @brief Returns if the (final or interim) result node is already available in the storage and up-to-date - */ - bool IsResultAvailable(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const; - - virtual mitk::DataNode::Pointer GetLatestResult(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const = 0; - /*! @brief Returns the next job that needs to be done in order to complete the workflow - */ - virtual QmitkDataGenerationJobBase* GetNextMissingJob(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const =0; -protected: - /*! @brief Creates a data node for interim results - @details It's the jobs responsibility to write the final results to the data of the DataNode. - */ - mitk::DataNode::Pointer CreateDataNodeInterimResults(mitk::BaseData::Pointer data) const; -}; - -#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h deleted file mode 100644 index cebb9e6b12..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h +++ /dev/null @@ -1,70 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef __QMITK_DATA_GENERATOR_H -#define __QMITK_DATA_GENERATOR_H - -#include "QmitkDataGeneratorBase.h" - -/*! -\brief QmitkRTDataGenerator -Class that implements the generation of the required data -\tparam DataGenerationRule a specific DataGenerationRule class, where details about (job) requirements and dependencies -are defined (e.g. which jobs needs to compute the DVH) -\details Required inputs are: -- storage, -- doseNodes (>=1) -- structNodes (>=1). -The given dataStorage is used to save all generated data. -The DataGenerationRule is executed on all combinations of doseNodes and structNodes. -If new data is generated, the signal NewDataAvailable is emitted. -\note has a base class, because templated classes can't have QObject as parent class. -@sa QmitkRTDataGeneratorBase -*/ -template -class QmitkRTDataGenerator : public QmitkRTDataGeneratorBase -{ - public: - /*! @brief Constructor - @param storage the data storage that should be used - */ - QmitkRTDataGenerator(mitk::DataStorage::Pointer storage) : QmitkRTDataGeneratorBase(storage) {}; - QmitkRTDataGenerator() : QmitkRTDataGeneratorBase() {}; - ~QmitkRTDataGenerator() override {}; - - mitk::DataStorage::SetOfObjects::ConstPointer GetImageNodes() const; - mitk::DataStorage::SetOfObjects::ConstPointer GetStructureNodes() const; - -public slots: - /*! @brief Setter for dose nodes - */ - void SetImageNodes(mitk::DataStorage::SetOfObjects::ConstPointer imageNodes); - /*! @brief Setter for struct nodes - */ - void SetStructureNodes(mitk::DataStorage::SetOfObjects::ConstPointer structureNodes); - -protected: - /*! @brief Generate the data (by default all combinations of doseNodes and structNodes with the specified DataGenerationRule) - */ - void DoGenerate() override; - bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const override; - std::vector> GetAllImageStructCombinations() const override; - - mitk::DataStorage::SetOfObjects::ConstPointer m_DoseNodes = nullptr; - mitk::DataStorage::SetOfObjects::ConstPointer m_StructureNodes = nullptr; -}; - -#ifndef ITK_MANUAL_INSTANTIATION -#include "QmitkDataGenerator.tpp" -#endif - -#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.tpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.tpp deleted file mode 100644 index f58ca0410c..0000000000 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.tpp +++ /dev/null @@ -1,94 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - - -#ifndef __QMITK_DATA_GENERATOR_TPP -#define __QMITK_DATA_GENERATOR_TPP - -#include -#include "mitkDataNode.h" -#include "mitkRTPredicates.h" -#include "QmitkDataGenerationJobBase.h" -#include "mitkPropertyConstants.h" - -#include "QmitkDataGenerator.h" - -mitk::DataStorage::SetOfObjects::ConstPointer QmitkDataGeneratorBase::GetStructureNodes() const -{ - return m_StructureNodes; -} - -mitk::DataStorage::SetOfObjects::ConstPointer QmitkDataGeneratorBase::GetDoseNodes() const -{ - return m_DoseNodes; -} - -void QmitkDataGeneratorBase::SetDoseNodes(mitk::DataStorage::SetOfObjects::ConstPointer doseNodes) -{ - m_DoseNodes = doseNodes; -} - -void QmitkDataGeneratorBase::SetStructureNodes(mitk::DataStorage::SetOfObjects::ConstPointer structureNodes) -{ - m_StructureNodes = structureNodes; -} - -std::vector > -QmitkDataGeneratorBase::GetAllDoseStructCombinations() const { - std::vector > allCombinations; - for (auto& doseNode : *m_DoseNodes) { - for (auto& structNode : *m_StructureNodes) { - allCombinations.push_back(std::make_pair(doseNode, structNode)); - } - } - return allCombinations; -} - - -template -void -QmitkRTDataGenerator::DoGenerate() { - auto doseAndStructCombinations = GetAllDoseStructCombinations(); - - QThreadPool* threadPool = QThreadPool::globalInstance(); - bool jobSpawned = false; - for (const auto& doseAndStruct : doseAndStructCombinations) { - MITK_INFO << "processing node " << doseAndStruct.first->GetName() << " and struct " << doseAndStruct.second->GetName(); - DataGenerationRule aDataGenerationRule = DataGenerationRule(); - if (!aDataGenerationRule.IsResultAvailable(m_Storage.GetPointer(), doseAndStruct.first.GetPointer(), doseAndStruct.second.GetPointer())) { - MITK_INFO << "no result available. Triggering computation of necessary jobs."; - auto job = aDataGenerationRule.GetNextMissingJob(m_Storage, doseAndStruct.first.GetPointer(), doseAndStruct.second.GetPointer()); - //other jobs are pending, nothing has to be done - if (!job) { - MITK_INFO << "waiting for other jobs to finish"; - return; - } - job->setAutoDelete(true); - for (auto& dataNode : *(job->GetDataNodes())) { - //only add interim nodes, their data get updated as soon as it's computed - if (dataNode->GetData()->GetProperty(mitk::RT_CACHE_STATUS_NAME.c_str())) { - m_Storage->Add(dataNode); - } - } - connect(job, SIGNAL(Error(QString, const QmitkDataGenerationJobBase*)), this, SLOT(OnJobError(QString, const QmitkDataGenerationJobBase*))); - connect(job, SIGNAL(ResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkDataGenerationJobBase*)), this, SLOT(OnFinalResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkDataGenerationJobBase*))); - threadPool->start(job); - jobSpawned = true; - } - } - if (!jobSpawned) { - emit AllJobsGenerated(); - } -} - - -#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp index a171237828..dfad7d0774 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,194 +1,222 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkDataGeneratorBase.h" #include "QmitkDataGenerationJobBase.h" #include "mitkDataNode.h" +#include "mitkProperties.h" #include -QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage) : - m_Storage(storage) +QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent), m_Storage(storage) {} -QmitkDataGeneratorBase::QmitkDataGeneratorBase() +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_RunningGeneration; } 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(mitk::DataStorage::SetOfObjects::ConstPointer results, const QmitkDataGenerationJobBase *job) const +void QmitkDataGeneratorBase::OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const { - if (results.IsNotNull()) + 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()) { - std::shared_lock mutexguard(m_DataMutex); - auto storage = m_Storage.Lock(); - if (storage.IsNull()) + for (auto pos = resultnodes->Begin(); pos != resultnodes->End(); ++pos) { - for (auto pos = results->Begin(); pos != results->End(); ++pos) - { - storage->Add(pos->Value()); - } + storage->Add(pos->Value()); } } - - emit NewDataAvailable(results); } + + emit NewDataAvailable(resultnodes.GetPointer()); } void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) { if (this->NodeChangeIsRelevant(node)) { - m_RestartGeneration = true; - if (!IsGenerating()) - { - this->Generate(); - } + this->EnsureRecheckingAndGeneration(); + } +} + +void QmitkDataGeneratorBase::EnsureRecheckingAndGeneration() +{ + m_RestartGeneration = true; + if (!IsGenerating()) + { + this->Generate(); } } void QmitkDataGeneratorBase::Generate() const { m_RunningGeneration = true; m_RestartGeneration = true; while (m_RestartGeneration) { m_RestartGeneration = false; DoGenerate(); } m_RunningGeneration = false; } +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_WORK_IN_PROGRESS)); + interimResultNode->SetVisibility(false); + interimResultNode->SetData(dataDummy); + if (!nodeName.empty()) + { + interimResultNode->SetName(nodeName); + } + return interimResultNode; +} + + void QmitkDataGeneratorBase::DoGenerate() const { - auto imageSegCombinations = this->GetAllImageStructCombinations(); + auto imageSegCombinations = this->GetAllImageROICombinations(); QThreadPool* threadPool = QThreadPool::globalInstance(); bool everythingValid = true; for (const auto& imageAndSeg : imageSegCombinations) { - MITK_INFO << "processing node " << imageAndSeg.first->GetName() << " and struct " << imageAndSeg.second->GetName(); + MITK_INFO << "processing node " << imageAndSeg.first << " and struct " << imageAndSeg.second; if (!this->IsValidResultAvailable(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer())) { this->IndicateFutureResults(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); if (everythingValid) { emit GenerationStarted(); everythingValid = false; } MITK_INFO << "no valid result available. Triggering computation of necessary jobs."; auto job = this->GetNextMissingGenerationJob(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); //other jobs are pending, nothing has to be done if (!job) { MITK_INFO << "waiting for other jobs to finish"; } else { job->setAutoDelete(true); - connect(job, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError); - connect(job, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable); + connect(job, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError, Qt::BlockingQueuedConnection); + connect(job, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable, Qt::BlockingQueuedConnection); threadPool->start(job); } } else { this->RemoveObsoleteDataNodes(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); } } if (everythingValid) { emit GenerationFinished(); } } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h index c49c7e2663..2d182cfefa 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -1,116 +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 __QMITK_DATA_GENERATOR_BASE_H #define __QMITK_DATA_GENERATOR_BASE_H #include //QT #include //MITK #include -#include +#include "QmitkDataGenerationJobBase.h" -class QmitkDataGenerationJobBase; +#include /*! \brief QmitkDataGeneratorBase BaseClass that implements the organisation of data generation. Use/see the class QmitkDataGenerator for more details. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGeneratorBase : public QObject { Q_OBJECT public: QmitkDataGeneratorBase(const QmitkDataGeneratorBase& other) = delete; QmitkDataGeneratorBase& operator=(const QmitkDataGeneratorBase& other) = delete; ~QmitkDataGeneratorBase(); + using JobResultMapType = QmitkDataGenerationJobBase::ResultMapType; + mitk::DataStorage::Pointer GetDataStorage() const; bool GetAutoUpdate() const; bool IsGenerating() const; void Generate() const; + /** Indicates if for a given image and ROI a valid final result is available.*/ + virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + public slots: void SetDataStorage(mitk::DataStorage* storage); void SetAutoUpdate(bool autoUpdate); protected slots: void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; /*! @brief Wraps the resulting BaseData* into DataNode objects */ - void OnFinalResultsAvailable(mitk::DataStorage::SetOfObjects::ConstPointer results, const QmitkDataGenerationJobBase *job) const; + void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; signals: /*! @brief Signal that is emitted if new final data is produced. */ void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; /*! @brief Signal that is emitted if the generator emits new jobs. */ void GenerationStarted() const; /*! @brief Signal that is emitted if all jobs are finished. */ void GenerationFinished() const; /*! @brief Signal that is emitted in case of job errors. */ void JobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; protected: /*! @brief Constructor @param storage the data storage where all produced data should be stored */ - QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage); - QmitkDataGeneratorBase(); + QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr); + QmitkDataGeneratorBase(QObject* parent = nullptr); + + using InputPairVectorType = std::vector>; virtual bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const = 0; - virtual std::vector> GetAllImageStructCombinations() const = 0; + virtual InputPairVectorType GetAllImageROICombinations() const = 0; /** Indicates if there is already an valid and up-to-date result for the given node pair.*/ - virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* segNode) const = 0; - /** Generate placeholder nodes for all (interim) results that will be produced to have a valid final result. - and add them to the data storage. It is important that the data generation property is set correctly for the - generated placeholder nodes.*/ - virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* segNode) const = 0; + virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; /*! @brief Returns the next job that needs to be done in order to complete the workflow. If no job instance is passed back, it either indicated that there is nothing to do (IsValidResultAvailable == true) or that currently a interim result is still missing and therefore the next job cannot be triggered.*/ - virtual QmitkDataGenerationJobBase* GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* segNode) const =0; + virtual QmitkDataGenerationJobBase* GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; /** Remove all obsolete data nodes for the given image and seg node from the data storage. Obsolete nodes are (interim) result nodes that are not the most recent any more.*/ - virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* segNode) const = 0; + virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + /** Prepares result to be added to the storage in an appropriate way and returns the data node for that.*/ + virtual mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const = 0; + + /*! @brief Creates a data node for interim results + @details It's the jobs responsibility to write the final results to the data of the DataNode. + */ + static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); void DoGenerate() const; mitk::WeakPointer m_Storage; bool m_AutoUpdate = false; mutable std::shared_mutex m_DataMutex; mutable bool m_RunningGeneration = false; mutable bool m_RestartGeneration = false; /**Member is called when a node is added to the storage.*/ void NodeAddedOrModified(const mitk::DataNode* node); - unsigned long m_DataStorageDeletedTag; + void EnsureRecheckingAndGeneration(); + unsigned long m_DataStorageDeletedTag; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp new file mode 100644 index 0000000000..0c93d72d43 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp @@ -0,0 +1,103 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkImageAndRoiDataGeneratorBase.h" + +QmitkImageAndRoiDataGeneratorBase::NodeVectorType +QmitkImageAndRoiDataGeneratorBase::GetROINodes() const +{ + return m_ROINodes; +} + +QmitkImageAndRoiDataGeneratorBase::NodeVectorType +QmitkImageAndRoiDataGeneratorBase::GetImageNodes() const +{ + return m_ImageNodes; +} + +void +QmitkImageAndRoiDataGeneratorBase::SetImageNodes(const NodeVectorType& imageNodes) +{ + if (m_ImageNodes != imageNodes) + { + { + std::shared_lock mutexguard(m_DataMutex); + m_ImageNodes = imageNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +void +QmitkImageAndRoiDataGeneratorBase::SetROINodes(const NodeVectorType& roiNodes) +{ + if (m_ROINodes != roiNodes) + { + { + std::shared_lock mutexguard(m_DataMutex); + m_ROINodes = roiNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +bool +QmitkImageAndRoiDataGeneratorBase::NodeChangeIsRelevant(const mitk::DataNode* changedNode) const +{ + if (m_AutoUpdate) + { + auto finding = std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); + if (finding != m_ImageNodes.end()) + { + return true; + } + + finding = std::find(m_ROINodes.begin(), m_ROINodes.end(), changedNode); + if (finding != m_ROINodes.end()) + { + return true; + } + } + + return false; +} + +QmitkImageAndRoiDataGeneratorBase::InputPairVectorType +QmitkImageAndRoiDataGeneratorBase::GetAllImageROICombinations() const +{ + std::shared_lock mutexguard(m_DataMutex); + + InputPairVectorType allCombinations; + for (auto& imageNode : m_ImageNodes) + { + if (m_ROINodes.empty()) + { + allCombinations.emplace_back(imageNode, nullptr); + } + else + { + for (auto& roiNode : m_ROINodes) + { + allCombinations.emplace_back(imageNode, roiNode); + } + } + } + return allCombinations; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h new file mode 100644 index 0000000000..5b12ca1670 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h @@ -0,0 +1,65 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef __QMITK_IMAGE_AND_ROI_DATA_GENERATOR_BASE_H +#define __QMITK_IMAGE_AND_ROI_DATA_GENERATOR_BASE_H + +#include "QmitkDataGeneratorBase.h" +#include + +/*! +\brief QmitkDataGenerator +Class that ensures/generates the statistic data for given image nodes and ROI nodes (e.g. Segmentations or planarfigures). +The class implements the checking if statistic data is still up-to-date and triggers the generation, +if needed. +\tparam TDataGenerationRule a specific DataGenerationRule class, where details about (job) requirements and dependencies +are defined (e.g. which jobs needs to compute the statistic data) +The given dataStorage is used to save all generated data. +The TDataGenerationRule is executed on all combinations of image and ROI nodes. +If new data is generated, the signal NewDataAvailable is emitted. +@sa QmitkDataGeneratorBase +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkImageAndRoiDataGeneratorBase : public QmitkDataGeneratorBase +{ +public: + using Superclass = QmitkDataGeneratorBase; + + using NodeVectorType = std::vector; + + NodeVectorType GetImageNodes() const; + NodeVectorType GetROINodes() const; + +public slots: + /*! @brief Setter for image nodes + */ + void SetImageNodes(const NodeVectorType& imageNodes); + /*! @brief Setter for roi nodes + */ + void SetROINodes(const NodeVectorType& roiNodes); + +protected: + QmitkImageAndRoiDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkDataGeneratorBase(storage, parent) {}; + QmitkImageAndRoiDataGeneratorBase(QObject* parent = nullptr) : QmitkDataGeneratorBase(parent) {}; + + using InputPairVectorType = Superclass::InputPairVectorType; + + bool NodeChangeIsRelevant(const mitk::DataNode *changedNode) const override; + InputPairVectorType GetAllImageROICombinations() const override; + + NodeVectorType m_ImageNodes; + NodeVectorType m_ROINodes; + + QmitkImageAndRoiDataGeneratorBase(const QmitkImageAndRoiDataGeneratorBase&) = delete; + QmitkImageAndRoiDataGeneratorBase& operator = (const QmitkImageAndRoiDataGeneratorBase&) = delete; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp index 69af24f548..d315ecbdfe 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp @@ -1,226 +1,215 @@ /*=================================================================== 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 "mitkProperties.h" QmitkImageStatisticsCalculationRunnable::QmitkImageStatisticsCalculationRunnable() - : QObject(), QRunnable() + : QmitkDataGenerationJobBase() , m_StatisticsImage(nullptr) , m_BinaryMask(nullptr) , m_PlanarFigureMask(nullptr) , m_IgnoreZeros(false) , m_HistogramNBins(100) - , m_CalculationSuccessful(false) { } 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; } -std::string QmitkImageStatisticsCalculationRunnable::GetLastErrorMessage() const +QmitkDataGenerationJobBase::ResultMapType QmitkImageStatisticsCalculationRunnable::GetResults() const { - return m_message; + ResultMapType result; + result.emplace("statistics", this->GetStatisticsData()); + return result; } -const QmitkImageStatisticsCalculationRunnable::HistogramType* -QmitkImageStatisticsCalculationRunnable::GetTimeStepHistogram(unsigned int t) const -{ - if (t >= this->m_HistogramVector.size()) - return nullptr; - - return this->m_HistogramVector.at(t).GetPointer(); -} - -bool QmitkImageStatisticsCalculationRunnable::GetStatisticsUpdateSuccessFlag() const -{ - return m_CalculationSuccessful; -} - -void QmitkImageStatisticsCalculationRunnable::run() +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_message = e.what(); + m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); - m_message = e.what(); + m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const std::runtime_error &e) { MITK_ERROR << "Runtime Exception: " << e.what(); - m_message = e.what(); + m_LastErrorMessage = e.what(); statisticCalculationSuccessful = false; } catch (const std::exception &e) { MITK_ERROR << "Standard Exception: " << e.what(); - m_message = 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_message = e.GetDescription(); + m_LastErrorMessage = e.GetDescription(); MITK_ERROR << "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch (const std::runtime_error &e) { - m_message = "Failure: " + std::string(e.what()); + m_LastErrorMessage = "Failure: " + std::string(e.what()); MITK_ERROR << "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch (const std::exception &e) { - m_message = "Failure: " + std::string(e.what()); + m_LastErrorMessage = "Failure: " + std::string(e.what()); MITK_ERROR << "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } - this->m_CalculationSuccessful = statisticCalculationSuccessful; - if (statisticCalculationSuccessful) { m_StatisticsContainer = calculator->GetStatistics(); - this->m_HistogramVector.clear(); - for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + imageRule->Connect(m_StatisticsContainer, m_StatisticsImage); + + if (nullptr != m_PlanarFigureMask) { - HistogramType::ConstPointer tempHistogram; - try { - if (calculator->GetStatistics()->TimeStepExists(i)) - { - tempHistogram = calculator->GetStatistics()->GetStatisticsForTimeStep(i).m_Histogram; - this->m_HistogramVector.push_back(tempHistogram); - } - } - catch (mitk::Exception&) { - MITK_WARN << ":-("; - } + 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)); } - emit finished(); + return statisticCalculationSuccessful; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h index d46d8e28c9..42769bded3 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h @@ -1,101 +1,92 @@ /*============================================================================ 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 -//QT headers -#include -#include -#include - //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsContainer.h" -#include + +#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 QObject, public QRunnable +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; - /*! - /brief Returns the histogram of the currently selected time step. */ - const HistogramType* GetTimeStepHistogram(unsigned int t = 0) const; - /*! - /brief Returns a flag the indicates if the statistics are updated successfully */ - bool GetStatisticsUpdateSuccessFlag() const; - /*! - /brief Method called once the thread is executed. */ - void run() override; + ResultMapType GetResults() const override; - std::string GetLastErrorMessage() const; -signals: - void finished(); +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. - bool m_CalculationSuccessful; ///< flag set if statistics calculation was successful - std::vector m_HistogramVector; ///< member holds the histograms of all time steps. - std::string m_message; }; #endif // QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp new file mode 100644 index 0000000000..ba394ca121 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -0,0 +1,312 @@ +/*============================================================================ + +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 "mitkNodePredicateDataProperty.h" +#include "mitkProperties.h" +#include "mitkImageStatisticsContainerManager.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + +void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + m_IgnoreZeroValueVoxel = _arg; + this->EnsureRecheckingAndGeneration(); + } +} + +bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + m_HistogramNBins = nbins; + 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) || status != mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS) + { + auto stats = dynamic_cast(changedNode->GetData()); + return stats != nullptr; + } + } + return false; +} + +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) + { + predicate = mitk::NodePredicateAnd::New(predicate, mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); + } + + std::shared_lock mutexguard(m_DataMutex); + auto statisticContainerCandidateNodes = storage->GetSubset(predicate); + + auto statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); + + for (const auto& node : *statisticContainerCandidateNodes) + { + auto isUpToDate = image->GetMTime() > node->GetData()->GetMTime() + && (mask == nullptr || mask->GetMTime() > node->GetData()->GetMTime()); + + if (!onlyIfUpToDate || isUpToDate) + { + statisticContainerCandidateNodesFiltered->push_back(node); + } + } + + if (!statisticContainerCandidateNodesFiltered->empty()) + { + auto newestElement = statisticContainerCandidateNodesFiltered->front(); + if (statisticContainerCandidateNodesFiltered->size() > 1) + { + //in case of multiple found statistics, return only newest one + auto newestIter = std::max_element(std::begin(*statisticContainerCandidateNodesFiltered), std::end(*statisticContainerCandidateNodesFiltered), [](mitk::DataNode::Pointer a, mitk::DataNode::Pointer b) { + return a->GetData()->GetMTime() < b->GetData()->GetMTime(); + }); + newestElement = *newestIter; + MITK_WARN << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; + for (const auto& node : *statisticContainerCandidateNodesFiltered) + { + MITK_DEBUG << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); + } + } + result = newestElement; + } + } + } + + return result; +} + +void QmitkImageStatisticsDataGenerator::IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + if (!imageNode || !imageNode->GetData()) + { + mitkThrow() << "Image node is nullptr"; + } + + auto image = dynamic_cast(imageNode->GetData()); + if (!image) + { + mitkThrow() << "Image node date is nullptr or no image."; + } + + const mitk::BaseData* mask = nullptr; + if (roiNode) + { + mask = roiNode->GetData(); + } + + if (!this->IsValidResultAvailable(imageNode, roiNode)) + { + auto dummyStats = mitk::ImageStatisticsContainer::New(); + + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + imageRule->Connect(dummyStats, image); + + if (nullptr != mask) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + maskRule->Connect(dummyStats, mask); + } + + dummyStats->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); + dummyStats->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); + + auto dummyNode = CreateWIPDataNode(dummyStats, "WIP_"+GenerateStatisticsNodeName(image, mask)); + + auto storage = m_Storage.Lock(); + if (storage) + { + std::shared_lock mutexguard(m_DataMutex); + storage->Add(dummyNode); + } + } +} + +QmitkDataGenerationJobBase* QmitkImageStatisticsDataGenerator::GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); + + if (resultDataNode.IsNull()) + { + 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); + + return newJob; + } + + return 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(predicate, notLatestPredicate); + predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate); + + auto storage = m_Storage.Lock(); + if (storage) + { + std::shared_lock mutexguard(m_DataMutex); + + auto oldStatisticContainerNodes = storage->GetSubset(predicate); + storage->Remove(oldStatisticContainerNodes); + } +} + +mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const +{ + auto statsJob = dynamic_cast(job); + + if (statsJob) + { + auto resultNode = mitk::DataNode::New(); + resultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); + resultNode->SetVisibility(false); + resultNode->SetData(result); + + const mitk::BaseData* roi = statsJob->GetMaskImage(); + if (!roi) + { + roi = statsJob->GetPlanarFigure(); + } + resultNode->SetName(this->GenerateStatisticsNodeName(statsJob->GetStatisticsImage(), roi)); + + return resultNode; + } + + return nullptr; +} + +std::string QmitkImageStatisticsDataGenerator::GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const +{ + std::stringstream statisticsNodeName; + statisticsNodeName << "statistics_bins-" << m_HistogramNBins <<"_"; + + if (m_IgnoreZeroValueVoxel) + { + statisticsNodeName << "noZeros_"; + } + + if (!image) + { + mitkThrow() << "Image is nullptr"; + } + + statisticsNodeName << image->GetUID(); + + if (roi) + { + statisticsNodeName << "_" + roi->GetUID(); + } + + return statisticsNodeName.str(); +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h new file mode 100644 index 0000000000..7175b19d7a --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h @@ -0,0 +1,63 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef __QMITK_IMAGE_STATISTICS_DATA_GENERATOR_H +#define __QMITK_IMAGE_STATISTICS_DATA_GENERATOR_H + +#include "QmitkImageAndRoiDataGeneratorBase.h" + +#include + +/*! +\brief QmitkImageStatisticsDataGenerator +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsDataGenerator : public QmitkImageAndRoiDataGeneratorBase +{ +public: + QmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(storage, parent) {}; + QmitkImageStatisticsDataGenerator(QObject* parent = nullptr) : QmitkImageAndRoiDataGeneratorBase(parent) {}; + + bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; + + /** Returns the latest result for a given image and ROI and the current settings of the generator. + @param onlyIfUpToDate Indicates if results should only be returned if the are up to date, thus not older then image and ROI. + @param noWIP If noWIP is true, the function only returns valid final result and not just its placeholder (WIP). + If noWIP equals false it might also return a WIP, thus the valid result is currently processed/ordered but might not be ready yet.*/ + mitk::DataNode::Pointer GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate = false, bool noWIP = true) const; + + std::string GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const; + + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + +protected: + bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const final; + void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; + QmitkDataGenerationJobBase* GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; + void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const final; + mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const final; + + QmitkImageStatisticsDataGenerator(const QmitkImageStatisticsDataGenerator&) = delete; + QmitkImageStatisticsDataGenerator& operator = (const QmitkImageStatisticsDataGenerator&) = delete; + + bool m_IgnoreZeroValueVoxel = false; + unsigned int m_HistogramNBins = 100; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/files.cmake b/Modules/ImageStatisticsUI/files.cmake index 4bfd046355..4d0f2f5d6a 100644 --- a/Modules/ImageStatisticsUI/files.cmake +++ b/Modules/ImageStatisticsUI/files.cmake @@ -1,41 +1,38 @@ set(CPP_FILES Qmitk/QmitkHistogramVisualizationWidget.cpp Qmitk/QmitkImageStatisticsCalculationRunnable.cpp Qmitk/QmitkIntensityProfileVisualizationWidget.cpp Qmitk/QmitkImageStatisticsTreeModel.cpp Qmitk/QmitkImageStatisticsCalculationJob.cpp Qmitk/QmitkStatisticsModelToStringConverter.cpp Qmitk/QmitkImageStatisticsWidget.cpp Qmitk/QmitkImageStatisticsTreeItem.cpp Qmitk/QmitkDataGenerationJobBase.cpp - Qmitk/QmitkDataGenerationRuleBase.cpp Qmitk/QmitkDataGeneratorBase.cpp + Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp + Qmitk/QmitkImageStatisticsDataGenerator.cpp ) set(H_FILES Qmitk/QmitkStatisticsModelToStringConverter.h Qmitk/QmitkImageStatisticsTreeItem.h - Qmitk/QmitkDataGenerationRuleBase.h - Qmitk/QmitkDataGenerator.h -) - -set(TPP_FILES - Qmitk/QmitkDataGenerator.tpp + Qmitk/QmitkImageAndRoiDataGeneratorBase.h + Qmitk/QmitkImageStatisticsDataGenerator.h ) set(UI_FILES Qmitk/QmitkHistogramVisualizationWidget.ui Qmitk/QmitkIntensityProfileVisualizationWidget.ui Qmitk/QmitkImageStatisticsWidget.ui ) set(MOC_H_FILES Qmitk/QmitkHistogramVisualizationWidget.h Qmitk/QmitkImageStatisticsCalculationRunnable.h Qmitk/QmitkIntensityProfileVisualizationWidget.h Qmitk/QmitkImageStatisticsTreeModel.h Qmitk/QmitkImageStatisticsCalculationJob.h Qmitk/QmitkImageStatisticsWidget.h Qmitk/QmitkDataGenerationJobBase.h Qmitk/QmitkDataGeneratorBase.h ) diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index fa2c3d0182..1dff59e495 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,599 +1,374 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsView.h" #include // berry includes #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include -#include -#include +#include "mitkPlanarFigureMaskGenerator.h" + +#include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainerManager.h" #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; QmitkImageStatisticsView::~QmitkImageStatisticsView() { if (nullptr != m_selectedPlanarFigure) { m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); m_Controls.sliderWidget_histogram->setPrefix("Time: "); m_Controls.sliderWidget_histogram->setDecimals(0); m_Controls.sliderWidget_histogram->setVisible(false); m_Controls.sliderWidget_intensityProfile->setPrefix("Time: "); m_Controls.sliderWidget_intensityProfile->setDecimals(0); m_Controls.sliderWidget_intensityProfile->setVisible(false); ResetGUI(); - m_Controls.widget_statistics->SetDataStorage(GetDataStorage()); + m_DataGenerator = new QmitkImageStatisticsDataGenerator(parent); + m_DataGenerator->SetDataStorage(this->GetDataStorage()); + m_DataGenerator->SetAutoUpdate(true); + m_Controls.widget_statistics->SetDataStorage(this->GetDataStorage()); CreateConnections(); } void QmitkImageStatisticsView::CreateConnections() { connect(m_Controls.checkBox_ignoreZero, &QCheckBox::stateChanged, this, &QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged); connect(m_Controls.buttonSelection, &QAbstractButton::clicked, this, &QmitkImageStatisticsView::OnButtonSelectionPressed); -} - -void QmitkImageStatisticsView::CalculateOrGetMultiStatistics() -{ - m_selectedImageNode = nullptr; - m_selectedMaskNode = nullptr; - if (m_selectedImageNodes.empty()) - { - // no images selected; reset everything - ResetGUI(); - } + connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, + this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); - for (auto imageNode : m_selectedImageNodes) - { - m_selectedImageNode = imageNode; + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationStarted, + this, &QmitkImageStatisticsView::OnGenerationStarted); + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::GenerationFinished, + this, &QmitkImageStatisticsView::OnGenerationFinished); + connect(m_DataGenerator, &QmitkImageStatisticsDataGenerator::JobError, + this, &QmitkImageStatisticsView::OnJobError); - if (m_selectedMaskNodes.empty()) - { - CalculateOrGetStatistics(); - } - else - { - for (auto maskNode : m_selectedMaskNodes) - { - m_selectedMaskNode = maskNode; - CalculateOrGetStatistics(); - } - } - } } -void QmitkImageStatisticsView::CalculateOrGetStatistics() +void QmitkImageStatisticsView::UpdateIntensityProfile() { - if (nullptr != m_selectedPlanarFigure) - { - m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); - m_selectedPlanarFigure = nullptr; - } - m_Controls.groupBox_intensityProfile->setVisible(false); - m_Controls.widget_statistics->setEnabled(m_selectedImageNode.IsNotNull()); - if (nullptr != m_selectedImageNode) - { - auto image = dynamic_cast(m_selectedImageNode->GetData()); - mitk::Image *mask = nullptr; - mitk::PlanarFigure *maskPlanarFigure = nullptr; + if (m_selectedImageNodes.size()==1) + { //only supported for one image and roi currently + auto image = dynamic_cast(m_selectedImageNodes.front()->GetData()); - if (image->GetDimension() == 4) - { - m_Controls.sliderWidget_histogram->setVisible(true); - unsigned int maxTimestep = image->GetTimeSteps(); - m_Controls.sliderWidget_histogram->setMaximum(maxTimestep - 1); - } - else + if (m_selectedPlanarFigure.IsNotNull()) { - m_Controls.sliderWidget_histogram->setVisible(false); - } - - if (nullptr != m_selectedMaskNode) - { - mask = dynamic_cast(m_selectedMaskNode->GetData()); - if (nullptr == mask) + if (!m_selectedPlanarFigure->IsClosed()) { - maskPlanarFigure = dynamic_cast(m_selectedMaskNode->GetData()); - } - } + mitk::Image::Pointer inputImage; + if (image->GetDimension() == 4) + { + m_Controls.sliderWidget_intensityProfile->setVisible(true); + unsigned int maxTimestep = image->GetTimeSteps(); + m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); + // Intensity profile can only be calculated on 3D, so extract if 4D + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); + timeSelector->SetInput(image); + timeSelector->SetTimeNr(currentTimestep); + timeSelector->Update(); + + inputImage = timeSelector->GetOutput(); + } + else + { + m_Controls.sliderWidget_intensityProfile->setVisible(false); + inputImage = image; + } - mitk::ImageStatisticsContainer::ConstPointer imageStatistics; - if (nullptr != mask) - { - imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - } - else if (nullptr != maskPlanarFigure) - { - m_selectedPlanarFigure = maskPlanarFigure; - ITKCommandType::Pointer changeListener = ITKCommandType::New(); - changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::CalculateOrGetMultiStatistics); - m_PlanarFigureObserverTag = - m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); - if (!maskPlanarFigure->IsClosed()) - { - ComputeAndDisplayIntensityProfile(image, maskPlanarFigure); + auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, m_selectedPlanarFigure); + m_Controls.groupBox_intensityProfile->setVisible(true); + m_Controls.widget_intensityProfile->Reset(); + m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), + "Intensity Profile of " + m_selectedImageNodes.front()->GetName()); } - imageStatistics = - mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, maskPlanarFigure); - } - else - { - imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image); } + } +} - bool imageStatisticsOlderThanInputs = false; - if (nullptr != imageStatistics && - ((nullptr != image && imageStatistics->GetMTime() < image->GetMTime()) || - (nullptr != mask && imageStatistics->GetMTime() < mask->GetMTime()) || - (nullptr != maskPlanarFigure && imageStatistics->GetMTime() < maskPlanarFigure->GetMTime()))) +void QmitkImageStatisticsView::UpdateHistogramWidget() +{ + m_Controls.groupBox_histogram->setVisible(false); + + if (m_selectedImageNodes.size() == 1 || m_selectedMaskNodes.size()<=1) + { //currently only supported for one image and roi due to histogram widget limitations. + auto imageNode = m_selectedImageNodes.front(); + const mitk::DataNode* roiNode = nullptr; + if (!m_selectedMaskNodes.empty()) { - imageStatisticsOlderThanInputs = true; + roiNode = m_selectedMaskNodes.front(); } + auto statisticsNode = m_DataGenerator->GetLatestResult(imageNode, roiNode); - if (nullptr != imageStatistics) + if (statisticsNode.IsNotNull()) { - // triggers re-computation when switched between images and the newest one has not 100 bins (default) - if (imageStatistics->TimeStepExists(0)) + auto statistics = dynamic_cast(statisticsNode->GetData()); + + if (statistics) { - auto calculatedBins = imageStatistics->GetStatisticsForTimeStep(0).m_Histogram.GetPointer()->Size(); - if (calculatedBins != 100) + std::stringstream label; + label << "Histogram " << imageNode->GetName(); + if (imageNode->GetData()->GetTimeSteps() > 1) { - OnRequestHistogramUpdate(m_Controls.widget_histogram->GetBins()); + label << "[0]"; } - } - } - // statistics need to be computed - if (!imageStatistics || imageStatisticsOlderThanInputs || m_ForceRecompute) - { - CalculateStatistics(image, mask, maskPlanarFigure); - } - // statistics already computed - else - { - // Not an open planar figure: show histogram (intensity profile already shown) - if (!(maskPlanarFigure && !maskPlanarFigure->IsClosed())) - { - if (imageStatistics->TimeStepExists(0)) + if (roiNode) { - auto histogram = imageStatistics->GetStatisticsForTimeStep(0).m_Histogram.GetPointer(); - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ histogram }, { histogramLabelName }); + label << " with " << roiNode->GetName(); } + + m_Controls.groupBox_histogram->setVisible(true); + m_Controls.widget_histogram->SetTheme(GetColorTheme()); + m_Controls.widget_histogram->SetHistogram(statistics->GetTimeStepHistogram(0), label.str()); } } } } -void QmitkImageStatisticsView::CalculateStatistics(const mitk::Image *image, const mitk::Image *mask, - const mitk::PlanarFigure *maskPlanarFigure) -{ - auto runnable = new QmitkImageStatisticsCalculationRunnable(); - runnable->Initialize(image, mask, maskPlanarFigure); - runnable->setAutoDelete(false); - runnable->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); - m_Runnables.push_back(runnable); - connect(runnable, &QmitkImageStatisticsCalculationRunnable::finished, - this, &QmitkImageStatisticsView::OnStatisticsCalculationEnds, Qt::QueuedConnection); - - try - { - // Compute statistics - QThreadPool::globalInstance()->start(runnable); - m_Controls.label_currentlyComputingStatistics->setVisible(true); - } - catch (const mitk::Exception &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.GetDescription()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } - catch (const std::runtime_error &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } - catch (const std::exception &e) - { - mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); - m_Controls.label_currentlyComputingStatistics->setVisible(false); - } -} - -void QmitkImageStatisticsView::ComputeAndDisplayIntensityProfile(mitk::Image *image, - mitk::PlanarFigure *maskPlanarFigure) -{ - mitk::Image::Pointer inputImage; - if (image->GetDimension() == 4) - { - m_Controls.sliderWidget_intensityProfile->setVisible(true); - unsigned int maxTimestep = image->GetTimeSteps(); - m_Controls.sliderWidget_intensityProfile->setMaximum(maxTimestep - 1); - // Intensity profile can only be calculated on 3D, so extract if 4D - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - int currentTimestep = static_cast(m_Controls.sliderWidget_intensityProfile->value()); - timeSelector->SetInput(image); - timeSelector->SetTimeNr(currentTimestep); - timeSelector->Update(); - - inputImage = timeSelector->GetOutput(); - } - else - { - m_Controls.sliderWidget_intensityProfile->setVisible(false); - inputImage = image; - } - - auto intensityProfile = mitk::ComputeIntensityProfile(inputImage, maskPlanarFigure); - // Don't show histogram for intensity profiles - m_Controls.groupBox_histogram->setVisible(false); - m_Controls.groupBox_intensityProfile->setVisible(true); - m_Controls.widget_intensityProfile->Reset(); - m_Controls.widget_intensityProfile->SetIntensityProfile(intensityProfile.GetPointer(), - "Intensity Profile of " + m_selectedImageNode->GetName()); -} - -void QmitkImageStatisticsView::FillHistogramWidget(const std::vector &histogram, - const std::vector &dataLabels) -{ - m_Controls.groupBox_histogram->setVisible(true); - m_Controls.widget_histogram->SetTheme(GetColorTheme()); - m_Controls.widget_histogram->SetHistogram(histogram.front(), dataLabels.front()); - connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, - this, &QmitkImageStatisticsView::OnRequestHistogramUpdate); -} - QmitkChartWidget::ColorTheme QmitkImageStatisticsView::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } void QmitkImageStatisticsView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); } void QmitkImageStatisticsView::ResetGUIDefault() { m_Controls.widget_histogram->ResetDefault(); m_Controls.checkBox_ignoreZero->setChecked(false); m_IgnoreZeroValueVoxel = false; } -void QmitkImageStatisticsView::OnStatisticsCalculationEnds() +void QmitkImageStatisticsView::OnGenerationStarted() { - auto runnable = qobject_cast(sender()); - if (nullptr == runnable) - { - return; - } - - mitk::StatusBar::GetInstance()->Clear(); - - auto it = std::find(m_Runnables.begin(), m_Runnables.end(), runnable); - if (it != m_Runnables.end()) - { - m_Runnables.erase(it); - } - - auto image = runnable->GetStatisticsImage(); - - // get mask - const mitk::BaseData* mask = nullptr; - if (runnable->GetMaskImage()) - { - mask = runnable->GetMaskImage(); - } - else if (runnable->GetPlanarFigure()) - { - mask = runnable->GetPlanarFigure(); - } - - // get current statistics - auto currentImageStatistics = - mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - - if (runnable->GetStatisticsUpdateSuccessFlag()) // case: calculation was successful - { - auto statistic = runnable->GetStatisticsData(); - - SetupRelationRules(image, mask, statistic); - - // checks for existing statistic, and add it to data manager - HandleExistingStatistics(image, mask, statistic); - - auto maskPlanarFigure = dynamic_cast(mask); - if (!maskPlanarFigure || maskPlanarFigure->IsClosed()) - { - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ runnable->GetTimeStepHistogram() }, { histogramLabelName }); - } - } - else // case: calculation was not successful - { - // handle histogram - auto emptyHistogram = HistogramType::New(); - auto histogramLabelName = GenerateStatisticsNodeName(image, mask); - FillHistogramWidget({ emptyHistogram }, { histogramLabelName }); - - // handle statistics - mitk::ImageStatisticsContainer::Pointer statistic = mitk::ImageStatisticsContainer::New(); - statistic->SetTimeGeometry(const_cast(image->GetTimeGeometry())); - - // add empty histogram to statistics for all time steps - for (unsigned int i = 0; i < image->GetTimeSteps(); ++i) - { - auto statisticObject = mitk::ImageStatisticsContainer::ImageStatisticsObject(); - statisticObject.m_Histogram = emptyHistogram; - statistic->SetStatisticsForTimeStep(i, statisticObject); - } + m_Controls.label_currentlyComputingStatistics->setVisible(true); +} - SetupRelationRules(image, mask, statistic); +void QmitkImageStatisticsView::OnGenerationFinished() +{ + m_Controls.label_currentlyComputingStatistics->setVisible(false); - HandleExistingStatistics(image, mask, statistic); + mitk::StatusBar::GetInstance()->Clear(); - mitk::StatusBar::GetInstance()->DisplayErrorText(runnable->GetLastErrorMessage().c_str()); - m_Controls.widget_histogram->setEnabled(false); - } + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); +} - m_Controls.label_currentlyComputingStatistics->setVisible(false); +void QmitkImageStatisticsView::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) +{ + mitk::StatusBar::GetInstance()->DisplayErrorText(error.toStdString().c_str()); + MITK_WARN << "Error when calculating statistics: " << error; } -void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int) +void QmitkImageStatisticsView::OnRequestHistogramUpdate(unsigned int nbins) { - // NEEDS TO BE IMPLEMENTED - SEE T27032 + m_DataGenerator->SetHistogramNBins(nbins); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { - m_ForceRecompute = true; m_IgnoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; - - CalculateOrGetMultiStatistics(); - - m_ForceRecompute = false; + m_DataGenerator->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); } void QmitkImageStatisticsView::OnButtonSelectionPressed() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(nullptr, "Select input for the statistic","You may select images and ROIs to compute their statistic. ROIs may be segmentations or planar figures."); dialog->SetDataStorage(GetDataStorage()); dialog->SetSelectionCheckFunction(CheckForSameGeometry()); // set predicates auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); auto isImageOrMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isMaskOrPlanarFigurePredicate, isImagePredicate); dialog->SetNodePredicate(isImageOrMaskOrPlanarFigurePredicate); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetCurrentSelection(m_SelectedNodeList); if (dialog->exec()) { - m_selectedImageNodes.resize(0); - m_selectedMaskNodes.resize(0); + std::vector imageNodes; + std::vector maskNodes; m_SelectedNodeList = dialog->GetSelectedNodes(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); for (const auto& node : m_SelectedNodeList) { if (isImagePredicate->CheckNode(node)) { - m_selectedImageNodes.push_back(node.GetPointer()); + imageNodes.push_back(node.GetPointer()); } else { - m_selectedMaskNodes.push_back(node.GetPointer()); + maskNodes.push_back(node.GetPointer()); } } - m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); - - m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); - - CalculateOrGetMultiStatistics(); - } - - delete dialog; -} + m_selectedImageNodes = imageNodes; + m_selectedMaskNodes = maskNodes; -void QmitkImageStatisticsView::HandleExistingStatistics(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask, - mitk::ImageStatisticsContainer::Pointer statistic) -{ - auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(GetDataStorage(), image, mask); - - // if statistics base data already exist: add to existing node - if (imageStatistics) - { - auto node = GetNodeForStatisticsContainer(imageStatistics); - node->SetData(statistic); - } - // statistics base data does not exist: add new node - else - { - auto statisticsNodeName = GenerateStatisticsNodeName(image, mask); - auto statisticsNode = mitk::CreateImageStatisticsNode(statistic, statisticsNodeName); - GetDataStorage()->Add(statisticsNode); - } -} - -std::string QmitkImageStatisticsView::GenerateStatisticsNodeName(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask) -{ - std::string statisticsNodeName = "no"; - - if (image.IsNotNull()) - { - statisticsNodeName = image->GetUID(); - } + if (nullptr != m_selectedPlanarFigure) + { + m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); + m_selectedPlanarFigure = nullptr; + } - if (mask.IsNotNull()) - { - statisticsNodeName += "_" + mask->GetUID(); - } + mitk::PlanarFigure* maskPlanarFigure = nullptr; - statisticsNodeName += "_statistics"; + if (m_selectedMaskNodes.size() == 1) + { //currently we support this only with one ROI selected + maskPlanarFigure = dynamic_cast(m_selectedMaskNodes.front()->GetData()); + } - return statisticsNodeName; -} + if (nullptr != maskPlanarFigure) + { + m_selectedPlanarFigure = maskPlanarFigure; + ITKCommandType::Pointer changeListener = ITKCommandType::New(); + changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::UpdateIntensityProfile); + m_PlanarFigureObserverTag = + m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); -void QmitkImageStatisticsView::SetupRelationRules(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask, - mitk::ImageStatisticsContainer::Pointer statistic) -{ - auto imageRule = mitk::StatisticsToImageRelationRule::New(); - imageRule->Connect(statistic, image); + this->UpdateIntensityProfile(); + } - if (nullptr != mask) - { - auto maskRule = mitk::StatisticsToMaskRelationRule::New(); - maskRule->Connect(statistic, mask); - } -} + m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); + m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); -mitk::DataNode::Pointer QmitkImageStatisticsView::GetNodeForStatisticsContainer(mitk::ImageStatisticsContainer::ConstPointer container) -{ - if (container.IsNull()) - { - mitkThrow() << "Given container is null!"; + m_DataGenerator->SetImageNodes(m_selectedImageNodes); + m_DataGenerator->SetROINodes(m_selectedMaskNodes); } - auto allDataNodes = GetDataStorage()->GetAll()->CastToSTLConstContainer(); - for (auto node : allDataNodes) - { - auto nodeData = node->GetData(); - if (nodeData && nodeData->GetUID() == container->GetUID()) - { - return node; - } - } - mitkThrow() << "No DataNode is found which holds the given statistics container!"; + delete dialog; } QmitkNodeSelectionDialog::SelectionCheckFunctionType QmitkImageStatisticsView::CheckForSameGeometry() const { auto lambda = [](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } const mitk::Image* imageNodeData = nullptr; for (auto& node : nodes) { imageNodeData = dynamic_cast(node->GetData()); if (imageNodeData) { break; } } if (imageNodeData == nullptr) { std::stringstream ss; ss << "

Select at least one image.

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

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

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

"; return ss.str(); } } } return std::string(); }; return lambda; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h index 23d456eb19..339dceb170 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,107 +1,91 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKIMAGESTATISTICSVIEW_H #define QMITKIMAGESTATISTICSVIEW_H #include "ui_QmitkImageStatisticsViewControls.h" #include -#include #include #include #include +class QmitkImageStatisticsDataGenerator; +class QmitkDataGenerationJobBase; + /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a separate thread to keep the gui accessible during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; /*! \brief default destructor */ ~QmitkImageStatisticsView() override; /*! \brief Creates the widget containing the application controls, like sliders, buttons etc.*/ void CreateQtPartControl(QWidget *parent) override; protected: using HistogramType = mitk::ImageStatisticsContainer::HistogramType; void SetFocus() override { }; virtual void CreateConnections(); - void CalculateOrGetMultiStatistics(); - void CalculateOrGetStatistics(); - void CalculateStatistics(const mitk::Image* image, const mitk::Image* mask = nullptr, - const mitk::PlanarFigure* maskPlanarFigure = nullptr); + void UpdateIntensityProfile(); + void UpdateHistogramWidget(); - void ComputeAndDisplayIntensityProfile(mitk::Image * image, mitk::PlanarFigure* maskPlanarFigure); - void FillHistogramWidget(const std::vector &histogram, - const std::vector &dataLabels); QmitkChartWidget::ColorTheme GetColorTheme() const; void ResetGUI(); void ResetGUIDefault(); - void OnStatisticsCalculationEnds(); + void OnGenerationStarted(); + void OnGenerationFinished(); + void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob); void OnRequestHistogramUpdate(unsigned int); void OnCheckBoxIgnoreZeroStateChanged(int state); void OnButtonSelectionPressed(); // member variable Ui::QmitkImageStatisticsViewControls m_Controls; private: - - void HandleExistingStatistics(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer, - mitk::ImageStatisticsContainer::Pointer); - - std::string GenerateStatisticsNodeName(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer); - - void SetupRelationRules(mitk::Image::ConstPointer, mitk::BaseData::ConstPointer, - mitk::ImageStatisticsContainer::Pointer); - - mitk::DataNode::Pointer GetNodeForStatisticsContainer(mitk::ImageStatisticsContainer::ConstPointer container); - QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry() const; typedef itk::SimpleMemberCommand ITKCommandType; - mitk::DataNode::ConstPointer m_selectedImageNode = nullptr; - mitk::DataNode::ConstPointer m_selectedMaskNode = nullptr; mitk::PlanarFigure::Pointer m_selectedPlanarFigure = nullptr; long m_PlanarFigureObserverTag; - bool m_ForceRecompute = false; bool m_IgnoreZeroValueVoxel = false; std::vector m_selectedMaskNodes; std::vector m_selectedImageNodes; QmitkNodeSelectionDialog::NodeList m_SelectedNodeList; std::vector m_StatisticsForSelection; - std::vector m_Runnables; - + QmitkImageStatisticsDataGenerator* m_DataGenerator = nullptr; }; #endif // QMITKIMAGESTATISTICSVIEW_H