diff --git a/vtkm/filter/ImageDifference.h b/vtkm/filter/ImageDifference.h index b13553536..e4a8085a2 100644 --- a/vtkm/filter/ImageDifference.h +++ b/vtkm/filter/ImageDifference.h @@ -35,16 +35,31 @@ public: VTKM_CONT ImageDifference(); - VTKM_CONT vtkm::IdComponent GetRadius() const { return this->Radius; } - VTKM_CONT void SetRadius(const vtkm::IdComponent& radius) { this->Radius = radius; } - - VTKM_CONT vtkm::FloatDefault GetThreshold() const { return this->Threshold; } - VTKM_CONT void SetThreshold(const vtkm::FloatDefault& threshold) { this->Threshold = threshold; } - - VTKM_CONT bool GetAveragePixels() const { return this->AveragePixels; } - VTKM_CONT void SetAveragePixels(const bool& averagePixels) + VTKM_CONT vtkm::IdComponent GetAverageRadius() const { return this->AverageRadius; } + VTKM_CONT void SetAverageRadius(const vtkm::IdComponent& averageRadius) { - this->AveragePixels = averagePixels; + this->AverageRadius = averageRadius; + } + + VTKM_CONT vtkm::IdComponent GetPixelShiftRadius() const { return this->PixelShiftRadius; } + VTKM_CONT void SetPixelShiftRadius(const vtkm::IdComponent& pixelShiftRadius) + { + this->PixelShiftRadius = pixelShiftRadius; + } + + VTKM_CONT vtkm::FloatDefault GetAllowedPixelErrorRatio() const + { + return this->AllowedPixelErrorRatio; + } + VTKM_CONT void SetAllowedPixelErrorRatio(const vtkm::FloatDefault& pixelErrorRatio) + { + this->AllowedPixelErrorRatio = pixelErrorRatio; + } + + VTKM_CONT vtkm::FloatDefault GetPixelDiffThreshold() const { return this->PixelDiffThreshold; } + VTKM_CONT void SetPixelDiffThreshold(const vtkm::FloatDefault& threshold) + { + this->PixelDiffThreshold = threshold; } VTKM_CONT bool GetImageDiffWithinThreshold() const { return this->ImageDiffWithinThreshold; } @@ -91,9 +106,10 @@ public: vtkm::filter::PolicyBase policy); private: - vtkm::IdComponent Radius; - vtkm::FloatDefault Threshold; - bool AveragePixels; + vtkm::IdComponent AverageRadius; + vtkm::IdComponent PixelShiftRadius; + vtkm::FloatDefault AllowedPixelErrorRatio; + vtkm::FloatDefault PixelDiffThreshold; bool ImageDiffWithinThreshold; std::string SecondaryFieldName; vtkm::cont::Field::Association SecondaryFieldAssociation; diff --git a/vtkm/filter/ImageDifference.hxx b/vtkm/filter/ImageDifference.hxx index c8f721bf2..65b1a24b5 100644 --- a/vtkm/filter/ImageDifference.hxx +++ b/vtkm/filter/ImageDifference.hxx @@ -12,7 +12,6 @@ #include -#include #include #include #include @@ -25,11 +24,25 @@ namespace vtkm namespace filter { +namespace detail +{ +struct GreaterThanThreshold +{ + GreaterThanThreshold(const vtkm::FloatDefault& thresholdError) + : ThresholdError(thresholdError) + { + } + VTKM_EXEC_CONT bool operator()(const vtkm::FloatDefault& x) const { return x > ThresholdError; } + vtkm::FloatDefault ThresholdError; +}; +} // namespace detail + inline VTKM_CONT ImageDifference::ImageDifference() : vtkm::filter::FilterField() - , Radius(0) - , Threshold(0.05f) - , AveragePixels(false) + , AverageRadius(0) + , PixelShiftRadius(0) + , AllowedPixelErrorRatio(0.0001f) + , PixelDiffThreshold(0.05f) , ImageDiffWithinThreshold(true) , SecondaryFieldName("image-2") , SecondaryFieldAssociation(vtkm::cont::Field::Association::ANY) @@ -64,10 +77,11 @@ inline VTKM_CONT vtkm::cont::DataSet ImageDifference::DoExecute( vtkm::cont::ArrayHandle secondaryOutput; vtkm::cont::ArrayHandle thresholdOutput; - if (this->AveragePixels && this->Radius > 0) + if (this->AverageRadius > 0) { - VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Performing Average with radius: " << this->Radius); - auto averageWorklet = vtkm::worklet::AveragePointNeighborhood(this->Radius); + VTKM_LOG_S(vtkm::cont::LogLevel::Info, + "Performing Average with radius: " << this->AverageRadius); + auto averageWorklet = vtkm::worklet::AveragePointNeighborhood(this->AverageRadius); this->Invoke(averageWorklet, cellSet, primary, primaryOutput); this->Invoke(averageWorklet, cellSet, secondary, secondaryOutput); } @@ -79,10 +93,11 @@ inline VTKM_CONT vtkm::cont::DataSet ImageDifference::DoExecute( secondaryField.GetData().template Cast>(); } - if (this->Radius > 0) + if (this->PixelShiftRadius > 0) { VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Diffing image in Neighborhood"); - auto diffWorklet = vtkm::worklet::ImageDifferenceNeighborhood(this->Radius, this->Threshold); + auto diffWorklet = + vtkm::worklet::ImageDifferenceNeighborhood(this->PixelShiftRadius, this->PixelDiffThreshold); this->Invoke(diffWorklet, cellSet, primaryOutput, secondaryOutput, diffOutput, thresholdOutput); } else @@ -92,17 +107,26 @@ inline VTKM_CONT vtkm::cont::DataSet ImageDifference::DoExecute( this->Invoke(diffWorklet, primaryOutput, secondaryOutput, diffOutput, thresholdOutput); } - // Dummy calculate the threshold. If any value is greater than the min our images - // are not similar enough. - vtkm::FloatDefault maxThreshold = - vtkm::cont::Algorithm::Reduce(thresholdOutput, vtkm::FloatDefault(0), vtkm::Maximum()); - if (maxThreshold > this->Threshold) + + vtkm::cont::ArrayHandle errorPixels; + vtkm::cont::Algorithm::CopyIf(thresholdOutput, + thresholdOutput, + errorPixels, + detail::GreaterThanThreshold(this->PixelDiffThreshold)); + if (errorPixels.GetNumberOfValues() > + thresholdOutput.GetNumberOfValues() * this->AllowedPixelErrorRatio) { this->ImageDiffWithinThreshold = false; } VTKM_LOG_S(vtkm::cont::LogLevel::Info, - "Difference within threshold: " << this->ImageDiffWithinThreshold); + "Difference within threshold: " + << this->ImageDiffWithinThreshold + << ", for pixels outside threshold: " << errorPixels.GetNumberOfValues() + << ", with total number of pixels: " << thresholdOutput.GetNumberOfValues() + << ", and an allowable percentage of errored pixels: " + << this->AllowedPixelErrorRatio << ", with a total summed threshold error: " + << vtkm::cont::Algorithm::Reduce(errorPixels, static_cast(0))); vtkm::cont::DataSet clone; clone.CopyStructure(input); diff --git a/vtkm/filter/testing/UnitTestImageDifferenceFilter.cxx b/vtkm/filter/testing/UnitTestImageDifferenceFilter.cxx index 8ca149032..c19a6f93b 100644 --- a/vtkm/filter/testing/UnitTestImageDifferenceFilter.cxx +++ b/vtkm/filter/testing/UnitTestImageDifferenceFilter.cxx @@ -102,8 +102,8 @@ void TestImageDifference() vtkm::filter::ImageDifference filter; filter.SetPrimaryField("primary"); filter.SetSecondaryField("secondary"); - filter.SetThreshold(0.05f); - filter.SetRadius(0); + filter.SetPixelDiffThreshold(0.05f); + filter.SetPixelShiftRadius(0); vtkm::cont::DataSet result = filter.Execute(dataSet); std::vector expectedDiff = { @@ -125,9 +125,9 @@ void TestImageDifference() vtkm::filter::ImageDifference filter; filter.SetPrimaryField("primary"); filter.SetSecondaryField("secondary"); - filter.SetThreshold(0.05f); - filter.SetRadius(1); - filter.SetAveragePixels(true); + filter.SetPixelDiffThreshold(0.05f); + filter.SetPixelShiftRadius(1); + filter.SetAverageRadius(1); vtkm::cont::DataSet result = filter.Execute(dataSet); std::vector expectedDiff = { @@ -149,8 +149,8 @@ void TestImageDifference() vtkm::filter::ImageDifference filter; filter.SetPrimaryField("primary"); filter.SetSecondaryField("secondary"); - filter.SetThreshold(0.05f); - filter.SetRadius(0); + filter.SetPixelDiffThreshold(0.05f); + filter.SetPixelShiftRadius(0); vtkm::cont::DataSet result = filter.Execute(dataSet); std::vector expectedDiff = { @@ -166,6 +166,30 @@ void TestImageDifference() expectedDiff, expectedThreshold, result, filter.GetImageDiffWithinThreshold(), false); } + { + VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Non Matching Images (Different R pixel)"); + auto dataSet = FillDataSet(static_cast(3)); + vtkm::filter::ImageDifference filter; + filter.SetPrimaryField("primary"); + filter.SetSecondaryField("secondary"); + filter.SetPixelDiffThreshold(0.05f); + filter.SetPixelShiftRadius(0); + filter.SetAllowedPixelErrorRatio(1.00f); + vtkm::cont::DataSet result = filter.Execute(dataSet); + + std::vector expectedDiff = { + { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, + { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, + { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, + { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, + { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 } + }; + std::vector expectedThreshold = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; + CheckResult( + expectedDiff, expectedThreshold, result, filter.GetImageDiffWithinThreshold(), true); + } + { VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Non Matching Images (Different R pixel), Large Threshold"); @@ -173,8 +197,8 @@ void TestImageDifference() vtkm::filter::ImageDifference filter; filter.SetPrimaryField("primary"); filter.SetSecondaryField("secondary"); - filter.SetThreshold(3.0f); - filter.SetRadius(0); + filter.SetPixelDiffThreshold(3.0f); + filter.SetPixelShiftRadius(0); vtkm::cont::DataSet result = filter.Execute(dataSet); std::vector expectedDiff = { diff --git a/vtkm/rendering/testing/Testing.h b/vtkm/rendering/testing/Testing.h index 3eb56d231..9ac19183b 100644 --- a/vtkm/rendering/testing/Testing.h +++ b/vtkm/rendering/testing/Testing.h @@ -38,9 +38,10 @@ template inline TestEqualResult test_equal_images(const std::shared_ptr view, const std::vector& fileNames, + const vtkm::IdComponent& averageRadius = 0, + const vtkm::IdComponent& pixelShiftRadius = 0, + const vtkm::FloatDefault& allowedPixelErrorRatio = 0.0001f, const vtkm::FloatDefault& threshold = 0.05f, - const vtkm::IdComponent& radius = 0, - const bool& average = false, const bool& writeDiff = true, const bool& returnOnPass = true) { @@ -95,9 +96,10 @@ inline TestEqualResult test_equal_images(const std::shared_ptr view, vtkm::filter::ImageDifference filter; filter.SetPrimaryField("baseline-image"); filter.SetSecondaryField("generated-image"); - filter.SetThreshold(threshold); - filter.SetRadius(radius); - filter.SetAveragePixels(average); + filter.SetAverageRadius(averageRadius); + filter.SetPixelShiftRadius(pixelShiftRadius); + filter.SetAllowedPixelErrorRatio(allowedPixelErrorRatio); + filter.SetPixelDiffThreshold(threshold); auto resultDataSet = filter.Execute(imageDataSet); if (!filter.GetImageDiffWithinThreshold()) @@ -133,13 +135,15 @@ inline TestEqualResult test_equal_images(const std::shared_ptr view, template inline TestEqualResult test_equal_images(const std::shared_ptr view, const std::string& fileName, + const vtkm::IdComponent& averageRadius = 0, + const vtkm::IdComponent& pixelShiftRadius = 0, + const vtkm::FloatDefault& allowedPixelErrorRatio = 0.0001f, const vtkm::FloatDefault& threshold = 0.05f, - const vtkm::IdComponent& radius = 0, - const bool& average = false, const bool& writeDiff = true) { std::vector fileNames{ fileName }; - return test_equal_images(view, fileNames, threshold, radius, average, writeDiff); + return test_equal_images( + view, fileNames, averageRadius, pixelShiftRadius, allowedPixelErrorRatio, threshold, writeDiff); } /// \brief Tests multiple images in the format `fileName#.png` @@ -156,13 +160,15 @@ inline TestEqualResult test_equal_images(const std::shared_ptr view, /// test_equal_images will then be called on the vector of valid fileNames /// template -inline TestEqualResult test_equal_images_matching_name(const std::shared_ptr view, - const std::string& fileName, - const vtkm::FloatDefault& threshold = 0.05f, - const vtkm::IdComponent& radius = 0, - const bool& average = false, - const bool& writeDiff = true, - const bool& returnOnPass = true) +inline TestEqualResult test_equal_images_matching_name( + const std::shared_ptr view, + const std::string& fileName, + const vtkm::IdComponent& averageRadius = 0, + const vtkm::IdComponent& pixelShiftRadius = 0, + const vtkm::FloatDefault& allowedPixelErrorRatio = 0.0001f, + const vtkm::FloatDefault& threshold = 0.05f, + const bool& writeDiff = true, + const bool& returnOnPass = true) { std::vector fileNames; auto found = fileName.rfind("."); @@ -183,7 +189,14 @@ inline TestEqualResult test_equal_images_matching_name(const std::shared_ptr