diff --git a/Modules/Core/src/DataManagement/mitkPropertyRelationRuleBase.cpp b/Modules/Core/src/DataManagement/mitkPropertyRelationRuleBase.cpp index b9cb50b848..bd0236c60a 100644 --- a/Modules/Core/src/DataManagement/mitkPropertyRelationRuleBase.cpp +++ b/Modules/Core/src/DataManagement/mitkPropertyRelationRuleBase.cpp @@ -1,594 +1,607 @@ /*=================================================================== 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 "mitkPropertyRelationRuleBase.h" #include #include #include #include #include #include #include bool mitk::PropertyRelationRuleBase::IsSourceCandidate(const IPropertyProvider *owner) const { return owner != nullptr; }; bool mitk::PropertyRelationRuleBase::IsDestinationCandidate(const IPropertyProvider *owner) const { return owner != nullptr; }; mitk::PropertyKeyPath mitk::PropertyRelationRuleBase::GetRootKeyPath() { return PropertyKeyPath().AddElement("MITK").AddElement("Relations"); }; mitk::PropertyKeyPath mitk::PropertyRelationRuleBase::GetRuleRootKeyPath() const { return GetRootKeyPath().AddElement(this->GetRuleID()); }; bool mitk::PropertyRelationRuleBase::IsSource(const IPropertyProvider *owner) const { if (!owner) { mitkThrow() << "Error. Passed owner pointer is NULL"; } - auto keys = owner->GetPropertyKeys(); + std::vector keys; + auto sourceCasted = dynamic_cast(owner); + if (sourceCasted) { + auto sourceData = sourceCasted->GetData(); + if (sourceData) { + keys = sourceData->GetPropertyKeys(); + } + else { + keys = sourceCasted->GetPropertyKeys(); + } + } + else { + keys = owner->GetPropertyKeys(); + } auto rootkey = PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath()); for (const auto &key : keys) { if (key.find(rootkey) == 0) { return true; } } return false; }; mitk::PropertyRelationRuleBase::RelationType mitk::PropertyRelationRuleBase::HasRelation( const IPropertyProvider *source, const IPropertyProvider *destination) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } if (!destination) { mitkThrow() << "Error. Passed owner pointer is NULL"; } RelationType result = RelationType::None; if (this->GetInstanceID_IDLayer(source, destination) != NULL_INSTANCE_ID()) { // has relations of type Connected_ID; result = RelationType::Connected_ID; } if (result == RelationType::None) { auto instanceIDs_data = this->GetInstanceID_datalayer(source, destination); if (instanceIDs_data.size() > 1) { MITK_WARN << "Property relation on data level is ambiguous. First relation is used. Instance ID: " << instanceIDs_data.front(); } if (!instanceIDs_data.empty() && instanceIDs_data.front() != NULL_INSTANCE_ID()) { // has relations of type Connected_Data; result = RelationType::Connected_Data; } } if (result == RelationType::None) { if (this->HasImplicitDataRelation(source, destination)) { // has relations of type Connected_Data; result = RelationType::Implicit_Data; } } return result; }; mitk::PropertyRelationRuleBase::RelationUIDVectorType mitk::PropertyRelationRuleBase::GetExistingRelations( const IPropertyProvider *source) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } auto relIDRegExStr = PropertyKeyPathToPropertyRegEx(this->GetRuleRootKeyPath().AddAnyElement().AddElement("relationUID")); auto regEx = std::regex(relIDRegExStr); const auto keys = source->GetPropertyKeys(); RelationUIDVectorType relationUIDs; for (const auto &key : keys) { if (std::regex_match(key, regEx)) { auto idProp = source->GetConstProperty(key); relationUIDs.push_back(idProp->GetValueAsString()); } } return relationUIDs; }; mitk::PropertyRelationRuleBase::RelationUIDType mitk::PropertyRelationRuleBase::GetRelationUID( const IPropertyProvider *source, const IPropertyProvider *destination) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } if (!destination) { mitkThrow() << "Error. Passed destination pointer is NULL"; } RelationUIDType result; auto instanceID = this->GetInstanceID_IDLayer(source, destination); if (instanceID == NULL_INSTANCE_ID()) { auto instanceID_data = this->GetInstanceID_datalayer(source, destination); if (!instanceID_data.empty()) { instanceID = instanceID_data.front(); } if (instanceID_data.size() > 1) { MITK_WARN << "Property relation on data level is ambigious. First relation is used. Instance ID: " << instanceID; } } return this->GetRelationUIDByInstanceID(source, instanceID); }; mitk::PropertyRelationRuleBase::InstanceIDType mitk::PropertyRelationRuleBase::NULL_INSTANCE_ID() { return std::string(); }; mitk::PropertyRelationRuleBase::RelationUIDType mitk::PropertyRelationRuleBase::GetRelationUIDByInstanceID( const IPropertyProvider *source, const InstanceIDType &instanceID) const { RelationUIDType result; if (instanceID != NULL_INSTANCE_ID()) { auto idProp = source->GetConstProperty( PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath().AddElement(instanceID).AddElement("relationUID"))); if (idProp.IsNotNull()) { result = idProp->GetValueAsString(); } } if (result.empty()) { mitkThrowException(NoPropertyRelationException); } return result; }; mitk::PropertyRelationRuleBase::InstanceIDType mitk::PropertyRelationRuleBase::GetInstanceIDByRelationUID( const IPropertyProvider *source, const RelationUIDType &relationUID) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } InstanceIDType result; auto destRegExStr = PropertyKeyPathToPropertyRegEx(this->GetRuleRootKeyPath().AddAnyElement().AddElement("relationUID")); auto regEx = std::regex(destRegExStr); std::smatch instance_matches; auto keys = source->GetPropertyKeys(); for (const auto &key : keys) { if (std::regex_search(key, instance_matches, regEx)) { auto idProp = source->GetConstProperty(key); if (idProp->GetValueAsString() == relationUID) { if (instance_matches.size()>1) { result = instance_matches[1]; break; } } } } return result; }; mitk::PropertyRelationRuleBase::InstanceIDType mitk::PropertyRelationRuleBase::GetInstanceID_IDLayer( const IPropertyProvider *source, const IPropertyProvider *destination) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } if (!destination) { mitkThrow() << "Error. Passed destination pointer is NULL"; } auto identifiable = dynamic_cast(destination); std::string result; if (identifiable) { // check for relations of type Connected_ID; auto destRegExStr = PropertyKeyPathToPropertyRegEx(this->GetRuleRootKeyPath().AddAnyElement().AddElement("destinationUID")); auto regEx = std::regex(destRegExStr); std::smatch instance_matches; auto destUID = identifiable->GetUID(); std::vector keys; auto sourceCasted = dynamic_cast(source); if (sourceCasted) { auto sourceData = sourceCasted->GetData(); if (sourceData) { keys = sourceData->GetPropertyKeys(); } else { keys = sourceCasted->GetPropertyKeys(); } } else { keys = source->GetPropertyKeys(); } for (const auto &key : keys) { if (std::regex_search(key, instance_matches, regEx)) { auto idProp = source->GetConstProperty(key); if (idProp->GetValueAsString() == destUID) { if (instance_matches.size()>1) { result = instance_matches[1]; break; } } } } } return result; }; void mitk::PropertyRelationRuleBase::Connect(IPropertyOwner *source, const IPropertyProvider *destination) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } if (!destination) { mitkThrow() << "Error. Passed destination pointer is NULL"; } InstanceIDType instanceID = this->GetInstanceID_IDLayer(source, destination); bool hasIDlayer = instanceID != NULL_INSTANCE_ID(); auto instanceIDs_data = this->GetInstanceID_datalayer(source, destination); if (instanceIDs_data.size() > 1) { MITK_WARN << "Property relation on data level is ambiguous. First relation is used. Instance ID: " << instanceIDs_data.front(); } bool hasDatalayer = !instanceIDs_data.empty(); if (hasIDlayer && hasDatalayer && instanceID != instanceIDs_data.front()) { mitkThrow() << "Property relation information is in an invalid state. ID and data layer point to different " "relation instances. Rule: " << this->GetRuleID() << "; ID based instance: " << instanceID << "; Data base instance: " << instanceIDs_data.front(); } RelationUIDType relationUID = this->CreateRelationUID(); if (!hasIDlayer) { if (hasDatalayer) { instanceID = instanceIDs_data.front(); } else { instanceID = this->CreateNewRelationInstance(source, relationUID); } } auto relUIDKey = PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath().AddElement(instanceID).AddElement("relationUID")); source->SetProperty(relUIDKey, mitk::StringProperty::New(relationUID)); if (!hasIDlayer) { auto identifiable = dynamic_cast(destination); if (identifiable) { auto destUIDKey = PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath().AddElement(instanceID).AddElement("destinationUID")); source->SetProperty(destUIDKey, mitk::StringProperty::New(identifiable->GetUID())); } } if (!hasDatalayer) { this->Connect_datalayer(source, destination, instanceID); } }; void mitk::PropertyRelationRuleBase::Disconnect(IPropertyOwner *source, const IPropertyProvider *destination) const { try { this->Disconnect(source, this->GetRelationUID(source, destination)); } catch (const NoPropertyRelationException &) { // nothing to do and no real error in context of disconnect. } }; void mitk::PropertyRelationRuleBase::Disconnect(IPropertyOwner *source, RelationUIDType relationUID) const { auto instanceID = this->GetInstanceIDByRelationUID(source, relationUID); if (instanceID != NULL_INSTANCE_ID()) { this->Disconnect_datalayer(source, instanceID); auto instancePrefix = PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath().AddElement(instanceID)); auto keys = source->GetPropertyKeys(); for (const auto &key : keys) { if (key.find(instancePrefix) == 0) { source->RemoveProperty(key); } } } }; mitk::PropertyRelationRuleBase::RelationUIDType mitk::PropertyRelationRuleBase::CreateRelationUID() { UIDGenerator generator; return generator.GetUID(); }; /**This mutex is used to guard mitk::PropertyRelationRuleBase::CreateNewRelationInstance by a class wide mutex to avoid racing conditions in a scenario where rules are used concurrently. It is not in the class interface itself, because it is an implementation detail. */ std::mutex relationCreationLock; mitk::PropertyRelationRuleBase::InstanceIDType mitk::PropertyRelationRuleBase::CreateNewRelationInstance( IPropertyOwner *source, const RelationUIDType &relationUID) const { std::lock_guard guard(relationCreationLock); ////////////////////////////////////// // Get all existing instanc IDs std::vector instanceIDs; InstanceIDType newID = "1"; auto destRegExStr = PropertyKeyPathToPropertyRegEx(this->GetRuleRootKeyPath().AddAnyElement().AddElement("destinationUID")); auto regEx = std::regex(destRegExStr); std::smatch instance_matches; auto keys = source->GetPropertyKeys(); for (const auto &key : keys) { if (std::regex_search(key, instance_matches, regEx)) { if (instance_matches.size()>1) { instanceIDs.push_back(std::stoi(instance_matches[1])); } } } ////////////////////////////////////// // Get new ID std::sort(instanceIDs.begin(), instanceIDs.end()); if (!instanceIDs.empty()) { newID = std::to_string(instanceIDs.back() + 1); } ////////////////////////////////////// // reserve new ID auto relUIDKey = PropertyKeyPathToPropertyName(this->GetRuleRootKeyPath().AddElement(newID).AddElement("relationUID")); source->SetProperty(relUIDKey, mitk::StringProperty::New(relationUID)); return newID; }; itk::LightObject::Pointer mitk::PropertyRelationRuleBase::InternalClone() const { return Superclass::InternalClone(); }; namespace mitk { /** * \brief Predicate used to wrap rule checks. * * \ingroup DataStorage */ class NodePredicateRuleFunction : public NodePredicateBase { public: using FunctionType = std::function; mitkClassMacro(NodePredicateRuleFunction, NodePredicateBase) mitkNewMacro2Param(NodePredicateRuleFunction, const FunctionType &, PropertyRelationRuleBase::ConstPointer) virtual ~NodePredicateRuleFunction() = default; virtual bool CheckNode(const mitk::DataNode *node) const override { if (!node) { return false; } return m_Function(node, m_Rule); }; protected: explicit NodePredicateRuleFunction(const FunctionType &function, PropertyRelationRuleBase::ConstPointer rule) : m_Function(function), m_Rule(rule) { }; FunctionType m_Function; PropertyRelationRuleBase::ConstPointer m_Rule; }; } // namespace mitk mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetSourceCandidateIndicator() const { auto check = [](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { return rule->IsSourceCandidate(node); }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetDestinationCandidateIndicator() const { auto check = [](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { return rule->IsDestinationCandidate(node); }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetConnectedSourcesDetector() const { auto check = [](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { return rule->IsSource(node); }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetSourcesDetector( const IPropertyProvider *destination, RelationType minimalRelation) const { if (!destination) { mitkThrow() << "Error. Passed destination pointer is NULL"; } auto check = [destination, minimalRelation](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { return rule->HasRelation(node, destination) >= minimalRelation; }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetDestinationsDetector( const IPropertyProvider *source, RelationType minimalRelation) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } auto check = [source, minimalRelation](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { return rule->HasRelation(source, node) >= minimalRelation; }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; mitk::NodePredicateBase::ConstPointer mitk::PropertyRelationRuleBase::GetDestinationDetector( const IPropertyProvider *source, RelationUIDType relationUID) const { if (!source) { mitkThrow() << "Error. Passed source pointer is NULL"; } auto relUIDs = this->GetExistingRelations(source); if (std::find(relUIDs.begin(), relUIDs.end(), relationUID) == relUIDs.end()) { mitkThrow() << "Error. Passed relationUID does not identify a relation instance of the passed source for this rule instance."; }; auto check = [source, relationUID](const mitk::IPropertyProvider *node, const mitk::PropertyRelationRuleBase *rule) { try { return rule->GetRelationUID(source, node) == relationUID; } catch(const NoPropertyRelationException &) { return false; } }; return NodePredicateRuleFunction::New(check, this).GetPointer(); }; diff --git a/Modules/ImageStatistics/Testing/files.cmake b/Modules/ImageStatistics/Testing/files.cmake index 41431d2af0..2be45c6a95 100644 --- a/Modules/ImageStatistics/Testing/files.cmake +++ b/Modules/ImageStatistics/Testing/files.cmake @@ -1,11 +1,12 @@ set(MODULE_TESTS mitkImageStatisticsCalculatorTest.cpp mitkPointSetStatisticsCalculatorTest.cpp mitkPointSetDifferenceStatisticsCalculatorTest.cpp mitkImageStatisticsTextureAnalysisTest.cpp + mitkImageStatisticsContainerManagerTest.cpp ) set(MODULE_CUSTOM_TESTS mitkImageStatisticsHotspotTest.cpp # mitkMultiGaussianTest.cpp # TODO: activate test to generate new test cases for mitkImageStatisticsHotspotTest ) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp new file mode 100644 index 0000000000..e325e09221 --- /dev/null +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsContainerManagerTest.cpp @@ -0,0 +1,317 @@ +/*=================================================================== +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. +===================================================================*/ +// Testing +#include "mitkTestingMacros.h" +#include "mitkTestFixture.h" + +//MITK includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class mitkImageStatisticsContainerManagerTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkImageStatisticsContainerManagerTestSuite); + MITK_TEST(SetDataStorageNull); + MITK_TEST(SetDataStorage); + MITK_TEST(GetImageStatisticsNoRules); + MITK_TEST(GetImageStatisticsWithImageConnected); + MITK_TEST(GetImageStatisticsWithImageNotConnected); + MITK_TEST(GetImageStatisticsWithImageAndMaskConnected); + MITK_TEST(GetImageStatisticsWithImageAndMaskNotConnected); + MITK_TEST(GetImageStatisticsInvalid); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::ImageStatisticsContainerManager::Pointer m_manager; + mitk::StatisticsContainer::Pointer m_statisticsContainer, m_statisticsContainer2, m_statisticsContainer3; + mitk::Image::Pointer m_image, m_image2; + mitk::Image::Pointer m_mask, m_mask2; + mitk::PlanarFigure::Pointer m_planarFigure, m_planarFigure2; + +public: + void setUp() override + { + m_manager = mitk::ImageStatisticsContainerManager::New(); + m_statisticsContainer = mitk::StatisticsContainer::New(); + m_statisticsContainer2 = mitk::StatisticsContainer::New(); + m_statisticsContainer3 = mitk::StatisticsContainer::New(); + m_image = mitk::Image::New(); + m_image2 = mitk::Image::New(); + m_mask = mitk::Image::New(); + m_mask2 = mitk::Image::New(); + m_planarFigure = mitk::PlanarCircle::New().GetPointer(); + m_planarFigure2 = mitk::PlanarCircle::New().GetPointer(); + } + + void tearDown() override + { + } + + void SetDataStorageNull() + { + CPPUNIT_ASSERT_THROW(m_manager->SetDataStorage(nullptr), mitk::Exception); + } + + void SetDataStorage() + { + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + CPPUNIT_ASSERT_NO_THROW(m_manager->SetDataStorage(standaloneDataStorage.GetPointer())); + } + + mitk::StatisticsToImageRelationRule::Pointer CreateNodeRelationImage(mitk::BaseData::Pointer statistics, mitk::BaseData::ConstPointer image) + { + auto rule = mitk::StatisticsToImageRelationRule::New(); + rule->Connect(statistics, image); + return rule; + } + + mitk::StatisticsToMaskRelationRule::Pointer CreateNodeRelationMask(mitk::BaseData::Pointer statistics, mitk::BaseData::ConstPointer image) + { + auto rule = mitk::StatisticsToMaskRelationRule::New(); + rule->Connect(statistics, image); + return rule; + } + + void GetImageStatisticsNoRules() { + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + + //no rules + 1 image --> test return nullptr + mitk::StatisticsContainer::ConstPointer emptyStatistic; + CPPUNIT_ASSERT_NO_THROW(emptyStatistic = m_manager->GetImageStatistics(m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); + + //no rules + 1 image + 1 mask --> test return nullptr + CPPUNIT_ASSERT_NO_THROW(emptyStatistic = m_manager->GetImageStatistics(m_image.GetPointer(), m_mask.GetPointer())); + CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); + + //no rules + 1 image + 1 planarFigure --> test return nullptr + CPPUNIT_ASSERT_NO_THROW(emptyStatistic = m_manager->GetImageStatistics(m_image.GetPointer(), m_planarFigure.GetPointer())); + CPPUNIT_ASSERT_EQUAL(emptyStatistic.IsNull(), true); + } + + void GetImageStatisticsWithImageConnected() + { + //create rules connection + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + mitk::PropertyRelations::RuleResultVectorType rules; + auto imageRule = CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + rules.push_back(imageRule.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + + //rule: (image-->statistics), 1 connected image --> test return image statistics + m_manager->SetRules({ rules }); + mitk::StatisticsContainer::ConstPointer statisticsWithImage; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = m_manager->GetImageStatistics(m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImage->GetUID(), m_statisticsContainer->GetUID()); + + //new rule: (image-->statistics2 AND mask --> statistics2) + mitk::PropertyRelations::RuleResultVectorType rules2; + imageRule = CreateNodeRelationImage(m_statisticsContainer2.GetPointer(), m_image.GetPointer()); + auto imageMaskRule = CreateNodeRelationMask(m_statisticsContainer2.GetPointer(), m_mask.GetPointer()); + rules2.push_back(imageRule.GetPointer()); + rules2.push_back(imageMaskRule.GetPointer()); + m_manager->SetRules({ rules2, rules }); + + auto statisticsNode2 = mitk::CreateImageStatisticsNode(m_statisticsContainer2, "testStatistics2"); + standaloneDataStorage->Add(statisticsNode2); + + //--> test return (still) image statistics (!= statistics2) + mitk::StatisticsContainer::ConstPointer statisticsWithImageAgain; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAgain = m_manager->GetImageStatistics(m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID(), m_statisticsContainer->GetUID()); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID()!= m_statisticsContainer2->GetUID(), true); + + //new rule: (image-->statistics3 AND planarFigure --> statistics3) + mitk::PropertyRelations::RuleResultVectorType rules3; + imageRule = CreateNodeRelationImage(m_statisticsContainer3.GetPointer(), m_image.GetPointer()); + auto imagePlanarFigureRule = CreateNodeRelationMask(m_statisticsContainer3.GetPointer(), m_planarFigure.GetPointer()); + rules3.push_back(imageRule.GetPointer()); + rules3.push_back(imagePlanarFigureRule.GetPointer()); + m_manager->SetRules({ rules3, rules2, rules }); + + auto statisticsNode3 = mitk::CreateImageStatisticsNode(m_statisticsContainer3, "testStatistics3"); + standaloneDataStorage->Add(statisticsNode3); + + //--> test return (still) image statistics (!= statistics3) + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAgain = m_manager->GetImageStatistics(m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID(), m_statisticsContainer->GetUID()); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAgain->GetUID() != m_statisticsContainer3->GetUID(), true); + + //add another newer statistic: should return this newer one + //CAUTION: MTime of statisticsContainerNew is not always newer than m_statisticsContainer + auto statisticsContainerNew = mitk::StatisticsContainer::New(); + mitk::PropertyRelations::RuleResultVectorType rules4; + auto newImageRule = CreateNodeRelationImage(statisticsContainerNew.GetPointer(), m_image.GetPointer()); + rules4.push_back(newImageRule.GetPointer()); + m_manager->SetRules({ rules2, rules, rules4 }); + + auto statisticsNodeNew = mitk::CreateImageStatisticsNode(statisticsContainerNew, "testStatisticsNew"); + standaloneDataStorage->Add(statisticsNodeNew); + + mitk::StatisticsContainer::ConstPointer statisticsWithImageNew; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageNew = m_manager->GetImageStatistics(m_image.GetPointer())); + /* + CPPUNIT_ASSERT_EQUAL(statisticsWithImageNew->GetUID(), statisticsContainerNew->GetUID()); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageNew->GetUID() != m_statisticsContainer->GetUID(), true);*/ + } + + void GetImageStatisticsWithImageNotConnected() { + //create rules connection + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + mitk::PropertyRelations::RuleResultVectorType rules; + auto imageRule = CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + rules.push_back(imageRule.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + m_manager->SetRules({ rules }); + + //rule: (image-->statistics), 1 unconnected image --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImage; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = m_manager->GetImageStatistics(m_image2.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImage.IsNull(), true); + + //rule: (image-->statistics), 1 connected image + 1 unconnected mask --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndMask; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMask = m_manager->GetImageStatistics(m_image.GetPointer(), m_mask.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask.IsNull(), true); + + //rule: (image-->statistics), 1 connected image + 1 unconnected planar figure --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndPlanarFigure; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigure = m_manager->GetImageStatistics(m_image.GetPointer(), m_planarFigure.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigure.IsNull(), true); + } + + void GetImageStatisticsWithImageAndMaskConnected() + { + //create rules connection + add statistics to dataStorage + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + + mitk::PropertyRelations::RuleResultVectorType rules; + auto imageRule = CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + auto maskRule = CreateNodeRelationMask(m_statisticsContainer.GetPointer(), m_mask.GetPointer()); + rules.push_back(imageRule.GetPointer()); + rules.push_back(maskRule.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + m_manager->SetRules({ rules }); + + //rule: (image-->statistics, mask-->statistics), 1 connected image, 1 connected mask --> test return statistics + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndMask; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMask = m_manager->GetImageStatistics(m_image.GetPointer(), m_mask.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask.IsNull(),false); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMask->GetUID(), m_statisticsContainer->GetUID()); + + //new rule: (image-->statistics2) + mitk::PropertyRelations::RuleResultVectorType rules2; + imageRule = CreateNodeRelationImage(m_statisticsContainer2.GetPointer(), m_image.GetPointer()); + rules2.push_back(imageRule.GetPointer()); + m_manager->SetRules({ rules2, rules }); + + auto statisticsNode2 = mitk::CreateImageStatisticsNode(m_statisticsContainer2, "testStatistics2"); + standaloneDataStorage->Add(statisticsNode2); + + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndMaskAgain; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskAgain = m_manager->GetImageStatistics(m_image.GetPointer(), m_mask.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskAgain->GetUID(), m_statisticsContainer->GetUID()); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskAgain->GetUID() != m_statisticsContainer2->GetUID(), true); + + //add another newer statistic: should return this newer one + //CAUTION: MTime of statisticsContainerNew is not always newer than m_statisticsContainer + auto statisticsContainerNew = mitk::StatisticsContainer::New(); + + mitk::PropertyRelations::RuleResultVectorType rules4; + auto newImageRule = CreateNodeRelationImage(statisticsContainerNew.GetPointer(), m_image.GetPointer()); + auto newMaskRule = CreateNodeRelationMask(statisticsContainerNew.GetPointer(), m_mask.GetPointer()); + rules4.push_back(newImageRule.GetPointer()); + rules4.push_back(newMaskRule.GetPointer()); + m_manager->SetRules({ rules2, rules, rules4 }); + auto statisticsNodeNew = mitk::CreateImageStatisticsNode(statisticsContainerNew, "testStatisticsNew"); + standaloneDataStorage->Add(statisticsNodeNew); + + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndMaskNew; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskNew = m_manager->GetImageStatistics(m_image.GetPointer(), m_mask.GetPointer())); + /*CPPUNIT_ASSERT_EQUAL(statisticsContainerNew->GetUID(), statisticsWithImageAndMaskNew->GetUID()); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskNew->GetUID() != m_statisticsContainer->GetUID(), true);*/ + } + + void GetImageStatisticsWithImageAndMaskNotConnected() + { + //create rules connection + add statistics to dataStorage + auto statisticsNode = mitk::CreateImageStatisticsNode(m_statisticsContainer, "testStatistics"); + mitk::PropertyRelations::RuleResultVectorType rules; + auto imageRule = CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + auto maskRule = CreateNodeRelationMask(m_statisticsContainer.GetPointer(), m_mask.GetPointer()); + rules.push_back(imageRule.GetPointer()); + rules.push_back(maskRule.GetPointer()); + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + standaloneDataStorage->Add(statisticsNode); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + + //rule: (image-->statistics, mask-->statistics), 1 connected image --> test return nullptr + m_manager->SetRules({ rules }); + mitk::StatisticsContainer::ConstPointer statisticsWithImage; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImage = m_manager->GetImageStatistics(m_image.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImage.IsNull(), true); + + //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 unconnected mask --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageNotConnectedAndMaskNotConnected; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageNotConnectedAndMaskNotConnected = m_manager->GetImageStatistics(m_image2.GetPointer(), m_mask2.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageNotConnectedAndMaskNotConnected.IsNull(), true); + + //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 connected mask --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndMaskNotConnected; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndMaskNotConnected = m_manager->GetImageStatistics(m_image2.GetPointer(), m_mask.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndMaskNotConnected.IsNull(), true); + + //rule: (image-->statistics, mask-->statistics), 1 connected image, 1 unconnected planarFigure --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageAndPlanarFigureNotConnected; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigureNotConnected = m_manager->GetImageStatistics(m_image.GetPointer(), m_planarFigure.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigureNotConnected.IsNull(), true); + + //rule: (image-->statistics, mask-->statistics), 1 unconnected image, 1 unconnected planarFigure --> test return nullptr + mitk::StatisticsContainer::ConstPointer statisticsWithImageNotConnectedAndPlanarFigureNotConnected; + CPPUNIT_ASSERT_NO_THROW(statisticsWithImageAndPlanarFigureNotConnected = m_manager->GetImageStatistics(m_image2.GetPointer(), m_planarFigure.GetPointer())); + CPPUNIT_ASSERT_EQUAL(statisticsWithImageAndPlanarFigureNotConnected.IsNull(), true); + } + + void GetImageStatisticsInvalid() + { + mitk::PropertyRelations::RuleResultVectorType rules; + auto imageRule = CreateNodeRelationImage(m_statisticsContainer.GetPointer(), m_image.GetPointer()); + rules.push_back(imageRule.GetPointer()); + m_manager->SetRules({ rules }); + + CPPUNIT_ASSERT_THROW(m_manager->GetImageStatistics(m_image.GetPointer()), mitk::Exception); + + auto standaloneDataStorage = mitk::StandaloneDataStorage::New(); + m_manager->SetDataStorage(standaloneDataStorage.GetPointer()); + + CPPUNIT_ASSERT_THROW(m_manager->GetImageStatistics(nullptr), mitk::Exception); + CPPUNIT_ASSERT_THROW(m_manager->GetImageStatistics(nullptr, m_mask.GetPointer()), mitk::Exception); + CPPUNIT_ASSERT_THROW(m_manager->GetImageStatistics(nullptr, m_planarFigure.GetPointer()), mitk::Exception); + } + +}; +MITK_TEST_SUITE_REGISTRATION(mitkImageStatisticsContainerManager) \ No newline at end of file diff --git a/Modules/ImageStatistics/files.cmake b/Modules/ImageStatistics/files.cmake index c6b003ec03..a19d564a8d 100644 --- a/Modules/ImageStatistics/files.cmake +++ b/Modules/ImageStatistics/files.cmake @@ -1,39 +1,45 @@ set(CPP_FILES mitkImageStatisticsCalculator.cpp mitkImageStatisticsContainer.cpp mitkPointSetStatisticsCalculator.cpp mitkPointSetDifferenceStatisticsCalculator.cpp mitkIntensityProfile.cpp mitkHotspotMaskGenerator.cpp mitkMaskGenerator.cpp mitkPlanarFigureMaskGenerator.cpp mitkMultiLabelMaskGenerator.cpp mitkImageMaskGenerator.cpp mitkHistogramStatisticsCalculator.cpp mitkMaskUtilities.cpp mitkIgnorePixelMaskGenerator.cpp mitkImageStatisticsPredicateHelper.cpp mitkImageStatisticsContainerNodeHelper.cpp + mitkImageStatisticsContainerManager.cpp + mitkStatisticsToImageRelationRule.cpp + mitkStatisticsToMaskRelationRule.cpp ) set(H_FILES mitkImageStatisticsCalculator.h mitkImageStatisticsContainer.h mitkPointSetDifferenceStatisticsCalculator.h mitkPointSetStatisticsCalculator.h mitkExtendedStatisticsImageFilter.h mitkExtendedLabelStatisticsImageFilter.h mitkHotspotMaskGenerator.h mitkMaskGenerator.h mitkPlanarFigureMaskGenerator.h mitkMultiLabelMaskGenerator.h mitkImageMaskGenerator.h mitkHistogramStatisticsCalculator.h mitkMaskUtilities.h mitkitkMaskImageFilter.h mitkIgnorePixelMaskGenerator.h mitkMinMaxImageFilterWithIndex.h mitkMinMaxLabelmageFilterWithIndex.h mitkImageStatisticsPredicateHelper.h mitkImageStatisticsContainerNodeHelper.h + mitkImageStatisticsContainerManager.h + mitkStatisticsToImageRelationRule.h + mitkStatisticsToMaskRelationRule.h ) diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp new file mode 100644 index 0000000000..fec711d394 --- /dev/null +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.cpp @@ -0,0 +1,163 @@ +/*=================================================================== + +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 "mitkImageStatisticsContainerManager.h" + +#include "mitkNodePredicateAnd.h" +#include "mitkNodePredicateOr.h" +#include "mitkNodePredicateDataType.h" + +mitk::ImageStatisticsContainerManager::ImageStatisticsContainerManager() +{ +} + +void mitk::ImageStatisticsContainerManager::SetDataStorage(mitk::DataStorage::Pointer dataStorage) +{ + if (!dataStorage) { + mitkThrow() << "dataStorage is nullptr"; + } + m_DataStorage = dataStorage; +} + +void mitk::ImageStatisticsContainerManager::SetRules(const std::vector& rules) +{ + m_ImageStatisticRules = rules; +} + +mitk::StatisticsContainer::ConstPointer mitk::ImageStatisticsContainerManager::GetImageStatistics(mitk::BaseData::ConstPointer image, mitk::BaseData::ConstPointer mask) const +{ + if (m_ImageStatisticRules.empty()) { + return nullptr; + } + + if (!m_DataStorage) { + mitkThrow() << "data storage is nullptr!"; + } + + mitk::NodePredicateBase::ConstPointer allPredicates = nullptr; + + for (const auto& imageStatisticOneRule : m_ImageStatisticRules) { + auto currentPredicate = GetPredicateForSources(imageStatisticOneRule, image, mask); + if (currentPredicate) { + if (!allPredicates) { + allPredicates = currentPredicate; + } + else { + allPredicates = mitk::NodePredicateOr::New(allPredicates, currentPredicate); + } + } + } + + if (allPredicates) { + auto nodePredicateImageStatisticsContainer = mitk::NodePredicateDataType::New("StatisticsContainer"); + allPredicates = mitk::NodePredicateAnd::New(allPredicates, nodePredicateImageStatisticsContainer); + + auto statisticContainerCandidateNodes = m_DataStorage->GetSubset(allPredicates); + mitk::DataStorage::SetOfObjects::Pointer statisticContainerCandidateNodesFiltered; + + statisticContainerCandidateNodesFiltered = mitk::DataStorage::SetOfObjects::New(); + for (const auto& node : *statisticContainerCandidateNodes) { + auto nodeData = node->GetData(); + bool ok = true; + //special case: we are looking for statistics --> image but might have statistics --> image AND statistics --> mask rule + if (!mask) { + if (nodeData) { + for (const auto& imageStatisticOneRule : m_ImageStatisticRules) { + for (const auto& aRule : imageStatisticOneRule) { + if (aRule->GetRuleID() == "IDRelation_statisticsToMask") { + auto isSource = aRule->IsSource(nodeData); + if (isSource) { + ok = false; + } + } + } + } + } + } + + if (ok) { + statisticContainerCandidateNodesFiltered->push_back(node); + } + } + + if (statisticContainerCandidateNodesFiltered->empty()) { + return nullptr; + } + if (statisticContainerCandidateNodesFiltered->size() > 1) { + //in case of multiple found statistics, return only newest one + std::sort(statisticContainerCandidateNodesFiltered->begin(), statisticContainerCandidateNodesFiltered->end(), [](mitk::DataNode::Pointer a, mitk::DataNode::Pointer b) { + return a->GetData()->GetMTime() > b->GetData()->GetMTime(); + }); + MITK_WARN << "multiple statistics (" << statisticContainerCandidateNodesFiltered->size() << ") for image/mask found. Returning only newest one."; + for (const auto& node : *statisticContainerCandidateNodesFiltered) { + MITK_DEBUG << node->GetName() << ", timestamp: " << node->GetData()->GetMTime(); + } + } + return dynamic_cast(statisticContainerCandidateNodesFiltered->front()->GetData()); + } + else { + return nullptr; + } +} + +mitk::NodePredicateBase::ConstPointer mitk::ImageStatisticsContainerManager::GetPredicateForSources(const mitk::PropertyRelations::RuleResultVectorType& rules, mitk::BaseData::ConstPointer image, mitk::BaseData::ConstPointer mask) const +{ + if (image.IsNull()) { + mitkThrow() << "Image is nullptr"; + } + + if (rules.empty()) { + return nullptr; + } + + //only image as input, but !=1 rules: can't find any imageStatistic object + if (!mask && rules.size() != 1) { + return nullptr; + } + //image+(mask || planarFigure) as input, but !=2 rules: can't find any imageStatistic object + if ((mask) && rules.size() != 2) { + return nullptr; + } + + mitk::NodePredicateBase::ConstPointer statisticsFromImageAndMaskAndPlanarFigure = nullptr; + + for (const auto& rule : rules) { + //choose correct rule for image, mask or planarFigure + if (rule->GetRuleID() == "IDRelation_statisticsToImage") { + auto statisticsFromImagePredicate = rule->GetSourcesDetector(image); + if (!statisticsFromImageAndMaskAndPlanarFigure) { + statisticsFromImageAndMaskAndPlanarFigure = statisticsFromImagePredicate; + } + else { + statisticsFromImageAndMaskAndPlanarFigure = mitk::NodePredicateAnd::New(statisticsFromImagePredicate, statisticsFromImageAndMaskAndPlanarFigure); + } + } + else if (mask && rule->GetRuleID() == "IDRelation_statisticsToMask") { + auto statisticsFromMaskPredicate = rule->GetSourcesDetector(mask); + if (!statisticsFromImageAndMaskAndPlanarFigure) { + statisticsFromImageAndMaskAndPlanarFigure = statisticsFromMaskPredicate; + } + else { + statisticsFromImageAndMaskAndPlanarFigure = mitk::NodePredicateAnd::New(statisticsFromMaskPredicate, statisticsFromImageAndMaskAndPlanarFigure); + } + } + else { + return nullptr; + } + } + + return statisticsFromImageAndMaskAndPlanarFigure; +} diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h new file mode 100644 index 0000000000..c3f9687e9b --- /dev/null +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerManager.h @@ -0,0 +1,70 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#ifndef QmitkImageStatisticsContainerManager_H__INCLUDED +#define QmitkImageStatisticsContainerManager_H__INCLUDED + +#include "MitkImageStatisticsExports.h" + +#include +#include +#include +#include +#include +#include + +/** +\brief Returns the StatisticsContainer that was computed on given input (image/mask/planar figure) and is added as DataNode in a DataStorage + +*/ +namespace mitk +{ + class MITKIMAGESTATISTICS_EXPORT ImageStatisticsContainerManager : public itk::Object + { + public: + mitkClassMacroItkParent(ImageStatisticsContainerManager, itk::Object) + itkNewMacro(Self) + + /**Documentation + @brief Set the data storage + @pre data storage must be valid + */ + void SetDataStorage(mitk::DataStorage::Pointer dataStorage); + + /**Documentation + @brief Set the rules for the relation statisticsContainer to image and mask + @details The RuleResultVectorType defines rules for one statisticsContainer to an image and [optionally + a mask]*/ + void SetRules(const std::vector& rules); + + /**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 + @details if more than one StatisticsContainer is found, only the newest (ModifiedTime) is returned + @exception if DataStorage is nullptr + */ + mitk::StatisticsContainer::ConstPointer GetImageStatistics(mitk::BaseData::ConstPointer image, mitk::BaseData::ConstPointer mask=nullptr) const; + + protected: + ImageStatisticsContainerManager(); + + private: + mitk::NodePredicateBase::ConstPointer GetPredicateForSources(const mitk::PropertyRelations::RuleResultVectorType& rules, mitk::BaseData::ConstPointer image, mitk::BaseData::ConstPointer mask = nullptr) const; + + mitk::DataStorage::Pointer m_DataStorage = nullptr; + std::vector m_ImageStatisticRules; + }; +} +#endif diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.cpp b/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.cpp index 16a0597fa8..42afdedf47 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.cpp @@ -1,52 +1,29 @@ /*=================================================================== 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 "mitkImageStatisticsContainerNodeHelper.h" #include -#include "mitkPropertyRelations.h" -#include "mitkGenericIDRelationRule.h" namespace mitk { DataNode::Pointer CreateImageStatisticsNode(StatisticsContainer::Pointer statistic, const std::string& name) { auto statisticsNode = mitk::DataNode::New(); statisticsNode->SetName(name); statisticsNode->SetData(statistic); statisticsNode->SetProperty("helper object", mitk::BoolProperty::New(true)); return statisticsNode; } - - void CreateNodeRelations(mitk::BaseData::Pointer statistics, mitk::BaseData::Pointer image, mitk::BaseData::Pointer mask, mitk::BaseData::Pointer planarFigure) - { - using StatisticsImageRule = mitk::GenericIDRelationRule; - using StatisticsMaskImageRule = mitk::GenericIDRelationRule; - using StatisticsPlanarFigureRule = mitk::GenericIDRelationRule; - - auto ruleImage = StatisticsImageRule::New("ruleStatisticsToImage"); - ruleImage->Connect(statistics, image); - - if (mask) { - auto ruleMask = StatisticsMaskImageRule::New("ruleStatisticsToMask"); - ruleMask->Connect(statistics, mask); - } - if (planarFigure) { - auto rulePlanarFigure = StatisticsPlanarFigureRule::New("ruleStatisticsToPlanarFigure"); - rulePlanarFigure->Connect(statistics, planarFigure); - } - } - - } diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h b/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h index 0979c303c5..7a5551332a 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h +++ b/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h @@ -1,31 +1,30 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKIMAGESTATISTICSCONTAINERNODEHELPER #define MITKIMAGESTATISTICSCONTAINERNODEHELPER #include #include #include namespace mitk { MITKIMAGESTATISTICS_EXPORT DataNode::Pointer CreateImageStatisticsNode(StatisticsContainer::Pointer statistic, const std::string& name = "statistics"); - MITKIMAGESTATISTICS_EXPORT void CreateNodeRelations(mitk::BaseData::Pointer statistics, mitk::BaseData::Pointer image, mitk::BaseData::Pointer mask, mitk::BaseData::Pointer planarFigure); } #endif // MITKIMAGESTATISTICSCONTAINERNODEHELPER diff --git a/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.cpp b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.cpp new file mode 100644 index 0000000000..97969d183e --- /dev/null +++ b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.cpp @@ -0,0 +1,21 @@ +/*=================================================================== + +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 "mitkStatisticsToImageRelationRule.h" + +mitk::StatisticsToImageRelationRule::StatisticsToImageRelationRule() : + GenericIDRelationRule("statisticsToImage", "relation between ImageStatisticsContainer and Image that was used as computation input", "ImageStatisticsContainer", "Image"){ +} diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h similarity index 50% copy from Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h copy to Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h index 0979c303c5..20325c7e3f 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h +++ b/Modules/ImageStatistics/mitkStatisticsToImageRelationRule.h @@ -1,31 +1,36 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#ifndef MITKIMAGESTATISTICSCONTAINERNODEHELPER -#define MITKIMAGESTATISTICSCONTAINERNODEHELPER +#ifndef mitkStatisticsToImageRelationRule_h +#define mitkStatisticsToImageRelationRule_h #include - -#include -#include +#include "mitkGenericIDRelationRule.h" namespace mitk { - MITKIMAGESTATISTICS_EXPORT DataNode::Pointer CreateImageStatisticsNode(StatisticsContainer::Pointer statistic, const std::string& name = "statistics"); - MITKIMAGESTATISTICS_EXPORT void CreateNodeRelations(mitk::BaseData::Pointer statistics, mitk::BaseData::Pointer image, mitk::BaseData::Pointer mask, mitk::BaseData::Pointer planarFigure); + class MITKIMAGESTATISTICS_EXPORT StatisticsToImageRelationRule : public mitk::GenericIDRelationRule + { + public: + mitkClassMacroItkParent(StatisticsToImageRelationRule, mitk::GenericIDRelationRule) + itkNewMacro(Self) + + protected: + StatisticsToImageRelationRule(); + }; } -#endif // MITKIMAGESTATISTICSCONTAINERNODEHELPER +#endif diff --git a/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.cpp b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.cpp new file mode 100644 index 0000000000..79f5245814 --- /dev/null +++ b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.cpp @@ -0,0 +1,21 @@ +/*=================================================================== + +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 "mitkStatisticsToMaskRelationRule.h" + +mitk::StatisticsToMaskRelationRule::StatisticsToMaskRelationRule() : + GenericIDRelationRule("statisticsToMask", "relation between ImageStatisticsContainer and Mask that was used as computation input", "ImageStatisticsContainer", "Mask") { +} diff --git a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h similarity index 50% copy from Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h copy to Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h index 0979c303c5..1bee1304df 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsContainerNodeHelper.h +++ b/Modules/ImageStatistics/mitkStatisticsToMaskRelationRule.h @@ -1,31 +1,36 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#ifndef MITKIMAGESTATISTICSCONTAINERNODEHELPER -#define MITKIMAGESTATISTICSCONTAINERNODEHELPER +#ifndef mitkStatisticsToMaskRelationRule_h +#define mitkStatisticsToMaskRelationRule_h #include - -#include -#include +#include "mitkGenericIDRelationRule.h" namespace mitk { - MITKIMAGESTATISTICS_EXPORT DataNode::Pointer CreateImageStatisticsNode(StatisticsContainer::Pointer statistic, const std::string& name = "statistics"); - MITKIMAGESTATISTICS_EXPORT void CreateNodeRelations(mitk::BaseData::Pointer statistics, mitk::BaseData::Pointer image, mitk::BaseData::Pointer mask, mitk::BaseData::Pointer planarFigure); + class MITKIMAGESTATISTICS_EXPORT StatisticsToMaskRelationRule : public mitk::GenericIDRelationRule + { + public: + mitkClassMacroItkParent(StatisticsToMaskRelationRule, mitk::GenericIDRelationRule) + itkNewMacro(Self) + + protected: + StatisticsToMaskRelationRule(); + }; } -#endif // MITKIMAGESTATISTICSCONTAINERNODEHELPER +#endif diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp index e6251c3754..ae94eda5c5 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.cpp @@ -1,246 +1,246 @@ /*=================================================================== 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 "QmitkImageStatisticsCalculationJob.h" //QT headers #include #include #include #include #include QmitkImageStatisticsCalculationJob::QmitkImageStatisticsCalculationJob() : QThread() , m_StatisticsImage(nullptr) , m_BinaryMask(nullptr) , m_PlanarFigureMask(nullptr) , m_TimeStep(0) , m_IgnoreZeros(false) , m_HistogramNBins(100) , m_StatisticChanged(false) , m_CalculationSuccessful(false) { } QmitkImageStatisticsCalculationJob::~QmitkImageStatisticsCalculationJob() { } -void QmitkImageStatisticsCalculationJob::Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ) +void QmitkImageStatisticsCalculationJob::Initialize( mitk::Image::ConstPointer image, mitk::Image::ConstPointer binaryImage, mitk::PlanarFigure::ConstPointer planarFig ) { // reset old values if( this->m_StatisticsImage.IsNotNull() ) this->m_StatisticsImage = nullptr; if( this->m_BinaryMask.IsNotNull() ) this->m_BinaryMask = nullptr; if( this->m_PlanarFigureMask.IsNotNull()) this->m_PlanarFigureMask = nullptr; // set new values if passed in if(image.IsNotNull()) - this->m_StatisticsImage = image->Clone(); + this->m_StatisticsImage = image; if(binaryImage.IsNotNull()) - this->m_BinaryMask = binaryImage->Clone(); + this->m_BinaryMask = binaryImage; if(planarFig.IsNotNull()) - this->m_PlanarFigureMask = planarFig->Clone(); + this->m_PlanarFigureMask = planarFig; } void QmitkImageStatisticsCalculationJob::SetTimeStep( int times ) { this->m_TimeStep = times; } int QmitkImageStatisticsCalculationJob::GetTimeStep() const { return this->m_TimeStep; } std::vector QmitkImageStatisticsCalculationJob::GetStatisticsData() const { return this->m_StatisticsVector; } -mitk::Image::Pointer QmitkImageStatisticsCalculationJob::GetStatisticsImage() const +mitk::Image::ConstPointer QmitkImageStatisticsCalculationJob::GetStatisticsImage() const { return this->m_StatisticsImage; } -mitk::Image::Pointer QmitkImageStatisticsCalculationJob::GetMaskImage() const +mitk::Image::ConstPointer QmitkImageStatisticsCalculationJob::GetMaskImage() const { return this->m_BinaryMask; } -mitk::PlanarFigure::Pointer QmitkImageStatisticsCalculationJob::GetPlanarFigure() const +mitk::PlanarFigure::ConstPointer QmitkImageStatisticsCalculationJob::GetPlanarFigure() const { return this->m_PlanarFigureMask; } void QmitkImageStatisticsCalculationJob::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationJob::GetIgnoreZeroValueVoxel() const { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationJob::SetHistogramNBins(unsigned int nbins) { this->m_HistogramNBins = nbins; } unsigned int QmitkImageStatisticsCalculationJob::GetHistogramNBins() const { return this->m_HistogramNBins; } std::string QmitkImageStatisticsCalculationJob::GetLastErrorMessage() const { return m_message; } QmitkImageStatisticsCalculationJob::HistogramType::ConstPointer QmitkImageStatisticsCalculationJob::GetTimeStepHistogram(unsigned int t) const { if (t >= this->m_HistogramVector.size()) return nullptr; return this->m_HistogramVector[t]; } bool QmitkImageStatisticsCalculationJob::GetStatisticsChangedFlag() const { return m_StatisticChanged; } bool QmitkImageStatisticsCalculationJob::GetStatisticsUpdateSuccessFlag() const { return m_CalculationSuccessful; } void QmitkImageStatisticsCalculationJob::run() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if(this->m_StatisticsImage.IsNotNull()) { - calculator->SetInputImage(m_StatisticsImage); + calculator->SetInputImage(m_StatisticsImage->Clone()); } 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); + 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); + pfMaskGen->SetInputImage(m_StatisticsImage->Clone()); + pfMaskGen->SetPlanarFigure(m_PlanarFigureMask->Clone()); calculator->SetMask(pfMaskGen.GetPointer()); } } catch (const mitk::Exception& e) { MITK_ERROR << "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch( const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); statisticCalculationSuccessful = false; } catch ( const std::runtime_error &e ) { MITK_ERROR<< "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::exception &e ) { MITK_ERROR<< "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } bool statisticChanged = false; if (this->m_IgnoreZeros) { mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); ignorePixelValueMaskGen->SetIgnoredPixelValue(0); - ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); + ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage->Clone()); calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); } else { calculator->SetSecondaryMask(nullptr); } calculator->SetNBinsForHistogramStatistics(m_HistogramNBins); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { try { calculator->GetStatistics(i); } 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(); statisticCalculationSuccessful = false; } } this->m_StatisticChanged = statisticChanged; this->m_CalculationSuccessful = statisticCalculationSuccessful; if(statisticCalculationSuccessful) { this->m_StatisticsVector.clear(); this->m_HistogramVector.clear(); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { this->m_StatisticsVector.push_back(calculator->GetStatistics(i).GetPointer()); this->m_HistogramVector.push_back((HistogramType*)this->m_StatisticsVector[i]->GetHistogram().GetPointer()); } } } diff --git a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h index 9721c7d1cc..7b87469157 100644 --- a/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h +++ b/Modules/ImageStatisticsUI/Qmitk/QmitkImageStatisticsCalculationJob.h @@ -1,114 +1,114 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED #define QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED //QT headers #include #include //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsCalculator.h" #include // itk headers #ifndef __itkHistogram_h #include #endif /** /brief This class is executed as background thread for image statistics calculation. * Documentation: This class is derived from QThread and is intended to be used by QmitkImageStatisticsView to run the image statistics calculation in a background thread keepung the gui usable. */ class MITKIMAGESTATISTICSUI_EXPORT QmitkImageStatisticsCalculationJob : public QThread { Q_OBJECT public: typedef itk::Statistics::Histogram HistogramType; /*! /brief standard constructor. */ QmitkImageStatisticsCalculationJob(); /*! /brief standard destructor. */ ~QmitkImageStatisticsCalculationJob(); /*! /brief Initializes the object with necessary data. */ - void Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ); + void Initialize( mitk::Image::ConstPointer image, mitk::Image::ConstPointer binaryImage, mitk::PlanarFigure::ConstPointer planarFig ); /*! /brief returns the calculated image statistics. */ std::vector GetStatisticsData() const; - mitk::Image::Pointer GetStatisticsImage() const; - mitk::Image::Pointer GetMaskImage() const; - mitk::PlanarFigure::Pointer GetPlanarFigure() const; + mitk::Image::ConstPointer GetStatisticsImage() const; + mitk::Image::ConstPointer GetMaskImage() const; + mitk::PlanarFigure::ConstPointer GetPlanarFigure() const; /*! /brief Set the time step of the image you want to process. */ void SetTimeStep( int times ); /*! /brief Get the time step of the image you want to process. */ int GetTimeStep() 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. */ HistogramType::ConstPointer GetTimeStepHistogram(unsigned int t = 0) const; /*! /brief Returns a flag indicating if the statistics have changed during calculation */ bool GetStatisticsChangedFlag() 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; std::string GetLastErrorMessage() const; private: //member declaration - mitk::Image::Pointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. - mitk::Image::Pointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. - mitk::PlanarFigure::Pointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. + 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. std::vector m_StatisticsVector; ///< member variable holds the result structs. int m_TimeStep; ///< member variable holds the time step for statistics calculation 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_StatisticChanged; ///< flag set if statistics have changed 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 // QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp index 1fe3d06cf0..5c38a44535 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.cpp @@ -1,292 +1,357 @@ /*=================================================================== 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 "QmitkImageStatisticsReloadedView.h" #include // berry includes #include #include #include #include #include #include #include #include #include +#include +#include +#include #include const std::string QmitkImageStatisticsReloadedView::VIEW_ID = "org.mitk.views.imagestatisticsReloaded"; QmitkImageStatisticsReloadedView::QmitkImageStatisticsReloadedView(QObject* /*parent*/, const char* /*name*/) { this->m_CalculationThread = new QmitkImageStatisticsCalculationJob(); } QmitkImageStatisticsReloadedView::~QmitkImageStatisticsReloadedView() { if (m_selectedPlanarFigure) m_selectedPlanarFigure->RemoveObserver(m_PlanarFigureObserverTag); } void QmitkImageStatisticsReloadedView::CreateQtPartControl(QWidget *parent) { m_Controls.setupUi(parent); m_Controls.widget_histogram->SetTheme(this->GetColorTheme()); m_Controls.widget_intensityProfile->SetTheme(this->GetColorTheme()); m_Controls.groupBox_histogram->setVisible(true); m_Controls.groupBox_intensityProfile->setVisible(false); m_Controls.label_currentlyComputingStatistics->setVisible(false); + m_statisticsManager = mitk::ImageStatisticsContainerManager::New(); + m_statisticsManager->SetDataStorage(this->GetDataStorage().GetPointer()); + PrepareDataStorageComboBoxes(); CreateConnections(); } void QmitkImageStatisticsReloadedView::CreateConnections() { connect(this->m_CalculationThread, &QmitkImageStatisticsCalculationJob::finished, this, &QmitkImageStatisticsReloadedView::OnStatisticsCalculationEnds, Qt::QueuedConnection); } void QmitkImageStatisticsReloadedView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsReloadedView::FillStatisticsWidget(const std::vector& statistics) { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->SetStatistics(statistics); m_Controls.widget_statistics->SetImageNodes({ m_selectedImageNode }); if (m_selectedMaskNode) { m_Controls.widget_statistics->SetMaskNodes({ m_selectedMaskNode }); } m_Controls.widget_statistics->setEnabled(true); } void QmitkImageStatisticsReloadedView::FillHistogramWidget(const std::vector& histogram, const std::vector& dataLabels) { m_Controls.groupBox_histogram->setVisible(true); m_Controls.widget_histogram->SetTheme(this->GetColorTheme()); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->SetHistogram(histogram.front(), dataLabels.front()); connect(m_Controls.widget_histogram, &QmitkHistogramVisualizationWidget::RequestHistogramUpdate, this, &QmitkImageStatisticsReloadedView::OnRequestHistogramUpdate); } QmitkChartWidget::ChartStyle QmitkImageStatisticsReloadedView::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::ChartStyle::darkstyle; } else { return QmitkChartWidget::ChartStyle::lightstyle; } } return QmitkChartWidget::ChartStyle::darkstyle; } void QmitkImageStatisticsReloadedView::OnImageOrMaskSelectorChanged() { if (this->m_selectedPlanarFigure) { this->m_selectedPlanarFigure->RemoveObserver(this->m_PlanarFigureObserverTag); this->m_selectedPlanarFigure = nullptr; } m_selectedImageNode = m_Controls.imageSelector->GetSelectedNode(); m_selectedMaskNode = m_Controls.maskImageSelector->GetSelectedNode(); m_Controls.groupBox_intensityProfile->setVisible(false); + //m_statisticContainerRules.clear(); + if (m_selectedImageNode != nullptr) { auto image = dynamic_cast(m_selectedImageNode->GetData()); mitk::Image::Pointer mask = nullptr; mitk::PlanarFigure::Pointer maskPlanarFigure = nullptr; if (m_selectedMaskNode != nullptr) { mask = dynamic_cast(m_selectedMaskNode->GetData()); if (mask == nullptr) { maskPlanarFigure = dynamic_cast(m_selectedMaskNode->GetData()); } } if (mask) { + auto imageStatistics = m_statisticsManager->GetImageStatistics(image, mask.GetPointer()); + bool imageStatisticsOlderThanInputs = false; + if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || imageStatistics->GetMTime() < mask->GetMTime())) { + imageStatisticsOlderThanInputs = true; + } + //compute statistics with given mask - CalculateStatistics(image, mask); + if (!imageStatistics || imageStatisticsOlderThanInputs) { + CalculateStatistics(image, mask.GetPointer()); + } + //statistics already computed + else { + this->FillStatisticsWidget({ imageStatistics }); + this->FillHistogramWidget({ imageStatistics->GetHistogram().GetPointer() }, { m_selectedImageNode->GetName() }); + } } else if (maskPlanarFigure) { m_selectedPlanarFigure = maskPlanarFigure; ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction(this, &QmitkImageStatisticsReloadedView::OnImageOrMaskSelectorChanged); this->m_PlanarFigureObserverTag = m_selectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); if (!maskPlanarFigure->IsClosed()) { //compute line profile and display statistics for voxels on line auto intensityProfile = mitk::ComputeIntensityProfile(image, 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()); } + auto imageStatistics = m_statisticsManager->GetImageStatistics(image, maskPlanarFigure.GetPointer()); + bool imageStatisticsOlderThanInputs = false; + if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime() || imageStatistics->GetMTime() < maskPlanarFigure->GetMTime())) { + imageStatisticsOlderThanInputs = true; + } + //for all planar figures: compute statistics with planarFigure as mask - CalculateStatistics(image, maskPlanarFigure); + if (!imageStatistics || imageStatisticsOlderThanInputs) { + CalculateStatistics(image, nullptr, maskPlanarFigure.GetPointer()); + } + //statistics already computed + else { + this->FillStatisticsWidget({ imageStatistics }); + if (maskPlanarFigure->IsClosed()) { + this->FillHistogramWidget({ imageStatistics->GetHistogram().GetPointer() }, { m_selectedImageNode->GetName() }); + } + } } else { + auto imageStatistics = m_statisticsManager->GetImageStatistics(image); + bool imageStatisticsOlderThanInputs = false; + if (imageStatistics && (imageStatistics->GetMTime() < image->GetMTime())) { + imageStatisticsOlderThanInputs = true; + } + //compute statistics with image only - CalculateStatistics(image); + if (!imageStatistics || imageStatisticsOlderThanInputs) { + CalculateStatistics(image); + } + //statistics already computed + else { + this->FillStatisticsWidget({ imageStatistics }); + this->FillHistogramWidget({ imageStatistics->GetHistogram().GetPointer() }, { m_selectedImageNode->GetName() }); + } } } else { ResetGUI(); } } void QmitkImageStatisticsReloadedView::ResetGUI() { m_Controls.widget_statistics->Reset(); m_Controls.widget_statistics->setEnabled(false); m_Controls.widget_histogram->Reset(); m_Controls.widget_histogram->setEnabled(false); } void QmitkImageStatisticsReloadedView::OnStatisticsCalculationEnds() { mitk::StatusBar::GetInstance()->Clear(); if (this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { auto statistics = m_CalculationThread->GetStatisticsData(); for (auto& statistic : statistics) { + mitk::PropertyRelations::RuleResultVectorType rulesForCurrentStatistic; auto statisticNonConst = statistic->Clone(); - auto statisticsNode = mitk::CreateImageStatisticsNode(statisticNonConst); - CreateNodeRelations(statisticNonConst.GetPointer(), m_CalculationThread->GetStatisticsImage().GetPointer(), m_CalculationThread->GetMaskImage().GetPointer(), m_CalculationThread->GetPlanarFigure().GetPointer()); + auto statisticsNodeName = m_selectedImageNode->GetName(); + if (m_selectedMaskNode) { + statisticsNodeName += "_" + m_selectedMaskNode->GetName(); + } + statisticsNodeName += "_statistics"; + auto statisticsNode = mitk::CreateImageStatisticsNode(statisticNonConst, statisticsNodeName); + auto imageRule = mitk::StatisticsToImageRelationRule::New(); + imageRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetStatisticsImage().GetPointer()); + rulesForCurrentStatistic.push_back(imageRule.GetPointer()); + + if (m_CalculationThread->GetMaskImage()) { + auto maskRule = mitk::StatisticsToMaskRelationRule::New(); + maskRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetMaskImage().GetPointer()); + rulesForCurrentStatistic.push_back(maskRule.GetPointer()); + } + else if (m_CalculationThread->GetPlanarFigure()) { + auto planarFigureRule = mitk::StatisticsToMaskRelationRule::New(); + planarFigureRule->Connect(statisticNonConst.GetPointer(), m_CalculationThread->GetPlanarFigure().GetPointer()); + rulesForCurrentStatistic.push_back(planarFigureRule.GetPointer()); + } + + m_statisticContainerRules.push_back(rulesForCurrentStatistic); + this->GetDataStorage()->Add(statisticsNode); + m_statisticsManager->SetRules(m_statisticContainerRules); } this->FillStatisticsWidget(statistics); if (!m_selectedPlanarFigure || m_selectedPlanarFigure->IsClosed()) { this->FillHistogramWidget({ m_CalculationThread->GetTimeStepHistogram() }, { m_selectedImageNode->GetName() }); } } else { mitk::StatusBar::GetInstance()->DisplayErrorText(m_CalculationThread->GetLastErrorMessage().c_str()); m_Controls.widget_histogram->setEnabled(false); m_Controls.widget_statistics->setEnabled(false); } m_Controls.label_currentlyComputingStatistics->setVisible(false); } void QmitkImageStatisticsReloadedView::OnRequestHistogramUpdate(unsigned int nBins) { m_CalculationThread->SetHistogramNBins(nBins); m_CalculationThread->start(); } -void QmitkImageStatisticsReloadedView::CalculateStatistics(mitk::Image::Pointer image, mitk::Image::Pointer mask) -{ - CalculateStatisticsInternal(image, mask); - -} - -void QmitkImageStatisticsReloadedView::CalculateStatistics(mitk::Image::Pointer image, mitk::PlanarFigure::Pointer mask) { - CalculateStatisticsInternal(image, nullptr, mask); -} - -void QmitkImageStatisticsReloadedView::CalculateStatisticsInternal(mitk::Image::Pointer image, mitk::Image::Pointer mask, mitk::PlanarFigure::Pointer maskPlanarFigure) +void QmitkImageStatisticsReloadedView::CalculateStatistics(mitk::Image::ConstPointer image, mitk::Image::ConstPointer mask, mitk::PlanarFigure::ConstPointer maskPlanarFigure) { this->m_StatisticsUpdatePending = true; + auto renderPart = this->GetRenderWindowPart(); + unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); this->m_CalculationThread->Initialize(image, mask, maskPlanarFigure); + this->m_CalculationThread->SetTimeStep(timeStep); try { // Compute statistics this->m_CalculationThread->start(); m_Controls.label_currentlyComputingStatistics->setVisible(true); } catch (const mitk::Exception& e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.GetDescription()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } catch (const std::runtime_error &e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } catch (const std::exception &e) { mitk::StatusBar::GetInstance()->DisplayErrorText(e.what()); this->m_StatisticsUpdatePending = false; m_Controls.label_currentlyComputingStatistics->setVisible(false); } } void QmitkImageStatisticsReloadedView::OnSelectionChanged( berry::IWorkbenchPart::Pointer part, const QList &nodes ) { Q_UNUSED(part); Q_UNUSED(nodes); } void QmitkImageStatisticsReloadedView::PrepareDataStorageComboBoxes() { auto isPlanarFigurePredicate = mitk::GetImageStatisticsPlanarFigurePredicate(); auto isMaskPredicate = mitk::GetImageStatisticsMaskPredicate(); auto isImagePredicate = mitk::GetImageStatisticsImagePredicate(); auto isMaskOrPlanarFigurePredicate = mitk::NodePredicateOr::New(isPlanarFigurePredicate, isMaskPredicate); m_Controls.imageSelector->SetDataStorage(GetDataStorage()); m_Controls.imageSelector->SetPredicate(isImagePredicate); m_Controls.maskImageSelector->SetDataStorage(GetDataStorage()); m_Controls.maskImageSelector->SetPredicate(isMaskOrPlanarFigurePredicate); m_Controls.maskImageSelector->SetZeroEntryText(""); } void QmitkImageStatisticsReloadedView::Activated() { } void QmitkImageStatisticsReloadedView::Deactivated() { } void QmitkImageStatisticsReloadedView::Visible() { connect(m_Controls.imageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnImageOrMaskSelectorChanged); connect(m_Controls.maskImageSelector, static_cast(&QComboBox::currentIndexChanged), this, &QmitkImageStatisticsReloadedView::OnImageOrMaskSelectorChanged); m_selectedImageNode = m_Controls.imageSelector->GetSelectedNode(); if (m_selectedImageNode) { OnImageOrMaskSelectorChanged(); } else { ResetGUI(); } } void QmitkImageStatisticsReloadedView::Hidden() { m_Controls.imageSelector->disconnect(); m_Controls.maskImageSelector->disconnect(); } void QmitkImageStatisticsReloadedView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h index d3947c35ed..48120a3517 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsReloadedView.h @@ -1,108 +1,107 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkImageStatisticsReloadedView_H__INCLUDED #define QmitkImageStatisticsReloadedView_H__INCLUDED #include "ui_QmitkImageStatisticsReloadedViewControls.h" // Qmitk includes #include #include +#include #include #include #include #include /*! \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 QmitkImageStatisticsReloadedView : public QmitkAbstractView, public mitk::ILifecycleAwarePart, public berry::IPartListener { Q_OBJECT public: using HistogramType = mitk::StatisticsContainer::HistogramType; /*! \brief default constructor */ QmitkImageStatisticsReloadedView(QObject *parent = nullptr, const char *name = nullptr); /*! \brief default destructor */ virtual ~QmitkImageStatisticsReloadedView(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent) override; /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); /*! \brief Is called from the selection mechanism once the data manager selection has changed*/ void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &selectedNodes) override; void PrepareDataStorageComboBoxes(); static const std::string VIEW_ID; void FillStatisticsWidget(const std::vector& statistics); void FillHistogramWidget(const std::vector& histogram, const std::vector& dataLabels); QmitkChartWidget::ChartStyle GetColorTheme() const; protected: virtual void Activated() override; virtual void Deactivated() override; virtual void Visible() override; virtual void Hidden() override; virtual void SetFocus() override; /** \brief Is called right before the view closes (before the destructor) */ virtual void PartClosed(const berry::IWorkbenchPartReference::Pointer&) override; /** \brief Required for berry::IPartListener */ virtual Events::Types GetPartEventTypes() const override { return Events::CLOSED; } void OnImageOrMaskSelectorChanged(); void ResetGUI(); void OnStatisticsCalculationEnds(); void OnRequestHistogramUpdate(unsigned int nBins); - void CalculateStatistics(mitk::Image::Pointer image, mitk::Image::Pointer mask=nullptr); - void CalculateStatistics(mitk::Image::Pointer image, mitk::PlanarFigure::Pointer mask); - - void CalculateStatisticsInternal(mitk::Image::Pointer image, mitk::Image::Pointer mask=nullptr, mitk::PlanarFigure::Pointer maskPlanarFigure=nullptr); + void CalculateStatistics(mitk::Image::ConstPointer image, mitk::Image::ConstPointer mask=nullptr, mitk::PlanarFigure::ConstPointer maskPlanarFigure = nullptr); // member variables Ui::QmitkImageStatisticsReloadedViewControls m_Controls; private: typedef itk::SimpleMemberCommand< QmitkImageStatisticsReloadedView > ITKCommandType; QmitkImageStatisticsCalculationJob * m_CalculationThread = nullptr; bool m_StatisticsUpdatePending=false; mitk::DataNode::ConstPointer m_selectedImageNode = nullptr, m_selectedMaskNode = nullptr; - mitk::PropertyRelations::RuleResultVectorType m_statisticContainerRules; + std::vector m_statisticContainerRules; mitk::PlanarFigure::Pointer m_selectedPlanarFigure=nullptr; + mitk::ImageStatisticsContainerManager::Pointer m_statisticsManager; long m_PlanarFigureObserverTag; }; #endif // QmitkImageStatisticsView_H__INCLUDED 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 6495056f70..f7c57678d1 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1313 +1,1313 @@ /*=================================================================== 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 "QmitkImageStatisticsView.h" // Qt includes #include #include #include // berry includes #include // mitk includes #include #include #include #include #include #include #include // itk includes #include "itksys/SystemTools.hxx" #include "itkImageRegionConstIteratorWithIndex.h" #include //blueberry includes #include #include const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; const int QmitkImageStatisticsView::STAT_TABLE_BASE_HEIGHT = 180; QmitkImageStatisticsView::QmitkImageStatisticsView(QObject* /*parent*/, const char* /*name*/) : m_Controls( nullptr ), m_SelectedImage( nullptr ), m_SelectedImageMask( nullptr ), m_SelectedPlanarFigure( nullptr ), m_ImageObserverTag( -1 ), m_ImageMaskObserverTag( -1 ), m_PlanarFigureObserverTag( -1 ), m_TimeObserverTag( -1 ), m_CurrentStatisticsValid( false ), m_StatisticsUpdatePending( false ), m_DataNodeSelectionChanged ( false ), m_Visible(false) { this->m_CalculationThread = new QmitkImageStatisticsCalculationJob; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if ( m_SelectedImage != nullptr ) m_SelectedImage->RemoveObserver( m_ImageObserverTag ); if ( m_SelectedImageMask != nullptr ) m_SelectedImageMask->RemoveObserver( m_ImageMaskObserverTag ); if ( m_SelectedPlanarFigure != nullptr ) m_SelectedPlanarFigure->RemoveObserver( m_PlanarFigureObserverTag ); while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculationThread; } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { if (m_Controls == nullptr) { m_Controls = new Ui::QmitkImageStatisticsViewControls; m_Controls->setupUi(parent); CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_Controls->m_BinSizeFrame->setEnabled(false); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) m_Controls->m_StatisticsWidgetStack->setVisible(false); m_Controls->label_HistogramIsInvisibleWarning->setEnabled(true); m_Controls->label_HistogramIsInvisibleWarning->setVisible(true); m_Controls->label_HistogramIsInvisibleWarning->setText("Histogram is not visible because Qt 5.10 is required. You can use the button Copy to Clipboard below to retrieve values."); m_Controls->groupBox_plot->setVisible(false); #else m_Controls->label_HistogramIsInvisibleWarning->setVisible(false); #endif } } void QmitkImageStatisticsView::OnPageSuccessfullyLoaded() { berry::IPreferencesService* prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); m_StylePref = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); QString styleName = m_StylePref->Get(berry::QtPreferences::QT_STYLE_NAME, ""); if (styleName == ":/org.blueberry.ui.qt/darkstyle.qss") { this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ChartStyle::darkstyle); } else { this->m_Controls->m_JSHistogram->SetTheme(QmitkChartWidget::ChartStyle::lightstyle); } } void QmitkImageStatisticsView::CreateConnections() { if ( m_Controls ) { connect( (QObject*)(this->m_Controls->m_ButtonCopyHistogramToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardHistogramButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_ButtonCopyStatisticsToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardStatisticsButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_IgnoreZerosCheckbox), SIGNAL(clicked()),(QObject*) this, SLOT(OnIgnoreZerosCheckboxClicked()) ); connect( (QObject*) this->m_CalculationThread, SIGNAL(finished()),this, SLOT( OnThreadedStatisticsCalculationEnds()),Qt::QueuedConnection); connect( (QObject*) this, SIGNAL(StatisticsUpdate()),this, SLOT( RequestStatisticsUpdate()), Qt::QueuedConnection); connect( (QObject*) this->m_Controls->m_StatisticsTable, SIGNAL(cellDoubleClicked(int,int)),this, SLOT( JumpToCoordinates(int,int)) ); connect((QObject*)(this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnBarRadioButtonSelected())); connect((QObject*)(this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*)(this), SLOT(OnLineRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_HistogramNBinsSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramNBinsCheckBoxValueChanged())); connect((QObject*)(this->m_Controls->m_UseDefaultNBinsCheckBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnDefaultNBinsSpinBoxChanged())); connect((QObject*)(this->m_Controls->m_ShowSubchartCheckBox), SIGNAL(clicked()), (QObject*) this, SLOT(OnShowSubchartBoxChanged())); connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*) this, SLOT(OnPageSuccessfullyLoaded())); } } void QmitkImageStatisticsView::OnDefaultNBinsSpinBoxChanged() { if (this->m_Controls->m_UseDefaultNBinsCheckBox->isChecked()) { m_Controls->m_HistogramNBinsSpinbox->setValue(100); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); } m_Controls->m_BinSizeFrame->setEnabled(!m_Controls->m_UseDefaultNBinsCheckBox->isChecked()); this->UpdateStatistics(); } void QmitkImageStatisticsView::OnShowSubchartBoxChanged() { bool showSubchart = this->m_Controls->m_ShowSubchartCheckBox->isChecked(); this->m_Controls->m_JSHistogram->Reload(showSubchart); } void QmitkImageStatisticsView::OnBarRadioButtonSelected() { this->m_Controls->m_JSHistogram->SetChartTypeForAllDataAndReload(QmitkChartWidget::ChartType::bar); } void QmitkImageStatisticsView::OnLineRadioButtonSelected() { this->m_Controls->m_JSHistogram->SetChartTypeForAllDataAndReload(QmitkChartWidget::ChartType::line); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == nullptr) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != nullptr); int timestep = timeEvent->GetPos(); if (this->m_SelectedImage->GetTimeSteps() > 1) { for (int x = 0; x < this->m_Controls->m_StatisticsTable->columnCount(); x++) { for (int y = 0; y < this->m_Controls->m_StatisticsTable->rowCount(); y++) { QTableWidgetItem* item = this->m_Controls->m_StatisticsTable->item(y, x); if (item == nullptr) break; if (x == timestep) { item->setBackgroundColor(Qt::yellow); } else { if (y % 2 == 0) item->setBackground(this->m_Controls->m_StatisticsTable->palette().base()); else item->setBackground(this->m_Controls->m_StatisticsTable->palette().alternateBase()); } } } this->m_Controls->m_StatisticsTable->viewport()->update(); } if ((this->m_SelectedImage->GetTimeSteps() == 1 && timestep == 0) || this->m_SelectedImage->GetTimeSteps() > 1) { // display histogram for selected timestep //bug in Qt thats leads to crash in debug builds. Fixed in Qt 5.10 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) m_Controls->m_JSHistogram->Clear(); #endif QmitkImageStatisticsCalculationJob::HistogramType::ConstPointer histogram = (QmitkImageStatisticsCalculationJob::HistogramType::ConstPointer)this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool closedFigure = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if (closedFigure) { auto imageNameLabel = m_Controls->m_SelectedFeatureImageLabel->text().toStdString(); this->m_Controls->m_JSHistogram->AddData2D(ConvertHistogramToMap(histogram), imageNameLabel); if (this->m_Controls->m_lineRadioButton->isChecked()) { this->m_Controls->m_JSHistogram->SetChartType(imageNameLabel, QmitkChartWidget::ChartType::line); } else { this->m_Controls->m_JSHistogram->SetChartType(imageNameLabel, QmitkChartWidget::ChartType::bar); } this->m_Controls->m_JSHistogram->SetXAxisLabel("Grey value"); this->m_Controls->m_JSHistogram->SetYAxisLabel("Frequency"); this->m_Controls->m_JSHistogram->Show(this->m_Controls->m_ShowSubchartCheckBox->isChecked()); } } } } void QmitkImageStatisticsView::JumpToCoordinates(int row ,int col) { if(m_SelectedDataNodes.isEmpty()) { MITK_WARN("QmitkImageStatisticsView") << "No data node selected for statistics calculation." ; return; } mitk::Point3D world; if (row==5 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row==4 && !m_WorldMaxList.empty()) world = m_WorldMaxList[col]; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), col); part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SetGeometryTime(timeEvent); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if (!m_CurrentStatisticsValid) { QApplication::clipboard()->clear(); } if (m_SelectedPlanarFigure == nullptr) { const unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram(t).GetPointer(); QString clipboard("Measurement \t Frequency\n"); for (HistogramType::ConstIterator it = histogram->Begin(); it != histogram->End(); ++it) { clipboard = clipboard.append("%L1 \t %L2\n") .arg(it.GetMeasurementVector()[0], 0, 'f', 2) .arg(it.GetFrequency()); } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard); } //If a (non-closed) PlanarFigure is selected, display a line profile widget else if (m_SelectedPlanarFigure != nullptr) { QString clipboard("Pixel \t Intensity\n"); for (unsigned int i = 0; i < m_IntensityProfileList.size(); i++) { clipboard = clipboard.append("%L1 \t %L2\n").arg(QString::number(i)).arg(QString::number(m_IntensityProfileList.at(i))); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != nullptr)) { const auto &statistics = this->m_CalculationThread->GetStatisticsData(); // Set time borders for for loop ;) unsigned int startT, endT; if(this->m_Controls->m_CheckBox4dCompleteTable->checkState()==Qt::CheckState::Unchecked) { startT = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); endT = startT+1; } else { startT = 0; endT = statistics.size(); } QVector< QVector > statisticsTable; QStringList headline{ "Timestep", "Mean", "Median", "StdDev", "RMS", "Max", "Min", "NumberOfVoxels", "Skewness", "Kurtosis", "Uniformity", "Entropy", "MPP", "UPP", "V [mm³]" }; for(int i=0;i row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for(unsigned int t=startT;tGetMean()) << QString::number(statistics[t]->GetMedian()) << QString::number(statistics[t]->GetStd()) << QString::number(statistics[t]->GetRMS()) << QString::number(statistics[t]->GetMax()) << QString::number(statistics[t]->GetMin()) << QString::number(statistics[t]->GetN()) << QString::number(statistics[t]->GetSkewness()) << QString::number(statistics[t]->GetKurtosis()) << QString::number(statistics[t]->GetUniformity()) << QString::number(statistics[t]->GetEntropy()) << QString::number(statistics[t]->GetMPP()) << QString::number(statistics[t]->GetUPP()) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for(int z=0;zsetText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes ) { if (this->m_Visible) { this->SelectionChanged( nodes ); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { //Clear Histogram if data node is deselected //bug in Qt thats leads to crash in debug builds. Fixed in Qt 5.10 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) m_Controls->m_JSHistogram->Clear(); #endif if( this->m_StatisticsUpdatePending ) { this->m_DataNodeSelectionChanged = true; return; // not ready for new data now! } if (selectedNodes.size() == this->m_SelectedDataNodes.size()) { int i = 0; for (; i < selectedNodes.size(); ++i) { if (selectedNodes.at(i) != this->m_SelectedDataNodes.at(i)) { break; } } // node selection did not change if (i == selectedNodes.size()) return; } //reset the feature image and image mask field m_Controls->m_SelectedFeatureImageLabel->setText("None"); m_Controls->m_SelectedMaskLabel->setText("None"); this->ReinitData(); if (selectedNodes.isEmpty()) { DisableHistogramGUIElements(); } else { EnableHistogramGUIElements(); ResetHistogramGUIElementsToDefault(); } if(selectedNodes.size() == 1 || selectedNodes.size() == 2) { bool isBinary = false; selectedNodes.value(0)->GetBoolProperty("binary",isBinary); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); isBinary |= isLabelSet->CheckNode(selectedNodes.value(0)); if(isBinary) { EnableHistogramGUIElements(); m_Controls->m_InfoLabel->setText(""); } for (int i= 0; i< selectedNodes.size(); ++i) { this->m_SelectedDataNodes.push_back(selectedNodes.at(i)); } this->m_DataNodeSelectionChanged = false; this->m_Controls->m_ErrorMessageLabel->setText( "" ); this->m_Controls->m_ErrorMessageLabel->hide(); emit StatisticsUpdate(); } else { this->m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::DisableHistogramGUIElements() { m_Controls->m_InfoLabel->setText(""); m_Controls->groupBox_histogram->setEnabled(false); m_Controls->groupBox_statistics->setEnabled(false); } void QmitkImageStatisticsView::ResetHistogramGUIElementsToDefault() { m_Controls->m_barRadioButton->setChecked(true); m_Controls->m_HistogramNBinsSpinbox->setValue(100); m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); m_Controls->m_UseDefaultNBinsCheckBox->setChecked(true); m_Controls->m_ShowSubchartCheckBox->setChecked(true); m_Controls->m_BinSizeFrame->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_HistogramNBinsSpinbox->setEnabled(true); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); } void QmitkImageStatisticsView::EnableHistogramGUIElements() { m_Controls->groupBox_histogram->setEnabled(true); m_Controls->groupBox_plot->setEnabled(true); m_Controls->groupBox_statistics->setEnabled(true); } void QmitkImageStatisticsView::ReinitData() { while( this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if(this->m_SelectedImage != nullptr) { this->m_SelectedImage->RemoveObserver( this->m_ImageObserverTag); this->m_SelectedImage = nullptr; } if(this->m_SelectedImageMask != nullptr) { this->m_SelectedImageMask->RemoveObserver( this->m_ImageMaskObserverTag); this->m_SelectedImageMask = nullptr; } if(this->m_SelectedPlanarFigure != nullptr) { this->m_SelectedPlanarFigure->RemoveObserver( this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = nullptr; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText( "" ); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { m_Controls->m_ErrorMessageLabel->setText(""); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if ( renderPart == nullptr ) { this->m_StatisticsUpdatePending = false; return; } m_WorldMinList.clear(); m_WorldMaxList.clear(); // classify selected nodes mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateOr::Pointer imagePredicate = mitk::NodePredicateOr::New(isImage, isLabelSet); std::string maskName; std::string maskType; std::string featureImageName; unsigned int maskDimension = 0; // reset data from last run ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction( this, &QmitkImageStatisticsView::SelectedDataModified ); mitk::DataNode::Pointer planarFigureNode; for( int i= 0 ; i < this->m_SelectedDataNodes.size(); ++i) { mitk::PlanarFigure::Pointer planarFig = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); if( imagePredicate->CheckNode(this->m_SelectedDataNodes.at(i)) ) { bool isMask = false; this->m_SelectedDataNodes.at(i)->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(this->m_SelectedDataNodes.at(i)); if( this->m_SelectedImageMask == nullptr && isMask) { this->m_SelectedImageMask = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageMaskObserverTag = this->m_SelectedImageMask->AddObserver(itk::ModifiedEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = m_SelectedImageMask->GetNameOfClass(); maskDimension = 3; } else if( !isMask ) { if(this->m_SelectedImage == nullptr) { this->m_SelectedImage = static_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } featureImageName = this->m_SelectedDataNodes.at(i)->GetName(); } } else if (planarFig.IsNotNull()) { if(this->m_SelectedPlanarFigure == nullptr) { this->m_SelectedPlanarFigure = planarFig; this->m_PlanarFigureObserverTag = this->m_SelectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = this->m_SelectedPlanarFigure->GetNameOfClass(); maskDimension = 2; planarFigureNode = m_SelectedDataNodes.at(i); } } else { m_Controls->m_ErrorMessageLabel->setText("Invalid data node type!"); m_Controls->m_ErrorMessageLabel->show(); } } if(maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if(featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != nullptr && m_SelectedImage == nullptr) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (unsigned int i=0; iSize(); i++) { mitk::DataNode::Pointer node = parentSet->ElementAt(i); if( imagePredicate->CheckNode(node) ) { bool isMask = false; node->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(node); if( !isMask ) { if(this->m_SelectedImage == nullptr) { this->m_SelectedImage = static_cast(node->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } } } } } unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); if ( m_SelectedImage != nullptr && m_SelectedImage->IsInitialized()) { // Check if a the selected image is a multi-channel image. If yes, statistics // cannot be calculated currently. if ( m_SelectedImage->GetPixelType().GetNumberOfComponents() > 1 ) { m_Controls->m_ErrorMessageLabel->setText( "Multi-component images not supported." ); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; this->DisableHistogramGUIElements(); m_Controls->m_InfoLabel->setText(""); return; } std::stringstream maskLabel; maskLabel << maskName; if ( maskDimension > 0 ) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText( maskLabel.str().c_str() ); m_Controls->m_SelectedFeatureImageLabel->setText(featureImageName.c_str()); // check time step validity if(m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3)-1) { timeStep = m_SelectedImage->GetDimension(3)-1; } // Add the used mask time step to the mask label so the user knows which mask time step was used // if the image time step is bigger than the total number of mask time steps (see // ImageStatisticsCalculator::ExtractImageAndMask) if (m_SelectedImageMask != nullptr) { unsigned int maskTimeStep = timeStep; if (maskTimeStep >= m_SelectedImageMask->GetTimeSteps()) { maskTimeStep = m_SelectedImageMask->GetTimeSteps() - 1; } m_Controls->m_SelectedMaskLabel->setText(m_Controls->m_SelectedMaskLabel->text() + QString(" (t=") + QString::number(maskTimeStep) + QString(")")); } // check if the segmentation mask is empty if (m_SelectedImageMask != NULL) { typedef itk::Image ItkImageType; typedef itk::ImageRegionConstIteratorWithIndex< ItkImageType > IteratorType; ItkImageType::Pointer itkImage; mitk::CastToItkImage( m_SelectedImageMask, itkImage ); bool empty = true; IteratorType it( itkImage, itkImage->GetLargestPossibleRegion() ); while ( !it.IsAtEnd() ) { ItkImageType::ValueType val = it.Get(); if ( val != 0 ) { empty = false; break; } ++it; } if ( empty ) { m_Controls->m_ErrorMessageLabel->setText( "Empty segmentation mask selected..." ); m_Controls->m_ErrorMessageLabel->show(); return; } } //// initialize thread and trigger it this->m_CalculationThread->SetIgnoreZeroValueVoxel( m_Controls->m_IgnoreZerosCheckbox->isChecked() ); this->m_CalculationThread->Initialize( m_SelectedImage, m_SelectedImageMask, m_SelectedPlanarFigure ); this->m_CalculationThread->SetTimeStep( timeStep ); m_Controls->m_ErrorMessageLabel->setText("Calculating statistics..."); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics this->m_CalculationThread->start(); } catch ( const mitk::Exception& e) { m_Controls->m_ErrorMessageLabel->setText("" + QString(e.GetDescription()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::runtime_error &e ) { // In case of exception, print error message on GUI m_Controls->m_ErrorMessageLabel->setText("" + QString(e.what()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::exception &e ) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI m_Controls->m_ErrorMessageLabel->setText("" + QString(e.what()) + ""); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } } else { this->m_StatisticsUpdatePending = false; } } void QmitkImageStatisticsView::SelectedDataModified() { if( !m_StatisticsUpdatePending ) { emit StatisticsUpdate(); } } void QmitkImageStatisticsView::NodeRemoved(const mitk::DataNode *node) { while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (node->GetData() == m_SelectedImage) { m_SelectedImage = nullptr; } } void QmitkImageStatisticsView::RequestStatisticsUpdate() { if ( !m_StatisticsUpdatePending ) { if(this->m_DataNodeSelectionChanged) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->m_StatisticsUpdatePending = true; this->UpdateStatistics(); } } if (this->GetRenderWindowPart()) this->GetRenderWindowPart()->RequestUpdate(); } void QmitkImageStatisticsView::OnHistogramNBinsCheckBoxValueChanged() { if (static_cast(m_Controls->m_HistogramNBinsSpinbox->value()) != m_HistogramNBins) { m_HistogramNBins = m_Controls->m_HistogramNBinsSpinbox->value(); this->m_CalculationThread->SetHistogramNBins(m_Controls->m_HistogramNBinsSpinbox->value()); this->UpdateStatistics(); } } void QmitkImageStatisticsView::WriteStatisticsToGUI() { //bug in Qt thats leads to crash in debug builds. Fixed in Qt 5.10 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) m_Controls->m_JSHistogram->Clear(); #endif m_IntensityProfileList.clear(); //Disconnect OnLineRadioButtonSelected() to prevent reloading chart when radiobutton is checked programmatically disconnect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), 0, 0); connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*) this, SLOT(OnPageSuccessfullyLoaded())); m_Controls->m_InfoLabel->setText(""); if (m_DataNodeSelectionChanged) { this->m_StatisticsUpdatePending = false; this->RequestStatisticsUpdate(); return; // stop visualization of results and calculate statistics of new selection } if (this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { if (this->m_CalculationThread->GetStatisticsChangedFlag()) { // Do not show any error messages m_Controls->m_ErrorMessageLabel->hide(); m_CurrentStatisticsValid = true; } if (m_SelectedImage != nullptr) { //all statistics are now computed also on planar figures (lines, paths...)! // If a (non-closed) PlanarFigure is selected, display a line profile widget if (m_SelectedPlanarFigure != nullptr) { // Check if the (closed) planar figure is out of bounds and so no image mask could be calculated--> Intensity Profile can not be calculated bool outOfBounds = false; if (m_SelectedPlanarFigure->IsClosed() && m_SelectedImageMask == nullptr) { outOfBounds = true; const QString message("Planar figure is on a rotated image plane or outside the image bounds."); m_Controls->m_InfoLabel->setText(message); } // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if (!(planarFigurePlaneGeometry == nullptr || outOfBounds)) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); mitk::Image::Pointer image; if (this->m_CalculationThread->GetStatisticsImage()->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(this->m_CalculationThread->GetStatisticsImage()); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { - image = this->m_CalculationThread->GetStatisticsImage(); + image = this->m_CalculationThread->GetStatisticsImage()->Clone(); } mitk::IntensityProfile::ConstPointer intensityProfile = (mitk::IntensityProfile::ConstPointer)mitk::ComputeIntensityProfile(image, m_SelectedPlanarFigure); m_IntensityProfileList = ConvertIntensityProfileToVector(intensityProfile); auto lineDataLabel = "Intensity profile " + m_Controls->m_SelectedMaskLabel->text().toStdString(); m_Controls->m_JSHistogram->SetChartType(lineDataLabel, QmitkChartWidget::ChartType::line); m_Controls->m_JSHistogram->AddData1D(m_IntensityProfileList, lineDataLabel); m_Controls->m_JSHistogram->SetXAxisLabel("Distance"); m_Controls->m_JSHistogram->SetYAxisLabel("Intensity"); m_Controls->m_JSHistogram->Show(m_Controls->m_ShowSubchartCheckBox->isChecked()); m_Controls->m_lineRadioButton->setChecked(true); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramNBinsSpinbox->setEnabled(false); m_Controls->m_BinSizeFrame->setEnabled(false); m_Controls->m_UseDefaultNBinsCheckBox->setEnabled(false); //Reconnect OnLineRadioButtonSelected() connect((QObject*)(this->m_Controls->m_JSHistogram), SIGNAL(PageSuccessfullyLoaded()), (QObject*) this, SLOT(OnLineRadioButtonSelected())); auto statisticsVector = this->m_CalculationThread->GetStatisticsData(); //only one entry (current timestep) this->FillLinearProfileStatisticsTableView(statisticsVector.front().GetPointer(), this->m_CalculationThread->GetStatisticsImage()); QString message("Only linegraph available for an intensity profile!"); if (this->m_CalculationThread->GetStatisticsImage()->GetDimension() == 4) { message += "Only current timestep displayed!"; } message += ""; m_Controls->m_InfoLabel->setText(message); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText("None"); this->m_StatisticsUpdatePending = false; if (!outOfBounds) m_Controls->m_InfoLabel->setText(""); return; } } else { m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); auto histogram = this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer(); auto imageLabelName = m_Controls->m_SelectedFeatureImageLabel->text().toStdString(); m_Controls->m_JSHistogram->AddData2D(ConvertHistogramToMap(histogram), imageLabelName); m_Controls->m_JSHistogram->SetChartType(imageLabelName, QmitkChartWidget::ChartType::bar); this->m_Controls->m_JSHistogram->SetXAxisLabel("Gray value"); this->m_Controls->m_JSHistogram->SetYAxisLabel("Frequency"); m_Controls->m_UseDefaultNBinsCheckBox->setEnabled(true); m_Controls->m_JSHistogram->Show(this->m_Controls->m_ShowSubchartCheckBox->isChecked()); this->FillStatisticsTableView(this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); for (const auto& aStatistic: this->m_CalculationThread->GetStatisticsData()) { auto statisticsNode = mitk::DataNode::New(); statisticsNode->SetName(m_Controls->m_SelectedFeatureImageLabel->text().toStdString()); statisticsNode->SetData(aStatistic->Clone()); statisticsNode->SetProperty("helper object", mitk::BoolProperty::New(true)); this->GetDataStorage()->Add(statisticsNode); } } m_CurrentStatisticsValid = true; } } else { m_Controls->m_SelectedMaskLabel->setText("None"); m_Controls->m_ErrorMessageLabel->setText(m_CalculationThread->GetLastErrorMessage().c_str()); m_Controls->m_ErrorMessageLabel->show(); // Clear statistics and histogram this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex(0); m_CurrentStatisticsValid = false; } berry::IPreferencesService* prefService = berry::WorkbenchPlugin::GetDefault()->GetPreferencesService(); m_StylePref = prefService->GetSystemPreferences()->Node(berry::QtPreferences::QT_STYLES_NODE); this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( const std::vector &statistics, const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(image->GetTimeSteps()); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(image->GetTimeSteps() > 1); // Set Checkbox for complete copy of statistic table if(image->GetTimeSteps()>1) { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(true); } else { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(false); this->m_Controls->m_CheckBox4dCompleteTable->setChecked(false); } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); if (statistics.at(t)->GetMaxIndex().size()==3) { mitk::Point3D index, max, min; index[0] = statistics.at(t)->GetMaxIndex()[0]; index[1] = statistics.at(t)->GetMaxIndex()[1]; index[2] = statistics.at(t)->GetMaxIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); index[0] = statistics.at(t)->GetMinIndex()[0]; index[1] = statistics.at(t)->GetMinIndex()[1]; index[2] = statistics.at(t)->GetMinIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } auto statisticsVector = AssembleStatisticsIntoVector(statistics.at(t).GetPointer(), image); unsigned int count = 0; for (const auto& entry : statisticsVector) { auto item = new QTableWidgetItem(entry); this->m_Controls->m_StatisticsTable->setItem(count, t, item); count++; } } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); // make sure the current timestep's column is highlighted (and the correct histogram is displayed) unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), t); this->OnTimeChanged(timeEvent); t = std::min(image->GetTimeSteps() - 1, t); // See bug 18340 /*QString hotspotMean; hotspotMean.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMean(), 0, 'f', decimals)); hotspotMean += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( hotspotMean ) ); QString hotspotMax; hotspotMax.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMax(), 0, 'f', decimals)); hotspotMax += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( hotspotMax ) ); QString hotspotMin; hotspotMin.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMin(), 0, 'f', decimals)); hotspotMin += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( hotspotMin ) );*/ } std::vector QmitkImageStatisticsView::AssembleStatisticsIntoVector(mitk::StatisticsContainer::ConstPointer statistics, mitk::Image::ConstPointer image, bool noVolumeDefined) const { std::vector result; unsigned int decimals = 2; //statistics of higher order should have 5 decimal places because they used to be very small unsigned int decimalsHigherOrderStatistics = 5; if (image->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE || image->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT) { decimals = 5; } result.push_back(GetFormattedString(statistics->GetMean(), decimals)); result.push_back(GetFormattedString(statistics->GetMedian(), decimals)); result.push_back(GetFormattedString(statistics->GetStd(), decimals)); result.push_back(GetFormattedString(statistics->GetRMS(), decimals)); result.push_back(GetFormattedString(statistics->GetMax(), decimals) + " " + GetFormattedIndex(statistics->GetMaxIndex())); result.push_back(GetFormattedString(statistics->GetMin(), decimals) + " " + GetFormattedIndex(statistics->GetMinIndex())); //to prevent large negative values of empty image statistics if (statistics->GetN() != std::numeric_limits::min()) { result.push_back(GetFormattedString(statistics->GetN(), 0)); const mitk::BaseGeometry *geometry = image->GetGeometry(); if (geometry != NULL && !noVolumeDefined) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); double volume = spacing[0] * spacing[1] * spacing[2] * static_cast(statistics->GetN()); result.push_back(GetFormattedString(volume, decimals)); } else { result.push_back("NA"); } } else { result.push_back("NA"); result.push_back("NA"); } result.push_back(GetFormattedString(statistics->GetSkewness(), decimalsHigherOrderStatistics)); result.push_back(GetFormattedString(statistics->GetKurtosis(), decimalsHigherOrderStatistics)); result.push_back(GetFormattedString(statistics->GetUniformity(), decimalsHigherOrderStatistics)); result.push_back(GetFormattedString(statistics->GetEntropy(), decimalsHigherOrderStatistics)); result.push_back(GetFormattedString(statistics->GetMPP(), decimals)); result.push_back(GetFormattedString(statistics->GetUPP(), decimalsHigherOrderStatistics)); return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView(mitk::StatisticsContainer::ConstPointer statistics, const mitk::Image *image) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->AssembleStatisticsIntoVector(statistics, image, true); for (unsigned int i = 0; i< m_PlanarFigureStatistics.size(); i++) { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem(m_PlanarFigureStatistics[i] )); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); this->m_Controls->m_StatisticsTable->setColumnCount(1); for ( int i = 0; i < this->m_Controls->m_StatisticsTable->rowCount(); ++i ) { { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem( "NA" ) ); } } this->m_Controls->m_StatisticsTable->setMinimumHeight(STAT_TABLE_BASE_HEIGHT); } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkImageStatisticsView::OnTimeChanged); // It is sufficient to add the observer to the axial render window since the GeometryTimeEvent // is always triggered by all views. m_TimeObserverTag = renderWindow->GetQmitkRenderWindow("axial")-> GetSliceNavigationController()-> AddObserver(mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); } if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; // The slice navigation controller observer is removed here instead of in the destructor. // If it was called in the destructor, the application would freeze because the view's // destructor gets called after the render windows have been destructed. if ( m_TimeObserverTag != 0 ) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver( m_TimeObserverTag ); } m_TimeObserverTag = 0; } } void QmitkImageStatisticsView::SetFocus() { } std::map QmitkImageStatisticsView::ConvertHistogramToMap(itk::Statistics::Histogram::ConstPointer histogram) const { std::map histogramMap; auto endIt = histogram->End(); auto it = histogram->Begin(); // generating Lists of measurement and frequencies for (; it != endIt; ++it) { double frequency = it.GetFrequency(); double measurement = it.GetMeasurementVector()[0]; histogramMap.emplace(measurement, frequency); } return histogramMap; } std::vector QmitkImageStatisticsView::ConvertIntensityProfileToVector(mitk::IntensityProfile::ConstPointer intensityProfile) const { std::vector intensityProfileList; auto end = intensityProfile->End(); for (auto it = intensityProfile->Begin(); it != end; ++it) { intensityProfileList.push_back(it.GetMeasurementVector()[0]); } return intensityProfileList; } QString QmitkImageStatisticsView::GetFormattedString(double value, unsigned int decimals) const { typedef mitk::StatisticsContainer::RealType RealType; RealType maxVal = std::numeric_limits::max(); if (value == maxVal) { return QString("NA"); } else { return QString("%1").arg(value, 0, 'f', decimals); } } QString QmitkImageStatisticsView::GetFormattedIndex(const vnl_vector& vector) const { if (vector.empty()) { return QString(); } QString formattedIndex("("); for (const auto& entry : vector) { formattedIndex += QString::number(entry); formattedIndex += ","; } formattedIndex.chop(1); formattedIndex += ")"; return formattedIndex; }