From 895645b296fe30579acce97c38665aca4584e3c3 Mon Sep 17 00:00:00 2001 From: Niels Dekker Date: Wed, 16 Dec 2020 23:02:56 +0100 Subject: [PATCH] ENH: Add operator== and operator!= to itk::Image Added `operator==` and `operator!=` overloads which tell whether or not two images are equal, based on their value semantics (not just their addresses in memory). Allowed testing if two images (not just image pointers) compare equal or unequal by using GoogleTest `EXPECT_EQ(image1, image2)` and `EXPECT_NE(image1, image2)`. --- Modules/Core/Common/include/itkImage.h | 45 ++++++ Modules/Core/Common/test/CMakeLists.txt | 1 + Modules/Core/Common/test/itkImageGTest.cxx | 177 +++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 Modules/Core/Common/test/itkImageGTest.cxx diff --git a/Modules/Core/Common/include/itkImage.h b/Modules/Core/Common/include/itkImage.h index 7ff14cea465..b8b2edc5c7e 100644 --- a/Modules/Core/Common/include/itkImage.h +++ b/Modules/Core/Common/include/itkImage.h @@ -318,6 +318,51 @@ class ITK_TEMPLATE_EXPORT Image : public ImageBase unsigned int GetNumberOfComponentsPerPixel() const override; + /** Returns (image1 == image2). */ + friend bool + operator==(const Image & lhs, const Image & rhs) + { + if ((lhs.GetBufferedRegion() != rhs.GetBufferedRegion()) || (lhs.m_Spacing != rhs.m_Spacing) || + (lhs.m_Origin != rhs.m_Origin) || (lhs.m_Direction != rhs.m_Direction) || + (lhs.m_InverseDirection != rhs.m_InverseDirection)) + { + return false; + } + + if (lhs.m_Buffer == rhs.m_Buffer) + { + return true; + } + + if ((lhs.m_Buffer == nullptr) || (rhs.m_Buffer == nullptr)) + { + return false; + } + + auto & lhsBuffer = *(lhs.m_Buffer); + auto & rhsBuffer = *(rhs.m_Buffer); + + const auto bufferSize = lhsBuffer.Size(); + + if (bufferSize != rhsBuffer.Size()) + { + return false; + } + + const TPixel * const lhsBufferPointer = lhsBuffer.GetBufferPointer(); + const TPixel * const rhsBufferPointer = rhsBuffer.GetBufferPointer(); + + return ((lhsBufferPointer == rhsBufferPointer) || + std::equal(lhsBufferPointer, lhsBufferPointer + bufferSize, rhsBufferPointer)); + } + + /** Returns (image1 != image2). */ + friend bool + operator!=(const Image & lhs, const Image & rhs) + { + return !(lhs == rhs); + } + protected: Image(); void diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 734002dbed0..864e0cbbc45 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -608,6 +608,7 @@ set(ITKCommonGTests itkConstantBoundaryImageNeighborhoodPixelAccessPolicyGTest.cxx itkFixedArrayGTest.cxx itkImageNeighborhoodOffsetsGTest.cxx + itkImageGTest.cxx itkImageBaseGTest.cxx itkImageBufferRangeGTest.cxx itkImageRegionRangeGTest.cxx diff --git a/Modules/Core/Common/test/itkImageGTest.cxx b/Modules/Core/Common/test/itkImageGTest.cxx new file mode 100644 index 00000000000..ed2f1e46ce8 --- /dev/null +++ b/Modules/Core/Common/test/itkImageGTest.cxx @@ -0,0 +1,177 @@ +/*========================================================================= + * + * 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 + * + * http://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. + * + *=========================================================================*/ + +// First include the header file to be tested: +#include "itkImage.h" +#include + +namespace +{ +template +void +Expect_equal(const T & object1, const T & object2) +{ + // Test that equal objects can be used as arguments to GoogleTest EXPECT_EQ. + EXPECT_EQ(object1, object2); + EXPECT_EQ(object2, object1); + + // Test symmetry, as well as consistency between equal and unequal. + EXPECT_TRUE(object1 == object2); + EXPECT_TRUE(object2 == object1); + EXPECT_FALSE(object1 != object2); + EXPECT_FALSE(object2 != object1); +} + + +template +void +Expect_unequal(const T & object1, const T & object2) +{ + // Test that unequal objects can be used as arguments to GoogleTest EXPECT_NE. + EXPECT_NE(object1, object2); + EXPECT_NE(object2, object1); + + // Test symmetry, as well as consistency between equal and unequal. + EXPECT_TRUE(object1 != object2); + EXPECT_TRUE(object2 != object1); + EXPECT_FALSE(object1 == object2); + EXPECT_FALSE(object2 == object1); +} + + +template +void +Expect_equal_to_itself(const T & object) +{ + Expect_equal(object, object); +} + + +template +void +Expect_new_objects_equal() +{ + Expect_equal(*T::New(), *T::New()); +} + + +template +void +Expect_new_object_equal_to_itself() +{ + Expect_equal_to_itself(*T::New()); +} + + +template +void +Expect_allocated_initialized_image_equal_to_itself() +{ + using SizeType = typename TImage::SizeType; + + const auto image = TImage::New(); + image->SetRegions(SizeType::Filled(2)); + + // Allocate and initialize the image: + image->Allocate(true); + + Expect_equal_to_itself(*image); +} + + +template +void +Expect_unequal_when_sizes_differ() +{ + using SizeType = typename TImage::SizeType; + + const auto image1 = TImage::New(); + image1->SetRegions(SizeType::Filled(2)); + image1->Allocate(true); + + const auto image2 = TImage::New(); + image2->SetRegions(SizeType::Filled(3)); + image2->Allocate(true); + + Expect_unequal(*image1, *image2); +} + + +template +void +Expect_unequal_when_pixel_values_differ() +{ + using SizeType = typename TImage::SizeType; + + const auto imageSize = SizeType::Filled(2); + + const auto image1 = TImage::New(); + image1->SetRegions(imageSize); + image1->Allocate(); + image1->FillBuffer(1); + + const auto image2 = TImage::New(); + image2->SetRegions(imageSize); + image2->Allocate(); + image2->FillBuffer(2); + + Expect_unequal(*image1, *image2); +} + +} // namespace + + +// Tests that for any ImageType, objects constructed by ImageType::New() +// compare equal, using operator==(const Image &, const Image &). +TEST(Image, NewObjectsEqual) +{ + Expect_new_objects_equal>(); + Expect_new_objects_equal>(); +} + + +// Tests that an image compares equal to itself, +// using operator==(const Image &, const Image &). +TEST(Image, EqualToItself) +{ + // Tests an object when it is newly created: + Expect_new_object_equal_to_itself>(); + Expect_new_object_equal_to_itself>(); + + // Tests an object that is allocated and intialized. + Expect_allocated_initialized_image_equal_to_itself>(); + Expect_allocated_initialized_image_equal_to_itself>(); +} + + +// Tests that two image compare unequal when their sizes differ, +// using operator!=(const Image &, const Image &). +TEST(Image, UnequalWhenSizesDiffer) +{ + Expect_unequal_when_sizes_differ>(); + Expect_unequal_when_sizes_differ>(); +} + + +// Tests that two image compare unequal when their pixel values differ, +// using operator!=(const Image &, const Image &). +TEST(Image, UnequalWhenPixelValuesDiffer) +{ + Expect_unequal_when_pixel_values_differ>(); + Expect_unequal_when_pixel_values_differ>(); +}