| /* |
| * Copyright 2024 The WebRTC project authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "video/corruption_detection/corruption_classifier.h" |
| |
| #include <vector> |
| |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "video/corruption_detection/halton_frame_sampler.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::DoubleNear; |
| #if GTEST_HAS_DEATH_TEST |
| using ::testing::_; |
| using ::testing::HasSubstr; |
| #endif // GTEST_HAS_DEATH_TEST |
| |
| constexpr int kLumaThreshold = 3; |
| constexpr int kChromaThreshold = 2; |
| |
| constexpr double kMaxAbsoluteError = 1e-4; |
| |
| // Arbitrary values for testing. |
| constexpr double kBaseOriginalLumaSampleValue1 = 1.0; |
| constexpr double kBaseOriginalLumaSampleValue2 = 2.5; |
| constexpr double kBaseOriginalChromaSampleValue1 = 0.5; |
| |
| constexpr FilteredSample kFilteredOriginalSampleValues[] = { |
| {.value = kBaseOriginalLumaSampleValue1, .plane = ImagePlane::kLuma}, |
| {.value = kBaseOriginalLumaSampleValue2, .plane = ImagePlane::kLuma}, |
| {.value = kBaseOriginalChromaSampleValue1, .plane = ImagePlane::kChroma}}; |
| |
| // The value 14.0 corresponds to the corruption probability being on the same |
| // side of 0.5 in the `ScalarConfig` and `LogisticFunctionConfig`. |
| constexpr float kScaleFactor = 14.0; |
| |
| constexpr float kGrowthRate = 1.0; |
| constexpr float kMidpoint = 7.0; |
| |
| // Helper function to create fake compressed sample values. |
| std::vector<FilteredSample> GetCompressedSampleValues( |
| double increase_value_luma, |
| double increase_value_chroma) { |
| return std::vector<FilteredSample>{ |
| {.value = kBaseOriginalLumaSampleValue1 + increase_value_luma, |
| .plane = ImagePlane::kLuma}, |
| {.value = kBaseOriginalLumaSampleValue2 + increase_value_luma, |
| .plane = ImagePlane::kLuma}, |
| {.value = kBaseOriginalChromaSampleValue1 + increase_value_chroma, |
| .plane = ImagePlane::kChroma}}; |
| } |
| |
| #if GTEST_HAS_DEATH_TEST |
| TEST(CorruptionClassifierTest, EmptySamplesShouldResultInDeath) { |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| EXPECT_DEATH(corruption_classifier.CalculateCorruptionProbability( |
| {}, {}, kLumaThreshold, kChromaThreshold), |
| _); |
| } |
| |
| TEST(CorruptionClassifierTest, DifferentAmountOfSamplesShouldResultInDeath) { |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| const std::vector<FilteredSample> filtered_compressed_samples = { |
| {.value = 1.0, .plane = ImagePlane::kLuma}}; |
| |
| EXPECT_DEATH(corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, filtered_compressed_samples, |
| kLumaThreshold, kChromaThreshold), |
| HasSubstr("The original and compressed frame have different " |
| "amounts of filtered samples.")); |
| } |
| #endif // GTEST_HAS_DEATH_TEST |
| |
| TEST(CorruptionClassifierTest, |
| SameSampleValuesShouldResultInNoCorruptionScalarConfig) { |
| float kIncreaseValue = 0.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = 0. |
| // Note that the `score` above corresponds to the value returned by the |
| // `GetScore` function. Then this value should be passed through the Scalar or |
| // Logistic function giving the expected result inside DoubleNear. This |
| // applies for all the following tests. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| SameSampleValuesShouldResultInNoCorruptionLogisticFunctionConfig) { |
| float kIncreaseValue = 0.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = 0. See above for explanation why we have `0.0009` below. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0009, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSampleDifferencesBelowThresholdScalarConfig) { |
| // Following value should be < `kLumaThreshold` and `kChromaThreshold`. |
| const double kIncreaseValue = 1; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = 0. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSampleDifferencesBelowThresholdLogisticFunctionConfig) { |
| // Following value should be < `kLumaThreshold` and `kChromaThreshold`. |
| const double kIncreaseValue = 1; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = 0. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0009, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenSmallPartOfSamplesAboveThresholdScalarConfig) { |
| const double kIncreaseValueLuma = 1; |
| const double kIncreaseValueChroma = 2.5; // Above `kChromaThreshold`. |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValueLuma, kIncreaseValueChroma); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = (0.5)^2 / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0060, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenSmallPartOfSamplesAboveThresholdLogisticFunctionConfig) { |
| const double kIncreaseValueLuma = 1; |
| const double kIncreaseValueChroma = 2.5; // Above `kChromaThreshold`. |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValueLuma, kIncreaseValueChroma); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = (0.5)^2 / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.001, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSamplesSlightlyAboveThresholdScalarConfig) { |
| const double kIncreaseValueLuma = 4.2; // Above `kLumaThreshold`. |
| const double kIncreaseValueChroma = 2.5; // Above `kChromaThreshold`. |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValueLuma, kIncreaseValueChroma); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = ((0.5)^2 + 2*(1.2)^2) / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.07452, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSamplesSlightlyAboveThresholdLogisticFunctionConfig) { |
| const double kIncreaseValueLuma = 4.2; // Above `kLumaThreshold`. |
| const double kIncreaseValueChroma = 2.5; // Above `kChromaThreshold`. |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValueLuma, kIncreaseValueChroma); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = ((0.5)^2 + 2*(1.2)^2) / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.0026, kMaxAbsoluteError)); |
| } |
| |
| // Observe that the following 2 tests in practice could be classified as |
| // corrupted, if so wanted. However, with the `kGrowthRate`, `kMidpoint` and |
| // `kScaleFactor` values chosen in these tests, the score is not high enough to |
| // be classified as corrupted. |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSamplesSomewhatAboveThresholdScalarConfig) { |
| const double kIncreaseValue = 5.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = ((3)^2 + 2*(2)^2) / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.4048, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| NoCorruptionWhenAllSamplesSomewhatAboveThresholdLogisticFunctionConfig) { |
| // Somewhat above `kLumaThreshold` and `kChromaThreshold`. |
| const double kIncreaseValue = 5.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = ((3)^2 + 2*(2)^2) / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(0.2086, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| CorruptionWhenAllSamplesWellAboveThresholdScalarConfig) { |
| // Well above `kLumaThreshold` and `kChromaThreshold`. |
| const double kIncreaseValue = 7.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kScaleFactor); |
| |
| // Expected: score = ((5)^2 + 2*(4)^2) / 3. Expected 1 because of capping. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(1, kMaxAbsoluteError)); |
| } |
| |
| TEST(CorruptionClassifierTest, |
| CorruptionWhenAllSamplesWellAboveThresholdLogisticFunctionConfig) { |
| // Well above `kLumaThreshold` and `kChromaThreshold`. |
| const double kIncreaseValue = 7.0; |
| const std::vector<FilteredSample> kFilteredCompressedSampleValues = |
| GetCompressedSampleValues(kIncreaseValue, kIncreaseValue); |
| |
| CorruptionClassifier corruption_classifier(kGrowthRate, kMidpoint); |
| |
| // Expected: score = ((5)^2 + 2*(4)^2) / 3. |
| EXPECT_THAT( |
| corruption_classifier.CalculateCorruptionProbability( |
| kFilteredOriginalSampleValues, kFilteredCompressedSampleValues, |
| kLumaThreshold, kChromaThreshold), |
| DoubleNear(1, kMaxAbsoluteError)); |
| } |
| |
| } // namespace |
| } // namespace webrtc |