diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h index 65096f5381..fd19abe50a 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -1,91 +1,91 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __QMITK_DATA_GENERATION_JOB_BASE_H #define __QMITK_DATA_GENERATION_JOB_BASE_H //QT #include #include #include //MITK #include #include #include #include /*! \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; void run() override; 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 the job that produced the data */ - void ResultsAvailable(const mitk::DataStorage::SetOfObjects* results, const QmitkDataGenerationJobBase* job); + void ResultsAvailable(mitk::DataStorage::SetOfObjects::ConstPointer 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); /**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; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp index b63abcbbf3..2ef7d82621 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.cpp @@ -1,91 +1,45 @@ /*=================================================================== 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 "QmitkTaskGenerationRuleBase.h" +#include "QmitkDataGenerationRuleBase.h" -#include "mitkRTHelper.h" #include "mitkProperties.h" -#include "mitkRTPropertyConstants.h" #include "mitkImage.h" -#include "mitkContourModelSet.h" -#include "mitkRTPredicates.h" -#include "QmitkVoxelizationJob.h" -bool QmitkTaskGenerationRuleBase::IsResultAvailable(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const { +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 QmitkTaskGenerationRuleBase::CreateDataNodeInterimResults(mitk::BaseData::Pointer data) const { +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::RT_CACHE_STATUS_NAME.c_str(), mitk::StringProperty::New(mitk::BASE_DATA_WORK_IN_PROGRESS_VALUE)); + 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; } - -QmitkRTJobBase* QmitkTaskGenerationRuleBase::GetVoxelizationJob(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const { - auto doseImage = dynamic_cast(doseNode->GetData()); - auto structContourModelSet = dynamic_cast(structNode->GetData()); - if (!doseImage || !structContourModelSet) { - mitkThrow() << "could not cast dose nodes to baseData"; - } - //QmitkVoxelizationJob needs image as input, but will be changed to geometry input - auto referenceGeometry = doseImage->GetSlicedGeometry(); - - //image as data placeholder (prevents rendering problems as GetData() never returns nullptr) - mitk::Image::Pointer imageBaseData = mitk::Image::New(); - mitk::PixelType pt = mitk::MakeScalarPixelType(); - unsigned int dim[] = { 1,1,1 }; - imageBaseData->Initialize(pt, 3, dim); - auto voxelizationNode = CreateDataNodeInterimResults(imageBaseData.GetPointer()); - voxelizationNode->SetName(doseNode->GetName()+"_"+structNode->GetName()); - auto voxelizationJob = new QmitkVoxelizationJob(doseImage, structContourModelSet, voxelizationNode); - voxelizationJob->connectDataToInputData(imageBaseData); - return voxelizationJob; -} - -const mitk::DataNode* QmitkTaskGenerationRuleBase::GetLatestVoxelization(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const { - auto voxelizationPredicate = mitk::GetVoxelizationPredicate(doseNode->GetData(), structNode->GetData()); - - auto resultNodes = storage->GetSubset(voxelizationPredicate); - if (resultNodes->empty()) { - return nullptr; - } - if (resultNodes->size() > 1) { - MITK_INFO << "multiple voxelization nodes found. Return only the newest one."; - } - //get newest element - auto latestNode = mitk::GetLatestDataNode(resultNodes); - return latestNode; -} - -mitk::BaseData::Pointer QmitkTaskGenerationRuleBase::GetVoxelizedResultTypeBaseData() const -{ - return mitk::Image::New().GetPointer(); -} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h index b669fb7230..9ce1ca1910 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationRuleBase.h @@ -1,64 +1,65 @@ /*=================================================================== 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_WORKFLOW_BASE_H -#define __QMITK_WORKFLOW_BASE_H +#ifndef __QMITK_DATA_GENERATION_RULE_BASE_H +#define __QMITK_DATA_GENERATION_RULE_BASE_H //MITK #include #include -//MITK-RTTB -#include +#include "QmitkDataGenerationJobBase.h" -#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 QmitkTaskGenerationRuleBase +\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 MITKRTTOOLBOXUI_EXPORT QmitkTaskGenerationRuleBase +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 QmitkRTJobBase* GetNextMissingJob(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const =0; + 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; - /*! @brief Returns the voxelization job - @details is required by two workflows (QmitkDoseStatisticsWorkflow and QmitkDVHWorkflow) and therefore provided in the base class - */ - QmitkRTJobBase* GetVoxelizationJob(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const; - const mitk::DataNode* GetLatestVoxelization(mitk::DataStorage::Pointer storage, mitk::DataNode::ConstPointer doseNode, mitk::DataNode::ConstPointer structNode) const; - mitk::BaseData::Pointer GetVoxelizedResultTypeBaseData() const; }; #endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h index 9bc770e913..cebb9e6b12 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.h @@ -1,70 +1,70 @@ /*============================================================================ 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 taskgenerationRule a specific taskgenerationRule class, where details about (job) requirements and dependencies +\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 taskgenerationRule is executed on all combinations of doseNodes and structNodes. +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 +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 taskgenerationRule) + /*! @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 index 391cd19b20..f58ca0410c 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.tpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerator.tpp @@ -1,94 +1,94 @@ /*============================================================================ 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 +template void -QmitkRTDataGenerator::DoGenerate() { +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(); - taskGenerationRule aTaskGenerationRule = taskGenerationRule(); - if (!aTaskGenerationRule.IsResultAvailable(m_Storage.GetPointer(), doseAndStruct.first.GetPointer(), doseAndStruct.second.GetPointer())) { + 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 = aTaskGenerationRule.GetNextMissingJob(m_Storage, doseAndStruct.first.GetPointer(), doseAndStruct.second.GetPointer()); + 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 QmitkRTJobBase*)), this, SLOT(OnJobError(QString, const QmitkRTJobBase*))); - connect(job, SIGNAL(ResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkRTJobBase*)), this, SLOT(OnFinalResultsAvailable(const mitk::DataStorage::SetOfObjects*, const QmitkRTJobBase*))); + 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 3c49c7fb9a..a171237828 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -1,118 +1,194 @@ /*============================================================================ 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 + QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage) : m_Storage(storage) {} QmitkDataGeneratorBase::QmitkDataGeneratorBase() {} QmitkDataGeneratorBase::~QmitkDataGeneratorBase() { auto dataStorage = m_Storage.Lock(); if (dataStorage.IsNotNull()) { // remove "add node listener" from data storage dataStorage->AddNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedToStorage)); + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // remove "remove node listener" from data storage dataStorage->ChangedNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeModified)); + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } -mitk::DataStorage* QmitkDataGeneratorBase::GetDataStorage() const +mitk::DataStorage::Pointer QmitkDataGeneratorBase::GetDataStorage() const { - return m_Storage; + 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::NodeAddedToStorage)); + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // remove "remove node listener" from old data storage oldStorage->ChangedNodeEvent.RemoveListener( - mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeModified)); + 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::NodeAddedToStorage)); + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); // add remove node listener for new data storage newStorage->ChangedNodeEvent.AddListener( - mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeModified)); + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); } } void QmitkDataGeneratorBase::SetAutoUpdate(bool autoUpdate) { m_AutoUpdate = autoUpdate; } -void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkRTJobBase* failedJob) +void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const { emit JobError(error, failedJob); } -void QmitkDataGeneratorBase::OnFinalResultsAvailable(const mitk::DataStorage::SetOfObjects* results, const QmitkRTJobBase *job) +void QmitkDataGeneratorBase::OnFinalResultsAvailable(mitk::DataStorage::SetOfObjects::ConstPointer results, const QmitkDataGenerationJobBase *job) const { - if (results) + if (results.IsNotNull()) { - TODO go through the results and ensure that the cache status property is removed - //no need to remove the WIP property (mitk::RT_CACHE_STATUS_NAME) again, as directly set the BaseData in the job (where this property is not set) + { + std::shared_lock mutexguard(m_DataMutex); + auto storage = m_Storage.Lock(); + if (storage.IsNull()) + { + for (auto pos = results->Begin(); pos != results->End(); ++pos) + { + storage->Add(pos->Value()); + } + } + } + emit NewDataAvailable(results); } } -TODO if storage is set the generator should register observers for node changes of datastorage. See e.g. node selection widget how to do it. -If some node has changed that is relevant it flags new generation if a doGeneration is ongoing or triggers a new one. +void QmitkDataGeneratorBase::NodeAddedOrModified(const mitk::DataNode* node) +{ + if (this->NodeChangeIsRelevant(node)) + { + m_RestartGeneration = true; + if (!IsGenerating()) + { + this->Generate(); + } + } +} -void QmitkDataGeneratorBase::Generate() +void QmitkDataGeneratorBase::Generate() const { m_RunningGeneration = true; + m_RestartGeneration = true; while (m_RestartGeneration) { m_RestartGeneration = false; - DoGeneration(); + DoGenerate(); } m_RunningGeneration = false; -} \ No newline at end of file +} + +void QmitkDataGeneratorBase::DoGenerate() const +{ + auto imageSegCombinations = this->GetAllImageStructCombinations(); + + QThreadPool* threadPool = QThreadPool::globalInstance(); + + bool everythingValid = true; + + for (const auto& imageAndSeg : imageSegCombinations) + { + MITK_INFO << "processing node " << imageAndSeg.first->GetName() << " and struct " << imageAndSeg.second->GetName(); + + 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); + 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 beefffde10..c49c7e2663 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -1,96 +1,116 @@ /*============================================================================ 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 -class QmitkRTJobBase; +class QmitkDataGenerationJobBase; /*! \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(); - mitk::DataStorage* GetDataStorage() const; + mitk::DataStorage::Pointer GetDataStorage() const; bool GetAutoUpdate() const; + bool IsGenerating() const; + void Generate() const; public slots: void SetDataStorage(mitk::DataStorage* storage); void SetAutoUpdate(bool autoUpdate); protected slots: - void OnJobError(QString error, const QmitkRTJobBase* failedJob); + void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; /*! @brief Wraps the resulting BaseData* into DataNode objects */ - void OnFinalResultsAvailable(const mitk::DataStorage::SetOfObjects* results, const QmitkRTJobBase *job); + void OnFinalResultsAvailable(mitk::DataStorage::SetOfObjects::ConstPointer results, const QmitkDataGenerationJobBase *job) const; signals: /*! @brief Signal that is emitted if new final data is produced. */ - void NewDataAvailable(const mitk::DataStorage::SetOfObjects* data); + 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 AllJobsGenerated(); + void GenerationFinished() const; + /*! @brief Signal that is emitted in case of job errors. */ - void JobError(QString error, const QmitkRTJobBase* failedJob); + 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(); virtual bool NodeChangeIsRelevant(const mitk::DataNode* changedNode) const = 0; virtual std::vector> GetAllImageStructCombinations() const = 0; - virtual void DoGenerate() 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; + /*! @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; + /** 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; + + 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 NodeAddedToStorage(const mitk::DataNode* node); - - /**Member is called when a node modified.*/ - void NodeModified(const mitk::DataNode* node); + void NodeAddedOrModified(const mitk::DataNode* node); unsigned long m_DataStorageDeletedTag; }; #endif