diff --git a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp index b01a3e70b7..2e375d4131 100644 --- a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp +++ b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp @@ -1,744 +1,610 @@ /*============================================================================ 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 <mitkSurfaceInterpolationController.h> #include <mitkTestFixture.h> #include <mitkTestingMacros.h> #include "mitkImageAccessByItk.h" #include <mitkImageTimeSelector.h> #include <mitkLabelSetImage.h> #include <vtkDebugLeaks.h> #include <vtkDoubleArray.h> #include <vtkFieldData.h> #include <vtkPolygon.h> #include <vtkRegularPolygonSource.h> template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } class mitkSurfaceInterpolationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceInterpolationControllerTestSuite); MITK_TEST(TestSingleton); MITK_TEST(TestSetCurrentInterpolationSession); MITK_TEST(TestRemoveAllInterpolationSessions); MITK_TEST(TestRemoveInterpolationSession); MITK_TEST(TestOnSegmentationDeleted); MITK_TEST(TestSetCurrentInterpolationSession4D); MITK_TEST(TestRemoveAllInterpolationSessions4D); MITK_TEST(TestRemoveInterpolationSession4D); MITK_TEST(TestOnSegmentationDeleted4D); /// \todo Workaround for memory leak in TestAddNewContour. Bug 18096. vtkDebugLeaks::SetExitError(0); MITK_TEST(TestAddNewContours); MITK_TEST(TestRemoveContours); CPPUNIT_TEST_SUITE_END(); private: mitk::SurfaceInterpolationController::Pointer m_Controller; public: mitk::Image::Pointer createImage(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); // mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType<unsigned char>(); newImage->Initialize(p_type, 3, dimensions); AccessFixedDimensionByItk(newImage, ClearBufferProcessing, 3); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage(unsigned int *dimensions) { mitk::Image::Pointer image = createImage(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } mitk::Image::Pointer createImage4D(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType<unsigned char>(); newImage->Initialize(p_type, 4, dimensions); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage4D(unsigned int *dimensions) { mitk::Image::Pointer image = createImage4D(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } void setUp() override { m_Controller = mitk::SurfaceInterpolationController::GetInstance(); m_Controller->RemoveAllInterpolationSessions(); m_Controller->SetCurrentTimePoint(0); vtkSmartPointer<vtkRegularPolygonSource> polygonSource = vtkSmartPointer<vtkRegularPolygonSource>::New(); polygonSource->SetRadius(100); polygonSource->SetNumberOfSides(7); polygonSource->Update(); mitk::Surface::Pointer surface = mitk::Surface::New(); surface->SetVtkPolyData(polygonSource->GetOutput()); } void TestSingleton() { mitk::SurfaceInterpolationController::Pointer controller2 = mitk::SurfaceInterpolationController::GetInstance(); CPPUNIT_ASSERT_MESSAGE("SurfaceInterpolationController pointers are not equal!", m_Controller.GetPointer() == controller2.GetPointer()); } void TestSetCurrentInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } - mitk::PlaneGeometry::Pointer GetPlaneGeometry() + mitk::PlaneGeometry::Pointer CreatePlaneGeometry(mitk::ScalarType zIndex) { mitk::Point3D origin; - mitk::Vector3D right, bottom, normal, spacing; - mitk::ScalarType width, height; + mitk::Vector3D right, bottom, spacing; mitk::ScalarType widthInMM, heightInMM, thicknessInMM; auto planegeometry = mitk::PlaneGeometry::New(); - width = 100; - widthInMM = width; - height = 200; - heightInMM = height; + widthInMM = 100; + heightInMM = 200; thicknessInMM = 1.0; - mitk::FillVector3D(origin, 4.5, 7.3, 11.2); + mitk::FillVector3D(origin, 4.5, 7.3, zIndex*thicknessInMM); mitk::FillVector3D(right, widthInMM, 0, 0); mitk::FillVector3D(bottom, 0, heightInMM, 0); - mitk::FillVector3D(normal, 0, 0, thicknessInMM); mitk::FillVector3D(spacing, 1.0, 1.0, thicknessInMM); planegeometry->InitializeStandardPlane(right, bottom); planegeometry->SetOrigin(origin); planegeometry->SetSpacing(spacing); return planegeometry; } + mitk::Surface::Pointer CreateContour(int numOfSides) + { + double center_1[3] = { 1.25f, 3.43f, 4.44f }; + double normal_1[3] = { 0.25f, 1.76f, 0.93f }; + vtkSmartPointer<vtkRegularPolygonSource> p_source = vtkSmartPointer<vtkRegularPolygonSource>::New(); + p_source->SetNumberOfSides(numOfSides*10); + p_source->SetCenter(center_1); + p_source->SetRadius(4); + p_source->SetNormal(normal_1); + p_source->Update(); + vtkPolyData* poly_1 = p_source->GetOutput(); + mitk::Surface::Pointer surf_1 = mitk::Surface::New(); + surf_1->SetVtkPolyData(poly_1); + return surf_1; + } + void TestRemoveAllInterpolationSessions() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; auto segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation() != nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation() == nullptr); } void TestOnSegmentationDeleted() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); segmentation_1 = nullptr; CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestAddNewContours() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - // Create some contours - double center_1[3] = {1.25f, 3.43f, 4.44f}; - double normal_1[3] = {0.25f, 1.76f, 0.93f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source->SetNumberOfSides(20); - p_source->SetCenter(center_1); - p_source->SetRadius(4); - p_source->SetNormal(normal_1); - p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); - mitk::Surface::Pointer surf_1 = mitk::Surface::New(); - surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer<vtkIntArray> int1Array = vtkSmartPointer<vtkIntArray>::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer<vtkDoubleArray> double1Array = vtkSmartPointer<vtkDoubleArray>::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_2 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer<vtkIntArray> int2Array = vtkSmartPointer<vtkIntArray>::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer<vtkDoubleArray> double2Array = vtkSmartPointer<vtkDoubleArray>::New(); - double2Array->InsertNextValue(center_2[0]); - double2Array->InsertNextValue(center_2[1]); - double2Array->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); - - double center_3[3] = {4.0f, 4.0f, 3.0f}; - double normal_3[3] = {0.0f, 0.0f, 1.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_3 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_3->SetNumberOfSides(10); - p_source_3->SetCenter(center_3); - p_source_3->SetRadius(4); - p_source_3->SetNormal(normal_3); - p_source_3->Update(); - vtkPolyData *poly_3 = p_source_3->GetOutput(); - mitk::Surface::Pointer surf_3 = mitk::Surface::New(); - surf_3->SetVtkPolyData(poly_3); - vtkSmartPointer<vtkIntArray> int3Array = vtkSmartPointer<vtkIntArray>::New(); - int3Array->InsertNextValue(1); - int3Array->InsertNextValue(0); - int3Array->InsertNextValue(0); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(int3Array); - vtkSmartPointer<vtkDoubleArray> double3Array = vtkSmartPointer<vtkDoubleArray>::New(); - double3Array->InsertNextValue(center_3[0]); - double3Array->InsertNextValue(center_3[1]); - double3Array->InsertNextValue(center_3[2]); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(double3Array); - - std::vector<mitk::Surface::Pointer> surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - surfaces.push_back(surf_3); - - auto planeGeometry1 = GetPlaneGeometry(); - auto planeGeometry2 = GetPlaneGeometry(); - auto planeGeometry3 = GetPlaneGeometry(); - - std::vector<const mitk::PlaneGeometry*> planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - planeGeometries.push_back(planeGeometry3); + auto surf_1 = CreateContour(3); + auto surf_2 = CreateContour(3); + auto surf_3 = CreateContour(3); + auto surf_3New = CreateContour(3); + + auto planeGeometry1 = CreatePlaneGeometry(1); + auto planeGeometry2 = CreatePlaneGeometry(2); + auto planeGeometry3 = CreatePlaneGeometry(3); + mitk::SurfaceInterpolationController::CPIVector cpis= { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 0}, {surf_3, planeGeometry3, 1, 0}, {surf_3New, planeGeometry3, 1, 0}, + {surf_1, planeGeometry1, 1, 1}, {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} + }; // Add contours - m_Controller->AddNewContours(surfaces, planeGeometries, true); + m_Controller->AddNewContours(cpis, true); // Check if all contours are there - auto contours = m_Controller->GetContours(0, 1); + auto contours = m_Controller->GetContours(1, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_3->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); // Create another segmentation image unsigned int dimensions2[] = {20, 20, 20}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); m_Controller->SetCurrentInterpolationSession(segmentation_2); - // Create some contours - double center_4[3] = {10.0f, 10.0f, 10.0f}; - double normal_4[3] = {0.0f, 1.0f, 0.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_4 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_4->SetNumberOfSides(8); - p_source_4->SetCenter(center_4); - p_source_4->SetRadius(5); - p_source_4->SetNormal(normal_4); - p_source_4->Update(); - vtkPolyData *poly_4 = p_source_4->GetOutput(); - mitk::Surface::Pointer surf_4 = mitk::Surface::New(); - surf_4->SetVtkPolyData(poly_4); - vtkSmartPointer<vtkIntArray> int4Array = vtkSmartPointer<vtkIntArray>::New(); - int4Array->InsertNextValue(2); - int4Array->InsertNextValue(0); - int4Array->InsertNextValue(0); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(int4Array); - vtkSmartPointer<vtkDoubleArray> double4Array = vtkSmartPointer<vtkDoubleArray>::New(); - double4Array->InsertNextValue(center_4[0]); - double4Array->InsertNextValue(center_4[1]); - double4Array->InsertNextValue(center_4[2]); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(double4Array); - - double center_5[3] = {3.0f, 10.0f, 10.0f}; - double normal_5[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_5 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_5->SetNumberOfSides(16); - p_source_5->SetCenter(center_5); - p_source_5->SetRadius(8); - p_source_5->SetNormal(normal_5); - p_source_5->Update(); - vtkPolyData *poly_5 = p_source_5->GetOutput(); - mitk::Surface::Pointer surf_5 = mitk::Surface::New(); - surf_5->SetVtkPolyData(poly_5); - vtkSmartPointer<vtkIntArray> int5Array = vtkSmartPointer<vtkIntArray>::New(); - int5Array->InsertNextValue(2); - int5Array->InsertNextValue(0); - int5Array->InsertNextValue(0); - surf_5->GetVtkPolyData()->GetFieldData()->AddArray(int5Array); - vtkSmartPointer<vtkDoubleArray> double5Array = vtkSmartPointer<vtkDoubleArray>::New(); - double5Array->InsertNextValue(center_5[0]); - double5Array->InsertNextValue(center_5[1]); - double5Array->InsertNextValue(center_5[2]); - surf_5->GetVtkPolyData()->GetFieldData()->AddArray(double5Array); - - double center_6[3] = {10.0f, 10.0f, 3.0f}; - double normal_6[3] = {0.0f, 0.0f, 1.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_6 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_6->SetNumberOfSides(100); - p_source_6->SetCenter(center_6); - p_source_6->SetRadius(5); - p_source_6->SetNormal(normal_6); - p_source_6->Update(); - vtkPolyData *poly_6 = p_source_6->GetOutput(); - mitk::Surface::Pointer surf_6 = mitk::Surface::New(); - surf_6->SetVtkPolyData(poly_6); - vtkSmartPointer<vtkIntArray> int6Array = vtkSmartPointer<vtkIntArray>::New(); - int6Array->InsertNextValue(2); - int6Array->InsertNextValue(0); - int6Array->InsertNextValue(0); - surf_6->GetVtkPolyData()->GetFieldData()->AddArray(int6Array); - vtkSmartPointer<vtkDoubleArray> double6Array = vtkSmartPointer<vtkDoubleArray>::New(); - double6Array->InsertNextValue(center_6[0]); - double6Array->InsertNextValue(center_6[1]); - double6Array->InsertNextValue(center_6[2]); - surf_6->GetVtkPolyData()->GetFieldData()->AddArray(double6Array); - - auto planeGeometry4 = GetPlaneGeometry(); - auto planeGeometry5 = GetPlaneGeometry(); - auto planeGeometry6 = GetPlaneGeometry(); - - std::vector<mitk::Surface::Pointer> surfaces2; - surfaces2.push_back(surf_4); - surfaces2.push_back(surf_5); - surfaces2.push_back(surf_6); - - std::vector<const mitk::PlaneGeometry*> planeGeometries2; - planeGeometries2.push_back(planeGeometry4); - planeGeometries2.push_back(planeGeometry5); - planeGeometries2.push_back(planeGeometry6); - - m_Controller->AddNewContours(surfaces2, planeGeometries2, true); + auto planeGeometry4 = CreatePlaneGeometry(4); + auto planeGeometry5 = CreatePlaneGeometry(5); + auto planeGeometry6 = CreatePlaneGeometry(6); - // Check if all contours are there + auto surf_4 = CreateContour(4); + auto surf_5 = CreateContour(5); + auto surf_6 = CreateContour(6); - CPPUNIT_ASSERT_MESSAGE("Wrong contours stored!", m_Controller->GetContours(0, 1) == nullptr); + mitk::SurfaceInterpolationController::CPIVector cpis2 = { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 0}, {surf_4, planeGeometry4, 1, 0}, + {surf_5, planeGeometry5, 1, 1}, {surf_6, planeGeometry6, 2, 0} + }; - auto contours2 = m_Controller->GetContours(0, 2); + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 2)->size() == 3); + m_Controller->AddNewContours(cpis2); + // Check if all contours are there + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_4->GetVtkPolyData()), *((*contours2)[0].Contour->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_5->GetVtkPolyData()), *((*contours2)[1].Contour->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_6->GetVtkPolyData()), *((*contours2)[2].Contour->GetVtkPolyData()), 0.000001, true)); - - // Modify some contours - vtkSmartPointer<vtkRegularPolygonSource> p_source_7 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_7->SetNumberOfSides(200); - p_source_7->SetCenter(3.0, 10.0, 10.0); - p_source_7->SetRadius(5); - p_source_7->SetNormal(1, 0, 0); - p_source_7->Update(); - vtkPolyData *poly_7 = p_source_7->GetOutput(); - mitk::Surface::Pointer surf_7 = mitk::Surface::New(); - surf_7->SetVtkPolyData(poly_7); - vtkSmartPointer<vtkIntArray> int7Array = vtkSmartPointer<vtkIntArray>::New(); - int7Array->InsertNextValue(2); - int7Array->InsertNextValue(0); - int7Array->InsertNextValue(0); - surf_7->GetVtkPolyData()->GetFieldData()->AddArray(int7Array); - vtkSmartPointer<vtkDoubleArray> double7Array = vtkSmartPointer<vtkDoubleArray>::New(); - double7Array->InsertNextValue(3.0); - double7Array->InsertNextValue(10.0); - double7Array->InsertNextValue(10.0); - surf_7->GetVtkPolyData()->GetFieldData()->AddArray(double7Array); - - - std::vector<mitk::Surface::Pointer> surfaces3; - surfaces3.push_back(surf_7); - - auto planeGeometry7 = GetPlaneGeometry(); - std::vector<const mitk::PlaneGeometry*> planeGeometries3; - planeGeometries3.push_back(planeGeometry7); - - m_Controller->AddNewContours(surfaces3, planeGeometries3, true); - - CPPUNIT_ASSERT_MESSAGE("Wrong contours stored!", m_Controller->GetContours(0, 1) == nullptr); - contours2 = m_Controller->GetContours(0, 2); - //CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - // mitk::Equal(*(surf_7->GetVtkPolyData()), *(contour_7->GetVtkPolyData()), 0.000001, true)); - - // Change session and test if all contours are available + mitk::Equal(*(surf_4->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_5->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_6->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + + // Check if all contours of segmentation_1 are still there m_Controller->SetCurrentInterpolationSession(segmentation_1); - /*auto contour_8 = m_Controller->GetContour(contourInfo1); - auto contour_9 = m_Controller->GetContour(contourInfo2); - auto contour_10 = m_Controller->GetContour(contourInfo3); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 2)->size() == 3); + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_8->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_9->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_10->GetVtkPolyData()), 0.000001, true));*/ + mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); } void TestRemoveContours() { // Create segmentation image unsigned int dimensions1[] = {12, 12, 12}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - // Create some contours - double center_1[3] = {4.0f, 4.0f, 4.0f}; - double normal_1[3] = {0.0f, 1.0f, 0.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source->SetNumberOfSides(20); - p_source->SetCenter(center_1); - p_source->SetRadius(4); - p_source->SetNormal(normal_1); - p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); - mitk::Surface::Pointer surf_1 = mitk::Surface::New(); - surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer<vtkIntArray> int1Array = vtkSmartPointer<vtkIntArray>::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer<vtkDoubleArray> double1Array = vtkSmartPointer<vtkDoubleArray>::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer<vtkRegularPolygonSource> p_source_2 = vtkSmartPointer<vtkRegularPolygonSource>::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer<vtkIntArray> int2Array = vtkSmartPointer<vtkIntArray>::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer<vtkDoubleArray> double2Array = vtkSmartPointer<vtkDoubleArray>::New(); - double2Array->InsertNextValue(center_2[0]); - double2Array->InsertNextValue(center_2[1]); - double2Array->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); - - std::vector<mitk::Surface::Pointer> surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - - auto planeGeometry1 = GetPlaneGeometry(); - auto planeGeometry2 = GetPlaneGeometry(); - std::vector<const mitk::PlaneGeometry*> planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - - m_Controller->AddNewContours(surfaces, planeGeometries, true); - // // Add contours - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 1)->size() == 2); + auto surf_1 = CreateContour(3); + auto surf_2 = CreateContour(3); + auto surf_3 = CreateContour(3); + + auto planeGeometry1 = CreatePlaneGeometry(1); + auto planeGeometry2 = CreatePlaneGeometry(2); + auto planeGeometry3 = CreatePlaneGeometry(3); + + mitk::SurfaceInterpolationController::CPIVector cpis = { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 1}, {surf_3, planeGeometry3, 1, 2}, + {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} + }; + m_Controller->AddNewContours(cpis); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + //Remove unkown label m_Controller->RemoveContours(9); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 1)->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); - m_Controller->RemoveContours(1, 9); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 1)->size() == 2); + //Remove unkown time step + m_Controller->RemoveContours(1,3); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + + m_Controller->RemoveContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); m_Controller->RemoveContours(1); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(0, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + m_Controller->RemoveContours(2); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0) == nullptr); } bool AssertImagesEqual4D(mitk::LabelSetImage *img1, mitk::LabelSetImage *img2) { auto selector1 = mitk::ImageTimeSelector::New(); selector1->SetInput(img1); selector1->SetChannelNr(0); auto selector2 = mitk::ImageTimeSelector::New(); selector2->SetInput(img2); selector2->SetChannelNr(0); int numTs1 = img1->GetTimeSteps(); int numTs2 = img2->GetTimeSteps(); if (numTs1 != numTs2) { return false; } /*mitk::ImagePixelWriteAccessor<unsigned char,4> accessor( img1 ); itk::Index<4> ind; ind[0] = 5; ind[1] = 5; ind[2] = 5; ind[3] = 2; accessor.SetPixelByIndex( ind, 7 );*/ for (int ts = 0; ts < numTs1; ++ts) { selector1->SetTimeNr(ts); selector2->SetTimeNr(ts); selector1->Update(); selector2->Update(); mitk::Image::Pointer imgSel1 = selector1->GetOutput(); mitk::Image::Pointer imgSel2 = selector2->GetOutput(); MITK_ASSERT_EQUAL(imgSel1, imgSel2, "Segmentation images are not equal"); } return true; } void TestSetCurrentInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 5}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); // mitk::Image * segmentationImage_1 = dynamic_cast<mitk::Image *>(segmentation_1.GetPointer()); unsigned int dimensions2[] = {20, 10, 30, 4}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); auto currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); // AssertImagesEqual4D(currentSegmentation, segmentation_2->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", currentSegmentation == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation()==nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } void TestRemoveAllInterpolationSessions4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 4}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 5}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 3}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 6}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation()!=nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation()==nullptr); } void TestOnSegmentationDeleted4D() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 7}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentTimePoint(3); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkSurfaceInterpolationController) diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index f936786a5a..182a833f89 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,1005 +1,813 @@ /*============================================================================ 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 <mitkSurfaceInterpolationController.h> +#include <shared_mutex> + #include <mitkCreateDistanceImageFromSurfaceFilter.h> #include <mitkComputeContourSetNormalsFilter.h> #include <mitkImageAccessByItk.h> #include <mitkImagePixelReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkImageToSurfaceFilter.h> #include <mitkLabelSetImage.h> #include <mitkMemoryUtilities.h> #include <mitkNodePredicateDataUID.h> #include <mitkNodePredicateProperty.h> +#include <mitkNodePredicateAnd.h> #include <mitkPlanarCircle.h> #include <mitkPlaneGeometry.h> #include <mitkReduceContourSetFilter.h> #include <vtkFieldData.h> #include <vtkMath.h> #include <vtkPolygon.h> -// Check whether the given contours are coplanar -bool ContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, - mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) -{ - // Here we check two things: - // 1. Whether the normals of both contours are at least parallel - // 2. Whether both contours lie in the same plane - - // Check for coplanarity: - // a. Span a vector between two points one from each contour - // b. Calculate dot product for the vector and one of the normals - // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar - - double vec[3]; - vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; - vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; - vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; - double n[3]; - n[0] = rightHandSide.ContourNormal[0]; - n[1] = rightHandSide.ContourNormal[1]; - n[2] = rightHandSide.ContourNormal[2]; - double dot = vtkMath::Dot(n, vec); - - double n2[3]; - n2[0] = leftHandSide.ContourNormal[0]; - n2[1] = leftHandSide.ContourNormal[1]; - n2[2] = leftHandSide.ContourNormal[2]; - - // The normals of both contours have to be parallel but not of the same orientation - double lengthLHS = leftHandSide.ContourNormal.GetNorm(); - double lengthRHS = rightHandSide.ContourNormal.GetNorm(); - double dot2 = vtkMath::Dot(n, n2); - bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); - - if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) - return true; - else - return false; -} - -mitk::SurfaceInterpolationController::ContourPositionInformation CreateContourPositionInformation( - const mitk::Surface* contour, const mitk::PlaneGeometry* planeGeometry) -{ - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.Contour = contour; - mitk::ScalarType n[3]; - vtkPolygon::ComputeNormal(contour->GetVtkPolyData()->GetPoints(), n); - contourInfo.ContourNormal = n; - contourInfo.Pos = -1; - contourInfo.TimeStep = std::numeric_limits<long unsigned int>::max(); - contourInfo.Plane = planeGeometry; - auto contourIntArray = vtkIntArray::SafeDownCast( contour->GetVtkPolyData()->GetFieldData()->GetAbstractArray(0) ); +typedef std::map<mitk::TimeStepType, mitk::SurfaceInterpolationController::CPIVector> CPITimeStepMap; +typedef std::map<mitk::LabelSetImage::LabelValueType, CPITimeStepMap> CPITimeStepLabelMap; - if (contourIntArray->GetSize() < 2) - { - MITK_ERROR << "In CreateContourPositionInformation. The contourIntArray is empty."; - } - contourInfo.LabelValue = contourIntArray->GetValue(0); +typedef std::map<const mitk::LabelSetImage*, CPITimeStepLabelMap> CPITimeStepLabelSegMap; - if (contourIntArray->GetSize() >= 3) - { - contourInfo.TimeStep = contourIntArray->GetValue(2); - } +CPITimeStepLabelSegMap cpiMap; +std::shared_mutex cpiMutex; - return contourInfo; -}; +std::map<mitk::LabelSetImage*, unsigned long> segmentationObserverTags; mitk::SurfaceInterpolationController::SurfaceInterpolationController() : m_SelectedSegmentation(nullptr), m_CurrentTimePoint(0.) { m_DistanceImageSpacing = 0.0; m_ReduceFilter = ReduceContourSetFilter::New(); m_NormalsFilter = ComputeContourSetNormalsFilter::New(); m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); m_ReduceFilter->SetUseProgressBar(false); - // m_ReduceFilter->SetProgressStepSize(1); m_NormalsFilter->SetUseProgressBar(true); m_NormalsFilter->SetProgressStepSize(1); m_InterpolateSurfaceFilter->SetUseProgressBar(true); m_InterpolateSurfaceFilter->SetProgressStepSize(7); - m_Contours = Surface::New(); - m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers - for (auto& [segmentation, tag] : m_SegmentationObserverTags) + while (segmentationObserverTags.size()) { - this->RemoveObserversInternal(segmentation); + this->RemoveObserversInternal(segmentationObserverTags.begin()->first); } - m_SegmentationObserverTags.clear(); } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } -void mitk::SurfaceInterpolationController::AddNewContours(const std::vector<mitk::Surface::Pointer>& newContours, - std::vector<const mitk::PlaneGeometry*>& contourPlanes, - bool reinitializationAction) +void mitk::SurfaceInterpolationController::AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, + bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; - if (newContours.size() != contourPlanes.size()) + for (auto cpi : newCPIs) { - MITK_ERROR << "SurfaceInterpolationController::AddNewContours. contourPlanes and newContours are not of the same size."; - } - - for (size_t i = 0; i < newContours.size(); ++i) - { - const auto &newContour = newContours[i]; - - const mitk::PlaneGeometry * planeGeometry = contourPlanes[i]; - if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) + if (cpi.Contour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - auto contourInfo = CreateContourPositionInformation(newContour, planeGeometry); - if (!reinitializationAction) - { - contourInfo.ContourPoint = this->ComputeInteriorPointOfContour(contourInfo, selectedSegmentation); - } - else - { - auto vtkPolyData = contourInfo.Contour->GetVtkPolyData(); - auto pointVtkArray = vtkDoubleArray::SafeDownCast(vtkPolyData->GetFieldData()->GetAbstractArray(1)); - mitk::ScalarType *ptArr = new mitk::ScalarType[3]; - for (int i = 0; i < pointVtkArray->GetSize(); ++i) - ptArr[i] = pointVtkArray->GetValue(i); - - mitk::Point3D pt3D; - pt3D.FillPoint(ptArr); - contourInfo.ContourPoint = pt3D; - } - - this->AddToInterpolationPipeline(contourInfo, reinitializationAction); + this->AddToInterpolationPipeline(cpi, reinitializationAction); } } this->Modified(); } -mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() + +mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, mitk::LabelSetImage* seg) { - if (m_DataStorage.IsNull()) return nullptr; - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - if (selectedSegmentation.IsNull()) return nullptr; + if (nullptr == ds) return nullptr; + if (nullptr == seg) return nullptr; - DataNode* segmentationNode = nullptr; - mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(selectedSegmentation->GetUID()); - auto dataNodeObjects = m_DataStorage->GetSubset(dataUIDPredicate); + mitk::DataNode* segmentationNode = nullptr; + mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(seg->GetUID()); + auto dataNodeObjects = ds->GetSubset(dataUIDPredicate); if (dataNodeObjects->Size() != 0) { for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) { segmentationNode = it->Value(); } } else { MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; } return segmentationNode; } -void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) +mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() const +{ + if (m_DataStorage.IsNull()) return nullptr; + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) return nullptr; + return GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation); +} + +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const +{ + DataStorage::SetOfObjects::Pointer relevantNodes = DataStorage::SetOfObjects::New(); + + if (m_DataStorage.IsNotNull()) + { + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(segNode, labelValue); + + for (auto it = nodes->Begin(); it != nodes->End(); ++it) + { + auto aTS = dynamic_cast<mitk::IntProperty*>(it->Value()->GetProperty("timeStep"))->GetValue(); + bool sameTS = (timeStep == aTS); + + if (sameTS) + { + relevantNodes->push_back(it->Value()); + } + } + } + return relevantNodes; +} + +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const +{ + auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + auto isCorrectLabel = NodePredicateProperty::New("labelID", mitk::UShortProperty::New(labelValue)); + auto searchPredicate = NodePredicateAnd::New(isContourPlaneGeometry, isCorrectLabel); + + mitk::DataStorage::SetOfObjects::ConstPointer result; + if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, searchPredicate); + return result; +} + +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const +{ + auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + + mitk::DataStorage::SetOfObjects::ConstPointer result; + if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, isContourPlaneGeometry); + return result; +} +void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow()<< "Cannot add plane geometries. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; return; } + if (m_DataStorage.IsNull()) + { + MITK_DEBUG << "Cannot add plane geometry nodes. No data storage is set."; + return; + } + auto planeGeometry = contourInfo.Plane; if (planeGeometry) { - auto planeGeometryData = mitk::PlanarCircle::New(); - planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); - mitk::Point2D p1; - planeGeometry->Map(planeGeometry->GetCenter(), p1); - planeGeometryData->PlaceFigure(p1); - planeGeometryData->SetCurrentControlPoint(p1); - planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); - auto segmentationNode = this->GetSegmentationImageNode(); - auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); - - mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = - m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); + mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = this->GetPlaneGeometryNodeFromDataStorage(segmentationNode, contourInfo.LabelValue, contourInfo.TimeStep); mitk::DataNode::Pointer contourPlaneGeometryDataNode; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { - auto labelID = dynamic_cast<mitk::UShortProperty *>(it->Value()->GetProperty("labelID"))->GetValue(); - auto posID = dynamic_cast<mitk::IntProperty *>(it->Value()->GetProperty("position"))->GetValue(); - bool sameLabel = (labelID == contourInfo.LabelValue); - bool samePos = (posID == contourInfo.Pos); + auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); + if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; - if (samePos && sameLabel) + bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); + + if (samePlane) { contourPlaneGeometryDataNode = it->Value(); break; } } // Go through the contourPlaneGeometry Data and add the segmentationNode to it. if (contourPlaneGeometryDataNode.IsNull()) { - const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + auto planeGeometryData = mitk::PlanarCircle::New(); + planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); + mitk::Point2D p1; + planeGeometry->Map(planeGeometry->GetCenter(), p1); + planeGeometryData->PlaceFigure(p1); + planeGeometryData->SetCurrentControlPoint(p1); + planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); - std::string contourName = "contourPlane T " + std::to_string(currentTimeStep) + " L " + std::to_string(contourInfo.LabelValue) + "P " + std::to_string(contourInfo.Pos); + std::string contourName = "contourPlane L " + std::to_string(contourInfo.LabelValue) + " T " + std::to_string(contourInfo.TimeStep); contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); - contourPlaneGeometryDataNode->SetProperty("position", mitk::IntProperty::New(contourInfo.Pos)); - contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(currentTimeStep)); + contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(contourInfo.TimeStep)); - contourPlaneGeometryDataNode->SetProperty("px", mitk::DoubleProperty::New(contourInfo.ContourPoint[0])); - contourPlaneGeometryDataNode->SetProperty("py", mitk::DoubleProperty::New(contourInfo.ContourPoint[1])); - contourPlaneGeometryDataNode->SetProperty("pz", mitk::DoubleProperty::New(contourInfo.ContourPoint[2])); + contourPlaneGeometryDataNode->SetData(planeGeometryData); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } - - contourPlaneGeometryDataNode->SetData(planeGeometryData); - } } void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } if (contourInfo.Plane == nullptr) { MITK_ERROR << "contourInfo plane is null."; return; } if (contourInfo.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); MITK_DEBUG << "contourInfo contour is empty."; return; } { - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - const auto currentTimeStep = reinitializationAction ? contourInfo.TimeStep : selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - - const auto currentLabelValue = reinitializationAction ? contourInfo.LabelValue : selectedSegmentation->GetActiveLabel()->GetValue(); + std::lock_guard<std::shared_mutex> guard(cpiMutex); + const auto& currentTimeStep = contourInfo.TimeStep; + const auto& currentLabelValue = contourInfo.LabelValue; - auto& currentImageContours = m_CPIMap[selectedSegmentation]; + auto& currentImageContours = cpiMap[selectedSegmentation]; auto& currentLabelContours = currentImageContours[currentLabelValue]; auto& currentContourList = currentLabelContours[currentTimeStep]; - auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return ContoursCoplanar(contourInfo, element); }); + auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return contourInfo.Plane->IsOnPlane(element.Plane); }); if (finding != currentContourList.end()) { - contourInfo.Pos = finding->Pos; + MITK_DEBUG << "CPI already exists. CPI is updated. Label: "<< currentLabelValue << "; Time Step: " << currentTimeStep; *finding = contourInfo; } else { - contourInfo.Pos = currentContourList.size(); currentContourList.push_back(contourInfo); } } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { return false; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return false; } bool removedIt = false; { - std::lock_guard<std::shared_mutex> cpiGuard(m_CPIMutex); + std::lock_guard<std::shared_mutex> cpiGuard(cpiMutex); - const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - const auto currentLabel = selectedSegmentation->GetActiveLabel()->GetValue(); - auto it = m_CPIMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).begin(); + const auto currentTimeStep = contourInfo.TimeStep; + const auto currentLabel = contourInfo.LabelValue; + auto it = cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).begin(); - while (it != m_CPIMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).end()) + while (it != cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).end()) { const ContourPositionInformation& currentContour = (*it); - if (ContoursCoplanar(currentContour, contourInfo)) + if (currentContour.Plane->IsOnPlane(contourInfo.Plane)) { - m_CPIMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).erase(it); + cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).erase(it); removedIt = true; + + if (m_DataStorage.IsNotNull()) + { + mitk::DataNode::Pointer contourPlaneGeometryDataNode; + + auto contourNodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(m_DataStorage, selectedSegmentation), currentLabel, currentTimeStep); + + // Go through the nodes and check if the contour position matches them. + for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) + { + auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); + if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; + + bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); + + if (samePlane) + { + m_DataStorage->Remove(it->Value()); + break; + } + } + } break; } ++it; } } if (removedIt) { this->ReinitializeInterpolation(); } return removedIt; } void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) { this->ReinitializeInterpolation(); auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot add active label contours. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - std::shared_lock<std::shared_mutex> guard(m_CPIMutex); + std::shared_lock<std::shared_mutex> guard(cpiMutex); - auto& currentImageContours = m_CPIMap.at(selectedSegmentation); + auto& currentImageContours = cpiMap.at(selectedSegmentation); auto finding = currentImageContours.find(activeLabel); if (finding == currentImageContours.end()) { MITK_INFO << "Contours for label don't exist. Label value: " << activeLabel; return; } auto currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(currentTimeStep); if (tsfinding == currentLabelContoursMap.end()) { MITK_INFO << "Contours for current time step don't exist."; return; } CPIVector& currentContours = tsfinding->second; unsigned int index = 0; m_ReduceFilter->Reset(); for (const auto& cpi : currentContours) { m_ReduceFilter->SetInput(index, cpi.Contour); ++index; } } void mitk::SurfaceInterpolationController::Interpolate() { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot interpolate. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; m_InterpolationResult = nullptr; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); m_ReduceFilter->Update(); m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } // We use the timeSelector to get the segmentation image for the current segmentation. mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(selectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); - itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); - AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) { mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); m_NormalsFilter->SetInput(i, reducedContour); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } if (m_CurrentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result MITK_INFO << "Interpolation impossible: not enough contours."; m_InterpolationResult = nullptr; return; } // Setting up progress bar mitk::ProgressBar::GetInstance()->AddStepsToDo(10); // create a surface from the distance-image mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(1); imageToSurfaceFilter->Update(); mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); interpolationResult->Expand(selectedSegmentation->GetTimeSteps()); auto geometry = selectedSegmentation->GetTimeGeometry()->Clone(); geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); interpolationResult->SetTimeGeometry(geometry); interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); m_InterpolationResult = interpolationResult; m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); - auto* contoursGeometry = static_cast<mitk::ProportionalTimeGeometry*>(m_Contours->GetTimeGeometry()); - auto timeBounds = geometry->GetTimeBounds(currentTimeStep); - contoursGeometry->SetFirstTimePoint(timeBounds[0]); - contoursGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); - // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } -mitk::Surface *mitk::SurfaceInterpolationController::GetContoursAsSurface() -{ - return m_Contours; -} - - void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) { m_ReduceFilter->SetMinSpacing(minSpacing); } void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) { m_ReduceFilter->SetMaxSpacing(maxSpacing); m_NormalsFilter->SetMaxSpacing(maxSpacing); } void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); } mitk::LabelSetImage* mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation.Lock(); } mitk::Image *mitk::SurfaceInterpolationController::GetInterpolationImage() { return m_InterpolateSurfaceFilter->GetOutput(); } -double mitk::SurfaceInterpolationController::EstimatePortionOfNeededMemory() -{ - double numberOfPointsAfterReduction = m_ReduceFilter->GetNumberOfPointsAfterReduction() * 3; - double sizeOfPoints = pow(numberOfPointsAfterReduction, 2) * sizeof(double); - double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); - double percentage = sizeOfPoints / totalMem; - return percentage; -} - unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { - return m_CPIMap.size(); + return cpiMap.size(); } template <typename TPixel, unsigned int VImageDimension> void mitk::SurfaceInterpolationController::GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::LabelSetImage* currentSegmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (currentSegmentationImage == selectedSegmentation) { return; } m_SelectedSegmentation = currentSegmentationImage; selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); + std::lock_guard<std::shared_mutex> guard(cpiMutex); - auto it = m_CPIMap.find(selectedSegmentation); - if (it == m_CPIMap.end()) + auto it = cpiMap.find(selectedSegmentation); + if (it == cpiMap.end()) { - m_CPIMap[selectedSegmentation] = CPITimeStepLabelMap(); + cpiMap[selectedSegmentation] = CPITimeStepLabelMap(); auto command = itk::MemberCommand<SurfaceInterpolationController>::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); - m_SegmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); + segmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); selectedSegmentation->AddLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); } } m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; m_NormalsFilter->SetSegmentationBinaryImage(nullptr); this->ReinitializeInterpolation(); } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::LabelSetImage* segmentationImage) { - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - if (nullptr != segmentationImage) { + auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == segmentationImage) { - selectedSegmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( - this, &SurfaceInterpolationController::OnRemoveLabel)); - m_NormalsFilter->SetSegmentationBinaryImage(nullptr); - m_SelectedSegmentation = nullptr; + this->SetCurrentInterpolationSession(nullptr); } { - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - RemoveObserversInternal(segmentationImage); - m_CPIMap.erase(segmentationImage); + std::lock_guard<std::shared_mutex> guard(cpiMutex); + this->RemoveObserversInternal(segmentationImage); + cpiMap.erase(segmentationImage); + if (m_DataStorage.IsNotNull()) + { + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage)); + this->m_DataStorage->Remove(nodes); + } } } } void mitk::SurfaceInterpolationController::RemoveObserversInternal(mitk::LabelSetImage* segmentationImage) { - auto pos = m_SegmentationObserverTags.find(segmentationImage); - if (pos != m_SegmentationObserverTags.end()) + auto pos = segmentationObserverTags.find(segmentationImage); + if (pos != segmentationObserverTags.end()) { segmentationImage->RemoveObserver((*pos).second); segmentationImage->RemoveLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); + segmentationObserverTags.erase(segmentationImage); } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { this->RemoveObservers(); m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - m_CPIMap.clear(); + std::lock_guard<std::shared_mutex> guard(cpiMutex); + cpiMap.clear(); } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); + std::lock_guard<std::shared_mutex> guard(cpiMutex); - auto cpiLabelMap = m_CPIMap[selectedSegmentation]; + auto& cpiLabelMap = cpiMap[selectedSegmentation]; auto finding = cpiLabelMap.find(label); if (finding != cpiLabelMap.end()) { finding->second.erase(timeStep); } + + if (m_DataStorage.IsNotNull()) + { + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label, timeStep); + + this->m_DataStorage->Remove(nodes); + } } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - m_CPIMap[selectedSegmentation].erase(label); + std::lock_guard<std::shared_mutex> guard(cpiMutex); + cpiMap[selectedSegmentation].erase(label); + + if (m_DataStorage.IsNotNull()) + { + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label); + this->m_DataStorage->Remove(nodes); + } } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { - auto *tempImage = dynamic_cast<mitk::LabelSetImage *>(const_cast<itk::Object *>(caller)); + auto tempImage = dynamic_cast<mitk::LabelSetImage *>(const_cast<itk::Object *>(caller)); if (tempImage) { - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - - if (selectedSegmentation == tempImage) - { - this->SetCurrentInterpolationSession(nullptr); - } - m_SegmentationObserverTags.erase(tempImage); - m_CPIMap.erase(tempImage); + this->RemoveInterpolationSession(tempImage); } } void mitk::SurfaceInterpolationController::ReinitializeInterpolation() { // If session has changed reset the pipeline m_ReduceFilter->Reset(); m_NormalsFilter->Reset(); m_InterpolateSurfaceFilter->Reset(); itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); // Set reference image for interpolation surface filter mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(selectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); } } void mitk::SurfaceInterpolationController::OnRemoveLabel(mitk::Label::PixelType removedLabelValue) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { this->RemoveContours(removedLabelValue); } } -mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(TimeStepType timeStep, LabelSetImage::LabelValueType labelValue) +mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == nullptr) return nullptr; - std::shared_lock<std::shared_mutex> guard(m_CPIMutex); + std::shared_lock<std::shared_mutex> guard(cpiMutex); - auto labelFinding = m_CPIMap[selectedSegmentation].find(labelValue); + auto labelFinding = cpiMap[selectedSegmentation].find(labelValue); - if (labelFinding != m_CPIMap[selectedSegmentation].end()) + if (labelFinding != cpiMap[selectedSegmentation].end()) { auto tsFinding = labelFinding->second.find(timeStep); if (tsFinding != labelFinding->second.end()) { return &(tsFinding->second); } } return nullptr; } -void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector<mitk::Surface::Pointer>& contourList, - std::vector<const mitk::PlaneGeometry *>& contourPlanes) +std::vector<mitk::LabelSetImage::LabelValueType> mitk::SurfaceInterpolationController::GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const { - this->ClearInterpolationSession(); + std::lock_guard<std::shared_mutex> guard(cpiMutex); - // Now the layers should be empty and the new layers can be added. - this->AddNewContours(contourList, contourPlanes, true); -} - -void mitk::SurfaceInterpolationController::ClearInterpolationSession() -{ - auto selectedSegmentation = m_SelectedSegmentation.Lock(); + std::vector<mitk::LabelSetImage::LabelValueType> result; - if (selectedSegmentation != nullptr) - { - std::lock_guard<std::shared_mutex> guard(m_CPIMutex); - m_CPIMap[selectedSegmentation].clear(); - } -} + auto finding = cpiMap.find(seg); + if (finding == cpiMap.end()) return result; + auto currentImageContours = finding->second; -std::vector< mitk::Point3D > mitk::ContourExt::GetBoundingBoxGridPoints( - size_t planeDimension, - double startDim1, - size_t numPointsToSampleDim1, - double deltaDim1, - double startDim2, - size_t numPointsToSampleDim2, - double deltaDim2, - double valuePlaneDim) -{ - std::vector< mitk::Point3D > gridPoints; - for (size_t i = 0; i < numPointsToSampleDim1; ++i) + for (auto [label, contours] : currentImageContours) { - for (size_t j = 0; j < numPointsToSampleDim2; ++j) + auto tsFinding = contours.find(timeStep); + if (tsFinding != contours.end()) { - mitk::ScalarType *ptVec = new mitk::ScalarType[3]; - - if (planeDimension == 0) - { - ptVec[0] = valuePlaneDim; - ptVec[1] = startDim1 + deltaDim1 * i; - ptVec[2] = startDim2 + deltaDim2 * j; - } - else if (planeDimension == 1) - { - ptVec[0] = startDim1 + deltaDim1 * i; - ptVec[1] = valuePlaneDim; - ptVec[2] = startDim2 + deltaDim2 * j; + auto cpis = tsFinding->second; + auto finding = std::find_if(cpis.begin(), cpis.end(), [plane](const ContourPositionInformation& element) {return plane->IsOnPlane(element.Plane); }); - } - else if (planeDimension == 2) + if (finding != cpis.end()) { - ptVec[0] = startDim1 + deltaDim1 * i; - ptVec[1] = startDim2 + deltaDim2 * j; - ptVec[2] = valuePlaneDim; + result.push_back(label); } - - mitk::Point3D pt3D; - pt3D.FillPoint(ptVec); - gridPoints.push_back(pt3D); } } - - return gridPoints; + return result; } -mitk::Point3D mitk::SurfaceInterpolationController::ComputeInteriorPointOfContour( - const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage) -{ - if (labelSetImage->GetDimension() == 4) - { - return mitk::ContourExt::ComputeInteriorPointOfContour<4>(contour, labelSetImage, m_CurrentTimePoint); - } - else - { - return mitk::ContourExt::ComputeInteriorPointOfContour<3>(contour, labelSetImage, m_CurrentTimePoint); - } -} -template<unsigned int VImageDimension> -mitk::Point3D mitk::ContourExt::ComputeInteriorPointOfContour( - const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage, - mitk::TimePointType currentTimePoint) +void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs) { - mitk::ImagePixelReadAccessor<mitk::Label::PixelType, VImageDimension> readAccessor(labelSetImage); - - if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) - { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; - mitk::Point3D pt; - return pt; - } - - std::vector<mitk::Label::PixelType> pixelsPresent; - const auto currentTimeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); - - auto polyData = contour.Contour->GetVtkPolyData(); - - polyData->ComputeCellsBounds(); - mitk::ScalarType cellBounds[6]; - polyData->GetCellsBounds(cellBounds); - - size_t numPointsToSample = 10; - mitk::ScalarType StartX = cellBounds[0]; - mitk::ScalarType StartY = cellBounds[2]; - mitk::ScalarType StartZ = cellBounds[4]; - - size_t deltaX = (cellBounds[1] - cellBounds[0]) / numPointsToSample; - size_t deltaY = (cellBounds[3] - cellBounds[2]) / numPointsToSample; - size_t deltaZ = (cellBounds[5] - cellBounds[4]) / numPointsToSample; - - auto planeOrientation = mitk::ContourExt::GetContourOrientation(contour.ContourNormal); - - std::vector<mitk::Point3D> points; - if (planeOrientation == 0) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartY, numPointsToSample, deltaY, - StartZ, numPointsToSample, deltaZ, - StartX); - } - else if (planeOrientation == 1) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartX, numPointsToSample, deltaX, - StartZ, numPointsToSample, deltaZ, - StartY); - } - else if (planeOrientation == 2) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartX, numPointsToSample, deltaX, - StartY, numPointsToSample, deltaY, - StartZ); - } - mitk::Label::PixelType pixelVal; - mitk::Point3D pt3D; - std::vector<mitk::Label::PixelType> pixelVals; - for (size_t i = 0; i < points.size(); ++i) - { - pt3D = points[i]; - itk::Index<3> itkIndex; - labelSetImage->GetGeometry()->WorldToIndex(pt3D, itkIndex); - - if (VImageDimension == 4) - { - itk::Index<VImageDimension> time3DIndex; - for (size_t i = 0; i < itkIndex.size(); ++i) - time3DIndex[i] = itkIndex[i]; - time3DIndex[3] = currentTimeStep; - - pixelVal = readAccessor.GetPixelByIndexSafe(time3DIndex); - } - else if (VImageDimension == 3) - { - itk::Index<VImageDimension> geomIndex; - for (size_t i=0;i<itkIndex.size();++i) - geomIndex[i] = itkIndex[i]; - - pixelVal = readAccessor.GetPixelByIndexSafe(geomIndex); - } + this->ClearInterpolationSession(); - if (pixelVal == contour.LabelValue) - break; - } - return pt3D; + // Now the layers should be empty and the new layers can be added. + this->AddNewContours(newCPIs, true); } -size_t mitk::ContourExt::GetContourOrientation(const mitk::Vector3D& ContourNormal) +void mitk::SurfaceInterpolationController::ClearInterpolationSession() { - double n[3]; - n[0] = ContourNormal[0]; - n[1] = ContourNormal[1]; - n[2] = ContourNormal[2]; - - double XVec[3]; - XVec[0] = 1.0; XVec[1] = 0.0; XVec[2] = 0.0; - double dotX = vtkMath::Dot(n, XVec); - - double YVec[3]; - YVec[0] = 0.0; YVec[1] = 1.0; YVec[2] = 0.0; - double dotY = vtkMath::Dot(n, YVec); - - double ZVec[3]; - ZVec[0] = 0.0; ZVec[1] = 0.0; ZVec[2] = 1.0; - double dotZ = vtkMath::Dot(n, ZVec); + auto selectedSegmentation = m_SelectedSegmentation.Lock(); - size_t planeOrientation = 0; - if (fabs(dotZ) > mitk::eps) - { - planeOrientation = 2; - } - else if (fabs(dotY) > mitk::eps) - { - planeOrientation = 1; - } - else if(fabs(dotX) > mitk::eps) + if (selectedSegmentation != nullptr) { - planeOrientation = 0; + std::lock_guard<std::shared_mutex> guard(cpiMutex); + cpiMap[selectedSegmentation].clear(); } - return planeOrientation; } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 3656e9cac9..4f09448856 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,359 +1,289 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSurfaceInterpolationController_h #define mitkSurfaceInterpolationController_h -#include <shared_mutex> - #include <mitkDataStorage.h> #include <mitkLabelSetImage.h> #include <mitkLabel.h> #include <mitkSurface.h> #include <MitkSurfaceInterpolationExports.h> namespace mitk { class ComputeContourSetNormalsFilter; class CreateDistanceImageFromSurfaceFilter; class LabelSetImage; class ReduceContourSetFilter; class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); itkGetMacro(DistanceImageSpacing, double); struct MITKSURFACEINTERPOLATION_EXPORT ContourPositionInformation { - int Pos; Surface::ConstPointer Contour; - Vector3D ContourNormal; - Point3D ContourPoint; PlaneGeometry::ConstPointer Plane; Label::PixelType LabelValue; TimeStepType TimeStep; ContourPositionInformation() - : Pos(-1), - Plane(nullptr), + : Plane(nullptr), LabelValue(std::numeric_limits<Label::PixelType>::max()), TimeStep(std::numeric_limits<TimeStepType>::max()) { } + ContourPositionInformation(Surface::ConstPointer contour, + PlaneGeometry::ConstPointer plane, + Label::PixelType labelValue, + TimeStepType timeStep) + : + Contour(contour), + Plane(plane), + LabelValue(labelValue), + TimeStep(timeStep) + { + } }; typedef std::vector<ContourPositionInformation> CPIVector; - typedef std::map<TimeStepType, CPIVector> CPITimeStepMap; - typedef std::map<LabelSetImage::LabelValueType, CPITimeStepMap> CPITimeStepLabelMap; - - typedef std::map<LabelSetImage*, CPITimeStepLabelMap> CPITimeStepLabelSegMap; static SurfaceInterpolationController *GetInstance(); void SetCurrentTimePoint(TimePointType tp) { if (m_CurrentTimePoint != tp) { m_CurrentTimePoint = tp; if (m_SelectedSegmentation) { this->ReinitializeInterpolation(); } } }; TimePointType GetCurrentTimePoint() const { return m_CurrentTimePoint; }; /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively */ - void AddNewContours(const std::vector<Surface::Pointer>& newContours, std::vector<const mitk::PlaneGeometry*>& contourPlanes, bool reinitializeAction = false); + void AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, bool reinitializeAction = false); /** * @brief Removes the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be removed * @return true if a contour was found and removed, false if no contour was found */ bool RemoveContour(ContourPositionInformation contourInfo); - /** - * @brief Computes an interior point of the input contour. It's used to detect merge and erase operations. - * - * @param contour Contour for which to compute the contour - * @param labelSetImage LabelSetImage used input to check contour Label. - * @return mitk::Point3D 3D Interior point of the contour returned. - */ - mitk::Point3D ComputeInteriorPointOfContour(const ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage); - - /** * @brief Resets the pipeline for interpolation. The various filters used are reset. * */ void ReinitializeInterpolation(); void RemoveObservers(); /** * @brief Performs the interpolation. * */ void Interpolate(); /** * @brief Get the Result of the interpolation operation. * * @return mitk::Surface::Pointer */ mitk::Surface::Pointer GetInterpolationResult(); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface. * * @param minSpacing Paramter to set */ void SetMinSpacing(double minSpacing); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface * @param maxSpacing Set the max Spacing for interpolation */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::LabelSetImage* GetCurrentSegmentation(); - Surface *GetContoursAsSurface(); - void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ void SetCurrentInterpolationSession(LabelSetImage* currentSegmentationImage); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(LabelSetImage* segmentationImage); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); mitk::Image *GetInterpolationImage(); /** * @brief Get the Contours at a certain timeStep and layerID. * * @param timeStep Time Step from which to get the contours. - * @param layerID Layer from which to get the contours. + * @param labelValue label from which to get the contours. * @return std::vector<ContourPositionInformation> Returns contours. */ - CPIVector* GetContours(TimeStepType timeStep, LabelSetImage::LabelValueType labelValue); + CPIVector* GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); + + std::vector<LabelSetImage::LabelValueType> GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const; /** * @brief Trigerred with the "Reinit Interpolation" action. The contours are used to repopulate the * surfaceInterpolator data structures so that interpolation can be performed after reloading data. * * @param contourList List of contours extracted * @param contourPlanes List of planes at which the contours were extracted */ - void CompleteReinitialization(const std::vector<mitk::Surface::Pointer>& contourList, - std::vector<const mitk::PlaneGeometry *>& contourPlanes); + void CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label); - /** - * Estimates the memory which is needed to build up the equationsystem for the interpolation. - * \returns The percentage of the real memory which will be used by the interpolation - */ - double EstimatePortionOfNeededMemory(); - /** * Adds Contours from the active Label to the interpolation pipeline */ void AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel); unsigned int GetNumberOfInterpolationSessions(); /** * @brief Get the Segmentation Image Node object * * @return DataNode* returns the DataNode containing the segmentation image. */ - mitk::DataNode* GetSegmentationImageNode(); + mitk::DataNode* GetSegmentationImageNode() const; protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template <typename TPixel, unsigned int VImageDimension> void GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result); private: /** * @brief * * @param caller * @param event */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /** * @brief Function that removes contours of a particular label when the "Remove Label" event is trigerred in the labelSetImage. * */ void OnRemoveLabel(mitk::Label::PixelType removedLabelValue); /** * @brief When a new contour is added to the pipeline or an existing contour is replaced, * the plane geometry information of that contour is added as a child node to the * current node of the segmentation image. This is useful in the retrieval of contour information * when data is reloaded after saving. * * @param contourInfo contourInfo struct to add to data storage. */ - void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo); + void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const; + + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const; + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const; + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const; /** * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). * */ void ClearInterpolationSession(); void RemoveObserversInternal(mitk::LabelSetImage* segmentationImage); /** * @brief Add contour to the interpolation pipeline * * @param contourInfo Contour information to be added * @param reinitializationAction If the contour is coming from a reinitialization process or not */ void AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction = false); itk::SmartPointer<ReduceContourSetFilter> m_ReduceFilter; itk::SmartPointer<ComputeContourSetNormalsFilter> m_NormalsFilter; itk::SmartPointer<CreateDistanceImageFromSurfaceFilter> m_InterpolateSurfaceFilter; - mitk::Surface::Pointer m_Contours; - double m_DistanceImageSpacing; mitk::DataStorage::Pointer m_DataStorage; - CPITimeStepLabelSegMap m_CPIMap; - mitk::Surface::Pointer m_InterpolationResult; unsigned int m_CurrentNumberOfReducedContours; WeakPointer<LabelSetImage> m_SelectedSegmentation; - std::map<mitk::LabelSetImage*, unsigned long> m_SegmentationObserverTags; - mitk::TimePointType m_CurrentTimePoint; - - std::shared_mutex m_CPIMutex; }; - - namespace ContourExt - { - /** - * @brief Returns the plane the contour belongs to. - * - * @param ContourNormal - * @return size_t - */ - size_t GetContourOrientation(const mitk::Vector3D& ContourNormal); - - /** - * @brief Function used to compute an interior point of the contour. - * Used to react to the merge label and erase label actions. - * - * - * @tparam VImageDimension Dimension of the image - * @param contour Contour for which to compute the interior point - * @param labelSetImage Label Set Image For which to find the contour - * @param currentTimePoint Current Time Point of the Image - * @return mitk::Point3D The returned point in the interior of the contour.s - */ - template<unsigned int VImageDimension> - mitk::Point3D ComputeInteriorPointOfContour(const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage, - mitk::TimePointType currentTimePoint); - /** - * @brief Get a Grid points within the bounding box of the contour at a certain spacing. - * - * @param planeDimension Plane orientation (Sagittal, Coronal, Axial) - * @param startDim1 Starting coordinate along dimension 1 to start the grid point sampling from - * @param numPointsToSampleDim1 Number of points to sample along dimension 1 - * @param deltaDim1 Spacing for dimension 1 at which points should be sampled - * @param startDim2 Starting coordinate along dimension 2 to start the grid point sampling from - * @param numPointsToSampleDim2 Number of points to sample along dimension 2 - * @param deltaDim2 Spacing for dimension 1 at which points should be sampled - * @param valuePlaneDim Slice index of the plane in the volume - * @return std::vector< mitk::Point3D > The computed grid points are returned by the function. - */ - std::vector< mitk::Point3D > GetBoundingBoxGridPoints(size_t planeDimension, - double startDim1, - size_t numPointsToSampleDim1, - double deltaDim1, - double startDim2, - size_t numPointsToSampleDim2, - double deltaDim2, - double valuePlaneDim); - }; - } #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 6e84f979e1..3cd4345e95 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1091 +1,1094 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include <berryIWorkbenchPage.h> // mitk #include <mitkApplicationCursor.h> #include <mitkBaseApplication.h> #include <mitkBaseRendererHelper.h> #include <mitkCameraController.h> #include <mitkLabelSetImage.h> #include <mitkLabelSetImageHelper.h> #include <mitkMultiLabelIOHelper.h> #include <mitkManualPlacementAnnotationRenderer.h> #include <mitkNodePredicateSubGeometry.h> #include <mitkSegmentationObjectFactory.h> #include <mitkSegTool2D.h> #include <mitkStatusBar.h> #include <mitkToolManagerProvider.h> #include <mitkVtkResliceInterpolationProperty.h> #include <mitkWorkbenchUtil.h> #include <mitkIPreferences.h> // Qmitk #include <QmitkRenderWindow.h> #include <QmitkStaticDynamicSegmentationDialog.h> #include <QmitkNewSegmentationDialog.h> #include <QmitkMultiLabelManager.h> // us #include <usModuleResource.h> #include <usModuleResourceStream.h> // Qt #include <QMessageBox> #include <QShortcut> #include <QDir> // vtk #include <vtkQImageToImageSource.h> #include <regex> namespace { QList<QmitkRenderWindow*> Get2DWindows(const QList<QmitkRenderWindow*> allWindows) { QList<QmitkRenderWindow*> all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType<mitk::Image>::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType<mitk::LabelSetImage>::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); this->RemoveObserversFromWorkingImage(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference<mitk::PlanePositionManagerService>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate<Self>(this, &Self::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList<mitk::DataNode::Pointer>) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList<mitk::DataNode::Pointer>) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand<Self>::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; this->RemoveObserversFromWorkingImage(); // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand<Self>::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->AddObserversToWorkingImage(); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnLabelAdded(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) { this->ValidateSelectionInput(); } mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() { if (m_WorkingNode.IsNull()) return nullptr; return dynamic_cast<mitk::LabelSetImage*>(m_WorkingNode->GetData()); } void QmitkSegmentationView::AddObserversToWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->AddLabelAddedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelAdded)); workingImage->AddLabelRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelRemoved)); workingImage->AddGroupRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::GroupIndexType>(this, &Self::OnGroupRemoved)); } } void QmitkSegmentationView::RemoveObserversFromWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->RemoveLabelAddedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelAdded)); workingImage->RemoveLabelRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelRemoved)); workingImage->RemoveGroupRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::GroupIndexType>(this, &Self::OnGroupRemoved)); } } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast<mitk::LabelSetImage*>(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); auto labels = workingImage->GetLabelValuesByGroup(workingImage->GetActiveLayer()); auto it = std::find(labels.begin(), labels.end(), workingImage->GetActiveLabel()->GetValue()); if (it != labels.end()) ++it; if (it == labels.end()) { it = labels.begin(); } workingImage->SetActiveLabel(*it); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast<mitk::Image*>(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast<mitk::LabelSetImage*>(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); newLabelSetImage->AddLabel(newLabel, newLabelSetImage->GetActiveLayer()); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast<mitk::SegTool2D*>(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); - if (labelValue != segmentation->GetActiveLabel()->GetValue()) segmentation->SetActiveLabel(labelValue); + if (nullptr == segmentation->GetActiveLabel() || labelValue != segmentation->GetActiveLabel()->GetValue()) + { + segmentation->SetActiveLabel(labelValue); + m_Controls->slicesInterpolator->SetActiveLabelValue(labelValue); - segmentation->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); return; } QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &Self::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire' 'Segment Anything'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &Self::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &Self::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand<Self>::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast<mitk::Image *>(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate<Self>(this, &Self::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate<Self>(this, &Self::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast<mitk::DataNode*>(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference<mitk::PlanePositionManagerService>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; auto image = dynamic_cast<mitk::LabelSetImage*>(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast<mitk::LabelSetImage*>(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference<mitk::PlanePositionManagerService>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList<mitk::DataNode::Pointer>& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "<br>" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast<mitk::LabelSetImage *>(workingNode->GetData()); numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData())); } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "<br>" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("<font color=\"red\">" + text + "</font>"); m_Controls->selectionWarningLabel->show(); } }