diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp index 5b8d159cba..7ddadc2f35 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,275 +1,277 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkDataGeneratorBase.h" #include "QmitkDataGenerationJobBase.h" #include "mitkDataNode.h" #include "mitkProperties.h" #include "mitkImageStatisticsContainerManager.h" #include QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent) { this->SetDataStorage(storage); } QmitkDataGeneratorBase::QmitkDataGeneratorBase(QObject* parent): QObject(parent) {} QmitkDataGeneratorBase::~QmitkDataGeneratorBase() { auto dataStorage = m_Storage.Lock(); if (dataStorage.IsNotNull()) { // remove "change node listener" from data storage dataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } mitk::DataStorage::Pointer QmitkDataGeneratorBase::GetDataStorage() const { return m_Storage.Lock(); } bool QmitkDataGeneratorBase::GetAutoUpdate() const { return m_AutoUpdate; } bool QmitkDataGeneratorBase::IsGenerating() const { return m_WIP; } void QmitkDataGeneratorBase::SetDataStorage(mitk::DataStorage* storage) { if (storage == m_Storage) return; std::lock_guard mutexguard(m_DataMutex); auto oldStorage = m_Storage.Lock(); if (oldStorage.IsNotNull()) { // remove "change node listener" from old data storage oldStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } m_Storage = storage; auto newStorage = m_Storage.Lock(); if (newStorage.IsNotNull()) { // add change node listener for new data storage newStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } void QmitkDataGeneratorBase::SetAutoUpdate(bool autoUpdate) { m_AutoUpdate = autoUpdate; } void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const { emit JobError(error, failedJob); } void QmitkDataGeneratorBase::OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const { auto resultnodes = mitk::DataStorage::SetOfObjects::New(); for (const auto &pos : results) { resultnodes->push_back(this->PrepareResultForStorage(pos.first, pos.second, job)); } { std::lock_guard mutexguard(m_DataMutex); auto storage = m_Storage.Lock(); if (storage.IsNotNull()) { m_AddingToStorage = true; for (auto pos = resultnodes->Begin(); pos != resultnodes->End(); ++pos) { storage->Add(pos->Value()); } m_AddingToStorage = false; } } emit NewDataAvailable(resultnodes.GetPointer()); if (!resultnodes->empty()) { this->EnsureRecheckingAndGeneration(); } } void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) { if (!m_AddingToStorage) { if (this->ChangedNodeIsRelevant(node)) { this->EnsureRecheckingAndGeneration(); } } } void QmitkDataGeneratorBase::EnsureRecheckingAndGeneration() const { m_RestartGeneration = true; if (!m_InGenerate) { this->Generate(); } } bool QmitkDataGeneratorBase::Generate() const { bool everythingValid = false; if (m_InGenerate) { m_RestartGeneration = true; } else { m_InGenerate = true; m_RestartGeneration = true; while (m_RestartGeneration) { m_RestartGeneration = false; everythingValid = DoGenerate(); } m_InGenerate = false; } return everythingValid; } mitk::DataNode::Pointer QmitkDataGeneratorBase::CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName) { if (!dataDummy) { mitkThrow() << "data is nullptr"; } auto interimResultNode = mitk::DataNode::New(); interimResultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); dataDummy->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_PENDING)); interimResultNode->SetVisibility(false); interimResultNode->SetData(dataDummy); if (!nodeName.empty()) { interimResultNode->SetName(nodeName); } return interimResultNode; } QmitkDataGeneratorBase::InputPairVectorType QmitkDataGeneratorBase::FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const { std::lock_guard mutexguard(m_DataMutex); InputPairVectorType filteredImageROICombinations; auto storage = m_Storage.Lock(); if (storage.IsNotNull()) { for (auto inputPair : imageROICombinations) { if (storage->Exists(inputPair.first) && (inputPair.second.IsNull() || storage->Exists(inputPair.second))) { filteredImageROICombinations.emplace_back(inputPair); } else { MITK_DEBUG << "Ignore pair because at least one of the nodes is not in storage. Pair: " << GetPairDescription(inputPair); } } } return filteredImageROICombinations; } std::string QmitkDataGeneratorBase::GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const { if (imageAndSeg.second.IsNotNull()) { return imageAndSeg.first->GetName() + " and ROI " + imageAndSeg.second->GetName(); } else { return imageAndSeg.first->GetName(); } } bool QmitkDataGeneratorBase::DoGenerate() const { auto filteredImageROICombinations = FilterImageROICombinations(this->GetAllImageROICombinations()); QThreadPool* threadPool = QThreadPool::globalInstance(); bool everythingValid = true; for (const auto& imageAndSeg : filteredImageROICombinations) { MITK_DEBUG << "checking node " << GetPairDescription(imageAndSeg); if (!this->IsValidResultAvailable(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer())) { this->IndicateFutureResults(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); if (everythingValid) { m_WIP = true; everythingValid = false; } MITK_DEBUG << "No valid result available. Requesting next necessary job." << imageAndSeg.first->GetName(); auto nextJob = this->GetNextMissingGenerationJob(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); //other jobs are pending, nothing has to be done if (nextJob.first==nullptr && nextJob.second.IsNotNull()) { MITK_DEBUG << "Last generation job still running, pass on till job is finished..."; } else if(nextJob.first != nullptr && nextJob.second.IsNotNull()) { MITK_DEBUG << "Next generation job started..."; nextJob.first->setAutoDelete(true); nextJob.second->GetData()->SetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), mitk::StringProperty::New(mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS)); connect(nextJob.first, &QmitkDataGenerationJobBase::Error, this, &QmitkDataGeneratorBase::OnJobError, Qt::BlockingQueuedConnection); connect(nextJob.first, &QmitkDataGenerationJobBase::ResultsAvailable, this, &QmitkDataGeneratorBase::OnFinalResultsAvailable, Qt::BlockingQueuedConnection); emit DataGenerationStarted(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer(), nextJob.first); threadPool->start(nextJob.first); } } else { this->RemoveObsoleteDataNodes(imageAndSeg.first.GetPointer(), imageAndSeg.second.GetPointer()); } } if (everythingValid && m_WIP) { m_WIP = false; emit GenerationFinished(); } + if (everythingValid) m_GenerationTime.Modified(); + return everythingValid; } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h index e43e33cbc0..50fd450622 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -1,175 +1,178 @@ /*============================================================================ 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 QmitkDataGeneratorBase_h #define QmitkDataGeneratorBase_h #include //QT #include //MITK #include #include "QmitkDataGenerationJobBase.h" #include /*! \brief QmitkDataGeneratorBase -BaseClass that implements the organisation of (statistic) data generation for pairs of images and ROIs. +BaseClass that implements the organization of (statistic) data generation for pairs of images and ROIs. The key idea is that this class ensures that for vector of given image ROI pairs (defined by derived classes) a result instance (e.g ImageStatisticsContainer) will be calculated, if needed (e.g. because it is missing or -not uptodate anymore), and stored in the data storage passed to a generator instance. While derived classes i.a. +not up to date anymore), and stored in the data storage passed to a generator instance. While derived classes i.a. specify how to generate the image ROI pairs, how to detect latest results, what the next generation step is and how to remove obsolete data from the storage, the base class takes care of the observation of the data storage and orchestrates the whole checking and generation workflow. In all the generation/orchestration process the data storage, passed to the generator, 1) serves as place where the final -results are stored and searched and 2) it resembles the state of the genertion process with these final results and WIP +results are stored and searched and 2) it resembles the state of the generation process with these final results and WIP place holder nodes that indicate planed or currently processed generation steps. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGeneratorBase : public QObject { Q_OBJECT public: QmitkDataGeneratorBase(const QmitkDataGeneratorBase& other) = delete; QmitkDataGeneratorBase& operator=(const QmitkDataGeneratorBase& other) = delete; virtual ~QmitkDataGeneratorBase(); using JobResultMapType = QmitkDataGenerationJobBase::ResultMapType; mitk::DataStorage::Pointer GetDataStorage() const; /** Indicates if the generator may trigger the update automatically (true). Reasons for an update are: - Input data has been changed or modified - Generation relevant settings in derived classes have been changed (must be implemented in derived classes) */ bool GetAutoUpdate() const; /** Indicates if there is currently work in progress, thus data generation jobs are running or pending. It is set to true when GenerationStarted is triggered and becomes false as soon as GenerationFinished is triggered. */ bool IsGenerating() const; /** Checks data validity and triggers generation of data, if needed. - The generation itselfs will be done with a thread pool and is orchestrated by this class. To learn if the threads are finished and + The generation itself will be done with a thread pool and is orchestrated by this class. To learn if the threads are finished and everything is uptodate, listen to the signal GenerationFinished. - @return indicates if everything is already valid (true) or if the generation of new data was triggerd (false).*/ + @return indicates if everything is already valid (true) or if the generation of new data was triggered (false).*/ bool Generate() const; /** Indicates if for a given image and ROI a valid final result is available.*/ virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; public slots: /** Sets the data storage the generator should monitor and where WIP placeholder nodes and final result nodes should be stored.*/ void SetDataStorage(mitk::DataStorage* storage); void SetAutoUpdate(bool autoUpdate); protected slots: - /** Used by QmitkDataGenerationJobBase to signal the generator that an error occured. */ + /** Used by QmitkDataGenerationJobBase to signal the generator that an error occurred. */ void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; /** Used by QmitkDataGenerationJobBase to signal and communicate the results of there computation. */ void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; signals: - /*! @brief Signal that is emitted if a data generation job is started to generat outdated/inexistant data. + /*! @brief Signal that is emitted if a data generation job is started to generate outdated/inexistent data. */ void DataGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job) const; /*! @brief Signal that is emitted if new final data is produced. */ void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; /*! @brief Signal that is emitted if all jobs are finished and everything is up to date. */ void GenerationFinished() const; /*! @brief Signal that is emitted in case of job errors. */ void JobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; protected: /*! @brief Constructor @param storage the data storage where all produced data should be stored @param parent */ QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr); QmitkDataGeneratorBase(QObject* parent = nullptr); using InputPairVectorType = std::vector>; /** This method must be implemented by derived to indicate if a changed node is relevant and therefore if an update must be triggered.*/ virtual bool ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const = 0; - /** This method must be impemented by derived classes to return the pairs of images and ROIs + /** This method must be implemented by derived classes to return the pairs of images and ROIs (ROI may be null if no ROI is needed) for which data are needed.*/ virtual InputPairVectorType GetAllImageROICombinations() const = 0; /** This method should indicate all missing and outdated (interim) results in the data storage, with new placeholder nodes and WIP dummy data added to the storage. The placeholder nodes will be replaced by the real results as soon as they are ready. - The strategy how to detact which placeholder node is need and how the dummy data should look like must be implemented by derived classes.*/ + The strategy how to detect which placeholder node is need and how the dummy data should look like must be implemented by derived classes.*/ virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; /*! @brief Is called to generate the next job instance that needs to be done and is associated dummy node in order to progress the data generation workflow. @remark The method can assume that the caller takes care of the job instance deletion. @return std::pair of job pointer and placeholder node associated with the job. Following combinations are possible: - Both are null: nothing to do; - - Both are set: there is something to do for a pending dumme node -> trigger computation; + - Both are set: there is something to do for a pending dummy node -> trigger computation; - Job null and node set: a job for this node is already work in progress -> pass on till its finished.*/ virtual std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; /** Remove all obsolete data nodes for the given image and ROI node from the data storage. Obsolete nodes are (interim) result nodes that are not the most recent any more.*/ virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; /** Prepares result to be added to the storage in an appropriate way and returns the data node for that.*/ virtual mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const = 0; /*! Creates a data node for WIP place holder results. It can be used by IndicateFutureResults().*/ static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); /** Filters a passed pair vector. The returned pair vector only contains pair of nodes that exist in the data storage.*/ InputPairVectorType FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const; /** Return a descriptive label of a passed pair. Used e.g. for some debug log messages.*/ std::string GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const; /** Internal part of the generation strategy. Here is where the heavy lifting is done.*/ bool DoGenerate() const; - /** Methods either directly calls generation or if its allready onging flags to restart the generation.*/ + /** Methods either directly calls generation or if its already ongoing flags to restart the generation.*/ void EnsureRecheckingAndGeneration() const; mitk::WeakPointer m_Storage; bool m_AutoUpdate = false; mutable std::mutex m_DataMutex; + /** Time stamp for the last successful run through with the current image roi pairs.*/ + mutable itk::TimeStamp m_GenerationTime; + private: /** Indicates if we are currently in the Generation() verification and generation of pending jobs triggering loop. Only needed for the internal logic.*/ mutable bool m_InGenerate = false; /** Internal flag that is set if a generation was requested, while one generation loop was already ongoing.*/ mutable bool m_RestartGeneration = false; /** Indicates if there are still jobs pending or computing (true) or if everything is valid (false).*/ mutable bool m_WIP = false; /** Internal flag that indicates that generator is currently in the process of adding results to the storage*/ mutable bool m_AddingToStorage = false; /**Member is called when a node is added to the storage.*/ void NodeAddedOrModified(const mitk::DataNode* node); unsigned long m_DataStorageDeletedTag; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp index 6505827548..294db02f12 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp @@ -1,121 +1,121 @@ /*============================================================================ 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::ConstNodeVectorType QmitkImageAndRoiDataGeneratorBase::GetROINodes() const { return m_ROINodes; } QmitkImageAndRoiDataGeneratorBase::ConstNodeVectorType QmitkImageAndRoiDataGeneratorBase::GetImageNodes() const { return m_ImageNodes; } void QmitkImageAndRoiDataGeneratorBase::SetImageNodes(const ConstNodeVectorType& imageNodes) { if (m_ImageNodes != imageNodes) { { std::lock_guard mutexguard(m_DataMutex); m_ImageNodes = imageNodes; } if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } void QmitkImageAndRoiDataGeneratorBase::SetImageNodes(const NodeVectorType& imageNodes) { ConstNodeVectorType constInput; constInput.resize(imageNodes.size()); std::copy(imageNodes.begin(), imageNodes.end(), constInput.begin()); this->SetImageNodes(constInput); } void QmitkImageAndRoiDataGeneratorBase::SetROINodes(const ConstNodeVectorType& roiNodes) { if (m_ROINodes != roiNodes) { { std::lock_guard mutexguard(m_DataMutex); m_ROINodes = roiNodes; } if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } void QmitkImageAndRoiDataGeneratorBase::SetROINodes(const NodeVectorType& roiNodes) { ConstNodeVectorType constInput; constInput.resize(roiNodes.size()); std::copy(roiNodes.begin(), roiNodes.end(), constInput.begin()); this->SetROINodes(constInput); } bool QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const { if (m_AutoUpdate) { auto finding = std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); if (finding != m_ImageNodes.end()) { - return true; + return (*finding)->GetData()->GetMTime() > this->m_GenerationTime.GetMTime(); } finding = std::find(m_ROINodes.begin(), m_ROINodes.end(), changedNode); if (finding != m_ROINodes.end()) { - return true; + return (*finding)->GetData()->GetMTime() > this->m_GenerationTime.GetMTime(); } } return false; } QmitkImageAndRoiDataGeneratorBase::InputPairVectorType QmitkImageAndRoiDataGeneratorBase::GetAllImageROICombinations() const { std::lock_guard mutexguard(m_DataMutex); InputPairVectorType allCombinations; for (const auto& imageNode : m_ImageNodes) { if (m_ROINodes.empty()) { allCombinations.emplace_back(imageNode, nullptr); } else { for (const 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 index 80d94d728b..54c87271ef 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h @@ -1,65 +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 QmitkImageAndRoiDataGeneratorBase_h #define QmitkImageAndRoiDataGeneratorBase_h #include "QmitkDataGeneratorBase.h" #include /*! -Base class that can be used for generators that should alow the image nodes and the ROI nodes as vectors (like generated by node selection widgets). -This class ensures that data for every combination of images and ROIs (basicly a folding) will be processed. +Base class that can be used for generators that should allow the image nodes and the ROI nodes as vectors (like generated by node selection widgets). +This class ensures that data for every combination of images and ROIs (basically a folding) will be processed. @sa QmitkDataGeneratorBase */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageAndRoiDataGeneratorBase : public QmitkDataGeneratorBase { public: using Superclass = QmitkDataGeneratorBase; using ConstNodeVectorType = std::vector; using NodeVectorType = std::vector; ConstNodeVectorType GetImageNodes() const; ConstNodeVectorType GetROINodes() const; public slots: /*! @brief Setter for image nodes */ void SetImageNodes(const ConstNodeVectorType& imageNodes); - /*! Convinience overload*/ + /*! Convenience overload*/ void SetImageNodes(const NodeVectorType& imageNodes); /*! @brief Setter for roi nodes */ void SetROINodes(const ConstNodeVectorType& roiNodes); - /*! Convinience overload*/ + /*! Convenience overload*/ 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 ChangedNodeIsRelevant(const mitk::DataNode *changedNode) const override; InputPairVectorType GetAllImageROICombinations() const override; ConstNodeVectorType m_ImageNodes; ConstNodeVectorType m_ROINodes; QmitkImageAndRoiDataGeneratorBase(const QmitkImageAndRoiDataGeneratorBase&) = delete; QmitkImageAndRoiDataGeneratorBase& operator = (const QmitkImageAndRoiDataGeneratorBase&) = delete; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp index 38f4484430..ed2002dc05 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -1,274 +1,274 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsDataGenerator.h" #include "mitkImageStatisticsContainer.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateDataProperty.h" #include "mitkProperties.h" #include "mitkImageStatisticsContainerManager.h" #include "QmitkImageStatisticsCalculationRunnable.h" void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) { if (m_IgnoreZeroValueVoxel != _arg) { m_IgnoreZeroValueVoxel = _arg; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeroValueVoxel; } void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) { if (m_HistogramNBins != nbins) { m_HistogramNBins = nbins; if (m_AutoUpdate) { this->EnsureRecheckingAndGeneration(); } } } unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const { return this->m_HistogramNBins; } bool QmitkImageStatisticsDataGenerator::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const { auto result = QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(changedNode); if (!result) { if (changedNode->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()) == nullptr) { auto stats = dynamic_cast(changedNode->GetData()); - result = stats != nullptr; + result = stats != nullptr && stats->GetMTime()> this->m_GenerationTime.GetMTime(); } } return result; } bool QmitkImageStatisticsDataGenerator::IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { auto resultNode = this->GetLatestResult(imageNode, roiNode, true, true); return resultNode.IsNotNull(); } mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate, bool noWIP) const { auto storage = m_Storage.Lock(); if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode) { mask = roiNode->GetData(); } std::lock_guard mutexguard(m_DataMutex); return mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(storage, image, mask, m_IgnoreZeroValueVoxel, m_HistogramNBins, onlyIfUpToDate, noWIP); } void QmitkImageStatisticsDataGenerator::IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image node is nullptr"; } auto image = dynamic_cast(imageNode->GetData()); if (!image) { mitkThrow() << "Image node date is nullptr or no image."; } const mitk::BaseData* mask = nullptr; if (roiNode != nullptr) { mask = roiNode->GetData(); } auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); if (resultDataNode.IsNull()) { auto dummyStats = mitk::ImageStatisticsContainer::New(); dummyStats->SetTimeGeometry(image->GetTimeGeometry()->Clone()); auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(dummyStats, image); if (nullptr != mask) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); maskRule->Connect(dummyStats, mask); } dummyStats->SetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); dummyStats->SetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); auto dummyNode = CreateWIPDataNode(dummyStats, "WIP_"+GenerateStatisticsNodeName(image, mask)); auto storage = m_Storage.Lock(); if (storage != nullptr) { std::lock_guard mutexguard(m_DataMutex); storage->Add(dummyNode); } } } std::pair QmitkImageStatisticsDataGenerator::GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { auto resultDataNode = this->GetLatestResult(imageNode, roiNode, true, false); std::string status; if (resultDataNode.IsNull() || (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_PENDING)) { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image node is nullptr"; } auto image = dynamic_cast(imageNode->GetData()); if (image == nullptr) { mitkThrow() << "Image node date is nullptr or no image."; } const mitk::Image* mask = nullptr; const mitk::PlanarFigure* planar = nullptr; if (roiNode != nullptr) { mask = dynamic_cast(roiNode->GetData()); planar = dynamic_cast(roiNode->GetData()); } auto newJob = new QmitkImageStatisticsCalculationRunnable; newJob->Initialize(image, mask, planar); newJob->SetIgnoreZeroValueVoxel(m_IgnoreZeroValueVoxel); newJob->SetHistogramNBins(m_HistogramNBins); return std::pair(newJob, resultDataNode.GetPointer()); } else if (resultDataNode->GetStringProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str(), status) && status == mitk::STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS) { return std::pair(nullptr, resultDataNode.GetPointer()); } return std::pair(nullptr, nullptr); } void QmitkImageStatisticsDataGenerator::RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const { if (imageNode == nullptr || !imageNode->GetData()) { mitkThrow() << "Image is nullptr"; } const auto image = imageNode->GetData(); const mitk::BaseData* mask = nullptr; if (roiNode != nullptr) { mask = roiNode->GetData(); } auto lastResult = this->GetLatestResult(imageNode, roiNode, false, false); auto rulePredicate = mitk::ImageStatisticsContainerManager::GetStatisticsPredicateForSources(image, mask); auto notLatestPredicate = mitk::NodePredicateFunction::New([lastResult](const mitk::DataNode* node) { return node != lastResult; }); auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(m_HistogramNBins)); auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(m_IgnoreZeroValueVoxel)); mitk::NodePredicateBase::ConstPointer predicate = mitk::NodePredicateAnd::New(rulePredicate, notLatestPredicate).GetPointer(); predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); auto storage = m_Storage.Lock(); if (storage != nullptr) { std::lock_guard mutexguard(m_DataMutex); auto oldStatisticContainerNodes = storage->GetSubset(predicate); storage->Remove(oldStatisticContainerNodes); } } mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::PrepareResultForStorage(const std::string& /*label*/, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const { auto statsJob = dynamic_cast(job); if (statsJob != nullptr) { auto resultNode = mitk::DataNode::New(); resultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); resultNode->SetVisibility(false); resultNode->SetData(result); const mitk::BaseData* roi = statsJob->GetMaskImage(); if (roi == nullptr) { roi = statsJob->GetPlanarFigure(); } resultNode->SetName(this->GenerateStatisticsNodeName(statsJob->GetStatisticsImage(), roi)); return resultNode; } return nullptr; } std::string QmitkImageStatisticsDataGenerator::GenerateStatisticsNodeName(const mitk::Image* image, const mitk::BaseData* roi) const { std::stringstream statisticsNodeName; statisticsNodeName << "statistics_bins-" << m_HistogramNBins <<"_"; if (m_IgnoreZeroValueVoxel) { statisticsNodeName << "noZeros_"; } if (image == nullptr) { mitkThrow() << "Image is nullptr"; } statisticsNodeName << image->GetUID(); if (roi != nullptr) { statisticsNodeName << "_" + roi->GetUID(); } return statisticsNodeName.str(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h index 2dc85d2f98..b65bd71513 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h @@ -1,69 +1,69 @@ /*============================================================================ 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 QmitkImageStatisticsDataGenerator_h #define QmitkImageStatisticsDataGenerator_h #include "QmitkImageAndRoiDataGeneratorBase.h" #include /** Generates ImageStatisticContainers by using QmitkImageStatisticsCalculationRunnables for each pair if image and ROIs and ensures their validity. It also encodes the HistogramNBins and IgnoreZeroValueVoxel as properties to the results as these settings are important criteria for -discreminating statistics results. +discriminating statistics results. For more details of how the generation is done see QmitkDataGenerationBase. */ 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; /** Returns the latest result for a given image and ROI and the current settings of the generator. @param imageNode @param roiNode @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 ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const; void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const; mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const; QmitkImageStatisticsDataGenerator(const QmitkImageStatisticsDataGenerator&) = delete; QmitkImageStatisticsDataGenerator& operator = (const QmitkImageStatisticsDataGenerator&) = delete; bool m_IgnoreZeroValueVoxel = false; unsigned int m_HistogramNBins = 100; }; #endif