diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index efc06d2eba..a28c491976 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,479 +1,502 @@ /*============================================================================ 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 <mitkIOUtil.h> #include <mitkImageStatisticsHolder.h> #include <mitkLabelSetImage.h> #include <mitkTestFixture.h> #include <mitkTestingMacros.h> #include <mitkAutoCropImageFilter.h> 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(TestSetUnlabeledLabelLock); MITK_TEST(TestGetTotalNumberOfLabels); MITK_TEST(TestExistsLabel); MITK_TEST(TestExistsLabelSet); 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; 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<char>(), 3, dimensions); m_LabelSetImage->Initialize(regularImage); } void tearDown() override { // Delete LabelSetImage m_LabelSetImage = nullptr; } 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::Label::PixelType>()); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType<char>(), 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() == 1); + m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - no active label should be selected", m_LabelSetImage->GetActiveLabel() == nullptr); 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); } 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<const mitk::LabelSetImage*>(m_LabelSetImage.GetPointer())->GetActiveLabel()->GetValue() == value2); } void TestInitializeByLabeledImage() { mitk::Image::Pointer image = mitk::IOUtil::Load<mitk::Image>(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(); 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->AddLayer(); m_LabelSetImage->AddLabel(label2,1); 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(200), *label2, 0.0001, true)); // Try to get a non existing label mitk::Label *label3 = m_LabelSetImage->GetLabel(1000); CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", label3 == nullptr); } 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() { 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->AddLayer(); m_LabelSetImage->AddLabel(label2,1); CPPUNIT_ASSERT_MESSAGE( "Wrong total number of labels", m_LabelSetImage->GetTotalNumberOfLabels() == 2); } 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 TestExistsLabelSet() { 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->ExistLabelSet(0) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(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->ExistLabelSet(2) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(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->ExistLabelSet(1) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(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->ExistLabelSet(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<mitk::Image>(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<mitk::Label::PixelType> 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<mitk::Image>(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<mitk::Label::PixelType> 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<mitk::Image>(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<mitk::Label::PixelType> 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<mitk::Image>(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); auto labelMask = m_LabelSetImage->CreateLabelMask(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 eaf3758dbc..0e402cf10c 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1683 +1,1678 @@ /*============================================================================ 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 "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include <vtkCell.h> #include <vtkTransform.h> #include <vtkTransformPolyDataFilter.h> #include <itkImageRegionIterator.h> #include <itkQuadEdgeMesh.h> #include <itkTriangleMeshToBinaryImageFilter.h> #include <itkLabelGeometryImageFilter.h> //#include <itkRelabelComponentImageFilter.h> #include <itkCommand.h> #include <itkBinaryFunctorImageFilter.h> namespace mitk { template <typename ImageType> 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::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ActiveLabelValue(0) { 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_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_LookupTable(other.m_LookupTable->Clone()), m_ActiveLabelValue(other.m_ActiveLabelValue) { - m_Groups = other.m_Groups; - - for (auto [value, label] : other.m_LabelMap) + GroupIndexType i = 0; + for (auto groupImage : other.m_LayerContainer) { - auto labelClone = label->Clone(); - auto groupID = other.m_LabelToGroupMap.at(value); - - DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); - this->AddLabelToMap(value, labelClone, groupID); - this->RegisterLabel(labelClone); + 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<LabelSetImage::PixelType>()); 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(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } 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: "<<indexToDelete; const auto activeIndex = GetActiveLayer(); auto newActiveIndex = activeIndex; auto newActiveIndexBeforeDeletion = activeIndex; //determin new active group index (afte the group will be removed); if (indexToDelete < activeIndex) { //lower the index because position in m_LayerContainer etc has changed newActiveIndex = activeIndex-1; } else if (indexToDelete == activeIndex) { if (this->GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/substract 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]; // 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->m_LabelRemovedMessage.Send(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 indeces in m_GroupToLabelMap to new layer indices for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->m_LabelsChangedMessage.Send(relevantLabels); this->m_GroupRemovedMessage.Send(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 = { UnlabeledValue }; 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::Pointer layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); // 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); } - SetActiveLayer(newGroupID); this->Modified(); this->m_GroupAddedMessage.Send(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-exising group. Invalid group id: "<<groupID; } //remove old group labels auto oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->m_LabelRemovedMessage.Send(labelID); } this->m_LabelsChangedMessage.Send(oldLabels); this->m_GroupModifiedMessage.Send(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 != UnlabeledValue) { 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(); } } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LayerContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector<PixelType>& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; auto 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->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of of instance. RemoveLabelFromMap was called for unkown label id. invalid label id: "<<pixelValue; auto groupID = this->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->m_LabelsChangedMessage.Send(vectorOfLabelPixelValues); } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetActiveLayer() != groupID ? this->GetLayerImage(groupID) : this; 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->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector<PixelType>& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } 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) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); 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); m_LabelAddedMessage.Send(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(PixelType pixelValue, const std::string& name, const mitk::Color& color) { mitk::Label* label = GetLabel(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 == UnlabeledValue) 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 == UnlabeledValue) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(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<int>(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<int>(pixelValue), rgba); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this, CalculateCenterOfMassProcessing, pixelValue); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { 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::UnlabeledValue, LabelSetImage::UnlabeledValue, 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<mitk::Image *>(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 intialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not intialize by provided labeled image due to unkown error."; } this->Modified(); } template <typename LabelSetImageType, typename ImageType> void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<LabelSetImageType> 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<PixelType>(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::UnlabeledValue!=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 <typename ImageType> void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template <typename ImageType> void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter<ImageType>::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 <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image<TPixel, VImageDimension> *target, unsigned int layer) { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image<TPixel, VImageDimension> *source, unsigned int layer) const { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename ImageType> void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator<ImageType> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template <typename ImageType> void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator<ImageType> 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 unkown. 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) { UpdateLookupTable(label->GetValue()); auto command = itk::MemberCommand<LabelSetImage>::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); label->AddObserver(itk::ModifiedEvent(), command); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; label->RemoveAllObservers(); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function<void(Label*)>&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); m_LabelsChangedMessage.Send(values); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function<void(const Label*)>&& 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<const Label*>(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->m_LabelModifiedMessage.Send(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(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } 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 == UnlabeledValue) { 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, 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<std::string> mitk::LabelSetImage::GetLabelClassNames() const { std::set<std::string> names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector<std::string>(names.begin(), names.end()); } std::vector<std::string> mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set<std::string> names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector<std::string>(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, 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, 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 tabels 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:" <<layerIndex; return false; } for (ConstLabelVector::size_type index = 0; index < leftLabelsInGroup.size(); ++index) { if (!mitk::Equal(*(leftHandSide.GetLabel(leftLabelsInGroup[index])), *(rightHandSide.GetLabel(rightLabelsInGroup[index])),eps,verbose)) { MITK_INFO(verbose) << "At least one label in layer is not equal. Invalid layer:" << layerIndex; return false; } } } return returnValue; } bool mitk::Equal(const mitk::LabelSetImage::ConstLabelVectorType& leftHandSide, const mitk::LabelSetImage::ConstLabelVectorType& rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // container size; returnValue = leftHandSide.size() == rightHandSide.size(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // m_LabelContainer; auto lhsit = leftHandSide.begin(); auto rhsit = rightHandSide.begin(); for (; lhsit != leftHandSide.end(); ++lhsit, ++rhsit) { returnValue = mitk::Equal(**rhsit, **lhsit,eps,verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } /**Helper function to convert a vector of labels into a label map * @pre every label in the vector has a unique value.*/ using ConstLabelMapType = std::map<mitk::LabelSetImage::LabelValueType, mitk::Label::ConstPointer>; 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 TDestinationPixel, class TSourcePixel, class TOutputpixel> 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<unsigned int VImageDimension> void TransferLabelContentAtTimeStepHelper(const itk::Image<mitk::Label::PixelType, VImageDimension>* 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<mitk::Label::PixelType, VImageDimension> 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 <mitk::Label::PixelType, mitk::Label::PixelType, mitk::Label::PixelType> LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter<ContentImageType, ContentImageType, ContentImageType, LabelTransferFunctorType> 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::UnlabeledValue!=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::UnlabeledValue != 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::UnlabeledValue, LabelSetImage::UnlabeledValue, 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/mitkMultiLabelIOHelper.cpp b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp index c60e2d4dda..10a9b8e9e3 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp @@ -1,444 +1,440 @@ /*============================================================================ 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 "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImage.h" #include <mitkBasePropertySerializer.h> #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include <tinyxml2.h> namespace { std::string EnsureExtension(const std::string& filename) { const std::string extension = ".lsetp"; if (filename.size() < extension.size() || std::string::npos == filename.find(extension, filename.size() - extension.size())) return filename + extension; return filename; } } bool mitk::MultiLabelIOHelper::SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage) { const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); auto *rootElement = xmlDocument.NewElement("LabelSetImagePreset"); rootElement->SetAttribute("layers", inputImage->GetNumberOfLayers()); xmlDocument.InsertEndChild(rootElement); for (unsigned int layerIndex = 0; layerIndex < inputImage->GetNumberOfLayers(); layerIndex++) { auto *layerElement = xmlDocument.NewElement("Layer"); layerElement->SetAttribute("index", layerIndex); layerElement->SetAttribute("labels", inputImage->GetNumberOfLabels(layerIndex)); rootElement->InsertEndChild(layerElement); auto labelsInGroup = inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(layerIndex)); for (const auto label : labelsInGroup) layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, label)); } return tinyxml2::XML_SUCCESS == xmlDocument.SaveFile(filename.c_str()); } bool mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage) { if (nullptr == inputImage) return false; const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; if (tinyxml2::XML_SUCCESS != xmlDocument.LoadFile(filename.c_str())) { MITK_WARN << "Label set preset file \"" << filename << "\" does not exist or cannot be opened"; return false; } auto *rootElement = xmlDocument.FirstChildElement("LabelSetImagePreset"); if (nullptr == rootElement) { MITK_WARN << "Not a valid Label set preset"; return false; } auto activeLayerBackup = inputImage->GetActiveLayer(); int numberOfLayers = 0; rootElement->QueryIntAttribute("layers", &numberOfLayers); auto* layerElement = rootElement->FirstChildElement("Layer"); if (nullptr == layerElement) { MITK_WARN << "Label set preset does not contain any layers"; return false; } for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { int numberOfLabels = 0; layerElement->QueryIntAttribute("labels", &numberOfLabels); if (!inputImage->ExistGroup(layerIndex)) { while (!inputImage->ExistGroup(layerIndex)) { inputImage->AddLayer(); } } - else - { - inputImage->SetActiveLayer(layerIndex); - } auto *labelElement = layerElement->FirstChildElement("Label"); if (nullptr == labelElement) continue; for (int labelIndex = 0; labelIndex < numberOfLabels; labelIndex++) { auto label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElement); const auto labelValue = label->GetValue(); if (LabelSetImage::UnlabeledValue != labelValue) { if (inputImage->ExistLabel(labelValue)) { // Override existing label with label from preset auto alreadyExistingLabel = inputImage->GetLabel(labelValue); alreadyExistingLabel->ConcatenatePropertyList(label); inputImage->UpdateLookupTable(labelValue); } else { inputImage->AddLabel(label, layerIndex, false); } } labelElement = labelElement->NextSiblingElement("Label"); if (nullptr == labelElement) continue; } layerElement = layerElement->NextSiblingElement("Layer"); if (nullptr == layerElement) continue; } inputImage->SetActiveLayer(activeLayerBackup); return true; } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, const Label *label) { auto *labelElem = doc.NewElement("Label"); if (nullptr != label) { // add XML contents const PropertyList::PropertyMap* propmap = label->GetMap(); for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) { std::string key = iter->first; const BaseProperty* property = iter->second; auto* element = PropertyToXMLElement(doc, key, property); if (element) labelElem->InsertEndChild(element); } } return labelElem; } mitk::Label::Pointer mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem) { // reread auto *propElem = labelElem->FirstChildElement("property"); std::string name; mitk::BaseProperty::Pointer prop; mitk::Label::Pointer label = mitk::Label::New(); while (propElem) { MultiLabelIOHelper::PropertyFromXMLElement(name, prop, propElem); label->SetProperty(name, prop); propElem = propElem->NextSiblingElement("property"); } return label.GetPointer(); } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::PropertyToXMLElement(tinyxml2::XMLDocument &doc, const std::string &key, const BaseProperty *property) { auto *keyelement = doc.NewElement("property"); keyelement->SetAttribute("key", key.c_str()); keyelement->SetAttribute("type", property->GetNameOfClass()); // construct name of serializer class std::string serializername(property->GetNameOfClass()); serializername += "Serializer"; std::list<itk::LightObject::Pointer> allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << property->GetNameOfClass() << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple serializers found for " << property->GetNameOfClass() << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast<BasePropertySerializer *>(iter->GetPointer())) { serializer->SetProperty(property); try { auto *valueelement = serializer->Serialize(doc); if (valueelement) keyelement->InsertEndChild(valueelement); } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return keyelement; } bool mitk::MultiLabelIOHelper::PropertyFromXMLElement(std::string &key, mitk::BaseProperty::Pointer &prop, const tinyxml2::XMLElement *elem) { const char* typeC = elem->Attribute("type"); std::string type = nullptr != typeC ? typeC : ""; const char* keyC = elem->Attribute("key"); key = nullptr != keyC ? keyC : ""; // construct name of serializer class std::string serializername(type); serializername += "Serializer"; std::list<itk::LightObject::Pointer> allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << type << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple deserializers found for " << type << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast<BasePropertySerializer *>(iter->GetPointer())) { try { prop = serializer->Deserialize(elem->FirstChildElement()); } catch (std::exception &e) { MITK_ERROR << "Deserializer " << serializer->GetNameOfClass() << " failed: " << e.what(); return false; } break; } } if (prop.IsNull()) return false; return true; } int mitk::MultiLabelIOHelper::GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector<std::string> imgMetaKeys = dic.GetKeys(); std::vector<std::string>::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData<std::string>(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return atoi(metaString.c_str()); } } return 0; } std::string mitk::MultiLabelIOHelper::GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector<std::string> imgMetaKeys = dic.GetKeys(); std::vector<std::string>::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData<std::string>(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return metaString; } } return metaString; } nlohmann::json mitk::MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage) { if (nullptr == inputImage) { mitkThrow() << "Invalid call of SerializeMultLabelGroupsToJSON. Passed image pointer is null."; } nlohmann::json result; for (LabelSetImage::GroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) { nlohmann::json jgroup; nlohmann::json jlabels; for (const auto& label : inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(i))) { jlabels.emplace_back(SerializeLabelToJSON(label)); } jgroup["labels"] = jlabels; result.emplace_back(jgroup); } return result; }; std::vector<mitk::LabelVector> mitk::MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) { std::vector<LabelVector> result; for (const auto& jlabelset : listOfLabelSets) { LabelVector labelSet; if (jlabelset.find("labels") != jlabelset.end()) { auto jlabels = jlabelset["labels"]; for (const auto& jlabel : jlabels) { labelSet.push_back(DeserializeLabelFromJSON(jlabel)); } } result.emplace_back(labelSet); } return result; } nlohmann::json mitk::MultiLabelIOHelper::SerializeLabelToJSON(const Label* label) { if (nullptr == label) { mitkThrow() << "Invalid call of GetLabelAsJSON. Passed label pointer is null."; } nlohmann::json j; j["name"] = label->GetName(); j["value"] = label->GetValue(); nlohmann::json jcolor; jcolor["type"] = "ColorProperty"; jcolor["value"] = {label->GetColor().GetRed(), label->GetColor().GetGreen(), label->GetColor().GetBlue() }; j["color"] = jcolor; j["locked"] = label->GetLocked(); j["opacity"] = label->GetOpacity(); j["visible"] = label->GetVisible(); return j; }; template<typename TValueType> bool GetValueFromJson(const nlohmann::json& labelJson, const std::string& key, TValueType& value) { if (labelJson.find(key) != labelJson.end()) { try { value = labelJson[key].get<TValueType>(); return true; } catch (...) { MITK_ERROR << "Unable to read label information from json. Value has wrong type. Failed key: " << key << "; invalid value: " << labelJson[key].dump(); throw; } } return false; } mitk::Label::Pointer mitk::MultiLabelIOHelper::DeserializeLabelFromJSON(const nlohmann::json& labelJson) { Label::Pointer resultLabel = Label::New(); std::string name = "Unkown label name"; GetValueFromJson(labelJson, "name", name); resultLabel->SetName(name); Label::PixelType value = 1; GetValueFromJson(labelJson, "value", value); resultLabel->SetValue(value); if (labelJson.find("color") != labelJson.end()) { auto jcolor = labelJson["color"]["value"]; Color color; color.SetRed(jcolor[0].get<float>()); color.SetGreen(jcolor[1].get<float>()); color.SetBlue(jcolor[2].get<float>()); resultLabel->SetColor(color); } bool locked = false; if (GetValueFromJson(labelJson, "locked", locked)) resultLabel->SetLocked(locked); float opacity = 1.; if (GetValueFromJson(labelJson, "opacity", opacity)) resultLabel->SetOpacity(opacity); bool visible = true; if (GetValueFromJson(labelJson, "visible", visible)) resultLabel->SetVisible(visible); return resultLabel; } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index b35e7c9129..baea11dc30 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,839 +1,840 @@ /*============================================================================ 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 "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include <mitkTimeNavigationController.h> #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast<const Image*>(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast<const LabelSetImage*>(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast<const Image*>(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate<SegWithPreviewTool>(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate<SegWithPreviewTool>(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate<SegWithPreviewTool>(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate<SegWithPreviewTool>(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } this->ConfirmCleanUp(); } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast<LabelSetImage*>(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast<LabelSetImage*>(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast<const Image*>(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast<const Image*>(m_ReferenceDataNode->GetData()); } template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast<LabelSetImage*>(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel<float> previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast<const LabelSetImage *>(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); if (newPreviewImage->GetNumberOfLayers() == 0) { newPreviewImage->AddLayer(); + newPreviewImage->SetActiveLayer(0); } auto* activeLabel = newPreviewImage->GetActiveLabel(); if (nullptr == activeLabel) { activeLabel = newPreviewImage->AddLabel("toolresult", previewColor, newPreviewImage->GetActiveLayer()); newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast<const Image*>(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } LabelMappingType labelMapping = {}; switch (this->m_LabelTransferScope) { case LabelTransferScope::SelectedLabels: { for (auto label : this->m_SelectedLabels) { labelMapping.push_back({label, label + offset}); } } break; case LabelTransferScope::AllLabels: { const auto labelValues = this->GetPreviewSegmentation()->GetLabelValuesByGroup(this->GetPreviewSegmentation()->GetActiveLayer()); for (auto labelValue : labelValues) { labelMapping.push_back({ labelValue, labelValue + offset}); } } break; default: { if (m_SelectedLabels.empty()) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " "implementation of derived tool class."; if (m_SelectedLabels.size() > 1) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but more then one selected label is indicated." "Should be only one. Check implementation of derived tool class."; labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); } break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); auto resultSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), destinationImage, timeStep)->Clone(); auto destLSImage = dynamic_cast<LabelSetImage *>(destinationImage); //We need to transfer explictly to a copy of the current working image to ensure that labelMapping is done and things //like merge style, overwrite style and locks are regarded. TransferLabelContentAtTimeStep(sourceSlice, resultSlice, destLSImage->GetConstLabelsByValue(destLSImage->GetLabelValuesByGroup(destLSImage->GetActiveLayer())), timeStep, 0, 0, destLSImage->GetUnlabeledLabelLock(), labelMapping, m_MergeStyle, m_OverwriteStyle); //We use WriteBackSegmentationResult to ensure undo/redo is supported also by derived tools of this class. SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, resultSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast<const LabelSetImage*>(sourceImage); auto destLSImage = dynamic_cast<LabelSetImage*>(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } catch (mitk::Exception& e) { Tool::ErrorMessage(e.GetDescription()); mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast<Image*>(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast<Image*>(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast<Image *>(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast<const Image*>(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast<const LabelSetImage*>(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel()->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast<const Image*>(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::ConfirmCleanUp() { // default implementation does nothing // reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue != sourceLabel && LabelSetImage::UnlabeledValue != targetLabel && !target->ExistLabel(targetLabel, target->GetActiveLayer())) { if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; } auto clonedLabel = source->GetLabel(sourceLabel)->Clone(); clonedLabel->SetValue(targetLabel); target->AddLabel(clonedLabel,target->GetActiveLayer(), false, false); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast<LabelSetImage*>(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const { const auto previewImage = this->GetPreviewSegmentation(); const auto activeLabel = previewImage->GetActiveLabel(); if (nullptr == activeLabel) mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " "preview has no active label indicated. Check " "implementation of the class."; return activeLabel->GetValue(); } const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast<LabelSetImage*>(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; const auto labelValues = source->GetLabelValuesByGroup(source->GetActiveLayer()); for (const auto& labelValue : labelValues) { labelMapping.push_back({ labelValue,labelValue }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index ffe0955d82..81ce0c816a 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,338 +1,339 @@ /*============================================================================ 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 "mitkTool.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include <mitkDICOMSegmentationPropertyHelper.cpp> #include <mitkToolManager.h> // us #include <usGetModuleContext.h> #include <usModuleResource.h> // itk #include <itkObjectFactory.h> namespace mitk { itkEventMacroDefinition(ToolEvent, itk::ModifiedEvent); } mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig(""), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractionConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } mitk::ToolManager* mitk::Tool::GetToolManager() const { return m_ToolManager; } mitk::DataStorage* mitk::Tool::GetDataStorage() const { if (nullptr != m_ToolManager) { return m_ToolManager->GetDataStorage(); } return nullptr; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractionConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences<InteractionEventObserver>(); for (const auto& eventObserver : eventObservers) { auto displayActionEventBroadcast = dynamic_cast<DisplayActionEventBroadcast*>( us::GetModuleContext()->GetService<InteractionEventObserver>(eventObserver)); if (nullptr != displayActionEventBroadcast) { // remember the original configuration m_DisplayInteractionConfigs.insert(std::make_pair(eventObserver, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->AddEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractionConfig : m_DisplayInteractionConfigs) { if (displayInteractionConfig.first) { auto displayActionEventBroadcast = static_cast<mitk::DisplayActionEventBroadcast*>( us::GetModuleContext()->GetService<mitk::InteractionEventObserver>(displayInteractionConfig.first)); if (nullptr != displayActionEventBroadcast) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(displayInteractionConfig.second); } } } m_DisplayInteractionConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list<itk::LightObject::Pointer> allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast<itk::Object *>(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType<DefaultSegmentationDataType>()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); + segmentation->SetActiveLayer(0); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->AddLabel(label,segmentation->GetActiveLayer()); segmentation->SetActiveLabel(label->GetValue()); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index b65ed9fe8d..edab65b958 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,347 +1,348 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkTotalSegmentatorTool.h" #include <mitkIOUtil.h> #include <mitkImageReadAccessor.h> #include <algorithm> #include <filesystem> #include <itksys/SystemTools.hxx> #include <regex> // us #include <usGetModuleContext.h> #include <usModule.h> #include <usModuleContext.h> #include <usModuleResource.h> #include <usServiceReference.h> namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast<const mitk::ExternalProcessStdOutEvent *>(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast<const mitk::ExternalProcessStdErrEvent *>(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); m_ProgressCommand->SetProgress(5); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; m_ProgressCommand->SetProgress(20); IOUtil::Save(inputAtTimeStep, inputImagePath); m_ProgressCommand->SetProgress(50); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { outputImagePath = outDir; this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector<std::string> files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); Image::Pointer outputImage = IOUtil::Load<Image>(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } m_ProgressCommand->SetProgress(180); mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { std::vector<std::string> files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector<std::string> &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType<mitk::Label::PixelType>(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); const auto layerIndex = aggloLabelImage->AddLayer(); + aggloLabelImage->SetActiveLayer(layerIndex); for (auto const &outputImagePath : filePaths) { double rgba[4]; aggloLabelImage->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); aggloLabelImage->AddLabel(label, layerIndex, false, false); Image::Pointer outputImage = IOUtil::Load<Image>(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); mitk::TransferLabelContent(source, aggloLabelImage, aggloLabelImage->GetConstLabelsByValue(aggloLabelImage->GetLabelValuesByGroup(layerIndex)), 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; #ifdef _WIN32 command += ".exe"; #endif args.clear(); args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { std::regex sanitizer(R"([^A-Za-z0-9_])"); std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { if (line > 111 && line < 229) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { std::string sanitized = std::regex_replace(val, sanitizer, ""); m_LabelMapTotal[std::stoi(key)] = sanitized; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map<mitk::Label::PixelType, std::string> &labelMap) { auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array<double, 3> lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); dest->AddLabel(label, 0,false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; std::filesystem::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 829c54dc01..29a2e91cfd 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1122 +1,1123 @@ /*============================================================================ 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 <QmitkMultiLabelInspector.h> // mitk #include <mitkRenderingManager.h> #include <mitkLabelSetImageHelper.h> #include <mitkDICOMSegmentationPropertyHelper.h> // Qmitk #include <QmitkMultiLabelTreeModel.h> #include <QmitkLabelColorItemDelegate.h> #include <QmitkLabelToggleItemDelegate.h> #include <QmitkStyleManager.h> // Qt #include <QMenu> #include <QLabel> #include <QWidgetAction> #include <QMessageBox> #include <ui_QmitkMultiLabelInspectorControls.h> QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value<LabelValueType>()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert<mitk::LabelSetImage::LabelValueType>()) { result.push_back(qvariantDataNode.value<mitk::LabelSetImage::LabelValueType>()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value<void*>(); currentIndexLabel = static_cast<mitk::Label*>(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); m_ModelManipulationOngoing = true; auto newLabel = m_Segmentation->AddLabel(templateLabel, groupID, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); m_ModelManipulationOngoing = true; m_Segmentation->AddLabel(newLabel, containingGroup, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal({ label->GetValue() }); } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal(relevantLabels); } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value<LabelValueType>(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); + m_Segmentation->SetActiveLayer(groupID); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value<LabelValueType>(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } const auto* selectedLabel = this->GetFirstSelectedLabelObject(); if (selectedLabel == nullptr) return; const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value<mitk::LabelSetImage::GroupIndexType>(); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything in group but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector<mitk::Label*> relevantLabels; if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast<int>(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels](const int value) { auto opacity = static_cast<float>(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value<mitk::LabelSetImage::GroupIndexType>(); this->AddNewLabelInternal(groupID); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); m_Segmentation->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); m_Segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); m_Segmentation->SetAllLabelsVisible(false); currentLabel->SetVisible(true); m_Segmentation->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value<mitk::Label::PixelType>(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp index 609c0643e1..cb629c9a9a 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp @@ -1,300 +1,301 @@ /*============================================================================ 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 "QmitkAutocropLabelSetImageAction.h" #include <mitkImagePixelReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkLabelSetImage.h> #include <mitkRenderingManager.h> namespace { // Iterate over all layers, time steps, and dimensions of a LabelSetImage to // determine the overall minimum and maximum indices of labeled pixels. // // Returns false if the input image is empty, minIndex and maxIndex contain // valid indices otherwise. // // Throws an mitk::Exception if read access was denied. // bool DetermineMinimumAndMaximumIndicesOfNonBackgroundPixels(mitk::LabelSetImage::Pointer labelSetImage, itk::Index<3>& minIndex, itk::Index<3>& maxIndex) { // We need a time selector to handle 3d+t images. It is not used for 3d images, though. auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(labelSetImage); const auto background = mitk::LabelSetImage::UnlabeledValue; const auto numLayers = labelSetImage->GetNumberOfLayers(); const auto numTimeSteps = labelSetImage->GetTimeSteps(); const itk::Index<3> dim = { labelSetImage->GetDimension(0), labelSetImage->GetDimension(1), labelSetImage->GetDimension(2) }; maxIndex = { 0, 0, 0 }; minIndex = dim; itk::Index<3> index; bool labelSetImageIsEmpty = true; for (std::remove_const_t<decltype(numLayers)> layer = 0; layer < numLayers; ++layer) { labelSetImage->SetActiveLayer(layer); for (std::remove_const_t<decltype(numTimeSteps)> timeStep = 0; timeStep < numTimeSteps; ++timeStep) { const mitk::Image* image = nullptr; if (numTimeSteps > 1) { timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = labelSetImage; } mitk::ImagePixelReadAccessor<mitk::LabelSetImage::PixelType, 3> pixelReader(image); bool imageIsEmpty = true; for (index[2] = 0; index[2] < dim[2]; ++index[2]) { for (index[1] = 0; index[1] < dim[1]; ++index[1]) { for (index[0] = 0; index[0] < dim[0]; ++index[0]) { if (background != pixelReader.GetPixelByIndex(index)) { imageIsEmpty = false; minIndex = { std::min(minIndex[0], index[0]), std::min(minIndex[1], index[1]), std::min(minIndex[2], index[2]) }; break; } } } } if (imageIsEmpty) continue; maxIndex = { std::max(maxIndex[0], minIndex[0]), std::max(maxIndex[1], minIndex[1]), std::max(maxIndex[2], minIndex[2]) }; for (index[2] = dim[2] - 1; index[2] >= 0; --index[2]) { for (index[1] = dim[1] - 1; index[1] >= 0; --index[1]) { for (index[0] = dim[0] - 1; index[0] >= 0; --index[0]) { if (background != pixelReader.GetPixelByIndex(index)) { maxIndex = { std::max(maxIndex[0], index[0]), std::max(maxIndex[1], index[1]), std::max(maxIndex[2], index[2]) }; break; } } } } if (!imageIsEmpty) labelSetImageIsEmpty = false; } } return !labelSetImageIsEmpty; } // Crop a LabelSetImage. Labels in the cropped LabelSetImage will still have // their original properties like names and colors. // // Returns a cropped LabelSetImage. // // Throws an mitk::Exception if read access was denied. // mitk::LabelSetImage::Pointer Crop(mitk::LabelSetImage::Pointer labelSetImage, const itk::Index<3>& minIndex, const itk::Index<3>& maxIndex) { // We need a time selector to handle 3d+t images. It is not used for 3d images, though. auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(labelSetImage); const auto numLayers = labelSetImage->GetNumberOfLayers(); const auto numTimeSteps = labelSetImage->GetTimeSteps(); const itk::Index<3> croppedDim = { 1 + maxIndex[0] - minIndex[0], 1 + maxIndex[1] - minIndex[1], 1 + maxIndex[2] - minIndex[2] }; const auto numPixels = croppedDim[0] * croppedDim[1] * croppedDim[2]; mitk::BaseGeometry::BoundsArrayType croppedBounds; croppedBounds[0] = 0; croppedBounds[1] = croppedDim[0]; croppedBounds[2] = 0; croppedBounds[3] = croppedDim[1]; croppedBounds[4] = 0; croppedBounds[5] = croppedDim[2]; // Clone and adapt the original TimeGeometry to the cropped region auto croppedTimeGeometry = labelSetImage->GetTimeGeometry()->Clone(); for (std::remove_const_t<decltype(numTimeSteps)> timeStep = 0; timeStep < numTimeSteps; ++timeStep) { auto geometry = croppedTimeGeometry->GetGeometryForTimeStep(timeStep); mitk::Point3D croppedOrigin; geometry->IndexToWorld(minIndex, croppedOrigin); geometry->SetOrigin(croppedOrigin); geometry->SetBounds(croppedBounds); } auto croppedLabelSetImage = mitk::LabelSetImage::New(); croppedLabelSetImage->Initialize(mitk::MakeScalarPixelType<mitk::LabelSetImage::PixelType>(), *croppedTimeGeometry); // Create cropped image volumes for all time steps in all layers for (std::remove_const_t<decltype(numLayers)> layer = 0; layer < numLayers; ++layer) { labelSetImage->SetActiveLayer(layer); - croppedLabelSetImage->AddLayer(); + auto groupID = croppedLabelSetImage->AddLayer(); + croppedLabelSetImage->SetActiveLayer(groupID); for (std::remove_const_t<decltype(numTimeSteps)> timeStep = 0; timeStep < numTimeSteps; ++timeStep) { const mitk::Image* image = nullptr; if (numTimeSteps > 1) { timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = labelSetImage; } mitk::ImagePixelReadAccessor<mitk::LabelSetImage::PixelType, 3> pixelReader(image); auto* croppedVolume = new mitk::LabelSetImage::PixelType[numPixels]; itk::Index<3> croppedIndex; itk::Index<3> index; for (croppedIndex[2] = 0; croppedIndex[2] < croppedDim[2]; ++croppedIndex[2]) { for (croppedIndex[1] = 0; croppedIndex[1] < croppedDim[1]; ++croppedIndex[1]) { for (croppedIndex[0] = 0; croppedIndex[0] < croppedDim[0]; ++croppedIndex[0]) { index[0] = croppedIndex[0] + minIndex[0]; index[1] = croppedIndex[1] + minIndex[1]; index[2] = croppedIndex[2] + minIndex[2]; const auto& pixel = pixelReader.GetPixelByIndex(index); croppedVolume[croppedIndex[2] * croppedDim[1] * croppedDim[0] + croppedIndex[1] * croppedDim[0] + croppedIndex[0]] = pixel; } } } croppedLabelSetImage->SetImportVolume(croppedVolume, timeStep, 0, mitk::Image::ReferenceMemory); croppedLabelSetImage->ReplaceGroupLabels(layer, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(layer))); } } return croppedLabelSetImage; } } QmitkAutocropLabelSetImageAction::QmitkAutocropLabelSetImageAction() { } QmitkAutocropLabelSetImageAction::~QmitkAutocropLabelSetImageAction() { } void QmitkAutocropLabelSetImageAction::Run(const QList<mitk::DataNode::Pointer>& selectedNodes) { for (const auto& dataNode : selectedNodes) { mitk::LabelSetImage::Pointer labelSetImage = dynamic_cast<mitk::LabelSetImage*>(dataNode->GetData()); if (labelSetImage.IsNull()) continue; // Backup currently active layer as we need to restore it later auto activeLayer = labelSetImage->GetActiveLayer(); mitk::LabelSetImage::Pointer croppedLabelSetImage; itk::Index<3> minIndex; itk::Index<3> maxIndex; try { if (!DetermineMinimumAndMaximumIndicesOfNonBackgroundPixels(labelSetImage, minIndex, maxIndex)) { MITK_WARN << "Autocrop was skipped: Image \"" << dataNode->GetName() << "\" is empty."; labelSetImage->SetActiveLayer(activeLayer); // Restore the originally active layer return; } croppedLabelSetImage = Crop(labelSetImage, minIndex, maxIndex); } catch (const mitk::Exception&) { MITK_ERROR << "Autocrop was aborted: Image read access to \"" << dataNode->GetName() << "\" was denied."; labelSetImage->SetActiveLayer(activeLayer); // Restore the originally active layer return; } // Restore the originally active layer in the cropped LabelSetImage croppedLabelSetImage->SetActiveLayer(activeLayer); // Override the original LabelSetImage with the cropped LabelSetImage dataNode->SetData(croppedLabelSetImage); // If we cropped a single LabelSetImage, reinit the views to give a visible feedback to the user if (1 == selectedNodes.size()) mitk::RenderingManager::GetInstance()->InitializeViews(croppedLabelSetImage->GetTimeGeometry()); } } void QmitkAutocropLabelSetImageAction::SetSmoothed(bool) { } void QmitkAutocropLabelSetImageAction::SetDecimated(bool) { } void QmitkAutocropLabelSetImageAction::SetDataStorage(mitk::DataStorage*) { } void QmitkAutocropLabelSetImageAction::SetFunctionality(berry::QtViewPart*) { }