diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index 32fc677470..e71358bd70 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,663 +1,664 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include +#include #include #include #include namespace CppUnit { namespace StringHelper { template<> inline std::string toString(const mitk::LabelSetImage::LabelValueVectorType& lvs) { std::ostringstream stream; stream << "["; for (mitk::LabelSetImage::LabelValueVectorType::const_iterator iter = lvs.begin(); iter!=lvs.end(); ++iter) { stream << *iter; if (iter + 1 != lvs.end()) stream << ", "; } stream << "]"; return stream.str(); } template<> inline std::string toString(const std::vector& strings) { std::ostringstream stream; stream << "["; for (std::vector::const_iterator iter = strings.begin(); iter != strings.end(); ++iter) { stream << *iter; if (iter + 1 != strings.end()) stream << ", "; } stream << "]"; return stream.str(); } } } class mitkLabelSetImageTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetImageTestSuite); MITK_TEST(TestInitialize); MITK_TEST(TestClone); MITK_TEST(TestAddLayer); MITK_TEST(TestGetActiveLabelSet); MITK_TEST(TestGetActiveLabel); MITK_TEST(TestInitializeByLabeledImage); MITK_TEST(TestGetLabel); MITK_TEST(TestGetLabelValues); MITK_TEST(TestGetLabelClassNames); MITK_TEST(TestSetUnlabeledLabelLock); MITK_TEST(TestGetTotalNumberOfLabels); MITK_TEST(TestGetNumberOfLabels); MITK_TEST(TestExistsLabel); MITK_TEST(TestExistsGroup); MITK_TEST(TestSetActiveLayer); MITK_TEST(TestRemoveLayer); MITK_TEST(TestRemoveLabels); MITK_TEST(TestEraseLabels); MITK_TEST(TestMergeLabels); MITK_TEST(TestCreateLabelMask); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSetImage::Pointer m_LabelSetImage; int m_LabelAddedEventCount; int m_LabelModifiedEventCount; int m_LabelRemovedEventCount; int m_LabelsChangedEventCount; int m_GroupAddedEventCount; int m_GroupModifiedEventCount; int m_GroupRemovedEventCount; public: void setUp() override { // Create a new labelset image m_LabelSetImage = mitk::LabelSetImage::New(); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); m_LabelSetImage->Initialize(regularImage); this->ResetEvents(); m_LabelSetImage->AddObserver(mitk::LabelAddedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelAddedEventCount); }); m_LabelSetImage->AddObserver(mitk::LabelModifiedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelModifiedEventCount); }); m_LabelSetImage->AddObserver(mitk::LabelRemovedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelRemovedEventCount); }); m_LabelSetImage->AddObserver(mitk::LabelsChangedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelsChangedEventCount); }); m_LabelSetImage->AddObserver(mitk::GroupAddedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupAddedEventCount); }); m_LabelSetImage->AddObserver(mitk::GroupModifiedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupModifiedEventCount); }); m_LabelSetImage->AddObserver(mitk::GroupRemovedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupRemovedEventCount); }); } void tearDown() override { // Delete LabelSetImage m_LabelSetImage = nullptr; } void ResetEvents() { m_LabelAddedEventCount = 0; m_LabelModifiedEventCount = 0; m_LabelRemovedEventCount = 0; m_LabelsChangedEventCount = 0; m_GroupAddedEventCount = 0; m_GroupModifiedEventCount = 0; m_GroupRemovedEventCount = 0; } bool CheckEvents(int lAdd, int lMod, int lRem, int lsC, int gAdd, int gMod, int gRem) { return m_GroupAddedEventCount == gAdd && m_GroupModifiedEventCount == gMod && m_GroupRemovedEventCount == gRem && m_LabelAddedEventCount == lAdd && m_LabelModifiedEventCount == lMod && m_LabelRemovedEventCount == lRem && m_LabelsChangedEventCount == lsC; } void InitializeTestSegmentation() { mitk::Label::Pointer label1 = mitk::Label::New(1, "Label1"); mitk::Label::Pointer label2 = mitk::Label::New(20, "Label2"); mitk::Label::Pointer label22 = mitk::Label::New(22, "Label2"); mitk::Label::Pointer label3 = mitk::Label::New(30, "Label3"); m_LabelSetImage->AddLabel(label1, 0); m_LabelSetImage->AddLayer({ label2, label22, label3 }); m_LabelSetImage->AddLayer(); this->ResetEvents(); } void TestInitialize() { // LabelSet image should always has the pixel type mitk::Label::PixelType CPPUNIT_ASSERT_MESSAGE("LabelSetImage has wrong pixel type", m_LabelSetImage->GetPixelType() == mitk::MakeScalarPixelType()); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); mitk::BaseGeometry::Pointer regularImageGeo = regularImage->GetGeometry(); mitk::BaseGeometry::Pointer labelImageGeo = m_LabelSetImage->GetGeometry(); MITK_ASSERT_EQUAL(labelImageGeo, regularImageGeo, "LabelSetImage has wrong geometry"); // By default one layer should be added CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - no active label should be selected", m_LabelSetImage->GetActiveLabel() == nullptr); } void TestClone() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); mitk::Label::Pointer label3 = mitk::Label::New(); label2->SetName("Label3"); label2->SetValue(300); m_LabelSetImage->AddLabel(label1, 0); m_LabelSetImage->AddLayer({ label2, label3 }); auto clone = m_LabelSetImage->Clone(); MITK_ASSERT_EQUAL(m_LabelSetImage, clone, "LabelSetImage clone is not equal."); } void TestAddLayer() { CPPUNIT_ASSERT_MESSAGE("Number of layers is not zero", m_LabelSetImage->GetNumberOfLayers() == 1); m_LabelSetImage->AddLayer(); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - no active label should be selected", m_LabelSetImage->GetActiveLabel() == nullptr); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0,0,0,0,1,0,0)); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); const auto layerID = m_LabelSetImage->AddLayer({ label1, label2 }); m_LabelSetImage->SetActiveLabel(200); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not two", m_LabelSetImage->GetNumberOfLayers() == 3); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == layerID); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == 200); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 2, 0, 0)); } void TestGetActiveLabelSet() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); mitk::LabelSetImage::ConstLabelVectorType refLayer = { label1, label2 }; unsigned int layerID = m_LabelSetImage->AddLayer(refLayer); m_LabelSetImage->SetActiveLabel(200); auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == 1); CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == m_LabelSetImage->GetActiveLayer()); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(refLayer, activeLayer, 0.00001, true)); } void TestGetActiveLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->AddLabel(label1,0); m_LabelSetImage->AddLabel(label2,0); m_LabelSetImage->SetActiveLabel(1); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value1); m_LabelSetImage->SetActiveLabel(value2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value2); CPPUNIT_ASSERT_MESSAGE("Active Label was not correctly retreived with const getter", const_cast(m_LabelSetImage.GetPointer())->GetActiveLabel()->GetValue() == value2); } void TestInitializeByLabeledImage() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 5", m_LabelSetImage->GetNumberOfLabels(0) == 5); } void TestGetLabel() { mitk::Label::Pointer label1 = mitk::Label::New(1, "Label1"); mitk::Label::Pointer label2 = mitk::Label::New(20,"Label2"); m_LabelSetImage->AddLabel(label1,0); m_LabelSetImage->AddLayer(); m_LabelSetImage->AddLabel(label2,1); this->ResetEvents(); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for active layer", mitk::Equal(*m_LabelSetImage->GetLabel(1), *label1, 0.0001, true)); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", mitk::Equal(*m_LabelSetImage->GetLabel(20), *label2, 0.0001, true)); // Try to get a non existing label mitk::Label *unkownLabel = m_LabelSetImage->GetLabel(1000); CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", unkownLabel == nullptr); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestGetLabelValues() { InitializeTestSegmentation(); auto labels = m_LabelSetImage->GetLabelValuesByGroup(0); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 0", mitk::LabelSetImage::LabelValueVectorType({ 1 }), labels); labels = m_LabelSetImage->GetLabelValuesByGroup(1); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 1", mitk::LabelSetImage::LabelValueVectorType({ 20, 22, 30 }), labels); labels = m_LabelSetImage->GetLabelValuesByGroup(2); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 2", mitk::LabelSetImage::LabelValueVectorType(), labels); CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByGroup(3), mitk::Exception); labels = m_LabelSetImage->GetLabelValuesByName(0, "Label1"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label2\" in group 0", mitk::LabelSetImage::LabelValueVectorType({ 1 }), labels); labels = m_LabelSetImage->GetLabelValuesByName(1, "Label2"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label2\" in group 1", mitk::LabelSetImage::LabelValueVectorType({ 20, 22 }), labels); labels = m_LabelSetImage->GetLabelValuesByName(1, "Label3"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label3\" in group 1", mitk::LabelSetImage::LabelValueVectorType({ 30 }), labels); labels = m_LabelSetImage->GetLabelValuesByName(2, "Label1"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 2", mitk::LabelSetImage::LabelValueVectorType(), labels); labels = m_LabelSetImage->GetLabelValuesByName(0, "unkown"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for unkown name", mitk::LabelSetImage::LabelValueVectorType(), labels); CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByName(3,"invalid"), mitk::Exception); labels = m_LabelSetImage->GetAllLabelValues(); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for unkown name", mitk::LabelSetImage::LabelValueVectorType({1,20,22,30}), labels); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestGetLabelClassNames() { InitializeTestSegmentation(); auto names = m_LabelSetImage->GetLabelClassNames(); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved", std::vector({ "Label1", "Label2", "Label3"}), names); names = m_LabelSetImage->GetLabelClassNamesByGroup(0); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 0", std::vector({ "Label1"}), names); names = m_LabelSetImage->GetLabelClassNamesByGroup(1); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 1", std::vector({"Label2", "Label3" }), names); names = m_LabelSetImage->GetLabelClassNamesByGroup(2); CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 2", std::vector(), names); CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByGroup(3), mitk::Exception); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestSetUnlabeledLabelLock() { auto locked = m_LabelSetImage->GetUnlabeledLabelLock(); CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock default state", locked == false); m_LabelSetImage->SetUnlabeledLabelLock(true); locked = m_LabelSetImage->GetUnlabeledLabelLock(); CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock state", locked == true); } void TestGetTotalNumberOfLabels() { this->InitializeTestSegmentation(); CPPUNIT_ASSERT_MESSAGE( "Wrong total number of labels", m_LabelSetImage->GetTotalNumberOfLabels() == 4); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestGetNumberOfLabels() { this->InitializeTestSegmentation(); CPPUNIT_ASSERT_MESSAGE( "Wrong number of labels in group 0", m_LabelSetImage->GetNumberOfLabels(0) == 1); CPPUNIT_ASSERT_MESSAGE( "Wrong number of labels in group 1", m_LabelSetImage->GetNumberOfLabels(1) == 3); CPPUNIT_ASSERT_MESSAGE( "Wrong number of labels in group 2", m_LabelSetImage->GetNumberOfLabels(2) == 0); CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetNumberOfLabels(3), mitk::Exception); CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestExistsLabel() { mitk::Label::Pointer label = mitk::Label::New(); label->SetName("Label2"); mitk::Label::PixelType value = 200; label->SetValue(value); m_LabelSetImage->AddLayer(); m_LabelSetImage->AddLabel(label,1); m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Existing label was not found", m_LabelSetImage->ExistLabel(value) == true); CPPUNIT_ASSERT_MESSAGE("Non existing label was found", m_LabelSetImage->ExistLabel(10000) == false); } void TestExistsGroup() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); m_LabelSetImage->AddLayer({label1, label2}); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(20) == false); } void TestSetActiveLayer() { // Cache active layer auto refActiveLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); // Add new layer mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); mitk::LabelSetImage::ConstLabelVectorType newlayer = { label1, label2 }; unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); // Set initial layer as active layer m_LabelSetImage->SetActiveLayer(0); auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(refActiveLayer, activeLayer, 0.00001, true)); // Set previously added layer as active layer m_LabelSetImage->SetActiveLayer(layerID); activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(newlayer, activeLayer, 0.00001, true)); // Set a non existing layer as active layer - nothing should change m_LabelSetImage->SetActiveLayer(10000); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(newlayer, activeLayer, 0.00001, true)); } void TestRemoveLayer() { // Cache active layer auto refActiveLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); // Add new layers m_LabelSetImage->AddLayer(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); mitk::LabelSetImage::ConstLabelVectorType newlayer = { label1, label2 }; unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); m_LabelSetImage->SetActiveLayer(layerID); auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(newlayer, activeLayer, 0.00001, true)); m_LabelSetImage->RemoveGroup(1); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(2) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); m_LabelSetImage->RemoveGroup(1); activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); CPPUNIT_ASSERT_MESSAGE("Wrong active layer", mitk::Equal(refActiveLayer, activeLayer, 0.00001, true)); m_LabelSetImage->RemoveGroup(0); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 0); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == false); CPPUNIT_ASSERT_THROW_MESSAGE("GetActiveLayers does not fail although all layer have been removed", m_LabelSetImage->GetActiveLayer(), mitk::Exception); } void TestRemoveLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->RemoveLabel(1); std::vector labelsToBeRemoved; labelsToBeRemoved.push_back(3); labelsToBeRemoved.push_back(7); m_LabelSetImage->RemoveLabels(labelsToBeRemoved); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels after some have been removed", m_LabelSetImage->GetNumberOfLabels(0) == 2); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestEraseLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->EraseLabel(1); std::vector labelsToBeErased; labelsToBeErased.push_back(3); labelsToBeErased.push_back(7); m_LabelSetImage->EraseLabels(labelsToBeErased); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels since none have been removed", m_LabelSetImage->GetNumberOfLabels(0) == 5); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestMergeLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 6 does not exist after initialization", m_LabelSetImage->ExistLabel(6) == true); // Merge label 7 with label 6. Result should be that label 7 is not present anymore. m_LabelSetImage->MergeLabel(6, 7); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); // Count all pixels with value 6 = 507 // Count all pixels with value 7 = 823 // Check if merged label has 507 + 823 = 1330 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 1330); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); CPPUNIT_ASSERT_MESSAGE("Label with ID 5 does not exist after initialization", m_LabelSetImage->ExistLabel(5) == true); // Merge labels 5 and 6 with 3. Result should be that labels 5 and 6 are not present anymore. std::vector vectorOfSourcePixelValues{ 5, 6 }; m_LabelSetImage->MergeLabels(3, vectorOfSourcePixelValues); // Values within the image are 0, 1, 3, 5, 6, 7 - New Max value should be 3 CPPUNIT_ASSERT_MESSAGE("Labels with value 5 and 6 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 3); // Count all pixels with value 3 = 1893 // Count all pixels with value 5 = 2143 // Count all pixels with value 6 = 1330 // Check if merged label has 1893 + 2143 + 1330 = 5366 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 5366); } void TestCreateLabelMask() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - auto labelMask = m_LabelSetImage->CreateLabelMask(6); + auto labelMask = mitk::CreateLabelMask(m_LabelSetImage,6); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(labelMask); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); auto maskImage = cropFilter->GetOutput(); // Count all pixels with value 6 = 507 CPPUNIT_ASSERT_MESSAGE("Label mask not correctly created", maskImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 507); } }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImage) diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index e073f30ec0..1e2e227b7c 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1662 +1,1635 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include #include #include #include #include #include #include #include #include #include namespace mitk { template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UNLABELED_VALUE = 0; mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLabelValue(0), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLabelValue(other.m_ActiveLabelValue), m_LookupTable(other.m_LookupTable->Clone()), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { GroupIndexType i = 0; for (auto groupImage : other.m_LayerContainer) { this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); i++; } m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto [value, label] : m_LabelMap) { this->ReleaseLabel(label); } m_LabelMap.clear(); } unsigned int mitk::LabelSetImage::GetActiveLayer() const { if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/subtract one more because we have not removed the layer yet, thus the group count is to 1 high. newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; } } if (activeIndex == indexToDelete) { // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; //copy the image content of the upcoming new active layer; SetActiveLayer(newActiveIndexBeforeDeletion); } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; { std::lock_guard guard(m_LabelNGroupMapsMutex); // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->InvokeEvent(LabelRemovedEvent(labelValue)); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); } //update old indexes in m_GroupToLabelMap to new layer indexes for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->InvokeEvent(LabelsChangedEvent(relevantLabels)); this->InvokeEvent(GroupRemovedEvent(indexToDelete)); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const { LabelValueVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UNLABELED_VALUE }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image* layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); if (nullptr == layerImage) mitkThrow() << "Cannot add group. Passed group image is nullptr."; if (!Equal(*(this->GetTimeGeometry()), *(layerImage->GetTimeGeometry()), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) mitkThrow() << "Cannot add group. Passed group image has not the same geometry like segmentation."; if (layerImage->GetPixelType() != MakePixelType()) mitkThrow() << "Cannot add group. Passed group image has incorrect pixel type. Only LabelValueType is supported. Invalid pixel type: "<< layerImage->GetPixelType().GetTypeAsString(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); m_GroupToLabelMap.push_back({}); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); } this->Modified(); this->InvokeEvent(GroupAddedEvent(newGroupID)); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-existing group. Invalid group id: "< guard(m_LabelNGroupMapsMutex); oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->InvokeEvent(LabelRemovedEvent(labelID)); } } this->InvokeEvent(LabelsChangedEvent(oldLabels)); this->InvokeEvent(GroupModifiedEvent(groupID)); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID].GetPointer(); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UNLABELED_VALUE) { auto groupID = this->GetGroupIndexOfLabel(label); if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(sourcePixelValue)); this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ sourcePixelValue, pixelValue })); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->InvokeEvent(LabelModifiedEvent(vectorOfSourcePixelValues[idx])); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(pixelValue)); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->InvokeEvent(LabelsChangedEvent(modifiedValues)); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { GroupIndexType groupID = 0; { std::lock_guard guard(m_LabelNGroupMapsMutex); if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } } this->InvokeEvent(LabelRemovedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of instance. RemoveLabelFromMap was called for unknown label id. invalid label id: "<GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->InvokeEvent(LabelsChangedEvent(vectorOfLabelPixelValues)); } void mitk::LabelSetImage::EraseLabel(LabelValueType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetGroupImage(groupID); if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); Modified(); } void mitk::LabelSetImage::EraseLabels(const LabelValueVectorType& labelValues) { for (auto labelValue : labelValues) { this->EraseLabel(labelValue); } } mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { auto usedValues = this->GetUsedLabelValues(); return usedValues.back() + 1; } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { mitk::Label::Pointer newLabel = label; { std::lock_guard guard(m_LabelNGroupMapsMutex); unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; if (addAsClone) newLabel = label->Clone(); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = this->GetUnusedLabelValue(); newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); } this->InvokeEvent(LabelAddedEvent(newLabel->GetValue())); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(LabelValueType pixelValue, const std::string& name, const mitk::Color& color) { std::shared_lock guard(m_LabelNGroupMapsMutex); mitk::Label* label = GetLabel(pixelValue); if (nullptr == label) mitkThrow() << "Cannot rename label.Unknown label value provided. Unknown label value:" << pixelValue; label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, pixelValue); } } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { if (layer >= m_Groups.size()) mitkThrow() << "Cannot get number of labels in group. Group is unknown. Invalid index:" << layer; return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } -mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index) -{ - if (!this->ExistLabel(index)) mitkThrow() << "Error, cannot return label mask. Label ID is invalid. Invalid ID: " << index; - - auto mask = mitk::Image::New(); - - // mask->Initialize(this) does not work here if this label set image has a single slice, - // since the mask would be automatically flattened to a 2-d image, whereas we expect the - // original dimension of this label set image. Hence, initialize the mask more explicitly: - mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); - mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); - - ClearImageBuffer(mask); - - const auto groupID = this->GetGroupIndexOfLabel(index); - - auto destinationLabel = this->GetLabel(index)->Clone(); - destinationLabel->SetValue(1); - - TransferLabelContent(this->GetGroupImage(groupID), mask.GetPointer(), - {destinationLabel}, - LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, - { { index, destinationLabel->GetValue()}}, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - - return mask; -} - void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (Exception& e) { mitkReThrow(e) << "Could not initialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UNLABELED_VALUE!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UNLABELED_VALUE) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unknown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of RegisterLabel with a nullptr."; UpdateLookupTable(label->GetValue()); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); m_LabelModEventGuardMap.emplace(label->GetValue(), ITKEventObserverGuard(label, itk::ModifiedEvent(), command)); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; m_LabelModEventGuardMap.erase(label->GetValue()); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); this->InvokeEvent(LabelsChangedEvent(values)); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const { auto labels = this->GetConstLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); } void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->InvokeEvent(LabelModifiedEvent(label->GetValue())); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { return finding->second == groupIndex; } return false; } bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LayerContainer.size(); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UNLABELED_VALUE) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { LabelVectorType result; for (const auto& labelValue : labelValues) { auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const { ConstLabelVectorType result; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; return m_GroupToLabelMap[index]; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, const std::string_view name) const { LabelValueVectorType result; auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } std::vector mitk::LabelSetImage::GetLabelClassNames() const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector(names.begin(), names.end()); } std::vector mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector(names.begin(), names.end()); } void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); } void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); } void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, const std::string_view name, bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); } void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetAllLabelValues(), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, const std::string_view name, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // m_LookupTable; const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tables not equal."; return returnValue; ; } // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are not equal."; return false; } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // label data auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const ConstLabelMapType& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabels(destinationLabels), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, timeStep, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index 0bec083a63..9fcd54594e 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,670 +1,667 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include #include #include #include #include #include namespace mitk { /** @brief LabelSetImage class for handling labels and layers in a segmentation session. * * Events that are potentially send by the class in regard to groups or labels: * - LabelAddedEvent is emitted whenever a new label has been added. * - LabelModifiedEvent is emitted whenever a label has been modified. * - LabelRemovedEvent is emitted whenever a label has been removed. * - LabelsChangedEvent is emitted when labels are changed (added, removed, modified). In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will * only be sent once (compared to LabelRemoved or LabelModified). * - GroupAddedEvent is emitted whenever a new group has been added. * - GroupModifiedEvent is emitted whenever a group has been modified. * - GroupRemovedEvent is emitted whenever a label has been removed. * * @ingroup Data */ class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; using ConstLabelVectorType = ConstLabelVector; using LabelVectorType = LabelVector; using LabelValueVectorType = std::vector; const static LabelValueType UNLABELED_VALUE; /** \brief Adds a label instance to a group of the multi label image. * @remark By default, if the pixel value of the label is already used in the image, the label * will get a new none conflicting value assigned. This can be controlled by correctLabelValue. * @param label Instance of an label that should be added or used as template * @param groupID The id of the group the label should be added to. * @param addAsClone Flag that controls, if the passed instance should be added (false; the image will then take ownership, * be aware that e.g. event observers will be added) * a clone of the instance (true). * @param correctLabelValue Flag that controls, if the value of the passed label should be correct, if this value is already used in * the multi label image. True: Conflicting values will be corrected, be assigning a none conflicting value. False: If the value is conflicting * an exception will be thrown. * @return Instance of the label as it was added to the label set. * @pre label must point to a valid instance. * @pre If correctLabelValue==false, label value must be non conflicting. * @pre groupID must indicate an existing group. */ mitk::Label* AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone = true, bool correctLabelValue = true); /** \brief Adds a new label to a group of the image by providing name and color. * @param name (Class) name of the label instance that should be added. * @param color Color of the new label instance. * @param groupID The id of the group the label should be added to. * @return Instance of the label as it was added to the label set. * @pre groupID must indicate an existing group. */ mitk::Label* AddLabel(const std::string& name, const Color& color, GroupIndexType groupID); /** \brief allows to adapt name and color of a certain label * @param labelValue Value of the label that should be changed * @param name New name for the label * @param color New color for the label * @pre Indicated label value must exist. */ void RenameLabel(LabelValueType labelValue, const std::string& name, const Color& color); /** * @brief Removes the label with the given value. * The label is removed from the labelset and * the pixel with the value of the label are set to UNLABELED_VALUE. * @param labelValue the pixel value of the label to be removed. If the label is unknown, * the method will return without doing anything. */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * The label is removed from the labelset and * the pixel with the value of the label are set to UNLABELED_VALUE. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param labelValue the pixel value of the label that will be erased from the labelset image * @pre labelValue must exist. */ void EraseLabel(LabelValueType labelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param labelValues the list of pixel values of the labels * that will be erased from the labelset image * @pre label values must exist */ void EraseLabels(const LabelValueVectorType& labelValues); /** * @brief Removes a whole group including all its labels. * @remark with removing a group all groups with greater index will be re-indexed to * close the gap. Hence externally stored spatial group indices may become invalid. * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre group index must be valid. */ void RemoveGroup(GroupIndexType group); /** \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ bool ExistLabel(LabelValueType value) const; /** * @brief Checks if a label belongs in a certain spatial group * @param value the label value * @param groupIndex Index of the spacial group which should be checked for the label * @return true if the label exists otherwise false */ bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; /** * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. * * @param index Group index of the group that should be checked for existence. */ bool ExistGroup(GroupIndexType index) const; /** Returns the group id of the based label value. * @pre label value must exists. */ GroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the label instance if defined in the segmentation, otherwise nullptr. */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns a vector with pointers to all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); /** Returns a vector of all label values currently defined in the MultiLabelSegmentation instance.*/ const LabelValueVectorType GetAllLabelValues() const; /** @brief Returns a vector with pointers to all labels in the MultiLabelSegmentation indicated * by the passed label value vector. * @param labelValues Vector of values of labels that should be returned. * @param ignoreMissing If true (default), unknown labels Will be skipped in the result. If false, * an exception will be raised, if a label is requested. */ const LabelVectorType GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = true); /** @brief Returns a vector with const pointers to all labels in the MultiLabelSegmentation indicated * by the passed label value vector. * For details see GetLabelsByValue(); */ const ConstLabelVectorType GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = false) const; /** Helper function that can be used to extract a vector of label values of a vector of label instance pointers.*/ static LabelValueVectorType ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels); /** Helper function that can be used to extract a vector of label values are vector of label instances.*/ static LabelValueVectorType ExtractLabelValuesFromLabelVector(const LabelVectorType& labels); /** Helper function that converts a given vector of label instance pointers into a vector of const pointers.*/ static ConstLabelVectorType ConvertLabelVectorConst(const LabelVectorType& labels); /** * @brief Returns a vector of all label values located on the specified group. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @return the respective vector of label values. * @pre group index must exist. */ const LabelValueVectorType GetLabelValuesByGroup(GroupIndexType index) const; /** * @brief Returns a vector of all label values located on the specified group having a certain name. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @param name Name of the label instances one is looking for. * @return the respective vector of label values. * @pre group index must exist. */ const LabelValueVectorType GetLabelValuesByName(GroupIndexType index, const std::string_view name) const; /** * Returns a vector with (class) names of all label instances used in the segmentation (over all groups) */ std::vector GetLabelClassNames() const; /** * Returns a vector with (class) names of all label instances present in a certain group. * @param index ID of the group, for which the label class names should be returned * @pre Indicated group must exist. */ std::vector GetLabelClassNamesByGroup(GroupIndexType index) const; /** Helper that returns an unused label value, that could be used e.g. if one wants to define a label externally * before adding it. * @return A label value currently not in use. * @remark is no unused label value can be provided an exception will be thrown.*/ LabelValueType GetUnusedLabelValue() const; itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); /** Set the visibility of all label instances accordingly to the passed state. */ void SetAllLabelsVisible(bool visible); /** Set the visibility of all label instances in a group accordingly to the passed state. * @pre The specified group must exist. */ void SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible); /** Set the visibility of all label instances In a group with a given class name * accordingly to the passed state. * @pre The specified group must exist. */ void SetAllLabelsVisibleByName(GroupIndexType group, const std::string_view name, bool visible); /** Returns the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Set the lock state of all label instances accordingly to the passed state. */ void SetAllLabelsLocked(bool locked); /** Set the lock state of all label instances in a group accordingly to the passed state. * @pre The specified group must exist. */ void SetAllLabelsLockedByGroup(GroupIndexType group, bool locked); /** Set the lock state of all label instances In a group with a given class name * accordingly to the passed state. * @pre The specified group must exist. */ void SetAllLabelsLockedByName(GroupIndexType group, const std::string_view name, bool locked); /** * \brief Replaces the labels of a group with a given vector of labels. * * @remark The passed label instances will be cloned before added to ensure clear ownership * of the new labels. * @remark The pixel content of the old labels will not be removed. * @param groupID The index of the group that should have its labels replaced * @param newLabels The vector of new labels * @pre Group that should be replaced must exist. * @pre new label values must not be used in other groups. */ void ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& newLabels); void ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& newLabels); /** Returns the pointer to the image that contains the labeling of the indicate group. *@pre groupID must reference an existing group.*/ mitk::Image* GetGroupImage(GroupIndexType groupID); /** Returns the pointer to the image that contains the labeling of the indicate group. *@pre groupID must reference an existing group.*/ const mitk::Image* GetGroupImage(GroupIndexType groupID) const; itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable); void SetLookupTable(LookupTable* lut); /** Updates the lookup table for a label indicated by the passed label value using the color of the label. * @pre labelValue must exist. */ void UpdateLookupTable(PixelType pixelValue); protected: void OnLabelModified(const Object* sender, const itk::EventObject&); /** Helper to ensure that the maps are correctly populated for a new label instance.*/ void AddLabelToMap(LabelValueType labelValue, Label* label, GroupIndexType groupID); void RemoveLabelFromMap(LabelValueType labelValue); /** Helper to ensure label events are correctly connected and lookup table is updated for a new label instance.*/ void RegisterLabel(Label* label); /** Helper to ensure label events are unregistered.*/ void ReleaseLabel(Label* label); /** Helper class used internally to apply lambda functions to the labels specified by the passed label value vector. */ void ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda); /** Helper class used internally to for visiting the labels specified by the passed label value vector * with the lambda function. */ void VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const; LabelValueType m_ActiveLabelValue; private: using LabelMapType = std::map; /** Dictionary that holds all known labels (label value is the key).*/ LabelMapType m_LabelMap; using GroupNameVectorType = std::vector; /** Vector storing the names of all groups. If a group has no user name defined, string is empty.*/ GroupNameVectorType m_Groups; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::vector; /* Dictionary that maps between group id (key) and label values in the group (vector of label value).*/ GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; /* Dictionary that maps between label value (key) and group id (value)*/ LabelToGroupMapType m_LabelToGroupMap; using LabelEventGuardMapType = std::map; LabelEventGuardMapType m_LabelModEventGuardMap; LookupTable::Pointer m_LookupTable; /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; /** Mutex used to secure manipulations of the internal state of label and group maps.*/ std::shared_mutex m_LabelNGroupMapsMutex; public: /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief removes all pixel content form the active layer.*/ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues); /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer * @pre at least on group must exist. */ unsigned int GetActiveLayer() const; Label* GetActiveLabel(); const Label* GetActiveLabel() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; - mitk::Image::Pointer CreateLabelMask(PixelType index); - - /** * @brief Initialize a new mitk::LabelSetImage by a given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * an exception will be raised. * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); void MaskStamp(mitk::Image *mask, bool forceOverwrite); void SetActiveLayer(unsigned int layer); void SetActiveLabel(LabelValueType label); unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labels Labels that will be added to the new layer if provided * \return the layer ID of the new layer */ GroupIndexType AddLayer(ConstLabelVector labels = {}); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labels labels that will be cloned and added to the new layer if provided * \return the layer ID of the new layer * \pre layerImage must be valid instance * \pre layerImage needs to have the same geometry then the segmentation * \pre layerImage must have the pixel value equal to LabelValueType. */ GroupIndexType AddLayer(mitk::Image* layerImage, ConstLabelVector labels = {}); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, LabelValueType index); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values. returns a sorted list of all labels (including the value for Unlabeled pixels..*/ LabelValueVectorType GetUsedLabelValues() const; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** * @brief Equal A function comparing two vectors of labels for being equal in data * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - Labels in vector * * @param rightHandSide An vector of labels to be compared * @param leftHandSide An vector of labels to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage::ConstLabelVectorType& leftHandSide, const mitk::LabelSetImage::ConstLabelVectorType& rightHandSide, ScalarType eps, bool verbose); /** temporary namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a label value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } using LabelValueMappingVector = std::vector < std::pair >; /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific time step. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transferring), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific time step. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made in-place for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelVector Reference to the vector of labels (incl. lock states) in the destination image. Unknown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transferring), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelVector must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelVector must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabelVector, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UNLABELED_VALUE, mitk::Label::PixelType destinationBackground = LabelSetImage::UNLABELED_VALUE, bool destinationBackgroundLocked = false, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabelVector, mitk::Label::PixelType sourceBackground = LabelSetImage::UNLABELED_VALUE, mitk::Label::PixelType destinationBackground = LabelSetImage::UNLABELED_VALUE, bool destinationBackgroundLocked = false, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.cpp b/Modules/Multilabel/mitkLabelSetImageConverter.cpp index 9373af8bd1..e18723bbc6 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageConverter.cpp @@ -1,298 +1,350 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include template static void ConvertLabelSetImageToImage(const itk::Image *, mitk::LabelSetImage::ConstPointer labelSetImage, mitk::Image::Pointer &image) { typedef itk::Image ImageType; typedef itk::ComposeImageFilter ComposeFilterType; typedef itk::ImageDuplicator DuplicatorType; auto numberOfLayers = labelSetImage->GetNumberOfLayers(); if (numberOfLayers > 1) { auto vectorImageComposer = ComposeFilterType::New(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerImage = mitk::ImageToItkImage( labelSetImage->GetGroupImage(layer)); vectorImageComposer->SetInput(layer, layerImage); } vectorImageComposer->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(vectorImageComposer->GetOutput())->Clone(); } else { auto layerImage = mitk::ImageToItkImage(labelSetImage); auto duplicator = DuplicatorType::New(); duplicator->SetInputImage(layerImage); duplicator->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(duplicator->GetOutput())->Clone(); } } mitk::Image::Pointer mitk::ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage) { Image::Pointer image; if (labelSetImage->GetNumberOfLayers() > 0) { if (labelSetImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(labelSetImage, ::ConvertLabelSetImageToImage, 4, (labelSetImage, image)); } else { AccessByItk_2(labelSetImage->GetGroupImage(0), ::ConvertLabelSetImageToImage, labelSetImage, image); } image->SetTimeGeometry(labelSetImage->GetTimeGeometry()->Clone()); } return image; } template static void SplitVectorImage(const itk::VectorImage* image, std::vector& result) { typedef itk::VectorImage VectorImageType; typedef itk::Image ImageType; typedef itk::VectorIndexSelectionCastImageFilter VectorIndexSelectorType; auto numberOfLayers = image->GetVectorLength(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerSelector = VectorIndexSelectorType::New(); layerSelector->SetInput(image); layerSelector->SetIndex(layer); layerSelector->Update(); mitk::Image::Pointer layerImage = mitk::GrabItkImageMemoryChannel(layerSelector->GetOutput(), nullptr, nullptr, false); result.push_back(layerImage); } } std::vector mitk::SplitVectorImage(const Image* vecImage) { if (nullptr == vecImage) { mitkThrow() << "Invalid usage; nullptr passed to SplitVectorImage."; } if (vecImage->GetChannelDescriptor().GetPixelType().GetPixelType() != itk::IOPixelEnum::VECTOR) { mitkThrow() << "Invalid usage of SplitVectorImage; passed image is not a vector image. Present pixel type: "<< vecImage->GetChannelDescriptor().GetPixelType().GetPixelTypeAsString(); } std::vector result; if (4 == vecImage->GetDimension()) { AccessVectorFixedDimensionByItk_n(vecImage, ::SplitVectorImage, 4, (result)); } else { AccessVectorPixelTypeByItk_n(vecImage, ::SplitVectorImage, (result)); } for (auto image : result) { image->SetTimeGeometry(vecImage->GetTimeGeometry()->Clone()); } return result; } mitk::LabelSetImage::Pointer mitk::ConvertImageToLabelSetImage(Image::Pointer image) { std::vector groupImages; if (image.IsNotNull()) { if (image->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::IOPixelEnum::VECTOR) { groupImages = SplitVectorImage(image); } else { groupImages.push_back(image); } } auto labelSetImage = ConvertImageVectorToLabelSetImage(groupImages, image->GetTimeGeometry()); return labelSetImage; } mitk::LabelSetImage::Pointer mitk::ConvertImageVectorToLabelSetImage(const std::vector& images, const mitk::TimeGeometry* timeGeometry) { LabelSetImage::Pointer labelSetImage = mitk::LabelSetImage::New(); for (auto& groupImage : images) { if (groupImage== images.front()) { labelSetImage->InitializeByLabeledImage(groupImage); } else { labelSetImage->AddLayer(groupImage); } } labelSetImage->SetTimeGeometry(timeGeometry->Clone()); return labelSetImage; } mitk::LabelSetImage::LabelVectorType mitk::GenerateLabelSetWithMappedValues(const LabelSetImage::ConstLabelVectorType& sourceLabelset, LabelValueMappingVector labelMapping) { LabelSetImage::LabelVectorType result; for (auto oldLabel : sourceLabelset) { auto finding = std::find_if(labelMapping.begin(), labelMapping.end(), [oldLabel](const std::pair& mapping) {return oldLabel->GetValue() == mapping.first; }); if (finding != labelMapping.end()) { auto clonedLabel = oldLabel->Clone(); clonedLabel->SetValue(finding->second); result.push_back(clonedLabel); } } return result; } template void ConvertImageToGroupImageInternal(const SourceImageType* sourceImage, mitk::Image* groupImage, mitk::LabelSetImage::LabelValueVectorType& foundLabels) { using GroupImageType = typename SourceImageType::RebindImageType; typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; auto targetImage = mitk::ImageToItkImage< mitk::LabelSetImage::LabelValueType, SourceImageType::ImageDimension>(groupImage); TargetIteratorType targetIter(targetImage, targetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(sourceImage, sourceImage->GetRequestedRegion()); sourceIter.GoToBegin(); std::set< mitk::LabelSetImage::LabelValueType> detecteValues; while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (sourceValue != mitk::Label::UNLABELED_VALUE) detecteValues.insert(sourceValue); ++sourceIter; ++targetIter; } foundLabels.clear(); foundLabels.insert(foundLabels.begin(), detecteValues.begin(), detecteValues.end()); } mitk::Image::Pointer mitk::ConvertImageToGroupImage(const Image* inputImage, mitk::LabelSetImage::LabelValueVectorType& foundLabels) { if (nullptr == inputImage || inputImage->IsEmpty() || !inputImage->IsInitialized()) mitkThrow() << "Invalid labeled image."; auto result = Image::New(); result->Initialize(mitk::MakePixelType(), *(inputImage->GetTimeGeometry())); try { if (result->GetDimension() == 3) { AccessFixedDimensionByItk_2(inputImage, ConvertImageToGroupImageInternal, 3, result, foundLabels); } else if (result->GetDimension() == 4) { AccessFixedDimensionByItk_2(inputImage, ConvertImageToGroupImageInternal, 4, result, foundLabels); } else { mitkThrow() << result->GetDimension() << "-dimensional group images not yet supported"; } } catch (Exception& e) { mitkReThrow(e) << "Could not initialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } return result; } bool mitk::CheckForLabelValueConflictsAndResolve(const mitk::LabelSetImage::LabelValueVectorType& newValues, mitk::LabelSetImage::LabelValueVectorType& usedLabelValues, mitk::LabelSetImage::LabelValueVectorType& correctedLabelValues) { bool corrected = false; correctedLabelValues.clear(); for (const auto newV : newValues) { auto finding = std::find(usedLabelValues.begin(), usedLabelValues.end(), newV); if (finding == usedLabelValues.end()) { correctedLabelValues.push_back(newV); usedLabelValues.push_back(newV); } else { const auto maxFinding = std::max_element(usedLabelValues.begin(), usedLabelValues.end()); if (*maxFinding == Label::MAX_LABEL_VALUE) mitkThrow() << "Cannot correct label values. All available values are used. A segmentation cannot contain more labels."; const auto correctedV = *(maxFinding)+1; correctedLabelValues.push_back(correctedV); usedLabelValues.push_back(correctedV); corrected = true; } } return corrected; } + +namespace +{ + template + void ClearBufferProcessing(ImageType* itkImage) + { + itkImage->FillBuffer(0); + } + + void ClearImageBuffer(mitk::Image* image) + { + if (image->GetDimension() == 4) + { //remark: this extra branch was added, because LabelSetImage instances can be + //dynamic (4D), but AccessByItk by support only supports 2D and 3D. + //The option to change the CMake default dimensions for AccessByItk was + //dropped (for details see discussion in T28756) + AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); + } + else + { + AccessByItk(image, ClearBufferProcessing); + } + } +} + +mitk::Image::Pointer mitk::CreateLabelMask(const LabelSetImage* segmentation, LabelSetImage::LabelValueType labelValue, bool createBinaryMap) +{ + if (nullptr==segmentation) mitkThrow() << "Error, cannot create label mask. Passed segmentation is nullptr."; + if (!segmentation->ExistLabel(labelValue)) mitkThrow() << "Error, cannot create label mask. Label ID is invalid. Invalid ID: " << labelValue; + + auto mask = mitk::Image::New(); + + // mask->Initialize(segmentation) does not work here if this label set image has a single slice, + // since the mask would be automatically flattened to a 2-d image, whereas we expect the + // original dimension of this label set image. Hence, initialize the mask more explicitly: + mask->Initialize(segmentation->GetPixelType(), segmentation->GetDimension(), segmentation->GetDimensions()); + mask->SetTimeGeometry(segmentation->GetTimeGeometry()->Clone()); + + ClearImageBuffer(mask); + + const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); + + auto destinationLabel = segmentation->GetLabel(labelValue)->Clone(); + if (createBinaryMap) destinationLabel->SetValue(1); + + TransferLabelContent(segmentation->GetGroupImage(groupID), mask.GetPointer(), + { destinationLabel }, + LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, + { { labelValue, destinationLabel->GetValue()} }, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + + return mask; +} diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.h b/Modules/Multilabel/mitkLabelSetImageConverter.h index c21bbce466..a0bacda5b4 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.h +++ b/Modules/Multilabel/mitkLabelSetImageConverter.h @@ -1,45 +1,54 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImageConverter_h #define mitkLabelSetImageConverter_h #include namespace mitk { /** * \brief Convert mitk::LabelSetImage to mitk::Image (itk::VectorImage) */ MITKMULTILABEL_EXPORT Image::Pointer ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage); /** * \brief Convert mitk::Image to mitk::LabelSetImage, templating and differentation between itk::Image and * itk::VectorImage is internal */ MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageToLabelSetImage(Image::Pointer image); MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageVectorToLabelSetImage(const std::vector& images, const TimeGeometry* timeGeometry); MITKMULTILABEL_EXPORT std::vector SplitVectorImage(const Image* vecImage); /** Function takes a vector of labels and transfers all labels as clones with adapted label values to the result vector. The values will be adapted according to the provided mapping (key is the old value, value the new). @remark: Only labels will be transfered, nothing else. So things like message observers or m_ReservedLabelValuesFunctor must be copied explicitly.*/ MITKMULTILABEL_EXPORT LabelSetImage::LabelVectorType GenerateLabelSetWithMappedValues(const LabelSetImage::ConstLabelVectorType&, LabelValueMappingVector labelMapping); MITKMULTILABEL_EXPORT Image::Pointer ConvertImageToGroupImage(const Image* inputImage, mitk::LabelSetImage::LabelValueVectorType& foundLabels); MITKMULTILABEL_EXPORT bool CheckForLabelValueConflictsAndResolve(const mitk::LabelSetImage::LabelValueVectorType& newValues, mitk::LabelSetImage::LabelValueVectorType& usedLabelValues, mitk::LabelSetImage::LabelValueVectorType& correctedLabelValues); + /** Function creates a binary mask representing only the specified label of the multi label segmentation. + * @param segmentation Pointer to the segmentation that is the source for the mask. + * @param labelValue the label that should be extracted. + * @param createBinaryMap indicates if the label pixels should be indicated by the value 1 (createBinaryMap==true) or by the value of the label + * (createBinaryMap==false). + * @pre segmentation must point to a valid instance. + * @pre labelValue must exist in segmentation.*/ + MITKMULTILABEL_EXPORT Image::Pointer CreateLabelMask(const LabelSetImage* segmentation, LabelSetImage::LabelValueType labelValue, bool createBinaryMap = true); + } #endif diff --git a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp index 64efffee41..dec15b7e21 100644 --- a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp +++ b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp @@ -1,325 +1,326 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkShowSegmentationAsSurface.h" #include "mitkManualSegmentationToSurfaceFilter.h" #include "mitkVtkRepresentationProperty.h" #include #include +#include #include namespace mitk { ShowSegmentationAsSurface::ShowSegmentationAsSurface() : m_UIDGeneratorSurfaces("Surface_"), m_IsLabelSetImage(false) { } ShowSegmentationAsSurface::~ShowSegmentationAsSurface() { } void ShowSegmentationAsSurface::Initialize(const NonBlockingAlgorithm *other) { Superclass::Initialize(other); bool syncVisibility(false); if (other) { other->GetParameter("Sync visibility", syncVisibility); } SetParameter("Sync visibility", syncVisibility); SetParameter("Median kernel size", 3u); SetParameter("Apply median", true); SetParameter("Smooth", true); SetParameter("Gaussian SD", 1.5); SetParameter("Decimate mesh", true); SetParameter("Decimation rate", 0.8); SetParameter("Wireframe", false); m_SurfaceNodes.clear(); } bool ShowSegmentationAsSurface::ReadyToRun() { try { Image::Pointer image; GetPointerParameter("Input", image); return image.IsNotNull() && GetGroupNode(); } catch (std::invalid_argument &) { return false; } } bool ShowSegmentationAsSurface::ThreadedUpdateFunction() { Image::Pointer image; GetPointerParameter("Input", image); bool smooth(true); GetParameter("Smooth", smooth); bool applyMedian(true); GetParameter("Apply median", applyMedian); bool decimateMesh(true); GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize(3); GetParameter("Median kernel size", medianKernelSize); double gaussianSD(1.5); GetParameter("Gaussian SD", gaussianSD); double reductionRate(0.8); GetParameter("Decimation rate", reductionRate); MITK_INFO << "Creating polygon model with smoothing " << smooth << " gaussianSD " << gaussianSD << " median " << applyMedian << " median kernel " << medianKernelSize << " mesh reduction " << decimateMesh << " reductionRate " << reductionRate; auto labelSetImage = dynamic_cast(image.GetPointer()); if (nullptr != labelSetImage) { const auto labels = labelSetImage->GetLabels(); for (auto label : labels) { - auto labelImage = labelSetImage->CreateLabelMask(label->GetValue()); + auto labelImage = CreateLabelMask(labelSetImage, label->GetValue()); if (labelImage.IsNull()) continue; auto labelSurface = this->ConvertBinaryImageToSurface(labelImage); if (labelSurface.IsNull()) continue; auto* polyData = labelSurface->GetVtkPolyData(); if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) { MITK_WARN << "Label \"" << label->GetName() << "\" didn't produce any smoothed surface data (try again without smoothing)."; continue; } auto node = DataNode::New(); node->SetData(labelSurface); node->SetColor(label->GetColor()); node->SetName(label->GetName()); m_SurfaceNodes.push_back(node); } } else { auto surface = this->ConvertBinaryImageToSurface(image); if (surface.IsNotNull()) { auto* polyData = surface->GetVtkPolyData(); if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) { MITK_WARN << "Could not produce smoothed surface data (try again without smoothing)."; } else { auto node = DataNode::New(); node->SetData(surface); m_SurfaceNodes.push_back(node); } } } m_IsLabelSetImage = nullptr != labelSetImage; return true; } void ShowSegmentationAsSurface::ThreadedUpdateSuccessful() { for (const auto &node : m_SurfaceNodes) { bool wireframe = false; GetParameter("Wireframe", wireframe); if (wireframe) { auto representation = dynamic_cast(node->GetProperty("material.representation")); if (nullptr != representation) representation->SetRepresentationToWireframe(); } node->SetProperty("opacity", FloatProperty::New(0.3f)); node->SetProperty("line width", FloatProperty::New(1.0f)); node->SetProperty("scalar visibility", BoolProperty::New(false)); auto name = node->GetName(); auto groupNode = this->GetGroupNode(); if (!m_IsLabelSetImage) { if ((name.empty() || DataNode::NO_NAME_VALUE() == name) && nullptr != groupNode) name = groupNode->GetName(); if (name.empty()) name = "Surface"; } bool smooth = true; GetParameter("Smooth", smooth); if (smooth) name.append(" (smoothed)"); node->SetName(name); if (!m_IsLabelSetImage) { auto colorProp = groupNode->GetProperty("color"); if (nullptr != colorProp) { node->ReplaceProperty("color", colorProp->Clone()); } else { node->SetProperty("color", ColorProperty::New(1.0, 1.0, 0.0)); } } bool showResult = true; GetParameter("Show result", showResult); bool syncVisibility = false; GetParameter("Sync visibility", syncVisibility); auto visibleProp = groupNode->GetProperty("visible"); if (nullptr != visibleProp && syncVisibility) { node->ReplaceProperty("visible", visibleProp->Clone()); } else { node->SetProperty("visible", BoolProperty::New(showResult)); } if (!m_IsLabelSetImage) { Image::Pointer image; GetPointerParameter("Input", image); if (image.IsNotNull()) { auto organTypeProp = image->GetProperty("organ type"); if (nullptr != organTypeProp) node->GetData()->SetProperty("organ type", organTypeProp); } } this->InsertBelowGroupNode(node); } Superclass::ThreadedUpdateSuccessful(); } Surface::Pointer ShowSegmentationAsSurface::ConvertBinaryImageToSurface(Image::Pointer binaryImage) { bool smooth = true; GetParameter("Smooth", smooth); bool applyMedian = true; GetParameter("Apply median", applyMedian); bool decimateMesh = true; GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize = 3; GetParameter("Median kernel size", medianKernelSize); double gaussianSD = 1.5; GetParameter("Gaussian SD", gaussianSD); double reductionRate = 0.8; GetParameter("Decimation rate", reductionRate); auto filter = ManualSegmentationToSurfaceFilter::New(); filter->SetInput(binaryImage); filter->SetThreshold(0.5); filter->SetUseGaussianImageSmooth(smooth); filter->SetSmooth(smooth); filter->SetMedianFilter3D(applyMedian); if (smooth) { filter->InterpolationOn(); filter->SetGaussianStandardDeviation(gaussianSD); } if (applyMedian) filter->SetMedianKernelSize(medianKernelSize, medianKernelSize, medianKernelSize); // Fix to avoid VTK warnings (see T5390) if (binaryImage->GetDimension() > 3) decimateMesh = false; if (decimateMesh) { filter->SetDecimate(ImageToSurfaceFilter::QuadricDecimation); filter->SetTargetReduction(reductionRate); } else { filter->SetDecimate(ImageToSurfaceFilter::NoDecimation); } filter->UpdateLargestPossibleRegion(); auto surface = filter->GetOutput(); auto polyData = surface->GetVtkPolyData(); if (nullptr == polyData) throw std::logic_error("Could not create polygon model"); polyData->SetVerts(nullptr); polyData->SetLines(nullptr); if (smooth || applyMedian || decimateMesh) { auto normals = vtkSmartPointer::New(); normals->AutoOrientNormalsOn(); normals->FlipNormalsOff(); normals->SetInputData(polyData); normals->Update(); surface->SetVtkPolyData(normals->GetOutput()); } else { surface->SetVtkPolyData(polyData); } return surface; } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp index 9b80e30d37..410fd5f329 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp @@ -1,555 +1,556 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include #include #include #include +#include #include #include #include #include // Qmitk #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include // itk #include #include QmitkMultiLabelManager::QmitkMultiLabelManager(QWidget *parent) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelManagerControls), m_Completer(nullptr), m_ProcessingManualSelection(false), m_DataStorage(nullptr) { m_Controls->setupUi(this); m_Controls->labelSearchBox->setAlwaysShowClearIcon(true); m_Controls->labelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls->labelSearchBox->setCompleter(m_Completer); m_Controls->labelInspector->SetAllowLabelModification(true); connect(m_Controls->labelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); // See T29549 m_Controls->labelSearchBox->hide(); m_Controls->btnSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); m_Controls->btnAddLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg"))); m_Controls->btnAddInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg"))); m_Controls->btnAddGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_add.svg"))); m_Controls->btnRemoveLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg"))); m_Controls->btnRemoveInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg"))); m_Controls->btnRemoveGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg"))); connect(m_Controls->btnAddLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); connect(m_Controls->btnRemoveLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabel); connect(m_Controls->btnAddInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); connect(m_Controls->btnRemoveInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabelInstance); connect(m_Controls->btnAddGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewGroup); connect(m_Controls->btnRemoveGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveGroup); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnSavePreset); connect(m_Controls->btnLoadPreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnLoadPreset); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::GoToLabel, this, &QmitkMultiLabelManager::OnGoToLabel); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::LabelRenameRequested, this, &QmitkMultiLabelManager::OnLabelRenameRequested); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::OnSelectedLabelChanged); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::ModelUpdated, this, &QmitkMultiLabelManager::OnModelUpdated); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::SegmentationChanged, this, &QmitkMultiLabelManager::OnSegmentationChanged); auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkMultiLabelManager::OnRenameLabelShortcutActivated); auto* newLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_A), this); connect(newLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); this->UpdateControls(); } QmitkMultiLabelManager::~QmitkMultiLabelManager() { this->SetMultiLabelSegmentation(nullptr); delete m_Controls; } QmitkMultiLabelManager::LabelValueVectorType QmitkMultiLabelManager::GetSelectedLabels() const { return m_Controls->labelInspector->GetSelectedLabels(); } void QmitkMultiLabelManager::OnRenameLabelShortcutActivated() { auto selectedLabels = this->GetSelectedLabels(); for (auto labelValue : selectedLabels) { auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(labelValue); emit LabelRenameRequested(currentLabel, true); } } void QmitkMultiLabelManager::OnSelectedLabelChanged(const LabelValueVectorType& labels) { this->UpdateControls(); if (labels.empty() || labels.size() > 1) return; emit CurrentSelectionChanged(labels); } QStringList &QmitkMultiLabelManager::GetLabelStringList() { return m_LabelStringList; } void QmitkMultiLabelManager::SetDefaultLabelNaming(bool defaultLabelNaming) { this->m_Controls->labelInspector->SetDefaultLabelNaming(defaultLabelNaming); } void QmitkMultiLabelManager::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { this->m_Controls->labelInspector->SetSelectedLabels(selectedLabels); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->m_Controls->labelInspector->SetSelectedLabel(selectedLabel); UpdateControls(); } void QmitkMultiLabelManager::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { this->m_Controls->labelInspector->SetMultiLabelSegmentation(segmentation); //Update of controls and observers is done in OnSegmentationChanged() // which is triggered by the inspector when setting the segmentation or node } mitk::LabelSetImage* QmitkMultiLabelManager::GetMultiLabelSegmentation() const { return this->m_Controls->labelInspector->GetMultiLabelSegmentation(); } void QmitkMultiLabelManager::SetMultiLabelNode(mitk::DataNode* node) { this->m_Controls->labelInspector->SetMultiLabelNode(node); } mitk::DataNode* QmitkMultiLabelManager::GetMultiLabelNode() const { return this->m_Controls->labelInspector->GetMultiLabelNode(); } void QmitkMultiLabelManager::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkMultiLabelManager::OnSearchLabel() { //std::string text = m_Controls->labelSearchBox->text().toStdString(); //int pixelValue = -1; //int row = -1; //for (int i = 0; i < m_Controls->m_LabelSetTableWidget->rowCount(); ++i) //{ // if (m_Controls->m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) // { // pixelValue = m_Controls->m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); // row = i; // break; // } //} //if (pixelValue == -1) //{ // return; //} //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //QTableWidgetItem *nameItem = m_Controls->m_LabelSetTableWidget->item(row, NAME_COL); //if (!nameItem) //{ // return; //} //m_Controls->m_LabelSetTableWidget->clearSelection(); //m_Controls->m_LabelSetTableWidget->selectRow(row); //m_Controls->m_LabelSetTableWidget->scrollToItem(nameItem); //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //this->WaitCursorOn(); //mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); //m_ToolManager->WorkingDataChanged(); //if (pos.GetVnlVector().max_value() > 0.0) //{ // emit goToLabel(pos); //} //else //{ // GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); // mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); // emit goToLabel(pos); //} //this->WaitCursorOff(); } void QmitkMultiLabelManager::UpdateControls() { bool hasWorkingData = this->GetMultiLabelSegmentation() != nullptr; auto labels = this->m_Controls->labelInspector->GetSelectedLabels(); bool hasMultipleInstances = this->m_Controls->labelInspector->GetLabelInstancesOfSelectedFirstLabel().size() > 1; m_Controls->labelSearchBox->setEnabled(hasWorkingData); m_Controls->btnAddGroup->setEnabled(hasWorkingData); m_Controls->btnAddInstance->setEnabled(hasWorkingData && labels.size()==1); m_Controls->btnAddLabel->setEnabled(hasWorkingData); m_Controls->btnLoadPreset->setEnabled(hasWorkingData); m_Controls->btnRemoveGroup->setEnabled(hasWorkingData && !labels.empty() && this->GetMultiLabelSegmentation()->GetNumberOfLayers()>1); m_Controls->btnRemoveLabel->setEnabled(hasWorkingData && !labels.empty()); m_Controls->btnRemoveInstance->setEnabled(hasWorkingData && !labels.empty() && hasMultipleInstances); m_Controls->btnSavePreset->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = dynamic_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkMultiLabelManager::OnCreateCroppedMask(bool) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); mitk::Image::Pointer maskImage; auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); try { this->WaitCursorOn(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); - cropFilter->SetInput(this->GetMultiLabelSegmentation()->CreateLabelMask(pixelValue)); + cropFilter->SetInput(mitk::CreateLabelMask(this->GetMultiLabelSegmentation(),pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->GetMultiLabelNode()); } void QmitkMultiLabelManager::OnCreateMask(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::Image::Pointer maskImage; try { this->WaitCursorOn(); - maskImage = GetMultiLabelSegmentation()->CreateLabelMask(pixelValue); + maskImage = mitk::CreateLabelMask(GetMultiLabelSegmentation(),pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->GetMultiLabelNode()); } void QmitkMultiLabelManager::OnCreateSmoothedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", true); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnCreateDetailedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->GetMultiLabelSegmentation()->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = this->GetMultiLabelNode(); surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", this->GetMultiLabelSegmentation()); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", false); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnSavePreset() { QmitkSaveMultiLabelPreset(this->GetMultiLabelSegmentation()); } void QmitkMultiLabelManager::OnLoadPreset() { QmitkLoadMultiLabelPreset({ this->GetMultiLabelSegmentation() }); } void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const { emit GoToLabel(label, position); } void QmitkMultiLabelManager::OnLabelRenameRequested(mitk::Label* label, bool rename) const { emit LabelRenameRequested(label, rename); } void QmitkMultiLabelManager::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelManager::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkMultiLabelManager::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkMultiLabelManager::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } void QmitkMultiLabelManager::AddSegmentationObserver() { if (this->GetMultiLabelSegmentation() != nullptr) { auto& widget = *this; m_LabelAddedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_LabelModifiedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelModifiedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_LabelRemovedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelEvent(labelEvent->GetLabelValue()); }); m_GroupAddedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupAddedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); m_GroupModifiedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupModifiedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); m_GroupRemovedObserver.Reset(this->GetMultiLabelSegmentation(), mitk::GroupRemovedEvent(), [&widget](const itk::EventObject& event) { auto groupEvent = dynamic_cast(&event); widget.OnGroupEvent(groupEvent->GetGroupID()); }); } } void QmitkMultiLabelManager::RemoveSegmentationObserver() { m_LabelAddedObserver.Reset(); m_LabelModifiedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_GroupAddedObserver.Reset(); m_GroupModifiedObserver.Reset(); m_GroupRemovedObserver.Reset(); } void QmitkMultiLabelManager::OnLabelEvent(mitk::LabelSetImage::LabelValueType /*labelValue*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnGroupEvent(mitk::LabelSetImage::GroupIndexType /*groupIndex*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnModelUpdated() { this->UpdateControls(); } void QmitkMultiLabelManager::OnSegmentationChanged() { this->RemoveSegmentationObserver(); this->AddSegmentationObserver(); UpdateControls(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 58dc2c7acc..eeffaaf1f9 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1499 +1,1500 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include // Includes for the merge operation #include "mitkImageToContourFilter.h" #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const QmitkSlicesInterpolator::ActionToSliceDimensionMapType QmitkSlicesInterpolator::CreateActionToSlicer(const QList& windows) { std::map actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, const mitk::PlaneGeometry * contourPlane, unsigned int timeStep) { vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(contourPlane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Update(); mitk::Image::Pointer slice = extractor->GetOutput(); return slice; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_CurrentActiveLabelValue(0), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } void QmitkSlicesInterpolator::SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue) { bool changedValue = labelValue != this->m_CurrentActiveLabelValue; this->m_CurrentActiveLabelValue = labelValue; if (changedValue) this->OnActiveLabelChanged(labelValue); }; mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag = timeNavigationController->AddObserver(mitk::TimeNavigationController::TimeEvent(0), timeChangedCommand); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicerMap = CreateActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); timeNavigationController->RemoveObserver(m_ControllerToTimeObserverTag); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSlicerMap.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); m_SurfaceInterpolator->SetCurrentInterpolationSession(nullptr); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); m_CmbInterpolation->setCurrentIndex(0); return; } // Updating the current selected segmentation for the 3D interpolation this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } const auto* timeNavigationController = dynamic_cast(sender); if (nullptr == timeNavigationController) { return; } bool timeChanged = m_TimePoint != timeNavigationController->GetSelectedTimePoint(); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (timeChanged) { if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (nullptr == m_LastSNC) { return; } if (TranslateAndInterpolateChangedSlice(m_LastSNC->GetCreatedWorldGeometry())) { m_LastSNC->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } auto sliceNavigationController = dynamic_cast(sender); if (nullptr == sliceNavigationController) { return; } if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, sliceNavigationController)) { sliceNavigationController->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject& e, mitk::SliceNavigationController* sliceNavigationController) { const mitk::SliceNavigationController::GeometrySliceEvent* event = dynamic_cast(&e); mitk::TimeGeometry* timeGeometry = event->GetTimeGeometry(); m_LastSNC = sliceNavigationController; return this->TranslateAndInterpolateChangedSlice(timeGeometry); } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry) { if (!m_2DInterpolationEnabled) { return false; } if (nullptr == timeGeometry) { return false; } if (!timeGeometry->IsValidTimePoint(m_TimePoint)) { return false; } mitk::SlicedGeometry3D* slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoint).GetPointer()); if (nullptr == slicedGeometry) { return false; } mitk::PlaneGeometry* plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(m_LastSNC->GetStepper()->GetPos())); if (nullptr == plane) { return false; } this->Interpolate(plane); return true; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane) { if (nullptr == m_ToolManager) { return; } mitk::DataNode* node = m_ToolManager->GetWorkingData(0); if (nullptr == node) { return; } m_Segmentation = dynamic_cast(node->GetData()); if (nullptr == m_Segmentation) { return; } if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. " "Time point: " << m_TimePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSliceIndex = clickedSliceIndex; } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode && workingNode->GetData()) { const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(segmentation, m_CurrentActiveLabelValue, segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); if (interpolatedSurface.IsNotNull()) { m_BtnApply3D->setEnabled(true);; m_InterpolatedSurfaceNode->SetData(interpolatedSurface); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } else { m_BtnApply3D->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast(workingNode->GetData()); if (nullptr == segmentationImage) return; if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the " "time bounds of segmentation. Time point: " << m_TimePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); auto activeValue = segmentationImage->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, segmentationImage->GetConstLabelsByValue(segmentationImage->GetLabelValuesByGroup(segmentationImage->GetActiveLayer())), timeStep, 0, mitk::LabelSetImage::UNLABELED_VALUE, false, { {0, mitk::LabelSetImage::UNLABELED_VALUE}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not " "within the time bounds of segmentation. Time point: " << m_TimePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); - activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabel()->GetValue()); + activeLabelImage = mitk::CreateLabelMask(labelSetImage, labelSetImage->GetActiveLabel()->GetValue()); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(m_TimePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); // Do the interpolation here. for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { interpolate(threadIndex); } m_Interpolator->DisableSliceImageCache(); const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicerMap.begin(); it != m_ActionToSlicerMap.end(); ++it) { orientationPopup.addAction(it->first); } connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); auto activeLabelColor = labelSetImage->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint) || !segmentationGeometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(m_TimePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } mitk::SurfaceInterpolationController::CPIVector newCPIs; // Adding label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); auto groupID = labelSetImage->GetGroupIndexOfLabel(labelID); auto sliceImage = ExtractSliceFromImage(labelSetImage->GetGroupImage(groupID), planeGeometry, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; contour->DisconnectPipeline(); newCPIs.emplace_back(contour, planeGeometry->Clone(),labelID,timeStep); } m_SurfaceInterpolator->CompleteReinitialization(newCPIs); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicerMap.find(action); if (iter != m_ActionToSlicerMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } const auto* activeLabel = labelSetImage->GetActiveLabel(); const auto* segmentation = dynamic_cast(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { - auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue()); + auto activeLabelImage = mitk::CreateLabelMask(labelSetImage, activeLabel->GetValue()); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { auto workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no working data set."; return; } const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) { MITK_ERROR << "Run3DInterpolation triggered with no valid label selected. Currently selected invalid label: "<Interpolate(segmentation,m_CurrentActiveLabelValue,segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { const auto* workingNode = m_ToolManager->GetWorkingData(0); const auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; try { // this->PrepareInputsFor3DInterpolation(); m_SurfaceInterpolator->Modified(); } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated this->On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { auto workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no working data set."; return; } const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no MultiLabelSegmentation as working data."; return; } if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no valid label selected. Currently selected invalid label: " << m_CurrentActiveLabelValue; return; } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); m_Future = QtConcurrent::run(&QmitkSlicesInterpolator::Run3DInterpolation, this); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << m_TimePoint; return; } m_SurfaceInterpolator->SetDistanceImageVolume(50000); auto segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_FeedbackNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); } } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } }