diff --git a/Modules/ImageStatistics/CMakeLists.txt b/Modules/ImageStatistics/CMakeLists.txt index 850733b3bd..73a49bc11d 100644 --- a/Modules/ImageStatistics/CMakeLists.txt +++ b/Modules/ImageStatistics/CMakeLists.txt @@ -1,12 +1,10 @@ MITK_CREATE_MODULE( DEPENDS MitkImageExtraction MitkPlanarFigure MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|ITKIOXML PRIVATE ITK|ITKVTK+ITKConvolution ) if(BUILD_TESTING) - add_subdirectory(Testing) - -endif(BUILD_TESTING) +endif() diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainer.cpp index 4339973779..34f21704af 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::GetHistogramForTimeStep(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..2bceefc675 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 passed time step. + @pre timeStep must be valid*/ + const HistogramType* GetHistogramForTimeStep(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..8465e18579 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -1,90 +1,130 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkImageStatisticsContainerManager.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateNot.h" +#include "mitkNodePredicateFunction.h" +#include "mitkNodePredicateDataProperty.h" +#include "mitkProperties.h" + #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" -mitk::ImageStatisticsContainer::ConstPointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask) +mitk::ImageStatisticsContainer::Pointer mitk::ImageStatisticsContainerManager::GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) +{ + auto node = GetImageStatisticsNode(dataStorage, image, mask, ignoreZeroVoxel, histogramNBins, onlyIfUpToDate, noWIP); + + mitk::ImageStatisticsContainer::Pointer result; + + if (node.IsNotNull()) + { + result = dynamic_cast(node->GetData()); + } + + return result; +} + +mitk::DataNode::Pointer mitk::ImageStatisticsContainerManager::GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask, bool ignoreZeroVoxel, unsigned int histogramNBins, bool onlyIfUpToDate, bool noWIP) { if (!dataStorage) { mitkThrow() << "data storage is nullptr!"; } if (!image) { mitkThrow() << "Image is nullptr"; } - mitk::NodePredicateBase::ConstPointer predicate = GetPredicateForSources(image, mask);; + mitk::NodePredicateBase::ConstPointer predicate = GetStatisticsPredicateForSources(image, mask); - if (predicate) { - auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New(ImageStatisticsContainer::GetStaticNameOfClass()); - predicate = mitk::NodePredicateAnd::New(predicate, nodePredicateImageStatisticsContainer); + mitk::DataNode::Pointer result; - auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); - mitk::DataStorage::SetOfObjects::Pointer statisticContainerCandidateNodesFiltered; + if (predicate) + { + auto binPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str(), mitk::UIntProperty::New(histogramNBins)); + auto zeroPredicate = mitk::NodePredicateDataProperty::New(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str(), mitk::BoolProperty::New(ignoreZeroVoxel)); - statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); - for (const auto& node : *statisticContainerCandidateNodes) { - statisticContainerCandidateNodesFiltered->push_back(node); + predicate = mitk::NodePredicateAnd::New(predicate, binPredicate, zeroPredicate).GetPointer(); + + if (noWIP) + { + auto noWIPPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateDataProperty::New(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str())); + predicate = mitk::NodePredicateAnd::New(predicate, noWIPPredicate).GetPointer(); } - if (statisticContainerCandidateNodesFiltered->empty()) { - return nullptr; + auto statisticContainerCandidateNodes = dataStorage->GetSubset(predicate); + + auto statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); + + for (const auto& node : *statisticContainerCandidateNodes) + { + auto isUpToDate = image->GetMTime() < node->GetData()->GetMTime() + && (mask == nullptr || mask->GetMTime() < node->GetData()->GetMTime()); + + if (!onlyIfUpToDate || isUpToDate) + { + statisticContainerCandidateNodesFiltered->push_back(node); + } } - auto newestElement = statisticContainerCandidateNodesFiltered->front(); - if (statisticContainerCandidateNodesFiltered->size() > 1) { + if (!statisticContainerCandidateNodesFiltered->empty()) + { + auto newestElement = statisticContainerCandidateNodesFiltered->front(); + if (statisticContainerCandidateNodesFiltered->size() > 1) + { //in case of multiple found statistics, return only newest one auto newestIter = std::max_element(std::begin(*statisticContainerCandidateNodesFiltered), std::end(*statisticContainerCandidateNodesFiltered), [](mitk::DataNode::Pointer a, mitk::DataNode::Pointer b) { return a->GetData()->GetMTime() < b->GetData()->GetMTime(); }); newestElement = *newestIter; - MITK_WARN << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; - for (const auto& node : *statisticContainerCandidateNodesFiltered) { + MITK_DEBUG << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; + for (const auto& node : *statisticContainerCandidateNodesFiltered) + { MITK_DEBUG << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); } } - return dynamic_cast(newestElement->GetData()); - } - else { - return nullptr; + result = newestElement; + } } + + return result; } -mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::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).GetPointer(); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); if (mask) { auto maskPredicate = maskRule->GetSourcesDetector(mask); - predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); + predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } else { auto maskPredicate = mitk::NodePredicateNot::New(maskRule->GetConnectedSourcesDetector()); - predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate); + predicate = mitk::NodePredicateAnd::New(predicate, maskPredicate).GetPointer(); } return predicate; } diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h index 403724fbe5..cab786019e 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h @@ -1,45 +1,60 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsContainerManager_H__INCLUDED #define QmitkImageStatisticsContainerManager_H__INCLUDED #include "MitkImageStatisticsExports.h" #include #include #include #include #include #include namespace mitk { + + static const std::string STATS_HISTOGRAM_BIN_PROPERTY_NAME = "MITK.statistic.histogram_bins"; + static const std::string STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME = "MITK.statistic.ignore_zero_voxel"; + static const std::string STATS_GENERATION_STATUS_PROPERTY_NAME = "MITK.statistic.generation.status"; + static const std::string STATS_GENERATION_STATUS_VALUE_WORK_IN_PROGRESS = "workInProgress"; + static const std::string STATS_GENERATION_STATUS_VALUE_PENDING = "pending"; + static const std::string STATS_GENERATION_STATUS_VALUE_BASE_DATA_FAILED = "failed"; + /** \brief Returns the StatisticsContainer that was computed on given input (image/mask/planar figure) and is added as DataNode in a DataStorage */ class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainerManager { public: /**Documentation - @brief Returns the StatisticContainer for the given image and mask - @return a valid StatisticsContainer or nullptr if no StatisticContainer is found or no rules have been defined + @brief Returns the StatisticsContainer for the given image and mask from the storage- + @return a valid StatisticsContainer or nullptr if no StatisticsContainer is found. @details if more than one StatisticsContainer is found, only the newest (ModifiedTime) is returned @pre Datastorage must point to a valid instance. @pre image must Point to a valid instance. + @param onlyIfUpToDate Indicates if results should only be returned if the are up to date, thus not older then image and ROI. + @param noWIP If noWIP is true, the function only returns valid final result and not just its placeholder (WIP). + If noWIP equals false it might also return a WIP, thus the valid result is currently processed/ordered but might not be ready yet. + @param ignoreZeroVoxel indicates the wanted statistics are calculated with or w/o zero voxels. + @param histogramNBins Number of bins the statistics should have that are searched for. */ - static mitk::ImageStatisticsContainer::ConstPointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr); + static mitk::ImageStatisticsContainer::Pointer GetImageStatistics(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask=nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); + static mitk::DataNode::Pointer GetImageStatisticsNode(const mitk::DataStorage* dataStorage, const mitk::BaseData* image, const mitk::BaseData* mask = nullptr, bool ignoreZeroVoxel = false, unsigned int histogramNBins = 100, bool onlyIfUpToDate = true, bool noWIP = true); - 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/ImageStatistics/mitkStatisticsToImageRelationRule.h b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h index e2a2cbd736..750801df0d 100644 --- a/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h +++ b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h @@ -1,32 +1,32 @@ /*============================================================================ 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 mitkStatisticsToImageRelationRule_h #define mitkStatisticsToImageRelationRule_h #include #include "mitkGenericIDRelationRule.h" namespace mitk { class MITKIMAGESTATISTICS_EXPORT StatisticsToImageRelationRule : public mitk::GenericIDRelationRule { public: - mitkClassMacroItkParent(StatisticsToImageRelationRule, mitk::GenericIDRelationRule); + mitkClassMacro(StatisticsToImageRelationRule, mitk::GenericIDRelationRule); itkNewMacro(Self); protected: StatisticsToImageRelationRule(); }; } #endif diff --git a/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h index b1b86d66b2..4516f21043 100644 --- a/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h +++ b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h @@ -1,32 +1,32 @@ /*============================================================================ 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 mitkStatisticsToMaskRelationRule_h #define mitkStatisticsToMaskRelationRule_h #include #include "mitkGenericIDRelationRule.h" namespace mitk { class MITKIMAGESTATISTICS_EXPORT StatisticsToMaskRelationRule : public mitk::GenericIDRelationRule { public: - mitkClassMacroItkParent(StatisticsToMaskRelationRule, mitk::GenericIDRelationRule); + mitkClassMacro(StatisticsToMaskRelationRule, mitk::GenericIDRelationRule); itkNewMacro(Self); protected: StatisticsToMaskRelationRule(); }; } #endif diff --git a/Modules/ImageStatisticsUI/CMakeLists.txt b/Modules/ImageStatisticsUI/CMakeLists.txt index 30b2a1de22..9aefacf838 100644 --- a/Modules/ImageStatisticsUI/CMakeLists.txt +++ b/Modules/ImageStatisticsUI/CMakeLists.txt @@ -1,4 +1,8 @@ MITK_CREATE_MODULE( INCLUDE_DIRS Qmitk DEPENDS MitkCore MitkChart MitkImageStatistics MitkQtWidgets -) \ No newline at end of file +) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp new file mode 100644 index 0000000000..8bc428671a --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.cpp @@ -0,0 +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" + + +std::string QmitkDataGenerationJobBase::GetLastErrorMessage() const +{ + return m_LastErrorMessage; +} + +bool QmitkDataGenerationJobBase::GetComputationSuccessFlag() const +{ + return m_ComputationSuccessful; +} + +void QmitkDataGenerationJobBase::run() +{ + try + { + m_ComputationSuccessful = this->RunComputation(); + if (m_ComputationSuccessful) + { + emit ResultsAvailable(this->GetResults(), this); + } + else + { + emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + } + } + catch (const std::exception& e) + { + m_LastErrorMessage = e.what(); + emit Error(QStringLiteral("Error while running computation. Error description: ") + QString::fromStdString(m_LastErrorMessage), this); + + } + catch (...) + { + m_LastErrorMessage = "Unknown exception"; + emit Error(QStringLiteral("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 new file mode 100644 index 0000000000..a9f1f61401 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGenerationJobBase.h @@ -0,0 +1,74 @@ +/*============================================================================ + +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 QmitkDataGenerationJobBase_h +#define QmitkDataGenerationJobBase_h + + +//QT +#include +#include +#include + +//MITK +#include + +#include + +/*! +\brief QmitkDataGenerationJobBase +Base class for generation jobs used by QmitkDataGenerationBase and derived classes. +*/ +class MITKIMAGESTATISTICSUI_EXPORT QmitkDataGenerationJobBase : public QObject, public QRunnable +{ + Q_OBJECT + +public: + /** 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; + + /** Calls RunComputation() and takes care of the error handling and result signalling.*/ + void run() final; + + /*! + /brief Returns a flag the indicates if the job 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 produced by the job and ready to be used. + @param the job that produced the data + */ + void ResultsAvailable(ResultMapType results, const QmitkDataGenerationJobBase* job); + +protected: + QmitkDataGenerationJobBase() = default; + + virtual ~QmitkDataGenerationJobBase() = default; + + /**Does the real computation. Returns true if there where results produced.*/ + virtual bool RunComputation() = 0; + + std::string m_LastErrorMessage; + +private: + bool m_ComputationSuccessful = false; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp new file mode 100644 index 0000000000..f32be6e324 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.cpp @@ -0,0 +1,275 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + + +#include "QmitkDataGeneratorBase.h" + +#include "QmitkDataGenerationJobBase.h" +#include "mitkDataNode.h" +#include "mitkProperties.h" +#include "mitkImageStatisticsContainerManager.h" + +#include + +QmitkDataGeneratorBase::QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent) : QObject(parent) +{ + this->SetDataStorage(storage); +} + +QmitkDataGeneratorBase::QmitkDataGeneratorBase(QObject* parent): QObject(parent) +{} + +QmitkDataGeneratorBase::~QmitkDataGeneratorBase() +{ + auto dataStorage = m_Storage.Lock(); + if (dataStorage.IsNotNull()) + { + // remove "change node listener" from data storage + dataStorage->ChangedNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } +} + +mitk::DataStorage::Pointer QmitkDataGeneratorBase::GetDataStorage() const +{ + return m_Storage.Lock(); +} + +bool QmitkDataGeneratorBase::GetAutoUpdate() const +{ + return m_AutoUpdate; +} + +bool QmitkDataGeneratorBase::IsGenerating() const +{ + return m_WIP; +} + +void QmitkDataGeneratorBase::SetDataStorage(mitk::DataStorage* storage) +{ + if (storage == m_Storage) return; + + std::lock_guard mutexguard(m_DataMutex); + + auto oldStorage = m_Storage.Lock(); + if (oldStorage.IsNotNull()) + { + // remove "change node listener" from old data storage + oldStorage->ChangedNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } + + m_Storage = storage; + + auto newStorage = m_Storage.Lock(); + + if (newStorage.IsNotNull()) + { + // add change node listener for new data storage + newStorage->ChangedNodeEvent.AddListener( + mitk::MessageDelegate1(this, &QmitkDataGeneratorBase::NodeAddedOrModified)); + } +} + +void QmitkDataGeneratorBase::SetAutoUpdate(bool autoUpdate) +{ + m_AutoUpdate = autoUpdate; +} + +void QmitkDataGeneratorBase::OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const +{ + emit JobError(error, failedJob); +} + +void QmitkDataGeneratorBase::OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const +{ + auto resultnodes = mitk::DataStorage::SetOfObjects::New(); + + for (auto pos : results) + { + resultnodes->push_back(this->PrepareResultForStorage(pos.first, pos.second, job)); + } + + { + std::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(); + } + + return everythingValid; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h new file mode 100644 index 0000000000..f3988f0060 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkDataGeneratorBase.h @@ -0,0 +1,174 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef 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. +The key idea is that this class ensures that for vector of given image ROI pairs (defined by derived classes) +a result instance (e.g ImageStatisticsContainer) will be calculated, if needed (e.g. because it is missing or +not uptodate anymore), and stored in the data storage passed to a generator instance. While derived classes i.a. +specify how to generate the image ROI pairs, how to detect latest results, what the next generation step is and +how to remove obsolete data from the storage, the base class takes 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 +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 + everything is uptodate, listen to the signal GenerationFinished. + @return indicates if everything is already valid (true) or if the generation of new data was triggerd (false).*/ + bool Generate() const; + + /** Indicates if for a given image and ROI a valid final result is available.*/ + virtual bool IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + +public slots: + /** Sets the data storage the generator should monitor and where WIP placeholder nodes and final result nodes should be stored.*/ + void SetDataStorage(mitk::DataStorage* storage); + + void SetAutoUpdate(bool autoUpdate); + +protected slots: + /** Used by QmitkDataGenerationJobBase to signal the generator that an error occured. */ + void OnJobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; + /** Used by QmitkDataGenerationJobBase to signal and communicate the results of there computation. */ + void OnFinalResultsAvailable(JobResultMapType results, const QmitkDataGenerationJobBase *job) const; + +signals: + + /*! @brief Signal that is emitted if a data generation job is started to generat outdated/inexistant data. + */ + void DataGenerationStarted(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job) const; + + /*! @brief Signal that is emitted if new final data is produced. + */ + void NewDataAvailable(mitk::DataStorage::SetOfObjects::ConstPointer data) const; + + /*! @brief Signal that is emitted if all jobs are finished and everything is up to date. + */ + void GenerationFinished() const; + + /*! @brief Signal that is emitted in case of job errors. + */ + void JobError(QString error, const QmitkDataGenerationJobBase* failedJob) const; + +protected: + /*! @brief Constructor + @param storage the data storage where all produced data should be stored + */ + QmitkDataGeneratorBase(mitk::DataStorage::Pointer storage, QObject* parent = nullptr); + QmitkDataGeneratorBase(QObject* parent = nullptr); + + using InputPairVectorType = std::vector>; + + /** This method must be implemented by derived to indicate if a changed node is relevant and therefore if an update must be triggered.*/ + virtual bool ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const = 0; + /** This method must be impemented by derived classes to return the pairs of images and ROIs + (ROI may be null if no ROI is needed) for which data are needed.*/ + virtual InputPairVectorType GetAllImageROICombinations() const = 0; + /** This method should indicate all missing and outdated (interim) results in the data storage, with new placeholder nodes and WIP dummy data + added to the storage. The placeholder nodes will be replaced by the real results as soon as they are ready. + The strategy how to detact which placeholder node is need and how the dummy data should look like must be implemented by derived classes.*/ + virtual void IndicateFutureResults(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + /*! @brief Is called to generate the next job instance that needs to be done and is associated dummy node + in order to progress the data generation workflow. + @remark The method can assume that the caller takes care of the job instance deletion. + @return std::pair of job pointer and placeholder node associated with the job. Following combinations are possible: + - Both are null: nothing to do; + - Both are set: there is something to do for a pending dumme node -> trigger computation; + - Job null and node set: a job for this node is already work in progress -> pass on till its finished.*/ + virtual std::pair GetNextMissingGenerationJob(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const =0; + /** Remove all obsolete data nodes for the given image and ROI node from the data storage. + Obsolete nodes are (interim) result nodes that are not the most recent any more.*/ + virtual void RemoveObsoleteDataNodes(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const = 0; + /** Prepares result to be added to the storage in an appropriate way and returns the data node for that.*/ + virtual mitk::DataNode::Pointer PrepareResultForStorage(const std::string& label, mitk::BaseData* result, const QmitkDataGenerationJobBase* job) const = 0; + + /*! Creates a data node for WIP place holder results. It can be used by IndicateFutureResults().*/ + static mitk::DataNode::Pointer CreateWIPDataNode(mitk::BaseData* dataDummy, const std::string& nodeName); + + /** Filters a passed pair vector. The returned pair vector only contains pair of nodes that exist in the data storage.*/ + InputPairVectorType FilterImageROICombinations(InputPairVectorType&& imageROICombinations) const; + + /** Return a descriptive label of a passed pair. Used e.g. for some debug log messages.*/ + std::string GetPairDescription(const InputPairVectorType::value_type& imageAndSeg) const; + + /** Internal part of the generation strategy. Here is where the heavy lifting is done.*/ + bool DoGenerate() const; + + /** Methods either directly calls generation or if its allready onging flags to restart the generation.*/ + void EnsureRecheckingAndGeneration() const; + + mitk::WeakPointer m_Storage; + + bool m_AutoUpdate = false; + + mutable std::mutex m_DataMutex; + +private: + /** Indicates if we are currently in the Generation() verification and generation of pending jobs triggering loop. + Only needed for the internal logic.*/ + mutable bool m_InGenerate = false; + /** Internal flag that is set if a generation was requested, while one generation loop was already ongoing.*/ + mutable bool m_RestartGeneration = false; + /** Indicates if there are still jobs pending or computing (true) or if everything is valid (false).*/ + mutable bool m_WIP = false; + /** Internal flag that indicates that generator is currently in the process of adding results to the storage*/ + mutable bool m_AddingToStorage = false; + + /**Member is called when a node is added to the storage.*/ + void NodeAddedOrModified(const mitk::DataNode* node); + + unsigned long m_DataStorageDeletedTag; +}; + +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp new file mode 100644 index 0000000000..6b6c95dfd1 --- /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::lock_guard mutexguard(m_DataMutex); + m_ImageNodes = imageNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +void +QmitkImageAndRoiDataGeneratorBase::SetROINodes(const NodeVectorType& roiNodes) +{ + if (m_ROINodes != roiNodes) + { + { + std::lock_guard mutexguard(m_DataMutex); + m_ROINodes = roiNodes; + } + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +bool +QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const +{ + if (m_AutoUpdate) + { + auto finding = std::find(m_ImageNodes.begin(), m_ImageNodes.end(), changedNode); + if (finding != m_ImageNodes.end()) + { + return true; + } + + finding = std::find(m_ROINodes.begin(), m_ROINodes.end(), changedNode); + if (finding != m_ROINodes.end()) + { + return true; + } + } + + return false; +} + +QmitkImageAndRoiDataGeneratorBase::InputPairVectorType +QmitkImageAndRoiDataGeneratorBase::GetAllImageROICombinations() const +{ + std::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 new file mode 100644 index 0000000000..164023b752 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageAndRoiDataGeneratorBase.h @@ -0,0 +1,58 @@ +/*============================================================================ + +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. +@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 ChangedNodeIsRelevant(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..51850b06ed 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.cpp @@ -1,226 +1,186 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsCalculationRunnable.h" #include "mitkImageStatisticsCalculator.h" #include #include #include +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkImageStatisticsContainerManager.h" +#include "mitkProperties.h" QmitkImageStatisticsCalculationRunnable::QmitkImageStatisticsCalculationRunnable() - : 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(); - statisticCalculationSuccessful = false; - } - catch (const itk::ExceptionObject& e) - { - MITK_ERROR << "ITK Exception:" << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } - catch (const std::runtime_error &e) - { - MITK_ERROR << "Runtime Exception: " << e.what(); - m_message = e.what(); - statisticCalculationSuccessful = false; - } catch (const std::exception &e) { - MITK_ERROR << "Standard Exception: " << e.what(); - m_message = e.what(); + MITK_ERROR << "Error while configuring the statistics calculator: " << 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(); - MITK_ERROR << "MITK Exception: " << e.what(); - statisticCalculationSuccessful = false; - } - catch (const std::runtime_error &e) - { - m_message = "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()); - MITK_ERROR << "Standard Exception: " << e.what(); + m_LastErrorMessage = "Failure while calculating the statistics: " + std::string(e.what()); + MITK_ERROR << m_LastErrorMessage; 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..53c0bc182f 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationRunnable.h @@ -1,101 +1,86 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED -#define QMITKIMAGESTATISTICSCALCULATIONRUNNABLE_H_INCLUDED - -//QT headers -#include -#include -#include +#ifndef QmitkImageStatisticsCalculationRunnable_h +#define QmitkImageStatisticsCalculationRunnable_h //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsContainer.h" -#include + +#include "QmitkDataGenerationJobBase.h" // itk headers #ifndef __itkHistogram_h #include #endif +#include /** * /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..70a8da970b --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.cpp @@ -0,0 +1,273 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkImageStatisticsDataGenerator.h" + +#include "mitkImageStatisticsContainer.h" +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkNodePredicateFunction.h" +#include "mitkNodePredicateAnd.h" +#include "mitkNodePredicateNot.h" +#include "mitkNodePredicateDataProperty.h" +#include "mitkProperties.h" +#include "mitkImageStatisticsContainerManager.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + +void QmitkImageStatisticsDataGenerator::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + m_IgnoreZeroValueVoxel = _arg; + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +bool QmitkImageStatisticsDataGenerator::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsDataGenerator::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + m_HistogramNBins = nbins; + + if (m_AutoUpdate) + { + this->EnsureRecheckingAndGeneration(); + } + } +} + +unsigned int QmitkImageStatisticsDataGenerator::GetHistogramNBins() const +{ + return this->m_HistogramNBins; +} + +bool QmitkImageStatisticsDataGenerator::ChangedNodeIsRelevant(const mitk::DataNode* changedNode) const +{ + auto result = QmitkImageAndRoiDataGeneratorBase::ChangedNodeIsRelevant(changedNode); + + if (!result) + { + if (changedNode->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()) == nullptr) + { + auto stats = dynamic_cast(changedNode->GetData()); + result = stats != nullptr; + } + } + return result; +} + +bool QmitkImageStatisticsDataGenerator::IsValidResultAvailable(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode) const +{ + auto resultNode = this->GetLatestResult(imageNode, roiNode, true, true); + return resultNode.IsNotNull(); +} + +mitk::DataNode::Pointer QmitkImageStatisticsDataGenerator::GetLatestResult(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, bool onlyIfUpToDate, bool noWIP) const +{ + auto storage = m_Storage.Lock(); + + if (imageNode == 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(); + + 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 new file mode 100644 index 0000000000..4d3766e246 --- /dev/null +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsDataGenerator.h @@ -0,0 +1,67 @@ +/*============================================================================ + +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. +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 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 diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp index 43ec8cf2f6..ca2c4de0f9 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.cpp @@ -1,100 +1,113 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsTreeItem.h" QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem( ImageStatisticsObject statisticsData, StatisticNameVector statisticNames, - QVariant label, + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parent) - : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent) + : m_statistics(statisticsData) , m_statisticNames(statisticNames), m_label(label), m_parentItem(parent), m_IsWIP(isWIP) { } QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, QVariant label, + bool isWIP, QmitkImageStatisticsTreeItem *parentItem) - : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, parentItem ) + : QmitkImageStatisticsTreeItem(ImageStatisticsObject(), statisticNames, label, isWIP, parentItem ) { } - QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), nullptr ) {} + QmitkImageStatisticsTreeItem::QmitkImageStatisticsTreeItem() : QmitkImageStatisticsTreeItem(StatisticNameVector(), QVariant(), false, nullptr ) {} QmitkImageStatisticsTreeItem::~QmitkImageStatisticsTreeItem() { qDeleteAll(m_childItems); } void QmitkImageStatisticsTreeItem::appendChild(QmitkImageStatisticsTreeItem *item) { m_childItems.append(item); } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::child(int row) { return m_childItems.value(row); } int QmitkImageStatisticsTreeItem::childCount() const { return m_childItems.count(); } int QmitkImageStatisticsTreeItem::columnCount() const { return m_statisticNames.size() + 1; } QVariant QmitkImageStatisticsTreeItem::data(int column) const { QVariant result; if (column > 0 && !m_statisticNames.empty()) { if (column - 1 < static_cast(m_statisticNames.size())) { - auto statisticKey = m_statisticNames.at(column - 1); - std::stringstream ss; - if (m_statistics.HasStatistic(statisticKey)) + if (m_IsWIP) { - ss << m_statistics.GetValueNonConverted(statisticKey); + result = QVariant(QString("...")); } else { - return QVariant(); + auto statisticKey = m_statisticNames.at(column - 1); + std::stringstream ss; + if (m_statistics.HasStatistic(statisticKey)) + { + ss << m_statistics.GetValueNonConverted(statisticKey); + } + else + { + return QVariant(); + } + result = QVariant(QString::fromStdString(ss.str())); } - result = QVariant(QString::fromStdString(ss.str())); } else { return QVariant(); } } else if (column == 0) { result = m_label; } return result; } QmitkImageStatisticsTreeItem *QmitkImageStatisticsTreeItem::parentItem() { return m_parentItem; } int QmitkImageStatisticsTreeItem::row() const { if (m_parentItem) return m_parentItem->m_childItems.indexOf(const_cast(this)); return 0; } + +bool QmitkImageStatisticsTreeItem::isWIP() const +{ + return m_IsWIP; +} diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h index 055cb54991..76c982b934 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeItem.h @@ -1,54 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeItem_h #define QmitkImageStatisticsTreeItem_h #include #include #include "mitkImageStatisticsContainer.h" /*! \class QmitkImageStatisticsTreeItem An item that represents an entry (usually ImageStatisticsObject) for the QmitkImageStatisticsTreeModel */ class QmitkImageStatisticsTreeItem { public: using ImageStatisticsObject = mitk::ImageStatisticsContainer::ImageStatisticsObject; using StatisticNameVector = mitk::ImageStatisticsContainer::ImageStatisticsObject::StatisticNameVector; QmitkImageStatisticsTreeItem(); explicit QmitkImageStatisticsTreeItem(ImageStatisticsObject statisticsData, - StatisticNameVector statisticNames, QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + StatisticNameVector statisticNames, QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); explicit QmitkImageStatisticsTreeItem(StatisticNameVector statisticNames, - QVariant label, QmitkImageStatisticsTreeItem *parentItem = nullptr); + QVariant label, bool isWIP, QmitkImageStatisticsTreeItem *parentItem = nullptr); ~QmitkImageStatisticsTreeItem(); void appendChild(QmitkImageStatisticsTreeItem *child); QmitkImageStatisticsTreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; int row() const; QmitkImageStatisticsTreeItem *parentItem(); + /**indicates that the statistic container owned by this instance is only a dummy + WIP containter and the calculation of the up-to-date statistic is not yet finished.**/ + bool isWIP() const; + private: ImageStatisticsObject m_statistics; StatisticNameVector m_statisticNames; QVariant m_label; QmitkImageStatisticsTreeItem *m_parentItem = nullptr; QList m_childItems; + bool m_IsWIP; }; #endif // QmitkImageStatisticsTreeItem_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp index 033598353c..f0b184289b 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.cpp @@ -1,383 +1,425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsTreeModel.h" #include "QmitkImageStatisticsTreeItem.h" #include "itkMutexLockHolder.h" #include "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" +#include "QmitkStyleManager.h" + QmitkImageStatisticsTreeModel::QmitkImageStatisticsTreeModel(QObject *parent) : QmitkAbstractDataStorageModel(parent) { m_RootItem = new QmitkImageStatisticsTreeItem(); } QmitkImageStatisticsTreeModel ::~QmitkImageStatisticsTreeModel() { // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); delete m_RootItem; }; void QmitkImageStatisticsTreeModel::DataStorageChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodePredicateChanged() { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } int QmitkImageStatisticsTreeModel::columnCount(const QModelIndex& /*parent*/) const { int columns = m_StatisticNames.size() + 1; return columns; } int QmitkImageStatisticsTreeModel::rowCount(const QModelIndex &parent) const { QmitkImageStatisticsTreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } QVariant QmitkImageStatisticsTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - if (role != Qt::DisplayRole) - return QVariant(); - - QmitkImageStatisticsTreeItem *item = static_cast(index.internalPointer()); + QmitkImageStatisticsTreeItem* item = static_cast(index.internalPointer()); - return item->data(index.column()); + if (role == Qt::DisplayRole) + { + return item->data(index.column()); + } + else if (role == Qt::DecorationRole && index.column() == 0 && item->isWIP() && item->childCount()==0) + { + return QVariant(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/hourglass-half-solid.svg"))); + } + return QVariant(); } QModelIndex QmitkImageStatisticsTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); QmitkImageStatisticsTreeItem *parentItem; if (!parent.isValid()) parentItem = m_RootItem; else parentItem = static_cast(parent.internalPointer()); QmitkImageStatisticsTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkImageStatisticsTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkImageStatisticsTreeItem *childItem = static_cast(child.internalPointer()); QmitkImageStatisticsTreeItem *parentItem = childItem->parentItem(); if (parentItem == m_RootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } Qt::ItemFlags QmitkImageStatisticsTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return QAbstractItemModel::flags(index); } QVariant QmitkImageStatisticsTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (section == 0) { return m_HeaderFirstColumn; } else { return QVariant(m_StatisticNames.at(section - 1).c_str()); } } return QVariant(); } void QmitkImageStatisticsTreeModel::SetImageNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedImageNodes = std::move(tempNodes); m_ImageNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::SetMaskNodes(const std::vector &nodes) { std::vector> tempNodes; for (const auto &node : nodes) { auto data = node->GetData(); if (data) { auto timeSteps = data->GetTimeSteps(); // special case: apply one mask to each timestep of an 4D image if (timeSteps == 1 && m_TimeStepResolvedImageNodes.size() > 1) { timeSteps = m_TimeStepResolvedImageNodes.size(); } for (unsigned int i = 0; i < timeSteps; i++) { tempNodes.push_back(std::make_pair(node, i)); } } } emit beginResetModel(); m_TimeStepResolvedMaskNodes = std::move(tempNodes); m_MaskNodes = nodes; UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::Clear() { emit beginResetModel(); m_Statistics.clear(); m_ImageNodes.clear(); m_TimeStepResolvedImageNodes.clear(); m_MaskNodes.clear(); m_StatisticNames.clear(); emit endResetModel(); emit modelChanged(); } +void QmitkImageStatisticsTreeModel::SetIgnoreZeroValueVoxel(bool _arg) +{ + if (m_IgnoreZeroValueVoxel != _arg) + { + emit beginResetModel(); + m_IgnoreZeroValueVoxel = _arg; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +bool QmitkImageStatisticsTreeModel::GetIgnoreZeroValueVoxel() const +{ + return this->m_IgnoreZeroValueVoxel; +} + +void QmitkImageStatisticsTreeModel::SetHistogramNBins(unsigned int nbins) +{ + if (m_HistogramNBins != nbins) + { + emit beginResetModel(); + m_HistogramNBins = nbins; + UpdateByDataStorage(); + emit endResetModel(); + emit modelChanged(); + } +} + +unsigned int QmitkImageStatisticsTreeModel::GetHistogramNBins() const +{ + return this->m_HistogramNBins; +} + void QmitkImageStatisticsTreeModel::UpdateByDataStorage() { StatisticsContainerVector newStatistics; auto datamanager = m_DataStorage.Lock(); if (datamanager.IsNotNull()) { for (const auto &image : m_ImageNodes) { if (m_MaskNodes.empty()) { - auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData()); + auto stats = mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), nullptr, m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } else { for (const auto &mask : m_MaskNodes) { auto stats = - mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData()); + mitk::ImageStatisticsContainerManager::GetImageStatistics(datamanager, image->GetData(), mask->GetData(), m_IgnoreZeroValueVoxel, m_HistogramNBins, true, false); if (stats.IsNotNull()) { newStatistics.emplace_back(stats); } } } } if (!newStatistics.empty()) { emit dataAvailable(); } } { itk::MutexLockHolder locked(m_Mutex); m_Statistics = newStatistics; } m_StatisticNames = mitk::GetAllStatisticNames(m_Statistics); BuildHierarchicalModel(); } void QmitkImageStatisticsTreeModel::BuildHierarchicalModel() { // reset old model delete m_RootItem; m_RootItem = new QmitkImageStatisticsTreeItem(); bool hasMask = false; bool hasMultipleTimesteps = false; std::map dataNodeToTreeItem; for (auto statistic : m_Statistics) { + bool isWIP = statistic->GetProperty(mitk::STATS_GENERATION_STATUS_PROPERTY_NAME.c_str()).IsNotNull(); // get the connected image data node/mask data node auto imageRule = mitk::StatisticsToImageRelationRule::New(); auto imageOfStatisticsPredicate = imageRule->GetDestinationsDetector(statistic); auto imageFinding = std::find_if(m_ImageNodes.begin(), m_ImageNodes.end(), [&imageOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return imageOfStatisticsPredicate->CheckNode(testNode); }); auto maskRule = mitk::StatisticsToMaskRelationRule::New(); auto maskOfStatisticsPredicate = maskRule->GetDestinationsDetector(statistic); auto maskFinding = std::find_if(m_MaskNodes.begin(), m_MaskNodes.end(), [&maskOfStatisticsPredicate](const mitk::DataNode::ConstPointer& testNode) { return maskOfStatisticsPredicate->CheckNode(testNode); }); if (imageFinding == m_ImageNodes.end()) { mitkThrow() << "no image found connected to statistic" << statistic << " Aborting."; } auto& image = *imageFinding; // image: 1. hierarchy level QmitkImageStatisticsTreeItem *imageItem = nullptr; auto search = dataNodeToTreeItem.find(image); // the tree item was created previously if (search != dataNodeToTreeItem.end()) { imageItem = search->second; } // create the tree item else { QString imageLabel = QString::fromStdString(image->GetName()); if (statistic->GetTimeSteps() == 1 && maskFinding == m_MaskNodes.end()) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, imageLabel, isWIP, m_RootItem); } else { - imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, m_RootItem); + imageItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, imageLabel, isWIP, m_RootItem); } m_RootItem->appendChild(imageItem); dataNodeToTreeItem.emplace(image, imageItem); } // mask: 2. hierarchy level (optional, only if mask exists) QmitkImageStatisticsTreeItem *lastParent = nullptr; if (maskFinding != m_MaskNodes.end()) { auto& mask = *maskFinding; QString maskLabel = QString::fromStdString(mask->GetName()); QmitkImageStatisticsTreeItem *maskItem; // add statistical values directly in this hierarchy level if (statistic->GetTimeSteps() == 1) { auto statisticsObject = statistic->GetStatisticsForTimeStep(0); - maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(statisticsObject, m_StatisticNames, maskLabel, isWIP, imageItem); } else { - maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, imageItem); + maskItem = new QmitkImageStatisticsTreeItem(m_StatisticNames, maskLabel, isWIP, imageItem); } imageItem->appendChild(maskItem); lastParent = maskItem; hasMask = true; } else { lastParent = imageItem; } // 3. hierarchy level (optional, only if >1 timestep) if (statistic->GetTimeSteps() > 1) { for (unsigned int i = 0; i < statistic->GetTimeSteps(); i++) { QString timeStepLabel = "[" + QString::number(i) + "] " + QString::number(statistic->GetTimeGeometry()->TimeStepToTimePoint(i)) + " ms"; if (statistic->TimeStepExists(i)) { auto statisticsItem = new QmitkImageStatisticsTreeItem( - statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, lastParent); + statistic->GetStatisticsForTimeStep(i), m_StatisticNames, timeStepLabel, isWIP, lastParent); lastParent->appendChild(statisticsItem); } } hasMultipleTimesteps = true; } } QString headerString = "Images"; if (hasMask) { headerString += "/Masks"; } if (hasMultipleTimesteps) { headerString += "/Timesteps"; } m_HeaderFirstColumn = headerString; } void QmitkImageStatisticsTreeModel::NodeRemoved(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeAdded(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } void QmitkImageStatisticsTreeModel::NodeChanged(const mitk::DataNode *) { emit beginResetModel(); UpdateByDataStorage(); emit endResetModel(); emit modelChanged(); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h index 4797719702..800959cba8 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsTreeModel.h @@ -1,111 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsTreeModel_h #define QmitkImageStatisticsTreeModel_h #include "itkSimpleFastMutexLock.h" #include "QmitkAbstractDataStorageModel.h" //MITK #include #include "mitkImageStatisticsContainer.h" class QmitkImageStatisticsTreeItem; /*! \class QmitkImageStatisticsTreeModel -Model that takes a mitk::ImageStatisticsContainer and represents it as model in context of the QT view-model-concept. +The class is used to represent the information of mitk::ImageStatisticsContainer in the set datastorage in the context of the QT view-model-concept. +The represented ImageStatisticContainer are specified by setting the image and mask nodes that should be regarded. +In addition you may specified the statistic computation property HistorgramNBins and IgnoreZeroValueVoxel to select the correct +statistics. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsTreeModel : public QmitkAbstractDataStorageModel { Q_OBJECT public: QmitkImageStatisticsTreeModel(QObject *parent = nullptr); ~QmitkImageStatisticsTreeModel() override; void SetImageNodes(const std::vector& nodes); void SetMaskNodes(const std::vector& nodes); void Clear(); + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; signals: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: /* * @brief See 'QmitkAbstractDataStorageModel' */ void DataStorageChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodePredicateChanged() override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeAdded(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeChanged(const mitk::DataNode *node) override; /* * @brief See 'QmitkAbstractDataStorageModel' */ void NodeRemoved(const mitk::DataNode *node) override; private: void UpdateByDataStorage(); using StatisticsContainerVector = std::vector; /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] --> 3. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); StatisticsContainerVector m_Statistics; /** Relevant images set by the user.*/ std::vector m_ImageNodes; /** Helper that is constructed when m_ImageNodes is set. It has the same order like m_ImageNodes, but each image is represented n times, while n is the number of time steps the respective image has. This structure makes the business logic to select the correct image given a QIndex much simpler and therefore easy to understand/maintain. */ std::vector > m_TimeStepResolvedImageNodes; /** relevant masks set by the user.*/ std::vector m_MaskNodes; /** @sa m_TimeStepResolvedImageNodes */ std::vector> m_TimeStepResolvedMaskNodes; std::vector m_StatisticNames; itk::SimpleFastMutexLock m_Mutex; QmitkImageStatisticsTreeItem *m_RootItem; QVariant m_HeaderFirstColumn; + + bool m_IgnoreZeroValueVoxel = false; + unsigned int m_HistogramNBins = 100; }; #endif // mitkQmitkImageStatisticsTreeModel_h diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp index 78047bcec0..98664744e6 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.cpp @@ -1,81 +1,102 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageStatisticsWidget.h" #include "QmitkStatisticsModelToStringConverter.h" #include "QmitkImageStatisticsTreeModel.h" #include #include QmitkImageStatisticsWidget::QmitkImageStatisticsWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_imageStatisticsModel = new QmitkImageStatisticsTreeModel(parent); CreateConnections(); m_ProxyModel = new QSortFilterProxyModel(this); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.treeViewStatistics->setModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_imageStatisticsModel); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::dataAvailable, this, &QmitkImageStatisticsWidget::OnDataAvailable); connect(m_imageStatisticsModel, &QmitkImageStatisticsTreeModel::modelChanged, m_Controls.treeViewStatistics, &QTreeView::expandAll); } void QmitkImageStatisticsWidget::SetDataStorage(mitk::DataStorage* newDataStorage) { m_imageStatisticsModel->SetDataStorage(newDataStorage); } void QmitkImageStatisticsWidget::SetImageNodes(const std::vector& nodes) { m_imageStatisticsModel->SetImageNodes(nodes); } void QmitkImageStatisticsWidget::SetMaskNodes(const std::vector& nodes) { m_imageStatisticsModel->SetMaskNodes(nodes); } void QmitkImageStatisticsWidget::Reset() { m_imageStatisticsModel->Clear(); m_Controls.treeViewStatistics->setEnabled(false); m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(false); } + +void QmitkImageStatisticsWidget::SetIgnoreZeroValueVoxel(bool _arg) +{ + m_imageStatisticsModel->SetIgnoreZeroValueVoxel(_arg); +} + +bool QmitkImageStatisticsWidget::GetIgnoreZeroValueVoxel() const +{ + return this->m_imageStatisticsModel->GetIgnoreZeroValueVoxel(); +} + +void QmitkImageStatisticsWidget::SetHistogramNBins(unsigned int nbins) +{ + m_imageStatisticsModel->SetHistogramNBins(nbins); +} + +unsigned int QmitkImageStatisticsWidget::GetHistogramNBins() const +{ + return this->m_imageStatisticsModel->GetHistogramNBins(); +} + void QmitkImageStatisticsWidget::CreateConnections() { connect(m_Controls.buttonCopyImageStatisticsToClipboard, &QPushButton::clicked, this, &QmitkImageStatisticsWidget::OnClipboardButtonClicked); } void QmitkImageStatisticsWidget::OnDataAvailable() { m_Controls.buttonCopyImageStatisticsToClipboard->setEnabled(true); m_Controls.treeViewStatistics->setEnabled(true); } void QmitkImageStatisticsWidget::OnClipboardButtonClicked() { QmitkStatisticsModelToStringConverter converter; converter.SetColumnDelimiter('\t'); converter.SetTableModel(m_imageStatisticsModel); converter.SetRootIndex(m_Controls.treeViewStatistics->rootIndex()); converter.SetIncludeHeaderData(true); QString clipboardAsString = converter.GetString(); QApplication::clipboard()->clear(); QApplication::clipboard()->setText(clipboardAsString, QClipboard::Clipboard); } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h index 2635b6fd29..13e4fef97d 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsWidget.h @@ -1,53 +1,63 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageStatisticsWidget_H__INCLUDED #define QmitkImageStatisticsWidget_H__INCLUDED #include #include #include #include class QSortFilterProxyModel; class QmitkImageStatisticsTreeModel; class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsWidget : public QWidget { Q_OBJECT public: QmitkImageStatisticsWidget(QWidget *parent = nullptr); /**Documentation Set the data storage the model should fetch its statistic objects from. @pre data storage must be valid */ void SetDataStorage(mitk::DataStorage *newDataStorage); void SetImageNodes(const std::vector &nodes); void SetMaskNodes(const std::vector &nodes); void Reset(); + /*! /brief Set flag to ignore zero valued voxels */ + void SetIgnoreZeroValueVoxel(bool _arg); + /*! /brief Get status of zero value voxel ignoring. */ + bool GetIgnoreZeroValueVoxel() const; + + /*! /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins(unsigned int nbins); + /*! /brief Get bin size for histogram resolution.*/ + unsigned int GetHistogramNBins() const; + private: void CreateConnections(); void OnDataAvailable(); /** \brief Saves the image statistics to the clipboard */ void OnClipboardButtonClicked(); private: Ui::QmitkImageStatisticsControls m_Controls; QmitkImageStatisticsTreeModel *m_imageStatisticsModel; QSortFilterProxyModel *m_ProxyModel; }; #endif // QmitkImageStatisticsWidget_H__INCLUDED diff --git a/Modules/ImageStatisticsUI/files.cmake b/Modules/ImageStatisticsUI/files.cmake index d94da90986..4d0f2f5d6a 100644 --- a/Modules/ImageStatisticsUI/files.cmake +++ b/Modules/ImageStatisticsUI/files.cmake @@ -1,33 +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/QmitkDataGeneratorBase.cpp + Qmitk/QmitkImageAndRoiDataGeneratorBase.cpp + Qmitk/QmitkImageStatisticsDataGenerator.cpp ) set(H_FILES Qmitk/QmitkStatisticsModelToStringConverter.h Qmitk/QmitkImageStatisticsTreeItem.h -) - -set(TPP_FILES + 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/Modules/ImageStatisticsUI/test/CMakeLists.txt b/Modules/ImageStatisticsUI/test/CMakeLists.txt new file mode 100644 index 0000000000..153cd81e2e --- /dev/null +++ b/Modules/ImageStatisticsUI/test/CMakeLists.txt @@ -0,0 +1 @@ +MITK_CREATE_MODULE_TESTS() diff --git a/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp new file mode 100644 index 0000000000..a64ab03339 --- /dev/null +++ b/Modules/ImageStatisticsUI/test/QmitkImageStatisticsDataGeneratorTest.cpp @@ -0,0 +1,519 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkImageStatisticsDataGenerator.h" +#include +#include +#include "mitkImage.h" +#include "mitkPlanarFigure.h" +#include "mitkIOUtil.h" + +#include "mitkStatisticsToImageRelationRule.h" +#include "mitkStatisticsToMaskRelationRule.h" +#include "mitkImageStatisticsContainerManager.h" +#include "mitkProperties.h" + +#include "QmitkImageStatisticsCalculationRunnable.h" + +#include +#include + +class TestQmitkImageStatisticsDataGenerator : public QmitkImageStatisticsDataGenerator +{ +public: + TestQmitkImageStatisticsDataGenerator(mitk::DataStorage::Pointer storage, QObject* parent = nullptr) : QmitkImageStatisticsDataGenerator(storage, parent) + { + connect(this, &QmitkDataGeneratorBase::NewDataAvailable, this, &TestQmitkImageStatisticsDataGenerator::NewDataAvailableEmited); + connect(this, &QmitkDataGeneratorBase::DataGenerationStarted, this, &TestQmitkImageStatisticsDataGenerator::DataGenerationStartedEmited); + connect(this, &QmitkDataGeneratorBase::GenerationFinished, this, &TestQmitkImageStatisticsDataGenerator::GenerationFinishedEmited); + connect(this, &QmitkDataGeneratorBase::JobError, this, &TestQmitkImageStatisticsDataGenerator::JobErrorEmited); + }; + + mutable std::vector m_NewDataAvailable; + void NewDataAvailableEmited(mitk::DataStorage::SetOfObjects::ConstPointer data) const + { + m_NewDataAvailable.emplace_back(data); + }; + + mutable int m_DataGenerationStartedEmited = 0; + void DataGenerationStartedEmited(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) const + { + m_DataGenerationStartedEmited++; + } + + mutable int m_GenerationFinishedEmited = 0; + void GenerationFinishedEmited() const + { + m_GenerationFinishedEmited++; + QCoreApplication::instance()->quit(); + } + + mutable std::vector m_JobErrorEmited_error; + void JobErrorEmited(QString error, const QmitkDataGenerationJobBase* /*failedJob*/) const + { + m_JobErrorEmited_error.emplace_back(error); + } + +}; + +class QmitkImageStatisticsDataGeneratorTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(QmitkImageStatisticsDataGeneratorTestSuite); + MITK_TEST(GetterSetterTest); + MITK_TEST(NullTest); + MITK_TEST(OneImageTest); + MITK_TEST(MultiImageTest); + MITK_TEST(ImageAndROITest); + MITK_TEST(ImageAndMultiROITest); + MITK_TEST(MultiMultiTest); + MITK_TEST(InputChangedTest); + MITK_TEST(SettingsChangedTest); + MITK_TEST(DataStorageModificationTest); + CPPUNIT_TEST_SUITE_END(); + + mitk::DataStorage::Pointer m_DataStorage; + mitk::DataNode::Pointer m_ImageNode1; + mitk::DataNode::Pointer m_ImageNode2; + mitk::DataNode::Pointer m_MaskImageNode; + mitk::DataNode::Pointer m_PFNode; + mitk::Image::Pointer m_Image1; + mitk::Image::Pointer m_Image2; + mitk::Image::Pointer m_Mask; + mitk::PlanarFigure::Pointer m_PF; + + QCoreApplication* m_TestApp; + +public: + void setUp() override + { + m_DataStorage = mitk::StandaloneDataStorage::New(); + + m_ImageNode1 = mitk::DataNode::New(); + m_ImageNode1->SetName("Image_1"); + auto pic3DCroppedFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_cropped.nrrd"); + m_Image1 = mitk::IOUtil::Load(pic3DCroppedFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image1.IsNotNull()); + m_ImageNode1->SetData(m_Image1); + m_DataStorage->Add(m_ImageNode1); + + m_ImageNode2 = mitk::DataNode::New(); + m_ImageNode2->SetName("Image_2"); + m_Image2 = mitk::IOUtil::Load(pic3DCroppedFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D_cropped", m_Image2.IsNotNull()); + m_ImageNode2->SetData(m_Image2); + m_DataStorage->Add(m_ImageNode2); + + m_MaskImageNode = mitk::DataNode::New(); + m_MaskImageNode->SetName("Mask"); + auto pic3DCroppedBinMaskFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedBinMask.nrrd"); + m_Mask = mitk::IOUtil::Load(pic3DCroppedBinMaskFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D binary mask", m_Mask.IsNotNull()); + m_MaskImageNode->SetData(m_Mask); + m_DataStorage->Add(m_MaskImageNode); + + m_PFNode = mitk::DataNode::New(); + m_PFNode->SetName("PF"); + auto pic3DCroppedPlanarFigureFile = this->GetTestDataFilePath("ImageStatisticsTestData/Pic3D_croppedPF.pf"); + m_PF = mitk::IOUtil::Load(pic3DCroppedPlanarFigureFile); + CPPUNIT_ASSERT_MESSAGE("Failed loading Pic3D planar figure", m_PF.IsNotNull()); + m_PFNode->SetData(m_PF); + m_DataStorage->Add(m_PFNode); + + int argc = 0; + char** argv = nullptr; + m_TestApp = new QCoreApplication(argc, argv); + } + + void tearDown() override + { + delete m_TestApp; + } + + + bool CheckResultNode(const std::vector resultNodes, const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, unsigned int histBin = 100, bool noZero = false) + { + for (auto& resultNode : resultNodes) + { + bool result = false; + + if (resultNode && resultNode->GetData() && imageNode && imageNode->GetData()) + { + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + result = !imageRule->GetRelationUIDs(resultNode, imageNode).empty(); + + if (roiNode) + { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + result = result && !maskRule->GetRelationUIDs(resultNode, roiNode).empty(); + } + + auto prop = resultNode->GetData()->GetProperty(mitk::STATS_HISTOGRAM_BIN_PROPERTY_NAME.c_str()); + auto binProp = dynamic_cast(prop.GetPointer()); + result = result && binProp->GetValue() == histBin; + + prop = resultNode->GetData()->GetProperty(mitk::STATS_IGNORE_ZERO_VOXEL_PROPERTY_NAME.c_str()); + auto zeroProp = dynamic_cast(prop.GetPointer()); + result = result && zeroProp->GetValue() == noZero; + } + + if (result) + { //node was in the result set + return true; + } + + } + + return false; + } + + void NullTest() + { + TestQmitkImageStatisticsDataGenerator generator(nullptr); + + generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); + + generator.SetDataStorage(m_DataStorage); + generator.Generate(); + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(generator.m_NewDataAvailable.empty()); + } + + void GetterSetterTest() + { + TestQmitkImageStatisticsDataGenerator generator(nullptr); + CPPUNIT_ASSERT(nullptr == generator.GetDataStorage()); + generator.SetDataStorage(m_DataStorage); + CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); + + TestQmitkImageStatisticsDataGenerator generator2(m_DataStorage); + CPPUNIT_ASSERT(m_DataStorage == generator.GetDataStorage()); + + CPPUNIT_ASSERT_EQUAL(100u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetHistogramNBins(3); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetIgnoreZeroValueVoxel(true); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(false, generator.GetAutoUpdate()); + + generator.SetAutoUpdate(true); + CPPUNIT_ASSERT_EQUAL(3u, generator.GetHistogramNBins()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetIgnoreZeroValueVoxel()); + CPPUNIT_ASSERT_EQUAL(true, generator.GetAutoUpdate()); + } + + void OneImageTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable.front()->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void MultiImageTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2u == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void ImageAndROITest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, m_MaskImageNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, m_PFNode)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void ImageAndMultiROITest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front()}, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void MultiMultiTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer(), m_ImageNode2.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer(), m_MaskImageNode.GetPointer(), nullptr }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(6, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(6 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode1, nullptr)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_PFNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, m_MaskImageNode)); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front(), generator.m_NewDataAvailable[1]->front(), + generator.m_NewDataAvailable[2]->front(), generator.m_NewDataAvailable[3]->front(), + generator.m_NewDataAvailable[4]->front(), generator.m_NewDataAvailable[5]->front() }, m_ImageNode2, nullptr)); + + CPPUNIT_ASSERT_MESSAGE("Error: Rerun has generated new data.", generator.Generate()); + } + + void InputChangedTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode2.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(0, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(0 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 1 == generator.m_NewDataAvailable.size()); + + generator.SetAutoUpdate(true); + generator.SetROINodes({ m_MaskImageNode.GetPointer() }); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 2 == generator.m_NewDataAvailable.size()); + } + + void SettingsChangedTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[0]->front() }, m_ImageNode1, nullptr)); + + generator.SetHistogramNBins(50); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[1]->front() }, m_ImageNode1, nullptr, 50)); + + generator.SetIgnoreZeroValueVoxel(true); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(3 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[2]->front() }, m_ImageNode1, nullptr, 50, true)); + + //now check auto update feature + generator.SetAutoUpdate(true); + generator.SetHistogramNBins(5); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 4 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[3]->front() }, m_ImageNode1, nullptr, 5, true)); + + generator.SetHistogramNBins(5); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 4 == generator.m_NewDataAvailable.size()); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 5 == generator.m_NewDataAvailable.size()); + CPPUNIT_ASSERT(CheckResultNode({ generator.m_NewDataAvailable[4]->front() }, m_ImageNode1, nullptr, 5, false)); + + generator.SetIgnoreZeroValueVoxel(false); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but input does not realy changed.", 5 == generator.m_NewDataAvailable.size()); + } + + void DataStorageModificationTest() + { + TestQmitkImageStatisticsDataGenerator generator(m_DataStorage); + + generator.SetImageNodes({ m_ImageNode1.GetPointer() }); + generator.SetROINodes({ m_PFNode.GetPointer() }); + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(1, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(1 == generator.m_NewDataAvailable.size()); + + generator.Generate(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL(2, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL(2, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT(generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT(2 == generator.m_NewDataAvailable.size()); + + //now check auto update feature + generator.SetAutoUpdate(true); + m_PF->Modified(); + m_PFNode->Modified(); + m_TestApp->exec(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update seemed not to work.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update seemed not to work.", 3 == generator.m_NewDataAvailable.size()); + + + m_DataStorage->Add(mitk::DataNode::New()); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); + + m_Image2->Modified(); + m_ImageNode2->Modified(); + m_TestApp->processEvents(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_DataGenerationStartedEmited); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3, generator.m_GenerationFinishedEmited); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", generator.m_JobErrorEmited_error.empty()); + CPPUNIT_ASSERT_MESSAGE("Error: Auto update was triggerd, but only irrelevant node was added.", 3 == generator.m_NewDataAvailable.size()); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(QmitkImageStatisticsDataGenerator) diff --git a/Modules/ImageStatisticsUI/test/files.cmake b/Modules/ImageStatisticsUI/test/files.cmake new file mode 100644 index 0000000000..36d9d98569 --- /dev/null +++ b/Modules/ImageStatisticsUI/test/files.cmake @@ -0,0 +1,3 @@ +set(MODULE_TESTS + QmitkImageStatisticsDataGeneratorTest.cpp +) diff --git a/Modules/QtWidgets/resource/Qmitk.qrc b/Modules/QtWidgets/resource/Qmitk.qrc index 4f12f499d8..788d6fc8cf 100644 --- a/Modules/QtWidgets/resource/Qmitk.qrc +++ b/Modules/QtWidgets/resource/Qmitk.qrc @@ -1,26 +1,27 @@ Binaerbilder_48.png Images_48.png PointSet_48.png Segmentation_48.png Surface_48.png mm_pointer.png mm_scroll.png mm_zoom.png mm_contrast.png mm_pan.png LabelSetImage_48.png mwLayout.png mwSynchronized.png mwDesynchronized.png mwMITK.png mwPACS.png star-solid.svg history-solid.svg tree_inspector.svg list-solid.svg favorite_add.svg favorite_remove.svg + hourglass-half-solid.svg diff --git a/Modules/QtWidgets/resource/hourglass-half-solid.svg b/Modules/QtWidgets/resource/hourglass-half-solid.svg new file mode 100644 index 0000000000..e668867d36 --- /dev/null +++ b/Modules/QtWidgets/resource/hourglass-half-solid.svg @@ -0,0 +1,35 @@ + + diff --git a/Modules/QtWidgets/resource/icon-license.txt b/Modules/QtWidgets/resource/icon-license.txt index 662c801d5f..df5504436b 100644 --- a/Modules/QtWidgets/resource/icon-license.txt +++ b/Modules/QtWidgets/resource/icon-license.txt @@ -1,6 +1,7 @@ See [Font Awsome 4] in Licenses/ICONS.md for: - history-solid.svg based on Font Awsome's history-solid.svg - list-solid.svg based on Font Awsome's list-solid.svg - star-solid.svg based on Font Awsome's star-solid.svg - favorite_add.svg based on Font Awsome's star-solid.svg - favorite_remove.svg based on Font Awsome's star-solid.svg +- hourglass-half-solid.svg based on Font Awsome's hourglass-half-solid.svg diff --git a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp index 45a78fe841..0d86d762d1 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp @@ -1,880 +1,881 @@ /*============================================================================ 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 #include #include #include #include #include #include #include #include #include #include "QmitkDataStorageTreeModel.h" #include "QmitkDataStorageTreeModelInternalItem.h" #include "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include QmitkDataStorageTreeModel::QmitkDataStorageTreeModel(mitk::DataStorage *_DataStorage, bool _PlaceNewNodesOnTop, QObject *parent) : QAbstractItemModel(parent), m_DataStorage(nullptr), m_PlaceNewNodesOnTop(_PlaceNewNodesOnTop), m_Root(nullptr), m_BlockDataStorageEvents(false), m_AllowHierarchyChange(false) { this->SetDataStorage(_DataStorage); } QmitkDataStorageTreeModel::~QmitkDataStorageTreeModel() { // set data storage to 0 = remove all listeners this->SetDataStorage(nullptr); m_Root->Delete(); m_Root = nullptr; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::GetNode(const QModelIndex &index) const { return this->TreeItemFromIndex(index)->GetDataNode(); } const mitk::DataStorage::Pointer QmitkDataStorageTreeModel::GetDataStorage() const { return m_DataStorage.Lock(); } QModelIndex QmitkDataStorageTreeModel::index(int row, int column, const QModelIndex &parent) const { TreeItem *parentItem; if (!parent.isValid()) parentItem = m_Root; else parentItem = static_cast(parent.internalPointer()); TreeItem *childItem = parentItem->GetChild(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } int QmitkDataStorageTreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentTreeItem = this->TreeItemFromIndex(parent); return parentTreeItem->GetChildCount(); } Qt::ItemFlags QmitkDataStorageTreeModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } else { return Qt::ItemIsDropEnabled; } } int QmitkDataStorageTreeModel::columnCount(const QModelIndex & /* parent = QModelIndex() */) const { return 1; } QModelIndex QmitkDataStorageTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = this->TreeItemFromIndex(index); TreeItem *parentItem = childItem->GetParent(); if (parentItem == m_Root) return QModelIndex(); return this->createIndex(parentItem->GetIndex(), 0, parentItem); } QmitkDataStorageTreeModel::TreeItem *QmitkDataStorageTreeModel::TreeItemFromIndex(const QModelIndex &index) const { if (index.isValid()) return static_cast(index.internalPointer()); else return m_Root; } Qt::DropActions QmitkDataStorageTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkDataStorageTreeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } bool QmitkDataStorageTreeModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { // Early exit, returning true, but not actually doing anything (ignoring data). if (action == Qt::IgnoreAction) { return true; } // Note, we are returning true if we handled it, and false otherwise bool returnValue = false; if (data->hasFormat("application/x-qabstractitemmodeldatalist")) { returnValue = true; // First we extract a Qlist of TreeItem* pointers. QList listOfItemsToDrop = ToTreeItemPtrList(data); if (listOfItemsToDrop.empty()) { return false; } // Retrieve the TreeItem* where we are dropping stuff, and its parent. TreeItem *dropItem = this->TreeItemFromIndex(parent); TreeItem *parentItem = dropItem->GetParent(); // If item was dropped onto empty space, we select the root node if (dropItem == m_Root) { parentItem = m_Root; } // Dragging and Dropping is only allowed within the same parent, so use the first item in list to validate. // (otherwise, you could have a derived image such as a segmentation, and assign it to another image). // NOTE: We are assuming the input list is valid... i.e. when it was dragged, all the items had the same parent. // Determine whether or not the drag and drop operation is a valid one. // Examples of invalid operations include: // - dragging nodes with different parents // - dragging nodes from one parent to another parent, if m_AllowHierarchyChange is false // - dragging a node on one of its child nodes (only relevant if m_AllowHierarchyChange is true) bool isValidDragAndDropOperation(true); // different parents { TreeItem *firstParent = listOfItemsToDrop[0]->GetParent(); QList::iterator diIter; for (diIter = listOfItemsToDrop.begin() + 1; diIter != listOfItemsToDrop.end(); diIter++) { if (firstParent != (*diIter)->GetParent()) { isValidDragAndDropOperation = false; break; } } } // dragging from one parent to another if ((!m_AllowHierarchyChange) && isValidDragAndDropOperation) { if (row == -1) // drag onto a node { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == parentItem; } else // drag between nodes { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == dropItem; } } // dragging on a child node of one the dragged nodes { QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *tempItem = dropItem; while (tempItem != m_Root) { tempItem = tempItem->GetParent(); if (tempItem == *diIter) { isValidDragAndDropOperation = false; } } } } if (!isValidDragAndDropOperation) return isValidDragAndDropOperation; if (listOfItemsToDrop[0] != dropItem && isValidDragAndDropOperation) { // Retrieve the index of where we are dropping stuff. QModelIndex parentModelIndex = this->IndexFromTreeItem(parentItem); int dragIndex = 0; // Iterate through the list of TreeItem (which may be at non-consecutive indexes). QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem *itemToDrop = *diIter; // if the item is dragged down we have to compensate its final position for the // fact it is deleted lateron, this only applies if it is dragged within the same level if ((itemToDrop->GetIndex() < row) && (itemToDrop->GetParent() == dropItem)) { dragIndex = 1; } // Here we assume that as you remove items, one at a time, that GetIndex() will be valid. this->beginRemoveRows( this->IndexFromTreeItem(itemToDrop->GetParent()), itemToDrop->GetIndex(), itemToDrop->GetIndex()); itemToDrop->GetParent()->RemoveChild(itemToDrop); this->endRemoveRows(); } // row = -1 dropped on an item, row != -1 dropped in between two items // Select the target index position, or put it at the end of the list. int dropIndex = 0; if (row != -1) { if (dragIndex == 0) dropIndex = std::min(row, parentItem->GetChildCount() - 1); else dropIndex = std::min(row - 1, parentItem->GetChildCount() - 1); } else { dropIndex = dropItem->GetIndex(); } QModelIndex dropItemModelIndex = this->IndexFromTreeItem(dropItem); if ((row == -1 && dropItemModelIndex.row() == -1) || dropItemModelIndex.row() > parentItem->GetChildCount()) dropIndex = parentItem->GetChildCount() - 1; // Now insert items again at the drop item position if (m_AllowHierarchyChange) { this->beginInsertRows(dropItemModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } else { this->beginInsertRows(parentModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { // dropped on node, behaviour depends on preference setting if (m_AllowHierarchyChange) { auto dataStorage = m_DataStorage.Lock(); m_BlockDataStorageEvents = true; mitk::DataNode *droppedNode = (*diIter)->GetDataNode(); mitk::DataNode *dropOntoNode = dropItem->GetDataNode(); dataStorage->Remove(droppedNode); dataStorage->Add(droppedNode, dropOntoNode); m_BlockDataStorageEvents = false; dropItem->InsertChild((*diIter), dropIndex); } else { if (row == -1) // drag onto a node { parentItem->InsertChild((*diIter), dropIndex); } else // drag between nodes { dropItem->InsertChild((*diIter), dropIndex); } } dropIndex++; } this->endInsertRows(); // Change Layers to match. this->AdjustLayerProperty(); } } else if (data->hasFormat("application/x-mitk-datanodes")) { returnValue = true; int numberOfNodesDropped = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode *node = nullptr; foreach (node, dataNodeList) { - if (node && !m_DataStorage.IsExpired() && !m_DataStorage.Lock()->Exists(node)) + auto datastorage = m_DataStorage.Lock(); + if (node && datastorage.IsNotNull() && !datastorage->Exists(node)) { m_DataStorage.Lock()->Add(node); mitk::BaseData::Pointer basedata = node->GetData(); if (basedata.IsNotNull()) { mitk::RenderingManager::GetInstance()->InitializeViews( basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); numberOfNodesDropped++; } } } // Only do a rendering update, if we actually dropped anything. if (numberOfNodesDropped > 0) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return returnValue; } QStringList QmitkDataStorageTreeModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << "application/x-qabstractitemmodeldatalist"; types << "application/x-mitk-datanodes"; return types; } QMimeData *QmitkDataStorageTreeModel::mimeData(const QModelIndexList &indexes) const { return mimeDataFromModelIndexList(indexes); } QMimeData *QmitkDataStorageTreeModel::mimeDataFromModelIndexList(const QModelIndexList &indexes) { QMimeData *ret = new QMimeData; QString treeItemAddresses(""); QString dataNodeAddresses(""); QByteArray baTreeItemPtrs; QByteArray baDataNodePtrs; QDataStream dsTreeItemPtrs(&baTreeItemPtrs, QIODevice::WriteOnly); QDataStream dsDataNodePtrs(&baDataNodePtrs, QIODevice::WriteOnly); for (int i = 0; i < indexes.size(); i++) { TreeItem *treeItem = static_cast(indexes.at(i).internalPointer()); dsTreeItemPtrs << reinterpret_cast(treeItem); dsDataNodePtrs << reinterpret_cast(treeItem->GetDataNode().GetPointer()); // --------------- deprecated ----------------- unsigned long long treeItemAddress = reinterpret_cast(treeItem); unsigned long long dataNodeAddress = reinterpret_cast(treeItem->GetDataNode().GetPointer()); QTextStream(&treeItemAddresses) << treeItemAddress; QTextStream(&dataNodeAddresses) << dataNodeAddress; if (i != indexes.size() - 1) { QTextStream(&treeItemAddresses) << ","; QTextStream(&dataNodeAddresses) << ","; } // -------------- end deprecated ------------- } // ------------------ deprecated ----------------- ret->setData("application/x-qabstractitemmodeldatalist", QByteArray(treeItemAddresses.toLatin1())); ret->setData("application/x-mitk-datanodes", QByteArray(dataNodeAddresses.toLatin1())); // --------------- end deprecated ----------------- ret->setData(QmitkMimeTypes::DataStorageTreeItemPtrs, baTreeItemPtrs); ret->setData(QmitkMimeTypes::DataNodePtrs, baDataNodePtrs); return ret; } QVariant QmitkDataStorageTreeModel::data(const QModelIndex &index, int role) const { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); // get name of treeItem (may also be edited) QString nodeName = QString::fromStdString(dataNode->GetName()); if (nodeName.isEmpty()) { nodeName = "unnamed"; } if (role == Qt::DisplayRole) return nodeName; else if (role == Qt::ToolTipRole) return nodeName; else if (role == Qt::DecorationRole) { QmitkNodeDescriptor *nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(dataNode); } else if (role == Qt::CheckStateRole) { return dataNode->IsVisible(nullptr); } else if (role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if (role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageTreeModel::DicomPropertiesExists(const mitk::DataNode &node) const { bool propertiesExists = false; mitk::BaseProperty *seriesDescription_deprecated = (node.GetProperty("dicom.series.SeriesDescription")); mitk::BaseProperty *studyDescription_deprecated = (node.GetProperty("dicom.study.StudyDescription")); mitk::BaseProperty *patientsName_deprecated = (node.GetProperty("dicom.patient.PatientsName")); mitk::BaseProperty *seriesDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); mitk::BaseProperty *studyDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); mitk::BaseProperty *patientsName = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); if (patientsName != nullptr && studyDescription != nullptr && seriesDescription != nullptr) { if ((!patientsName->GetValueAsString().empty()) && (!studyDescription->GetValueAsString().empty()) && (!seriesDescription->GetValueAsString().empty())) { propertiesExists = true; } } /** Code coveres the deprecated property naming for backwards compatibility */ if (patientsName_deprecated != nullptr && studyDescription_deprecated != nullptr && seriesDescription_deprecated != nullptr) { if ((!patientsName_deprecated->GetValueAsString().empty()) && (!studyDescription_deprecated->GetValueAsString().empty()) && (!seriesDescription_deprecated->GetValueAsString().empty())) { propertiesExists = true; } } return propertiesExists; } QVariant QmitkDataStorageTreeModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && m_Root) return QString::fromStdString(m_Root->GetDataNode()->GetName()); return QVariant(); } void QmitkDataStorageTreeModel::SetDataStorage(mitk::DataStorage *_DataStorage) { if (m_DataStorage != _DataStorage) // dont take the same again { - if (!m_DataStorage.IsExpired()) + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNotNull()) { - auto dataStorage = m_DataStorage.Lock(); - // remove Listener for the data storage itself dataStorage->RemoveObserver(m_DataStorageDeletedTag); // remove listeners for the nodes dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageTreeModel::AddNode)); dataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); } + this->beginResetModel(); + // take over the new data storage m_DataStorage = _DataStorage; // delete the old root (if necessary, create new) if (m_Root) m_Root->Delete(); mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Manager"); m_Root = new TreeItem(rootDataNode, nullptr); - this->beginResetModel(); - this->endResetModel(); - if (!m_DataStorage.IsExpired()) + dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNotNull()) { - auto dataStorage = m_DataStorage.Lock(); - // add Listener for the data storage itself auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkDataStorageTreeModel::SetDataStorageDeleted); m_DataStorageDeletedTag = dataStorage->AddObserver(itk::DeleteEvent(), command); // add listeners for the nodes dataStorage->AddNodeEvent.AddListener(mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::AddNode)); dataStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified)); dataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode)); - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = dataStorage->GetSubset(m_Predicate); - // finally add all nodes to the model this->Update(); } + + this->endResetModel(); } } void QmitkDataStorageTreeModel::SetDataStorageDeleted() { this->SetDataStorage(nullptr); } void QmitkDataStorageTreeModel::AddNodeInternal(const mitk::DataNode *node) { if (node == nullptr || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; // find out if we have a root node TreeItem *parentTreeItem = m_Root; QModelIndex index; mitk::DataNode *parentDataNode = this->GetParentNode(node); if (parentDataNode) // no top level data node { parentTreeItem = m_Root->Find(parentDataNode); // find the corresponding tree item if (!parentTreeItem) { this->AddNode(parentDataNode); parentTreeItem = m_Root->Find(parentDataNode); if (!parentTreeItem) return; } // get the index of this parent with the help of the grand parent index = this->createIndex(parentTreeItem->GetIndex(), 0, parentTreeItem); } // add node if (m_PlaceNewNodesOnTop) { // emit beginInsertRows event beginInsertRows(index, 0, 0); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), 0); } else { int firstRowWithASiblingBelow = 0; int nodeLayer = -1; node->GetIntProperty("layer", nodeLayer); for (TreeItem* siblingTreeItem: parentTreeItem->GetChildren()) { int siblingLayer = -1; if (mitk::DataNode* siblingNode = siblingTreeItem->GetDataNode()) { siblingNode->GetIntProperty("layer", siblingLayer); } if (nodeLayer > siblingLayer) { break; } ++firstRowWithASiblingBelow; } beginInsertRows(index, firstRowWithASiblingBelow, firstRowWithASiblingBelow); parentTreeItem->InsertChild(new TreeItem(const_cast(node)), firstRowWithASiblingBelow); } // emit endInsertRows event endInsertRows(); if(m_PlaceNewNodesOnTop) { this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::AddNode(const mitk::DataNode *node) { if (node == nullptr || m_BlockDataStorageEvents || m_DataStorage.IsExpired() || !m_DataStorage.Lock()->Exists(node) || m_Root->Find(node) != nullptr) return; this->AddNodeInternal(node); } void QmitkDataStorageTreeModel::SetPlaceNewNodesOnTop(bool _PlaceNewNodesOnTop) { m_PlaceNewNodesOnTop = _PlaceNewNodesOnTop; } void QmitkDataStorageTreeModel::RemoveNodeInternal(const mitk::DataNode *node) { if (!m_Root) return; TreeItem *treeItem = m_Root->Find(node); if (!treeItem) return; // return because there is no treeitem containing this node TreeItem *parentTreeItem = treeItem->GetParent(); QModelIndex parentIndex = this->IndexFromTreeItem(parentTreeItem); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(parentIndex, treeItem->GetIndex(), treeItem->GetIndex()); // remove node - std::vector children = treeItem->GetChildren(); + std::vector children = treeItem->GetChildren(); delete treeItem; // emit endRemoveRows event endRemoveRows(); // move all children of deleted node into its parent - for (std::vector::iterator it = children.begin(); it != children.end(); it++) + for (std::vector::iterator it = children.begin(); it != children.end(); it++) { // emit beginInsertRows event beginInsertRows(parentIndex, parentTreeItem->GetChildCount(), parentTreeItem->GetChildCount()); // add nodes again parentTreeItem->AddChild(*it); // emit endInsertRows event endInsertRows(); } this->AdjustLayerProperty(); } void QmitkDataStorageTreeModel::RemoveNode(const mitk::DataNode *node) { if (node == nullptr || m_BlockDataStorageEvents) return; this->RemoveNodeInternal(node); } void QmitkDataStorageTreeModel::SetNodeModified(const mitk::DataNode *node) { TreeItem *treeItem = m_Root->Find(node); if (treeItem) { TreeItem *parentTreeItem = treeItem->GetParent(); // as the root node should not be removed one should always have a parent item if (!parentTreeItem) return; QModelIndex index = this->createIndex(treeItem->GetIndex(), 0, treeItem); // now emit the dataChanged signal emit dataChanged(index, index); } } mitk::DataNode *QmitkDataStorageTreeModel::GetParentNode(const mitk::DataNode *node) const { mitk::DataNode *dataNode = nullptr; mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage.Lock()->GetSources(node); if (_Sources->Size() > 0) dataNode = _Sources->front(); return dataNode; } bool QmitkDataStorageTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { mitk::DataNode *dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if (!dataNode) return false; if (role == Qt::EditRole && !value.toString().isEmpty()) { dataNode->SetStringProperty("name", value.toString().toStdString().c_str()); mitk::PlanarFigure *planarFigure = dynamic_cast(dataNode->GetData()); if (planarFigure != nullptr) mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if (role == Qt::CheckStateRole) { // Please note: value.toInt() returns 2, independentely from the actual checkstate of the index element. // Therefore the checkstate is being estimated again here. QVariant qcheckstate = index.data(Qt::CheckStateRole); int checkstate = qcheckstate.toInt(); bool isVisible = bool(checkstate); dataNode->SetVisibility(!isVisible); emit nodeVisibilityChanged(); } // inform listeners about changes emit dataChanged(index, index); return true; } bool QmitkDataStorageTreeModel::setHeaderData(int /*section*/, Qt::Orientation /*orientation*/, const QVariant & /* value */, int /*role = Qt::EditRole*/) { return false; } void QmitkDataStorageTreeModel::AdjustLayerProperty() { /// transform the tree into an array and set the layer property descending std::vector vec; this->TreeToVector(m_Root, vec); int i = vec.size() - 1; for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) { mitk::DataNode::Pointer dataNode = (*it)->GetDataNode(); bool fixedLayer = false; if (!(dataNode->GetBoolProperty("fixedLayer", fixedLayer) && fixedLayer)) dataNode->SetIntProperty("layer", i); --i; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkDataStorageTreeModel::TreeToVector(TreeItem *parent, std::vector &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); this->TreeToVector(current, vec); vec.push_back(current); } } QModelIndex QmitkDataStorageTreeModel::IndexFromTreeItem(TreeItem *item) const { if (item == m_Root) return QModelIndex(); else return this->createIndex(item->GetIndex(), 0, item); } QList QmitkDataStorageTreeModel::GetNodeSet() const { QList res; if (m_Root) this->TreeToNodeSet(m_Root, res); return res; } void QmitkDataStorageTreeModel::TreeToNodeSet(TreeItem *parent, QList &vec) const { TreeItem *current; for (int i = 0; i < parent->GetChildCount(); ++i) { current = parent->GetChild(i); vec.push_back(current->GetDataNode()); this->TreeToNodeSet(current, vec); } } QModelIndex QmitkDataStorageTreeModel::GetIndex(const mitk::DataNode *node) const { if (m_Root) { TreeItem *item = m_Root->Find(node); if (item) return this->IndexFromTreeItem(item); } return QModelIndex(); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QMimeData *mimeData) { if (mimeData == nullptr || !mimeData->hasFormat(QmitkMimeTypes::DataStorageTreeItemPtrs)) { return QList(); } return ToTreeItemPtrList(mimeData->data(QmitkMimeTypes::DataStorageTreeItemPtrs)); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QByteArray &ba) { QList result; QDataStream ds(ba); while (!ds.atEnd()) { quintptr treeItemPtr; ds >> treeItemPtr; result.push_back(reinterpret_cast(treeItemPtr)); } return result; } void QmitkDataStorageTreeModel::Update() { - if (!m_DataStorage.IsExpired()) + auto datastorage = m_DataStorage.Lock(); + if (datastorage.IsNotNull()) { - mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage.Lock()->GetAll(); + mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = datastorage->GetAll(); /// Regardless the value of this preference, the new nodes must not be inserted /// at the top now, but at the position according to their layer. bool newNodesWereToBePlacedOnTop = m_PlaceNewNodesOnTop; m_PlaceNewNodesOnTop = false; - for (const auto& node: *_NodeSet) + for (const auto& node : *_NodeSet) { this->AddNodeInternal(node); } m_PlaceNewNodesOnTop = newNodesWereToBePlacedOnTop; /// Adjust the layers to ensure that derived nodes are above their sources. this->AdjustLayerProperty(); } } void QmitkDataStorageTreeModel::SetAllowHierarchyChange(bool allowHierarchyChange) { m_AllowHierarchyChange = allowHierarchyChange; } diff --git a/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp index 2b0baa06e0..0caa074875 100644 --- a/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp +++ b/Modules/SemanticRelationsUI/src/QmitkStatisticsCalculator.cpp @@ -1,233 +1,233 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // semantic relations UI module #include "QmitkStatisticsCalculator.h" // semantic relations module #include #include #include #include #include // mitk image statistics module #include #include #include #include QmitkStatisticsCalculator::QmitkStatisticsCalculator() : m_CalculationJob(nullptr) , m_DataStorage(nullptr) , m_MaskVolume(0.0) { m_CalculationJob = new QmitkImageStatisticsCalculationJob(); connect(m_CalculationJob, &QmitkImageStatisticsCalculationJob::finished, this, &QmitkStatisticsCalculator::OnStatisticsCalculationEnds, Qt::QueuedConnection); } QmitkStatisticsCalculator::~QmitkStatisticsCalculator() { if (!m_CalculationJob->isFinished()) { m_CalculationJob->terminate(); m_CalculationJob->wait(); } m_CalculationJob->deleteLater(); } void QmitkStatisticsCalculator::ComputeLesionVolume(mitk::LesionData& lesionData, const mitk::SemanticTypes::CaseID& caseID) { if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); std::vector lesionVolume; mitk::SemanticTypes::Lesion lesion = lesionData.GetLesion(); double volume = 0.0; mitk::SemanticTypes::ControlPointVector controlPoints = mitk::RelationStorage::GetAllControlPointsOfCase(caseID); // sort the vector of control points for the timeline std::sort(controlPoints.begin(), controlPoints.end()); mitk::SemanticTypes::InformationTypeVector informationTypes = mitk::RelationStorage::GetAllInformationTypesOfCase(caseID); for (const auto& informationType : informationTypes) { for (const auto& controlPoint : controlPoints) { mitk::SemanticRelationsDataStorageAccess semanticRelationsDataStorageAccess(dataStorage); mitk::DataNode::Pointer specificImage; mitk::DataNode::Pointer specificSegmentation; try { specificSegmentation = semanticRelationsDataStorageAccess.GetSpecificSegmentation(caseID, controlPoint, informationType, lesion); if (nullptr == specificSegmentation) { volume = 0.0; } else { // get parent node of the specific segmentation node with the node predicate auto parentNodes = dataStorage->GetSources(specificSegmentation, mitk::NodePredicates::GetImagePredicate(), false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { specificImage = it->Value(); } volume = GetSegmentationMaskVolume(specificImage, specificSegmentation); } } catch (mitk::SemanticRelationException&) { volume = 0.0; } lesionVolume.push_back(volume); } } lesionData.SetLesionVolume(lesionVolume); } double QmitkStatisticsCalculator::GetSegmentationMaskVolume(mitk::DataNode::Pointer imageNode, mitk::DataNode::Pointer segmentationNode) { m_MaskVolume = 0.0; if (m_DataStorage.IsExpired()) { return m_MaskVolume; } auto dataStorage = m_DataStorage.Lock(); if (imageNode.IsNull() || segmentationNode.IsNull()) { return m_MaskVolume; } m_ImageNode = imageNode; m_SegmentationNode = segmentationNode; auto image = dynamic_cast(m_ImageNode->GetData()); auto segmentation = dynamic_cast(m_SegmentationNode->GetData()); if (nullptr == image || nullptr == segmentation) { return m_MaskVolume; } // all nodes and images are valid, retrieve statistics - mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation); + mitk::ImageStatisticsContainer::ConstPointer imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, segmentation).GetPointer(); bool imageStatisticsOlderThanInputs = false; if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || (imageStatistics->GetMTime() < segmentation->GetMTime()))) { imageStatisticsOlderThanInputs = true; } // statistics need to be (re)computed if (!imageStatistics || imageStatisticsOlderThanInputs) { m_CalculationJob->Initialize(image, segmentation, nullptr); try { m_CalculationJob->start(); return m_MaskVolume; } catch (const std::exception&) { return m_MaskVolume; } } // use a valid statistics object to get the volume of the image-segmentation pair mitk::ImageStatisticsContainer::ImageStatisticsObject statisticsObject; try { statisticsObject = imageStatistics->GetStatisticsForTimeStep(0); } catch (mitk::Exception&) { return m_MaskVolume; } try { if (statisticsObject.HasStatistic(mitk::ImageStatisticsConstants::VOLUME())) { auto valueVariant = statisticsObject.GetValueNonConverted(mitk::ImageStatisticsConstants::VOLUME()); m_MaskVolume = boost::get(valueVariant); } } catch (mitk::Exception&) { return m_MaskVolume; } return m_MaskVolume; } void QmitkStatisticsCalculator::OnStatisticsCalculationEnds() { // taken from 'QmitkImageStatisticsView' (see measurementtoolbox plugin) if (m_DataStorage.IsExpired()) { return; } auto dataStorage = m_DataStorage.Lock(); if (m_CalculationJob->GetStatisticsUpdateSuccessFlag()) { auto statistic = m_CalculationJob->GetStatisticsData(); auto image = m_CalculationJob->GetStatisticsImage(); mitk::BaseData::ConstPointer mask = nullptr; auto imageRule = mitk::StatisticsToImageRelationRule::New(); imageRule->Connect(statistic, image); if (m_CalculationJob->GetMaskImage()) { auto maskRule = mitk::StatisticsToMaskRelationRule::New(); mask = m_CalculationJob->GetMaskImage(); maskRule->Connect(statistic, mask); } auto imageStatistics = mitk::ImageStatisticsContainerManager::GetImageStatistics(dataStorage, image, mask); // if statistics base data already exist: add to existing node if (nullptr != imageStatistics) { auto allDataNodes = dataStorage->GetAll()->CastToSTLConstContainer(); for (auto node : allDataNodes) { auto nodeData = node->GetData(); if (nullptr != nodeData && nodeData->GetUID() == imageStatistics->GetUID()) { node->SetData(statistic); } } } // statistics base data does not exist: add new node else { auto statisticsNodeName = m_ImageNode->GetName(); if (m_SegmentationNode) { statisticsNodeName += "_" + m_SegmentationNode->GetName(); } statisticsNodeName += "_statistics"; auto statisticsNode = mitk::CreateImageStatisticsNode(statistic, statisticsNodeName); dataStorage->Add(statisticsNode); } } } 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..e1a1c1261f 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,378 @@ /*============================================================================ 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::DataGenerationStarted, + 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 (image->GetDimension() == 4) - { - m_Controls.sliderWidget_histogram->setVisible(true); - unsigned int maxTimestep = image->GetTimeSteps(); - m_Controls.sliderWidget_histogram->setMaximum(maxTimestep - 1); - } - else - { - m_Controls.sliderWidget_histogram->setVisible(false); - } + if (m_selectedImageNodes.size()==1) + { //only supported for one image and roi currently + auto image = dynamic_cast(m_selectedImageNodes.front()->GetData()); - if (nullptr != m_selectedMaskNode) + if (m_selectedPlanarFigure.IsNotNull()) { - 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(true); + + 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, true); - 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.widget_histogram->SetTheme(GetColorTheme()); + m_Controls.widget_histogram->SetHistogram(statistics->GetHistogramForTimeStep(0), label.str()); } + m_Controls.groupBox_histogram->setVisible(statisticsNode.IsNotNull()); } } } -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() +void QmitkImageStatisticsView::OnGenerationStarted(const mitk::DataNode* /*imageNode*/, const mitk::DataNode* /*roiNode*/, const QmitkDataGenerationJobBase* /*job*/) { - m_Controls.widget_histogram->ResetDefault(); - m_Controls.checkBox_ignoreZero->setChecked(false); - m_IgnoreZeroValueVoxel = false; + m_Controls.label_currentlyComputingStatistics->setVisible(true); } -void QmitkImageStatisticsView::OnStatisticsCalculationEnds() +void QmitkImageStatisticsView::OnGenerationFinished() { - auto runnable = qobject_cast(sender()); - if (nullptr == runnable) - { - return; - } + m_Controls.label_currentlyComputingStatistics->setVisible(false); 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); - } - - SetupRelationRules(image, mask, statistic); - - HandleExistingStatistics(image, mask, statistic); - - 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_Controls.widget_statistics->SetHistogramNBins(nbins); + m_DataGenerator->SetHistogramNBins(nbins); + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); } void QmitkImageStatisticsView::OnCheckBoxIgnoreZeroStateChanged(int state) { - m_ForceRecompute = true; - m_IgnoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; - - CalculateOrGetMultiStatistics(); - - m_ForceRecompute = false; + auto ignoreZeroValueVoxel = (state == Qt::Unchecked) ? false : true; + m_Controls.widget_statistics->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); + m_DataGenerator->SetIgnoreZeroValueVoxel(ignoreZeroValueVoxel); + this->UpdateIntensityProfile(); + this->UpdateHistogramWidget(); } 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_selectedImageNodes = imageNodes; + m_selectedMaskNodes = maskNodes; - m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); - - CalculateOrGetMultiStatistics(); - } - - delete dialog; -} - -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 (nullptr != m_selectedPlanarFigure) + { + m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); + m_selectedPlanarFigure = nullptr; + } - if (image.IsNotNull()) - { - statisticsNodeName = image->GetUID(); - } + mitk::PlanarFigure* maskPlanarFigure = nullptr; - if (mask.IsNotNull()) - { - statisticsNodeName += "_" + mask->GetUID(); - } + if (m_selectedMaskNodes.size() == 1) + { //currently we support this only with one ROI selected + maskPlanarFigure = dynamic_cast(m_selectedMaskNodes.front()->GetData()); + } - statisticsNodeName += "_statistics"; + if (nullptr != maskPlanarFigure) + { + m_selectedPlanarFigure = maskPlanarFigure; + ITKCommandType::Pointer changeListener = ITKCommandType::New(); + changeListener->SetCallbackFunction(this, &QmitkImageStatisticsView::UpdateIntensityProfile); + m_PlanarFigureObserverTag = + m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); - return statisticsNodeName; -} + this->UpdateIntensityProfile(); + } -void QmitkImageStatisticsView::SetupRelationRules(mitk::Image::ConstPointer image, - mitk::BaseData::ConstPointer mask, - mitk::ImageStatisticsContainer::Pointer statistic) -{ - auto imageRule = mitk::StatisticsToImageRelationRule::New(); - imageRule->Connect(statistic, image); + m_Controls.widget_statistics->SetImageNodes(m_selectedImageNodes); + m_Controls.widget_statistics->SetMaskNodes(m_selectedMaskNodes); - if (nullptr != mask) - { - auto maskRule = mitk::StatisticsToMaskRelationRule::New(); - maskRule->Connect(statistic, mask); - } -} + m_DataGenerator->SetAutoUpdate(false); + m_DataGenerator->SetImageNodes(m_selectedImageNodes); + m_DataGenerator->SetROINodes(m_selectedMaskNodes); + m_DataGenerator->Generate(); + m_DataGenerator->SetAutoUpdate(true); -mitk::DataNode::Pointer QmitkImageStatisticsView::GetNodeForStatisticsContainer(mitk::ImageStatisticsContainer::ConstPointer container) -{ - if (container.IsNull()) - { - mitkThrow() << "Given container is null!"; + m_Controls.widget_statistics->setEnabled(!m_selectedImageNodes.empty()); } - 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..1d8c754f5c 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,89 @@ /*============================================================================ 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(const mitk::DataNode* imageNode, const mitk::DataNode* roiNode, const QmitkDataGenerationJobBase* job); + 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