diff --git a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp index d813903a84..b6661faa7a 100644 --- a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp @@ -1,168 +1,159 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include mitk::ImageToContourFilter::ImageToContourFilter() + : m_SliceGeometry(nullptr), + m_UseProgressBar(false), + m_ProgressStepSize(1), + m_ContourValue(1.0) { - this->m_UseProgressBar = false; - this->m_ProgressStepSize = 1; - this->m_SliceGeometry = nullptr; } mitk::ImageToContourFilter::~ImageToContourFilter() { } void mitk::ImageToContourFilter::GenerateData() { mitk::Image::ConstPointer sliceImage = ImageToSurfaceFilter::GetInput(); if (!sliceImage) { MITK_ERROR << "mitk::ImageToContourFilter: No input available. Please set the input!" << std::endl; itkExceptionMacro("mitk::ImageToContourFilter: No input available. Please set the input!"); return; } if (sliceImage->GetDimension() > 2 || sliceImage->GetDimension() < 2) { MITK_ERROR << "mitk::ImageToImageFilter::GenerateData() works only with 2D images. Please assure that your input " "image is 2D!" << std::endl; itkExceptionMacro( "mitk::ImageToImageFilter::GenerateData() works only with 2D images. Please assure that your input image is 2D!"); return; } m_SliceGeometry = sliceImage->GetGeometry(); AccessFixedDimensionByItk(sliceImage, Itk2DContourExtraction, 2); // Setting progressbar if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(this->m_ProgressStepSize); } template void mitk::ImageToContourFilter::Itk2DContourExtraction(const itk::Image *sliceImage) { - typedef itk::Image ImageType; - typedef itk::ContourExtractor2DImageFilter ContourExtractor; + using ImageType = itk::Image; + using BinaryFilter = itk::UnaryGeneratorImageFilter; + using PadFilter = itk::ConstantPadImageFilter; + using ContourExtractorFilter = itk::ContourExtractor2DImageFilter; - typedef itk::ConstantPadImageFilter PadFilterType; - typename PadFilterType::Pointer padFilter = PadFilterType::New(); - typename ImageType::SizeType lowerExtendRegion; - lowerExtendRegion[0] = 1; - lowerExtendRegion[1] = 1; + // Create a binary mask - typename ImageType::SizeType upperExtendRegion; - upperExtendRegion[0] = 1; - upperExtendRegion[1] = 1; + auto binaryFilter = BinaryFilter::New(); - auto filter = itk::UnaryGeneratorImageFilter::New(); + binaryFilter->SetInput(sliceImage); + binaryFilter->SetFunctor([this](TPixel pixVal) { return fabs(pixVal - m_ContourValue) < mitk::eps ? 1.0 : 0.0; }); - auto contourValPicker = [this] (TPixel pixVal) - { - return fabs(pixVal-m_ContourValue) < mitk::eps - ? pixVal - : TPixel(); - }; - - filter->SetInput(sliceImage); - filter->SetFunctor(contourValPicker); - filter->Update(); - - /* - * We need to pad here, since the ITK contour extractor fails if the - * segmentation touches more than one image edge. - * By padding the image for one row at each edge we overcome this issue - */ - padFilter->SetInput(filter->GetOutput()); - padFilter->SetConstant(0); - padFilter->SetPadLowerBound(lowerExtendRegion); - padFilter->SetPadUpperBound(upperExtendRegion); - - typename ContourExtractor::Pointer contourExtractor = ContourExtractor::New(); - contourExtractor->SetInput(padFilter->GetOutput()); - contourExtractor->SetContourValue(m_ContourValue-1.0); - contourExtractor->Update(); - - unsigned int foundPaths = contourExtractor->GetNumberOfOutputs(); - - vtkSmartPointer contourSurface = vtkSmartPointer::New(); - vtkSmartPointer points = vtkSmartPointer::New(); - vtkSmartPointer polygons = vtkSmartPointer::New(); + // Pad the mask since the contour extractor would not close contours along pixels at the border + + typename ImageType::SizeType bound; + bound.Fill(1); + + auto padFilter = PadFilter::New(); + + padFilter->SetInput(binaryFilter->GetOutput()); + padFilter->SetPadLowerBound(bound); + padFilter->SetPadUpperBound(bound); + + // Extract the contour(s) + + auto contourExtractorFilter = ContourExtractorFilter::New(); + + contourExtractorFilter->SetInput(padFilter->GetOutput()); + contourExtractorFilter->SetContourValue(0.5); + contourExtractorFilter->Update(); + + unsigned int foundPaths = contourExtractorFilter->GetNumberOfOutputs(); + + auto contourSurface = vtkSmartPointer::New(); + auto points = vtkSmartPointer::New(); + auto polygons = vtkSmartPointer::New(); unsigned int pointId = 0; for (unsigned int i = 0; i < foundPaths; i++) { - const ContourPath *currentPath = contourExtractor->GetOutput(i)->GetVertexList(); + const auto* currentPath = contourExtractorFilter->GetOutput(i)->GetVertexList(); - vtkSmartPointer polygon = vtkSmartPointer::New(); + auto polygon = vtkSmartPointer::New(); polygon->GetPointIds()->SetNumberOfIds(currentPath->Size()); Point3D currentPoint; Point3D currentWorldPoint; for (unsigned int j = 0; j < currentPath->Size(); j++) { currentPoint[0] = currentPath->ElementAt(j)[0]; currentPoint[1] = currentPath->ElementAt(j)[1]; currentPoint[2] = 0; m_SliceGeometry->IndexToWorld(currentPoint, currentWorldPoint); points->InsertPoint(pointId, currentWorldPoint[0], currentWorldPoint[1], currentWorldPoint[2]); polygon->GetPointIds()->SetId(j, pointId); pointId++; } // for2 polygons->InsertNextCell(polygon); } // for1 contourSurface->SetPoints(points); contourSurface->SetPolys(polygons); contourSurface->BuildLinks(); Surface::Pointer finalSurface = this->GetOutput(); finalSurface->SetVtkPolyData(contourSurface); } void mitk::ImageToContourFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } void mitk::ImageToContourFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::ImageToContourFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; } \ No newline at end of file diff --git a/Modules/Segmentation/Testing/mitkImageToContourFilterTest.cpp b/Modules/Segmentation/Testing/mitkImageToContourFilterTest.cpp index 7ff58e43ed..107b35c5db 100644 --- a/Modules/Segmentation/Testing/mitkImageToContourFilterTest.cpp +++ b/Modules/Segmentation/Testing/mitkImageToContourFilterTest.cpp @@ -1,117 +1,165 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include class mitkImageToContourFilterTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkImageToContourFilterTestSuite); MITK_TEST(TestExtractContoursFromAnEmptySlice); MITK_TEST(TestExtractASingleContourFromASlice); MITK_TEST(TestExtractTwoContoursFromASingleSlice); + MITK_TEST(TestExtractContoursFromDifferentPixelValues); CPPUNIT_TEST_SUITE_END(); private: - mitk::Image::Pointer m_EmptySlice; - mitk::Image::Pointer m_SliceWithSingleContour; - mitk::Image::Pointer m_SliceWithTwoContours; mitk::ImageToContourFilter::Pointer m_ContourExtractor; + mitk::Image::Pointer LoadTestImage(const std::string& filename) + { + auto image = mitk::IOUtil::Load(GetTestDataFilePath(filename)); + + CPPUNIT_ASSERT_MESSAGE( + "Failed to load image for test: [" + filename + "]", + image.IsNotNull()); + + return image; + } + + void CompareContourToReference(const mitk::Surface* contour, const std::string& referenceFilename) + { + CPPUNIT_ASSERT_MESSAGE( + "No contour has been extracted", + contour != nullptr); + + auto referenceContour = mitk::IOUtil::Load(this->GetTestDataFilePath(referenceFilename)); + + CPPUNIT_ASSERT_MESSAGE( + "Extracted contour has wrong number of points", + contour->GetVtkPolyData()->GetNumberOfPoints() == referenceContour->GetVtkPolyData()->GetNumberOfPoints()); + + CPPUNIT_ASSERT_MESSAGE( + "Unequal contours", + mitk::Equal(*(contour->GetVtkPolyData()), *(referenceContour->GetVtkPolyData()), 0.000001, true)); + } + public: void setUp() override { - // Load the image - // TODO Move/create segmentation subfolder - m_EmptySlice = mitk::IOUtil::Load(GetTestDataFilePath("SurfaceInterpolation/ImageToContour/EmptySlice.nrrd")); - CPPUNIT_ASSERT_MESSAGE("Failed to load image for test: [EmptySlice.nrrd]", m_EmptySlice.IsNotNull()); + m_ContourExtractor = mitk::ImageToContourFilter::New(); - m_SliceWithSingleContour = - mitk::IOUtil::Load(GetTestDataFilePath("SurfaceInterpolation/ImageToContour/SliceWithSingleContour.nrrd")); - CPPUNIT_ASSERT_MESSAGE("Failed to load image for test: [SliceWithSingleContour.nrrd]", - m_SliceWithSingleContour.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE( + "Failed to initialize ImageToContourFilter", + m_ContourExtractor.IsNotNull()); + } - m_SliceWithTwoContours = - mitk::IOUtil::Load(GetTestDataFilePath("SurfaceInterpolation/ImageToContour/SliceWithTwoContours.nrrd")); - CPPUNIT_ASSERT_MESSAGE("Failed to load image for test: [SliceWithTwoContours.nrrd]", - m_SliceWithTwoContours.IsNotNull()); + // Extract multiple contours from an image containing different pixel values + void TestExtractContoursFromDifferentPixelValues() + { + auto labelsImage = this->LoadTestImage("SurfaceInterpolation/ImageToContour/Labels.nrrd"); - m_ContourExtractor = mitk::ImageToContourFilter::New(); - CPPUNIT_ASSERT_MESSAGE("Failed to initialize ImageToContourFilter", m_ContourExtractor.IsNotNull()); + m_ContourExtractor->SetInput(labelsImage); + m_ContourExtractor->Update(); + + mitk::Surface::Pointer contour = m_ContourExtractor->GetOutput(); + + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/Contour_Label1.vtk"); + + m_ContourExtractor->SetContourValue(2.0); + m_ContourExtractor->Update(); + + contour = m_ContourExtractor->GetOutput(); + + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/Contour_Label2.vtk"); + + m_ContourExtractor->SetContourValue(3.0); + m_ContourExtractor->Update(); + + contour = m_ContourExtractor->GetOutput(); + + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/Contour_Label3.vtk"); + + m_ContourExtractor->SetContourValue(4.0); + m_ContourExtractor->Update(); + + contour = m_ContourExtractor->GetOutput(); + + CPPUNIT_ASSERT_MESSAGE( + "Extracted contour is not empty", + contour->GetVtkPolyData()->GetNumberOfPoints() == 0); + + m_ContourExtractor->SetContourValue(5.0); + m_ContourExtractor->Update(); + + contour = m_ContourExtractor->GetOutput(); + + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/Contour_Label5.vtk"); } // Extract contours from an empty slice void TestExtractContoursFromAnEmptySlice() { - m_ContourExtractor->SetInput(m_EmptySlice); + auto emptySlice = this->LoadTestImage("SurfaceInterpolation/ImageToContour/EmptySlice.nrrd"); + + m_ContourExtractor->SetInput(emptySlice); m_ContourExtractor->Update(); mitk::Surface::Pointer emptyContour = m_ContourExtractor->GetOutput(); - CPPUNIT_ASSERT_MESSAGE("Extracted contour is not empty", emptyContour->GetVtkPolyData()->GetNumberOfPoints() == 0); + CPPUNIT_ASSERT_MESSAGE( + "Extracted contour is not empty", + emptyContour->GetVtkPolyData()->GetNumberOfPoints() == 0); } // Extract a single contour from a slice void TestExtractASingleContourFromASlice() { - m_ContourExtractor->SetInput(m_SliceWithSingleContour); + auto sliceWithSingleContour = this->LoadTestImage("SurfaceInterpolation/ImageToContour/SliceWithSingleContour.nrrd"); + + m_ContourExtractor->SetInput(sliceWithSingleContour); m_ContourExtractor->Update(); - CPPUNIT_ASSERT_MESSAGE("ImageToContourFilter has wrong number of outputs!", - m_ContourExtractor->GetNumberOfOutputs() == 1); + CPPUNIT_ASSERT_MESSAGE( + "ImageToContourFilter has wrong number of outputs", + m_ContourExtractor->GetNumberOfOutputs() == 1); mitk::Surface::Pointer contour = m_ContourExtractor->GetOutput(); - mitk::Surface::Pointer referenceContour = - mitk::IOUtil::Load(GetTestDataFilePath("SurfaceInterpolation/Reference/SingleContour.vtk")); - - CPPUNIT_ASSERT_MESSAGE( - "Extracted contour has wrong number of points!", - contour->GetVtkPolyData()->GetNumberOfPoints() == referenceContour->GetVtkPolyData()->GetNumberOfPoints()); - - CPPUNIT_ASSERT_MESSAGE( - "Unequal contours", - mitk::Equal(*(contour->GetVtkPolyData()), *(referenceContour->GetVtkPolyData()), 0.000001, true)); + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/SingleContour.vtk"); } // Extract multiple contours from a single slice void TestExtractTwoContoursFromASingleSlice() { - m_ContourExtractor->SetInput(m_SliceWithTwoContours); + auto sliceWithTwoContours = LoadTestImage("SurfaceInterpolation/ImageToContour/SliceWithTwoContours.nrrd"); + + m_ContourExtractor->SetInput(sliceWithTwoContours); m_ContourExtractor->Update(); - CPPUNIT_ASSERT_MESSAGE("ImageToContourFilter has wrong number of outputs!", - m_ContourExtractor->GetNumberOfOutputs() == 1); + CPPUNIT_ASSERT_MESSAGE( + "ImageToContourFilter has wrong number of outputs", + m_ContourExtractor->GetNumberOfOutputs() == 1); mitk::Surface::Pointer contour = m_ContourExtractor->GetOutput(0); + this->CompareContourToReference(contour, "SurfaceInterpolation/Reference/TwoContours.vtk"); + mitk::Surface::Pointer referenceContour = mitk::IOUtil::Load(GetTestDataFilePath("SurfaceInterpolation/Reference/TwoContours.vtk")); - - CPPUNIT_ASSERT_MESSAGE( - "Extracted contour1 has wrong number of points!", - contour->GetVtkPolyData()->GetNumberOfPoints() == referenceContour->GetVtkPolyData()->GetNumberOfPoints()); - - CPPUNIT_ASSERT_MESSAGE( - "Extracted contour1 has wrong number of points!", - contour->GetVtkPolyData()->GetNumberOfPolys() == referenceContour->GetVtkPolyData()->GetNumberOfPolys()); - - CPPUNIT_ASSERT_MESSAGE( - "Unequal contours", - mitk::Equal(*(contour->GetVtkPolyData()), *(referenceContour->GetVtkPolyData()), 0.000001, true)); } }; MITK_TEST_SUITE_REGISTRATION(mitkImageToContourFilter)