diff --git a/Modules/Filtering/LabelMap/ITKKWStyleOverwrite.txt b/Modules/Filtering/LabelMap/ITKKWStyleOverwrite.txt index 833fbdf5696..3ea0f336e71 100644 --- a/Modules/Filtering/LabelMap/ITKKWStyleOverwrite.txt +++ b/Modules/Filtering/LabelMap/ITKKWStyleOverwrite.txt @@ -2,4 +2,5 @@ test/*\.cxx Namespace Disable test/itkAttributePositionLabelMapFilterTest1.cxx Namespace Disable test/itkShapeLabelMapFilterGTest.cxx Namespace Disable test/itkStatisticsLabelMapFilterGTest.cxx Namespace Disable +test/itkUniqueLabelMapFiltersGTest.cxx Namespace Disable test/itkShapeLabelObjectAccessorsTest1.cxx SemicolonSpace Disable diff --git a/Modules/Filtering/LabelMap/test/CMakeLists.txt b/Modules/Filtering/LabelMap/test/CMakeLists.txt index 32008f30ebc..0e15f959ea8 100644 --- a/Modules/Filtering/LabelMap/test/CMakeLists.txt +++ b/Modules/Filtering/LabelMap/test/CMakeLists.txt @@ -1592,6 +1592,8 @@ itk_add_test( 1 100) -set(ITKLabelMapGTests itkShapeLabelMapFilterGTest.cxx itkStatisticsLabelMapFilterGTest.cxx) +set(ITKLabelMapGTests itkShapeLabelMapFilterGTest.cxx + itkStatisticsLabelMapFilterGTest.cxx + itkUniqueLabelMapFiltersGTest.cxx) creategoogletestdriver(ITKLabelMap "${ITKLabelMap-Test_LIBRARIES}" "${ITKLabelMapGTests}") diff --git a/Modules/Filtering/LabelMap/test/itkUniqueLabelMapFiltersGTest.cxx b/Modules/Filtering/LabelMap/test/itkUniqueLabelMapFiltersGTest.cxx new file mode 100644 index 00000000000..ae1c3957cfb --- /dev/null +++ b/Modules/Filtering/LabelMap/test/itkUniqueLabelMapFiltersGTest.cxx @@ -0,0 +1,309 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkGTest.h" + +#include "itkImage.h" +#include "itkLabelImageToStatisticsLabelMapFilter.h" +#include "itkImageFileWriter.h" +#include + + +#include "itkLabelImageToLabelMapFilter.h" +#include "itkObjectByObjectLabelMapFilter.h" +#include "itkShapeLabelObjectAccessors.h" +#include "itkFlatStructuringElement.h" +#include "itkBinaryDilateImageFilter.h" +#include "itkLabelUniqueLabelMapFilter.h" +#include "itkTestingHashImageFilter.h" +#include "itksys/SystemTools.hxx" +#include "itkTestDriverIncludeRequiredFactories.h" + + +namespace +{ + +class UniqueLabelMapFixture : public ::testing::Test +{ +public: + UniqueLabelMapFixture() = default; + ~UniqueLabelMapFixture() override = default; + +protected: + void + SetUp() override + { + RegisterRequiredFactories(); + } + void + TearDown() override + {} + + std::string + GetTestName() const + { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); + } + + template + struct FixtureUtilities + { + static const unsigned int Dimension = D; + + using LabelPixelType = TPixelType; + using LabelImageType = itk::Image; + using IndexType = typename LabelImageType::IndexType; + + using LabelObjectType = itk::StatisticsLabelObject; + + using LabelMapType = itk::LabelMap>; + + + static typename LabelImageType::Pointer + CreateLabelImage(const std::vector & indices) + { + const size_t size = 25; + auto image = LabelImageType::New(); + + typename LabelImageType::SizeType imageSize; + imageSize.Fill(size); + image->SetRegions(typename LabelImageType::RegionType(imageSize)); + image->Allocate(); + image->FillBuffer(0); + + for (LabelPixelType id = 0; id < indices.size(); ++id) + { + image->SetPixel(indices[id], id + 1); + } + return image; + } + + static typename LabelMapType::Pointer + LabelMapFromLabelImage(const LabelImageType * image, unsigned int dilateRadius = 0) + { + using ToLabelMapType = itk::LabelImageToLabelMapFilter; + auto toLabelMap = ToLabelMapType::New(); + toLabelMap->SetInput(image); + + if (dilateRadius == 0) + { + toLabelMap->Update(); + return toLabelMap->GetOutput(); + } + + using KernelType = itk::FlatStructuringElement; + using DilateType = itk::BinaryDilateImageFilter; + auto dilate = DilateType::New(); + typename KernelType::SizeType rad; + rad.Fill(dilateRadius); + dilate->SetKernel(KernelType::Ball(rad)); + + + using OIType = itk::ObjectByObjectLabelMapFilter; + auto oi = OIType::New(); + oi->SetInput(toLabelMap->GetOutput()); + oi->SetFilter(dilate); + oi->SetPadSize(rad); + + oi->Update(); + + return oi->GetOutput(); + } + }; + + template + static typename itk::Image::Pointer + LabelMapToLabelImage(TLabelMap * labelMap) + { + using ImageType = itk::Image; + using L2IType = itk::LabelMapToLabelImageFilter; + auto l2i = L2IType::New(); + l2i->SetInput(labelMap); + l2i->Update(); + return l2i->GetOutput(); + } + + + template + void + CheckLabelMapOverlap(TLabelMap * labelMap) + { + for (auto & labelObject : labelMap->GetLabelObjects()) + { + // Manually check each label object against all other label objects, to ensure that no two label objects share an + // index. + for (itk::SizeValueType lineNumber = 0; lineNumber < labelObject->GetNumberOfLines(); ++lineNumber) + { + auto line = labelObject->GetLine(lineNumber); + auto idx = line.GetIndex(); + ASSERT_LE(line.GetLength(), labelObject->Size()); + for (itk::SizeValueType lengthIndex = 0; lengthIndex < line.GetLength(); ++lengthIndex) + { + for (auto & checkObject : labelMap->GetLabelObjects()) + { + if (checkObject != labelObject) + { + EXPECT_FALSE(checkObject->HasIndex(idx)) + << "Label: " << int(labelObject->GetLabel()) << " and " << int(checkObject->GetLabel()) << " has index " + << idx << std::endl; + } + } + ++idx[0]; + } + } + } + } + + template + static std::string + MD5Hash(const TImageType * image) + { + + using HashFilter = itk::Testing::HashImageFilter; + auto hasher = HashFilter::New(); + hasher->SetInput(image); + hasher->InPlaceOff(); + hasher->Update(); + return hasher->GetHash(); + } +}; +} // namespace + + +TEST_F(UniqueLabelMapFixture, EmptyImage) +{ + const std::vector::IndexType> indices = {}; + auto image = FixtureUtilities<2>::CreateLabelImage(indices); + auto labelMap = FixtureUtilities<2>::LabelMapFromLabelImage(image.GetPointer(), 15); + + auto filter = itk::LabelUniqueLabelMapFilter::New(); + filter->SetInput(labelMap); + + CheckLabelMapOverlap(filter->GetOutput()); + filter->Update(); + + auto out = LabelMapToLabelImage(filter->GetOutput()); + + // check the hash of out, should be all zeros + EXPECT_EQ(MD5Hash(out.GetPointer()), "393017b9101a884b66d64849d99a7d05"); +} + + +TEST_F(UniqueLabelMapFixture, OneLabel) +{ + const std::vector::IndexType> indices = { { 10, 10 } }; + auto image = FixtureUtilities<2>::CreateLabelImage(indices); + auto labelMap = FixtureUtilities<2>::LabelMapFromLabelImage(image.GetPointer(), 0); + + auto filter = itk::LabelUniqueLabelMapFilter::New(); + filter->SetInput(labelMap); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + auto out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), MD5Hash(image.GetPointer())); + EXPECT_EQ(MD5Hash(out.GetPointer()), "9c8ee8f2fe887fd6d2393d6416df3fb6"); +} + + +TEST_F(UniqueLabelMapFixture, OnesLabel) +{ + const std::vector::IndexType> indices = { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 4 }, { 2, 4 }, { 0, 5 } }; + auto image = FixtureUtilities<2>::CreateLabelImage(indices); + auto labelMap = FixtureUtilities<2>::LabelMapFromLabelImage(image.GetPointer(), 0); + + auto filter = itk::LabelUniqueLabelMapFilter::New(); + filter->SetInput(labelMap); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + auto out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), MD5Hash(image.GetPointer())); + EXPECT_EQ(MD5Hash(out.GetPointer()), "220048b56395d98a8f20a5b1733bdde6"); +} + + +TEST_F(UniqueLabelMapFixture, Dilate1) +{ + const std::vector::IndexType> indices = { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 4 }, { 2, 4 }, { 0, 10 } }; + auto image = FixtureUtilities<2>::CreateLabelImage(indices); + auto labelMap = FixtureUtilities<2>::LabelMapFromLabelImage(image.GetPointer(), 1); + + auto filter = itk::LabelUniqueLabelMapFilter::New(); + filter->SetInput(labelMap); + filter->InPlaceOff(); + filter->ReverseOrderingOff(); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + auto out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), "8bfc8570ee203fa68be18fe055cf389d"); + itk::WriteImage(out.GetPointer(), GetTestName() + "_off.png"); + + filter->ReverseOrderingOn(); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), "bef76c79168969548f1a8090d46b5f7e"); + itk::WriteImage(out.GetPointer(), GetTestName() + "_on.png"); +} + + +TEST_F(UniqueLabelMapFixture, Dilate2) +{ + const std::vector::IndexType> indices = { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 4 }, { 2, 4 }, { 0, 5 } }; + auto image = FixtureUtilities<2>::CreateLabelImage(indices); + auto labelMap = FixtureUtilities<2>::LabelMapFromLabelImage(image.GetPointer(), 2); + + auto filter = itk::LabelUniqueLabelMapFilter::New(); + filter->SetInput(labelMap); + filter->InPlaceOff(); + filter->ReverseOrderingOff(); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + auto out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), "02ec62c193413dd82cb0feaee1ee0b12"); + itk::WriteImage(out.GetPointer(), GetTestName() + "_off.png"); + + + filter->ReverseOrderingOn(); + filter->Update(); + + CheckLabelMapOverlap(filter->GetOutput()); + + out = LabelMapToLabelImage(filter->GetOutput()); + + EXPECT_EQ(MD5Hash(out.GetPointer()), "1c6901199b01ac095511792f447578a3"); + + itk::WriteImage(out.GetPointer(), GetTestName() + "_on.png"); +}