|  | /* | 
|  | * Copyright (c) 2018, Alliance for Open Media. All rights reserved. | 
|  | * | 
|  | * This source code is subject to the terms of the BSD 2 Clause License and | 
|  | * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
|  | * was not distributed with this source code in the LICENSE file, you can | 
|  | * obtain it at www.aomedia.org/license/software. If the Alliance for Open | 
|  | * Media Patent License 1.0 was not distributed with this source code in the | 
|  | * PATENTS file, you can obtain it at www.aomedia.org/license/patent. | 
|  | */ | 
|  |  | 
|  | #include <limits.h> | 
|  | #include <math.h> | 
|  | #include <algorithm> | 
|  | #include <vector> | 
|  |  | 
|  | #include "aom_dsp/noise_model.h" | 
|  | #include "aom_dsp/noise_util.h" | 
|  | #include "config/aom_dsp_rtcd.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include "test/acm_random.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Return normally distrbuted values with standard deviation of sigma. | 
|  | double randn(libaom_test::ACMRandom *random, double sigma) { | 
|  | while (true) { | 
|  | const double u = 2.0 * ((double)random->Rand31() / | 
|  | testing::internal::Random::kMaxRange) - | 
|  | 1.0; | 
|  | const double v = 2.0 * ((double)random->Rand31() / | 
|  | testing::internal::Random::kMaxRange) - | 
|  | 1.0; | 
|  | const double s = u * u + v * v; | 
|  | if (s > 0 && s < 1) { | 
|  | return sigma * (u * sqrt(-2.0 * log(s) / s)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Synthesizes noise using the auto-regressive filter of the given lag, | 
|  | // with the provided n coefficients sampled at the given coords. | 
|  | void noise_synth(libaom_test::ACMRandom *random, int lag, int n, | 
|  | const int (*coords)[2], const double *coeffs, double *data, | 
|  | int w, int h) { | 
|  | const int pad_size = 3 * lag; | 
|  | const int padded_w = w + pad_size; | 
|  | const int padded_h = h + pad_size; | 
|  | int x = 0, y = 0; | 
|  | std::vector<double> padded(padded_w * padded_h); | 
|  |  | 
|  | for (y = 0; y < padded_h; ++y) { | 
|  | for (x = 0; x < padded_w; ++x) { | 
|  | padded[y * padded_w + x] = randn(random, 1.0); | 
|  | } | 
|  | } | 
|  | for (y = lag; y < padded_h; ++y) { | 
|  | for (x = lag; x < padded_w; ++x) { | 
|  | double sum = 0; | 
|  | int i = 0; | 
|  | for (i = 0; i < n; ++i) { | 
|  | const int dx = coords[i][0]; | 
|  | const int dy = coords[i][1]; | 
|  | sum += padded[(y + dy) * padded_w + (x + dx)] * coeffs[i]; | 
|  | } | 
|  | padded[y * padded_w + x] += sum; | 
|  | } | 
|  | } | 
|  | // Copy over the padded rows to the output | 
|  | for (y = 0; y < h; ++y) { | 
|  | memcpy(data + y * w, &padded[0] + y * padded_w, sizeof(*data) * w); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<float> get_noise_psd(double *noise, int width, int height, | 
|  | int block_size) { | 
|  | float *block = | 
|  | (float *)aom_memalign(32, block_size * block_size * sizeof(block)); | 
|  | std::vector<float> psd(block_size * block_size); | 
|  | if (block == nullptr) { | 
|  | EXPECT_NE(block, nullptr); | 
|  | return psd; | 
|  | } | 
|  | int num_blocks = 0; | 
|  | struct aom_noise_tx_t *tx = aom_noise_tx_malloc(block_size); | 
|  | if (tx == nullptr) { | 
|  | EXPECT_NE(tx, nullptr); | 
|  | return psd; | 
|  | } | 
|  | for (int y = 0; y <= height - block_size; y += block_size / 2) { | 
|  | for (int x = 0; x <= width - block_size; x += block_size / 2) { | 
|  | for (int yy = 0; yy < block_size; ++yy) { | 
|  | for (int xx = 0; xx < block_size; ++xx) { | 
|  | block[yy * block_size + xx] = (float)noise[(y + yy) * width + x + xx]; | 
|  | } | 
|  | } | 
|  | aom_noise_tx_forward(tx, &block[0]); | 
|  | aom_noise_tx_add_energy(tx, &psd[0]); | 
|  | num_blocks++; | 
|  | } | 
|  | } | 
|  | for (int yy = 0; yy < block_size; ++yy) { | 
|  | for (int xx = 0; xx <= block_size / 2; ++xx) { | 
|  | psd[yy * block_size + xx] /= num_blocks; | 
|  | } | 
|  | } | 
|  | // Fill in the data that is missing due to symmetries | 
|  | for (int xx = 1; xx < block_size / 2; ++xx) { | 
|  | psd[(block_size - xx)] = psd[xx]; | 
|  | } | 
|  | for (int yy = 1; yy < block_size; ++yy) { | 
|  | for (int xx = 1; xx < block_size / 2; ++xx) { | 
|  | psd[(block_size - yy) * block_size + (block_size - xx)] = | 
|  | psd[yy * block_size + xx]; | 
|  | } | 
|  | } | 
|  | aom_noise_tx_free(tx); | 
|  | aom_free(block); | 
|  | return psd; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(NoiseStrengthSolver, GetCentersTwoBins) { | 
|  | aom_noise_strength_solver_t solver; | 
|  | aom_noise_strength_solver_init(&solver, 2, 8); | 
|  | EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5); | 
|  | EXPECT_NEAR(255, aom_noise_strength_solver_get_center(&solver, 1), 1e-5); | 
|  | aom_noise_strength_solver_free(&solver); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthSolver, GetCentersTwoBins10bit) { | 
|  | aom_noise_strength_solver_t solver; | 
|  | aom_noise_strength_solver_init(&solver, 2, 10); | 
|  | EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5); | 
|  | EXPECT_NEAR(1023, aom_noise_strength_solver_get_center(&solver, 1), 1e-5); | 
|  | aom_noise_strength_solver_free(&solver); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthSolver, GetCenters256Bins) { | 
|  | const int num_bins = 256; | 
|  | aom_noise_strength_solver_t solver; | 
|  | aom_noise_strength_solver_init(&solver, num_bins, 8); | 
|  |  | 
|  | for (int i = 0; i < 256; ++i) { | 
|  | EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5); | 
|  | } | 
|  | aom_noise_strength_solver_free(&solver); | 
|  | } | 
|  |  | 
|  | // Tests that the noise strength solver returns the identity transform when | 
|  | // given identity-like constraints. | 
|  | TEST(NoiseStrengthSolver, ObserveIdentity) { | 
|  | const int num_bins = 256; | 
|  | aom_noise_strength_solver_t solver; | 
|  | ASSERT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8)); | 
|  |  | 
|  | // We have to add a big more strength to constraints at the boundary to | 
|  | // overcome any regularization. | 
|  | for (int j = 0; j < 5; ++j) { | 
|  | aom_noise_strength_solver_add_measurement(&solver, 0, 0); | 
|  | aom_noise_strength_solver_add_measurement(&solver, 255, 255); | 
|  | } | 
|  | for (int i = 0; i < 256; ++i) { | 
|  | aom_noise_strength_solver_add_measurement(&solver, i, i); | 
|  | } | 
|  | EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); | 
|  | for (int i = 2; i < num_bins - 2; ++i) { | 
|  | EXPECT_NEAR(i, solver.eqns.x[i], 0.1); | 
|  | } | 
|  |  | 
|  | aom_noise_strength_lut_t lut; | 
|  | EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, 2, &lut)); | 
|  |  | 
|  | ASSERT_EQ(2, lut.num_points); | 
|  | EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); | 
|  | EXPECT_NEAR(0.0, lut.points[0][1], 0.5); | 
|  | EXPECT_NEAR(255.0, lut.points[1][0], 1e-5); | 
|  | EXPECT_NEAR(255.0, lut.points[1][1], 0.5); | 
|  |  | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | aom_noise_strength_solver_free(&solver); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthSolver, SimplifiesCurve) { | 
|  | const int num_bins = 256; | 
|  | aom_noise_strength_solver_t solver; | 
|  | EXPECT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8)); | 
|  |  | 
|  | // Create a parabolic input | 
|  | for (int i = 0; i < 256; ++i) { | 
|  | const double x = (i - 127.5) / 63.5; | 
|  | aom_noise_strength_solver_add_measurement(&solver, i, x * x); | 
|  | } | 
|  | EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); | 
|  |  | 
|  | // First try to fit an unconstrained lut | 
|  | aom_noise_strength_lut_t lut; | 
|  | EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, -1, &lut)); | 
|  | ASSERT_LE(20, lut.num_points); | 
|  | aom_noise_strength_lut_free(&lut); | 
|  |  | 
|  | // Now constrain the maximum number of points | 
|  | const int kMaxPoints = 9; | 
|  | EXPECT_EQ(1, | 
|  | aom_noise_strength_solver_fit_piecewise(&solver, kMaxPoints, &lut)); | 
|  | ASSERT_EQ(kMaxPoints, lut.num_points); | 
|  |  | 
|  | // Check that the input parabola is still well represented | 
|  | EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); | 
|  | EXPECT_NEAR(4.0, lut.points[0][1], 0.1); | 
|  | for (int i = 1; i < lut.num_points - 1; ++i) { | 
|  | const double x = (lut.points[i][0] - 128.) / 64.; | 
|  | EXPECT_NEAR(x * x, lut.points[i][1], 0.1); | 
|  | } | 
|  | EXPECT_NEAR(255.0, lut.points[kMaxPoints - 1][0], 1e-5); | 
|  |  | 
|  | EXPECT_NEAR(4.0, lut.points[kMaxPoints - 1][1], 0.1); | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | aom_noise_strength_solver_free(&solver); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthLut, LutInitNegativeOrZeroSize) { | 
|  | aom_noise_strength_lut_t lut; | 
|  | ASSERT_FALSE(aom_noise_strength_lut_init(&lut, -1)); | 
|  | ASSERT_FALSE(aom_noise_strength_lut_init(&lut, 0)); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthLut, LutEvalSinglePoint) { | 
|  | aom_noise_strength_lut_t lut; | 
|  | ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 1)); | 
|  | ASSERT_EQ(1, lut.num_points); | 
|  | lut.points[0][0] = 0; | 
|  | lut.points[0][1] = 1; | 
|  | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, -1)); | 
|  | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 0)); | 
|  | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 1)); | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | } | 
|  |  | 
|  | TEST(NoiseStrengthLut, LutEvalMultiPointInterp) { | 
|  | const double kEps = 1e-5; | 
|  | aom_noise_strength_lut_t lut; | 
|  | ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 4)); | 
|  | ASSERT_EQ(4, lut.num_points); | 
|  |  | 
|  | lut.points[0][0] = 0; | 
|  | lut.points[0][1] = 0; | 
|  |  | 
|  | lut.points[1][0] = 1; | 
|  | lut.points[1][1] = 1; | 
|  |  | 
|  | lut.points[2][0] = 2; | 
|  | lut.points[2][1] = 1; | 
|  |  | 
|  | lut.points[3][0] = 100; | 
|  | lut.points[3][1] = 1001; | 
|  |  | 
|  | // Test lower boundary | 
|  | EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, -1)); | 
|  | EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, 0)); | 
|  |  | 
|  | // Test first part that should be identity | 
|  | EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut, 0.25), kEps); | 
|  | EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut, 0.75), kEps); | 
|  |  | 
|  | // This is a constant section (should evaluate to 1) | 
|  | EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.25), kEps); | 
|  | EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.75), kEps); | 
|  |  | 
|  | // Test interpolation between to non-zero y coords. | 
|  | EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut, 2), kEps); | 
|  | EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut, 26.5), kEps); | 
|  | EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut, 75.5), kEps); | 
|  |  | 
|  | // Test upper boundary | 
|  | EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 100)); | 
|  | EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 101)); | 
|  |  | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitSuccessWithValidSquareShape) { | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  |  | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  |  | 
|  | const int kNumCoords = 12; | 
|  | const int kCoords[][2] = { { -2, -2 }, { -1, -2 }, { 0, -2 },  { 1, -2 }, | 
|  | { 2, -2 },  { -2, -1 }, { -1, -1 }, { 0, -1 }, | 
|  | { 1, -1 },  { 2, -1 },  { -2, 0 },  { -1, 0 } }; | 
|  | EXPECT_EQ(kNumCoords, model.n); | 
|  | for (int i = 0; i < kNumCoords; ++i) { | 
|  | const int *coord = kCoords[i]; | 
|  | EXPECT_EQ(coord[0], model.coords[i][0]); | 
|  | EXPECT_EQ(coord[1], model.coords[i][1]); | 
|  | } | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitSuccessWithValidDiamondShape) { | 
|  | aom_noise_model_t model; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_DIAMOND, 2, 8, 0 }; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  | EXPECT_EQ(6, model.n); | 
|  | const int kNumCoords = 6; | 
|  | const int kCoords[][2] = { { 0, -2 }, { -1, -1 }, { 0, -1 }, | 
|  | { 1, -1 }, { -2, 0 },  { -1, 0 } }; | 
|  | EXPECT_EQ(kNumCoords, model.n); | 
|  | for (int i = 0; i < kNumCoords; ++i) { | 
|  | const int *coord = kCoords[i]; | 
|  | EXPECT_EQ(coord[0], model.coords[i][0]); | 
|  | EXPECT_EQ(coord[1], model.coords[i][1]); | 
|  | } | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitFailsWithTooLargeLag) { | 
|  | aom_noise_model_t model; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 10, 8, 0 }; | 
|  | EXPECT_FALSE(aom_noise_model_init(&model, params)); | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitFailsWithTooSmallLag) { | 
|  | aom_noise_model_t model; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 0, 8, 0 }; | 
|  | EXPECT_FALSE(aom_noise_model_init(&model, params)); | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitFailsWithInvalidShape) { | 
|  | aom_noise_model_t model; | 
|  | aom_noise_model_params_t params = { aom_noise_shape(100), 3, 8, 0 }; | 
|  | EXPECT_FALSE(aom_noise_model_init(&model, params)); | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModel, InitFailsWithInvalidBitdepth) { | 
|  | aom_noise_model_t model; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 }; | 
|  | for (int i = 0; i <= 32; ++i) { | 
|  | params.bit_depth = i; | 
|  | if (i == 8 || i == 10 || i == 12) { | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)) << "bit_depth: " << i; | 
|  | aom_noise_model_free(&model); | 
|  | } else { | 
|  | EXPECT_FALSE(aom_noise_model_init(&model, params)) << "bit_depth: " << i; | 
|  | } | 
|  | } | 
|  | params.bit_depth = INT_MAX; | 
|  | EXPECT_FALSE(aom_noise_model_init(&model, params)); | 
|  | } | 
|  |  | 
|  | // A container template class to hold a data type and extra arguments. | 
|  | // All of these args are bundled into one struct so that we can use | 
|  | // parameterized tests on combinations of supported data types | 
|  | // (uint8_t and uint16_t) and bit depths (8, 10, 12). | 
|  | template <typename T, int bit_depth, bool use_highbd> | 
|  | struct BitDepthParams { | 
|  | typedef T data_type_t; | 
|  | static const int kBitDepth = bit_depth; | 
|  | static const bool kUseHighBD = use_highbd; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | class FlatBlockEstimatorTest : public ::testing::Test, public T { | 
|  | public: | 
|  | void SetUp() override { random_.Reset(171); } | 
|  | typedef std::vector<typename T::data_type_t> VecType; | 
|  | VecType data_; | 
|  | libaom_test::ACMRandom random_; | 
|  | }; | 
|  |  | 
|  | TYPED_TEST_SUITE_P(FlatBlockEstimatorTest); | 
|  |  | 
|  | TYPED_TEST_P(FlatBlockEstimatorTest, ExtractBlock) { | 
|  | const int kBlockSize = 16; | 
|  | aom_flat_block_finder_t flat_block_finder; | 
|  | ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize, | 
|  | this->kBitDepth, this->kUseHighBD)); | 
|  | const double normalization = flat_block_finder.normalization; | 
|  |  | 
|  | // Test with an image of more than one block. | 
|  | const int h = 2 * kBlockSize; | 
|  | const int w = 2 * kBlockSize; | 
|  | const int stride = 2 * kBlockSize; | 
|  | this->data_.resize(h * stride, 128); | 
|  |  | 
|  | // Set up the (0,0) block to be a plane and the (0,1) block to be a | 
|  | // checkerboard | 
|  | const int shift = this->kBitDepth - 8; | 
|  | for (int y = 0; y < kBlockSize; ++y) { | 
|  | for (int x = 0; x < kBlockSize; ++x) { | 
|  | this->data_[y * stride + x] = (-y + x + 128) << shift; | 
|  | this->data_[y * stride + x + kBlockSize] = | 
|  | ((x % 2 + y % 2) % 2 ? 128 - 20 : 128 + 20) << shift; | 
|  | } | 
|  | } | 
|  | std::vector<double> block(kBlockSize * kBlockSize, 1); | 
|  | std::vector<double> plane(kBlockSize * kBlockSize, 1); | 
|  |  | 
|  | // The block data should be a constant (zero) and the rest of the plane | 
|  | // trend is covered in the plane data. | 
|  | aom_flat_block_finder_extract_block(&flat_block_finder, | 
|  | (uint8_t *)&this->data_[0], w, h, stride, | 
|  | 0, 0, &plane[0], &block[0]); | 
|  | for (int y = 0; y < kBlockSize; ++y) { | 
|  | for (int x = 0; x < kBlockSize; ++x) { | 
|  | EXPECT_NEAR(0, block[y * kBlockSize + x], 1e-5); | 
|  | EXPECT_NEAR((double)(this->data_[y * stride + x]) / normalization, | 
|  | plane[y * kBlockSize + x], 1e-5); | 
|  | } | 
|  | } | 
|  |  | 
|  | // The plane trend is a constant, and the block is a zero mean checkerboard. | 
|  | aom_flat_block_finder_extract_block(&flat_block_finder, | 
|  | (uint8_t *)&this->data_[0], w, h, stride, | 
|  | kBlockSize, 0, &plane[0], &block[0]); | 
|  | const int mid = 128 << shift; | 
|  | for (int y = 0; y < kBlockSize; ++y) { | 
|  | for (int x = 0; x < kBlockSize; ++x) { | 
|  | EXPECT_NEAR(((double)this->data_[y * stride + x + kBlockSize] - mid) / | 
|  | normalization, | 
|  | block[y * kBlockSize + x], 1e-5); | 
|  | EXPECT_NEAR(mid / normalization, plane[y * kBlockSize + x], 1e-5); | 
|  | } | 
|  | } | 
|  | aom_flat_block_finder_free(&flat_block_finder); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(FlatBlockEstimatorTest, FindFlatBlocks) { | 
|  | const int kBlockSize = 32; | 
|  | aom_flat_block_finder_t flat_block_finder; | 
|  | ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize, | 
|  | this->kBitDepth, this->kUseHighBD)); | 
|  |  | 
|  | const int num_blocks_w = 8; | 
|  | const int h = kBlockSize; | 
|  | const int w = kBlockSize * num_blocks_w; | 
|  | const int stride = w; | 
|  | this->data_.resize(h * stride, 128); | 
|  | std::vector<uint8_t> flat_blocks(num_blocks_w, 0); | 
|  |  | 
|  | const int shift = this->kBitDepth - 8; | 
|  | for (int y = 0; y < kBlockSize; ++y) { | 
|  | for (int x = 0; x < kBlockSize; ++x) { | 
|  | // Block 0 (not flat): constant doesn't have enough variance to qualify | 
|  | this->data_[y * stride + x + 0 * kBlockSize] = 128 << shift; | 
|  |  | 
|  | // Block 1 (not flat): too high of variance is hard to validate as flat | 
|  | this->data_[y * stride + x + 1 * kBlockSize] = | 
|  | ((uint8_t)(128 + randn(&this->random_, 5))) << shift; | 
|  |  | 
|  | // Block 2 (flat): slight checkerboard added to constant | 
|  | const int check = (x % 2 + y % 2) % 2 ? -2 : 2; | 
|  | this->data_[y * stride + x + 2 * kBlockSize] = (128 + check) << shift; | 
|  |  | 
|  | // Block 3 (flat): planar block with checkerboard pattern is also flat | 
|  | this->data_[y * stride + x + 3 * kBlockSize] = | 
|  | (y * 2 - x / 2 + 128 + check) << shift; | 
|  |  | 
|  | // Block 4 (flat): gaussian random with standard deviation 1. | 
|  | this->data_[y * stride + x + 4 * kBlockSize] = | 
|  | ((uint8_t)(randn(&this->random_, 1) + x + 128.0)) << shift; | 
|  |  | 
|  | // Block 5 (flat): gaussian random with standard deviation 2. | 
|  | this->data_[y * stride + x + 5 * kBlockSize] = | 
|  | ((uint8_t)(randn(&this->random_, 2) + y + 128.0)) << shift; | 
|  |  | 
|  | // Block 6 (not flat): too high of directional gradient. | 
|  | const int strong_edge = x > kBlockSize / 2 ? 64 : 0; | 
|  | this->data_[y * stride + x + 6 * kBlockSize] = | 
|  | ((uint8_t)(randn(&this->random_, 1) + strong_edge + 128.0)) << shift; | 
|  |  | 
|  | // Block 7 (not flat): too high gradient. | 
|  | const int big_check = ((x >> 2) % 2 + (y >> 2) % 2) % 2 ? -16 : 16; | 
|  | this->data_[y * stride + x + 7 * kBlockSize] = | 
|  | ((uint8_t)(randn(&this->random_, 1) + big_check + 128.0)) << shift; | 
|  | } | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(4, aom_flat_block_finder_run(&flat_block_finder, | 
|  | (uint8_t *)&this->data_[0], w, h, | 
|  | stride, &flat_blocks[0])); | 
|  |  | 
|  | // First two blocks are not flat | 
|  | EXPECT_EQ(0, flat_blocks[0]); | 
|  | EXPECT_EQ(0, flat_blocks[1]); | 
|  |  | 
|  | // Next 4 blocks are flat. | 
|  | EXPECT_EQ(255, flat_blocks[2]); | 
|  | EXPECT_EQ(255, flat_blocks[3]); | 
|  | EXPECT_EQ(255, flat_blocks[4]); | 
|  | EXPECT_EQ(255, flat_blocks[5]); | 
|  |  | 
|  | // Last 2 are not flat by threshold | 
|  | EXPECT_EQ(0, flat_blocks[6]); | 
|  | EXPECT_EQ(0, flat_blocks[7]); | 
|  |  | 
|  | // Add the noise from non-flat block 1 to every block. | 
|  | for (int y = 0; y < kBlockSize; ++y) { | 
|  | for (int x = 0; x < kBlockSize * num_blocks_w; ++x) { | 
|  | this->data_[y * stride + x] += | 
|  | (this->data_[y * stride + x % kBlockSize + kBlockSize] - | 
|  | (128 << shift)); | 
|  | } | 
|  | } | 
|  | // Now the scored selection will pick the one that is most likely flat (block | 
|  | // 0) | 
|  | EXPECT_EQ(1, aom_flat_block_finder_run(&flat_block_finder, | 
|  | (uint8_t *)&this->data_[0], w, h, | 
|  | stride, &flat_blocks[0])); | 
|  | EXPECT_EQ(1, flat_blocks[0]); | 
|  | EXPECT_EQ(0, flat_blocks[1]); | 
|  | EXPECT_EQ(0, flat_blocks[2]); | 
|  | EXPECT_EQ(0, flat_blocks[3]); | 
|  | EXPECT_EQ(0, flat_blocks[4]); | 
|  | EXPECT_EQ(0, flat_blocks[5]); | 
|  | EXPECT_EQ(0, flat_blocks[6]); | 
|  | EXPECT_EQ(0, flat_blocks[7]); | 
|  |  | 
|  | aom_flat_block_finder_free(&flat_block_finder); | 
|  | } | 
|  |  | 
|  | REGISTER_TYPED_TEST_SUITE_P(FlatBlockEstimatorTest, ExtractBlock, | 
|  | FindFlatBlocks); | 
|  |  | 
|  | typedef ::testing::Types<BitDepthParams<uint8_t, 8, false>,   // lowbd | 
|  | BitDepthParams<uint16_t, 8, true>,   // lowbd in 16-bit | 
|  | BitDepthParams<uint16_t, 10, true>,  // highbd data | 
|  | BitDepthParams<uint16_t, 12, true> > | 
|  | AllBitDepthParams; | 
|  | // Note the empty final argument can be removed if C++20 is made the minimum | 
|  | // requirement. | 
|  | INSTANTIATE_TYPED_TEST_SUITE_P(FlatBlockInstatiation, FlatBlockEstimatorTest, | 
|  | AllBitDepthParams, ); | 
|  |  | 
|  | template <typename T> | 
|  | class NoiseModelUpdateTest : public ::testing::Test, public T { | 
|  | public: | 
|  | static const int kWidth = 128; | 
|  | static const int kHeight = 128; | 
|  | static const int kBlockSize = 16; | 
|  | static const int kNumBlocksX = kWidth / kBlockSize; | 
|  | static const int kNumBlocksY = kHeight / kBlockSize; | 
|  |  | 
|  | void SetUp() override { | 
|  | const aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3, | 
|  | T::kBitDepth, T::kUseHighBD }; | 
|  | ASSERT_TRUE(aom_noise_model_init(&model_, params)); | 
|  |  | 
|  | random_.Reset(100171); | 
|  |  | 
|  | data_.resize(kWidth * kHeight * 3); | 
|  | denoised_.resize(kWidth * kHeight * 3); | 
|  | noise_.resize(kWidth * kHeight * 3); | 
|  | renoise_.resize(kWidth * kHeight); | 
|  | flat_blocks_.resize(kNumBlocksX * kNumBlocksY); | 
|  |  | 
|  | for (int c = 0, offset = 0; c < 3; ++c, offset += kWidth * kHeight) { | 
|  | data_ptr_[c] = &data_[offset]; | 
|  | noise_ptr_[c] = &noise_[offset]; | 
|  | denoised_ptr_[c] = &denoised_[offset]; | 
|  | strides_[c] = kWidth; | 
|  |  | 
|  | data_ptr_raw_[c] = (uint8_t *)&data_[offset]; | 
|  | denoised_ptr_raw_[c] = (uint8_t *)&denoised_[offset]; | 
|  | } | 
|  | chroma_sub_[0] = 0; | 
|  | chroma_sub_[1] = 0; | 
|  | } | 
|  |  | 
|  | int NoiseModelUpdate(int block_size = kBlockSize) { | 
|  | return aom_noise_model_update(&model_, data_ptr_raw_, denoised_ptr_raw_, | 
|  | kWidth, kHeight, strides_, chroma_sub_, | 
|  | &flat_blocks_[0], block_size); | 
|  | } | 
|  |  | 
|  | void TearDown() override { aom_noise_model_free(&model_); } | 
|  |  | 
|  | protected: | 
|  | aom_noise_model_t model_; | 
|  | std::vector<typename T::data_type_t> data_; | 
|  | std::vector<typename T::data_type_t> denoised_; | 
|  |  | 
|  | std::vector<double> noise_; | 
|  | std::vector<double> renoise_; | 
|  | std::vector<uint8_t> flat_blocks_; | 
|  |  | 
|  | typename T::data_type_t *data_ptr_[3]; | 
|  | typename T::data_type_t *denoised_ptr_[3]; | 
|  |  | 
|  | double *noise_ptr_[3]; | 
|  | int strides_[3]; | 
|  | int chroma_sub_[2]; | 
|  | libaom_test::ACMRandom random_; | 
|  |  | 
|  | private: | 
|  | uint8_t *data_ptr_raw_[3]; | 
|  | uint8_t *denoised_ptr_raw_[3]; | 
|  | }; | 
|  |  | 
|  | TYPED_TEST_SUITE_P(NoiseModelUpdateTest); | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) { | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS, | 
|  | this->NoiseModelUpdate()); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForZeroNoiseAllFlat) { | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | this->denoised_.assign(this->denoised_.size(), 128); | 
|  | this->data_.assign(this->denoised_.size(), 128); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_INTERNAL_ERROR, this->NoiseModelUpdate()); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsBlockSizeTooSmall) { | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | this->denoised_.assign(this->denoised_.size(), 128); | 
|  | this->data_.assign(this->denoised_.size(), 128); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_INVALID_ARGUMENT, | 
|  | this->NoiseModelUpdate(6 /* block_size=6 is too small*/)); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForWhiteRandomNoise) { | 
|  | aom_noise_model_t &model = this->model_; | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  |  | 
|  | const int shift = this->kBitDepth - 8; | 
|  | for (int y = 0; y < height; ++y) { | 
|  | for (int x = 0; x < width; ++x) { | 
|  | this->data_ptr_[0][y * width + x] = int(64 + y + randn(&this->random_, 1)) | 
|  | << shift; | 
|  | this->denoised_ptr_[0][y * width + x] = (64 + y) << shift; | 
|  | // Make the chroma planes completely correlated with the Y plane | 
|  | for (int c = 1; c < 3; ++c) { | 
|  | this->data_ptr_[c][y * width + x] = this->data_ptr_[0][y * width + x]; | 
|  | this->denoised_ptr_[c][y * width + x] = | 
|  | this->denoised_ptr_[0][y * width + x]; | 
|  | } | 
|  | } | 
|  | } | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | const double kCoeffEps = 0.075; | 
|  | const int n = model.n; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | for (int i = 0; i < n; ++i) { | 
|  | EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps); | 
|  | EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps); | 
|  | } | 
|  | // The second and third channels are highly correlated with the first. | 
|  | if (c > 0) { | 
|  | ASSERT_EQ(n + 1, model.latest_state[c].eqns.n); | 
|  | ASSERT_EQ(n + 1, model.combined_state[c].eqns.n); | 
|  |  | 
|  | EXPECT_NEAR(1, model.latest_state[c].eqns.x[n], kCoeffEps); | 
|  | EXPECT_NEAR(1, model.combined_state[c].eqns.x[n], kCoeffEps); | 
|  | } | 
|  | } | 
|  |  | 
|  | // The fitted noise strength should be close to the standard deviation | 
|  | // for all intensity bins. | 
|  | const double kStdEps = 0.1; | 
|  | const double normalize = 1 << shift; | 
|  |  | 
|  | for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) { | 
|  | EXPECT_NEAR(1.0, | 
|  | model.latest_state[0].strength_solver.eqns.x[i] / normalize, | 
|  | kStdEps); | 
|  | EXPECT_NEAR(1.0, | 
|  | model.combined_state[0].strength_solver.eqns.x[i] / normalize, | 
|  | kStdEps); | 
|  | } | 
|  |  | 
|  | aom_noise_strength_lut_t lut; | 
|  | aom_noise_strength_solver_fit_piecewise( | 
|  | &model.latest_state[0].strength_solver, -1, &lut); | 
|  | ASSERT_EQ(2, lut.num_points); | 
|  | EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); | 
|  | EXPECT_NEAR(1.0, lut.points[0][1] / normalize, kStdEps); | 
|  | EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5); | 
|  | EXPECT_NEAR(1.0, lut.points[1][1] / normalize, kStdEps); | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForScaledWhiteNoise) { | 
|  | aom_noise_model_t &model = this->model_; | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  |  | 
|  | const double kCoeffEps = 0.055; | 
|  | const double kLowStd = 1; | 
|  | const double kHighStd = 4; | 
|  | const int shift = this->kBitDepth - 8; | 
|  | for (int y = 0; y < height; ++y) { | 
|  | for (int x = 0; x < width; ++x) { | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | // The image data is bimodal: | 
|  | // Bottom half has low intensity and low noise strength | 
|  | // Top half has high intensity and high noise strength | 
|  | const int avg = (y < height / 2) ? 4 : 245; | 
|  | const double std = (y < height / 2) ? kLowStd : kHighStd; | 
|  | this->data_ptr_[c][y * width + x] = | 
|  | ((uint8_t)std::min((int)255, | 
|  | (int)(2 + avg + randn(&this->random_, std)))) | 
|  | << shift; | 
|  | this->denoised_ptr_[c][y * width + x] = (2 + avg) << shift; | 
|  | } | 
|  | } | 
|  | } | 
|  | // Label all blocks as flat for the update | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | const int n = model.n; | 
|  | // The noise is uncorrelated spatially and with the y channel. | 
|  | // All coefficients should be reasonably close to zero. | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | for (int i = 0; i < n; ++i) { | 
|  | EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps); | 
|  | EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps); | 
|  | } | 
|  | if (c > 0) { | 
|  | ASSERT_EQ(n + 1, model.latest_state[c].eqns.n); | 
|  | ASSERT_EQ(n + 1, model.combined_state[c].eqns.n); | 
|  |  | 
|  | // The correlation to the y channel should be low (near zero) | 
|  | EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps); | 
|  | EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Noise strength should vary between kLowStd and kHighStd. | 
|  | const double kStdEps = 0.15; | 
|  | // We have to normalize fitted standard deviation based on bit depth. | 
|  | const double normalize = (1 << shift); | 
|  |  | 
|  | ASSERT_EQ(20, model.latest_state[0].strength_solver.eqns.n); | 
|  | for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) { | 
|  | const double a = i / 19.0; | 
|  | const double expected = (kLowStd * (1.0 - a) + kHighStd * a); | 
|  | EXPECT_NEAR(expected, | 
|  | model.latest_state[0].strength_solver.eqns.x[i] / normalize, | 
|  | kStdEps); | 
|  | EXPECT_NEAR(expected, | 
|  | model.combined_state[0].strength_solver.eqns.x[i] / normalize, | 
|  | kStdEps); | 
|  | } | 
|  |  | 
|  | // If we fit a piecewise linear model, there should be two points: | 
|  | // one near kLowStd at 0, and the other near kHighStd and 255. | 
|  | aom_noise_strength_lut_t lut; | 
|  | aom_noise_strength_solver_fit_piecewise( | 
|  | &model.latest_state[0].strength_solver, 2, &lut); | 
|  | ASSERT_EQ(2, lut.num_points); | 
|  | EXPECT_NEAR(0, lut.points[0][0], 1e-4); | 
|  | EXPECT_NEAR(kLowStd, lut.points[0][1] / normalize, kStdEps); | 
|  | EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5); | 
|  | EXPECT_NEAR(kHighStd, lut.points[1][1] / normalize, kStdEps); | 
|  | aom_noise_strength_lut_free(&lut); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForCorrelatedNoise) { | 
|  | aom_noise_model_t &model = this->model_; | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  | const int kNumCoeffs = 24; | 
|  | const double kStd = 4; | 
|  | const double kStdEps = 0.3; | 
|  | const double kCoeffEps = 0.065; | 
|  | // Use different coefficients for each channel | 
|  | const double kCoeffs[3][24] = { | 
|  | { 0.02884, -0.03356, 0.00633,  0.01757,  0.02849,  -0.04620, | 
|  | 0.02833, -0.07178, 0.07076,  -0.11603, -0.10413, -0.16571, | 
|  | 0.05158, -0.07969, 0.02640,  -0.07191, 0.02530,  0.41968, | 
|  | 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 }, | 
|  | { 0.00269, -0.01291, -0.01513, 0.07234,  0.03208,   0.00477, | 
|  | 0.00226, -0.00254, 0.03533,  0.12841,  -0.25970,  -0.06336, | 
|  | 0.05238, -0.00845, -0.03118, 0.09043,  -0.36558,  0.48903, | 
|  | 0.00595, -0.11938, 0.02106,  0.095956, -0.350139, 0.59305 }, | 
|  | { -0.00643, -0.01080, -0.01466, 0.06951, 0.03707,  -0.00482, | 
|  | 0.00817,  -0.00909, 0.02949,  0.12181, -0.25210, -0.07886, | 
|  | 0.06083,  -0.01210, -0.03108, 0.08944, -0.35875, 0.49150, | 
|  | 0.00415,  -0.12905, 0.02870,  0.09740, -0.34610, 0.58824 }, | 
|  | }; | 
|  |  | 
|  | ASSERT_EQ(model.n, kNumCoeffs); | 
|  | this->chroma_sub_[0] = this->chroma_sub_[1] = 1; | 
|  |  | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  |  | 
|  | // Add different noise onto each plane | 
|  | const int shift = this->kBitDepth - 8; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | noise_synth(&this->random_, model.params.lag, model.n, model.coords, | 
|  | kCoeffs[c], this->noise_ptr_[c], width, height); | 
|  | const int x_shift = c > 0 ? this->chroma_sub_[0] : 0; | 
|  | const int y_shift = c > 0 ? this->chroma_sub_[1] : 0; | 
|  | for (int y = 0; y < (height >> y_shift); ++y) { | 
|  | for (int x = 0; x < (width >> x_shift); ++x) { | 
|  | const uint8_t value = 64 + x / 2 + y / 4; | 
|  | this->data_ptr_[c][y * width + x] = | 
|  | (uint8_t(value + this->noise_ptr_[c][y * width + x] * kStd)) | 
|  | << shift; | 
|  | this->denoised_ptr_[c][y * width + x] = value << shift; | 
|  | } | 
|  | } | 
|  | } | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | // For the Y plane, the solved coefficients should be close to the original | 
|  | const int n = model.n; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | for (int i = 0; i < n; ++i) { | 
|  | EXPECT_NEAR(kCoeffs[c][i], model.latest_state[c].eqns.x[i], kCoeffEps); | 
|  | EXPECT_NEAR(kCoeffs[c][i], model.combined_state[c].eqns.x[i], kCoeffEps); | 
|  | } | 
|  | // The chroma planes should be uncorrelated with the luma plane | 
|  | if (c > 0) { | 
|  | EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps); | 
|  | EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps); | 
|  | } | 
|  | // Correlation between the coefficient vector and the fitted coefficients | 
|  | // should be close to 1. | 
|  | EXPECT_LT(0.98, aom_normalized_cross_correlation( | 
|  | model.latest_state[c].eqns.x, kCoeffs[c], kNumCoeffs)); | 
|  |  | 
|  | noise_synth(&this->random_, model.params.lag, model.n, model.coords, | 
|  | model.latest_state[c].eqns.x, &this->renoise_[0], width, | 
|  | height); | 
|  |  | 
|  | EXPECT_TRUE(aom_noise_data_validate(&this->renoise_[0], width, height)); | 
|  | } | 
|  |  | 
|  | // Check fitted noise strength | 
|  | const double normalize = 1 << shift; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | for (int i = 0; i < model.latest_state[c].strength_solver.eqns.n; ++i) { | 
|  | EXPECT_NEAR(kStd, | 
|  | model.latest_state[c].strength_solver.eqns.x[i] / normalize, | 
|  | kStdEps); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, | 
|  | NoiseStrengthChangeSignalsDifferentNoiseType) { | 
|  | aom_noise_model_t &model = this->model_; | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  | const int block_size = this->kBlockSize; | 
|  | // Create a gradient image with std = 2 uncorrelated noise | 
|  | const double kStd = 2; | 
|  | const int shift = this->kBitDepth - 8; | 
|  |  | 
|  | for (int i = 0; i < width * height; ++i) { | 
|  | const uint8_t val = (i % width) < width / 2 ? 64 : 192; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | this->noise_ptr_[c][i] = randn(&this->random_, 1); | 
|  | this->data_ptr_[c][i] = ((uint8_t)(this->noise_ptr_[c][i] * kStd + val)) | 
|  | << shift; | 
|  | this->denoised_ptr_[c][i] = val << shift; | 
|  | } | 
|  | } | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | const int kNumBlocks = width * height / block_size / block_size; | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations); | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[1].strength_solver.num_equations); | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[2].strength_solver.num_equations); | 
|  | EXPECT_EQ(kNumBlocks, model.combined_state[0].strength_solver.num_equations); | 
|  | EXPECT_EQ(kNumBlocks, model.combined_state[1].strength_solver.num_equations); | 
|  | EXPECT_EQ(kNumBlocks, model.combined_state[2].strength_solver.num_equations); | 
|  |  | 
|  | // Bump up noise by an insignificant amount | 
|  | for (int i = 0; i < width * height; ++i) { | 
|  | const uint8_t val = (i % width) < width / 2 ? 64 : 192; | 
|  | this->data_ptr_[0][i] = | 
|  | ((uint8_t)(this->noise_ptr_[0][i] * (kStd + 0.085) + val)) << shift; | 
|  | } | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | const double kARGainTolerance = 0.02; | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations); | 
|  | EXPECT_EQ(15250, model.latest_state[c].num_observations); | 
|  | EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance); | 
|  |  | 
|  | EXPECT_EQ(2 * kNumBlocks, | 
|  | model.combined_state[c].strength_solver.num_equations); | 
|  | EXPECT_EQ(2 * 15250, model.combined_state[c].num_observations); | 
|  | EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance); | 
|  | } | 
|  |  | 
|  | // Bump up the noise strength on half the image for one channel by a | 
|  | // significant amount. | 
|  | for (int i = 0; i < width * height; ++i) { | 
|  | const uint8_t val = (i % width) < width / 2 ? 64 : 128; | 
|  | if (i % width < width / 2) { | 
|  | this->data_ptr_[0][i] = | 
|  | ((uint8_t)(randn(&this->random_, kStd + 0.5) + val)) << shift; | 
|  | } | 
|  | } | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate()); | 
|  |  | 
|  | // Since we didn't update the combined state, it should still be at 2 * | 
|  | // num_blocks | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations); | 
|  | EXPECT_EQ(2 * kNumBlocks, | 
|  | model.combined_state[0].strength_solver.num_equations); | 
|  |  | 
|  | // In normal operation, the "latest" estimate can be saved to the "combined" | 
|  | // state for continued updates. | 
|  | aom_noise_model_save_latest(&model); | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations); | 
|  | EXPECT_EQ(15250, model.latest_state[c].num_observations); | 
|  | EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance); | 
|  |  | 
|  | EXPECT_EQ(kNumBlocks, | 
|  | model.combined_state[c].strength_solver.num_equations); | 
|  | EXPECT_EQ(15250, model.combined_state[c].num_observations); | 
|  | EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance); | 
|  | } | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(NoiseModelUpdateTest, NoiseCoeffsSignalsDifferentNoiseType) { | 
|  | aom_noise_model_t &model = this->model_; | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  | const double kCoeffs[2][24] = { | 
|  | { 0.02884, -0.03356, 0.00633,  0.01757,  0.02849,  -0.04620, | 
|  | 0.02833, -0.07178, 0.07076,  -0.11603, -0.10413, -0.16571, | 
|  | 0.05158, -0.07969, 0.02640,  -0.07191, 0.02530,  0.41968, | 
|  | 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 }, | 
|  | { 0.00269, -0.01291, -0.01513, 0.07234,  0.03208,   0.00477, | 
|  | 0.00226, -0.00254, 0.03533,  0.12841,  -0.25970,  -0.06336, | 
|  | 0.05238, -0.00845, -0.03118, 0.09043,  -0.36558,  0.48903, | 
|  | 0.00595, -0.11938, 0.02106,  0.095956, -0.350139, 0.59305 } | 
|  | }; | 
|  |  | 
|  | noise_synth(&this->random_, model.params.lag, model.n, model.coords, | 
|  | kCoeffs[0], this->noise_ptr_[0], width, height); | 
|  | for (int i = 0; i < width * height; ++i) { | 
|  | this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]); | 
|  | } | 
|  | this->flat_blocks_.assign(this->flat_blocks_.size(), 1); | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); | 
|  |  | 
|  | // Now try with the second set of AR coefficients | 
|  | noise_synth(&this->random_, model.params.lag, model.n, model.coords, | 
|  | kCoeffs[1], this->noise_ptr_[0], width, height); | 
|  | for (int i = 0; i < width * height; ++i) { | 
|  | this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]); | 
|  | } | 
|  | EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate()); | 
|  | } | 
|  | REGISTER_TYPED_TEST_SUITE_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks, | 
|  | UpdateSuccessForZeroNoiseAllFlat, | 
|  | UpdateFailsBlockSizeTooSmall, | 
|  | UpdateSuccessForWhiteRandomNoise, | 
|  | UpdateSuccessForScaledWhiteNoise, | 
|  | UpdateSuccessForCorrelatedNoise, | 
|  | NoiseStrengthChangeSignalsDifferentNoiseType, | 
|  | NoiseCoeffsSignalsDifferentNoiseType); | 
|  |  | 
|  | // Note the empty final argument can be removed if C++20 is made the minimum | 
|  | // requirement. | 
|  | INSTANTIATE_TYPED_TEST_SUITE_P(NoiseModelUpdateTestInstatiation, | 
|  | NoiseModelUpdateTest, AllBitDepthParams, ); | 
|  |  | 
|  | TEST(NoiseModelGetGrainParameters, TestLagSize) { | 
|  | aom_film_grain_t film_grain; | 
|  | for (int lag = 1; lag <= 3; ++lag) { | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  | EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); | 
|  | EXPECT_EQ(lag, film_grain.ar_coeff_lag); | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 4, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  | EXPECT_FALSE(aom_noise_model_get_grain_parameters(&model, &film_grain)); | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModelGetGrainParameters, TestARCoeffShiftBounds) { | 
|  | struct TestCase { | 
|  | double max_input_value; | 
|  | int expected_ar_coeff_shift; | 
|  | int expected_value; | 
|  | }; | 
|  | const int lag = 1; | 
|  | const int kNumTestCases = 19; | 
|  | const TestCase test_cases[] = { | 
|  | // Test cases for ar_coeff_shift = 9 | 
|  | { 0, 9, 0 }, | 
|  | { 0.125, 9, 64 }, | 
|  | { -0.125, 9, -64 }, | 
|  | { 0.2499, 9, 127 }, | 
|  | { -0.25, 9, -128 }, | 
|  | // Test cases for ar_coeff_shift = 8 | 
|  | { 0.25, 8, 64 }, | 
|  | { -0.2501, 8, -64 }, | 
|  | { 0.499, 8, 127 }, | 
|  | { -0.5, 8, -128 }, | 
|  | // Test cases for ar_coeff_shift = 7 | 
|  | { 0.5, 7, 64 }, | 
|  | { -0.5001, 7, -64 }, | 
|  | { 0.999, 7, 127 }, | 
|  | { -1, 7, -128 }, | 
|  | // Test cases for ar_coeff_shift = 6 | 
|  | { 1.0, 6, 64 }, | 
|  | { -1.0001, 6, -64 }, | 
|  | { 2.0, 6, 127 }, | 
|  | { -2.0, 6, -128 }, | 
|  | { 4, 6, 127 }, | 
|  | { -4, 6, -128 }, | 
|  | }; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  |  | 
|  | for (int i = 0; i < kNumTestCases; ++i) { | 
|  | const TestCase &test_case = test_cases[i]; | 
|  | model.combined_state[0].eqns.x[0] = test_case.max_input_value; | 
|  |  | 
|  | aom_film_grain_t film_grain; | 
|  | EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); | 
|  | EXPECT_EQ(1, film_grain.ar_coeff_lag); | 
|  | EXPECT_EQ(test_case.expected_ar_coeff_shift, film_grain.ar_coeff_shift); | 
|  | EXPECT_EQ(test_case.expected_value, film_grain.ar_coeffs_y[0]); | 
|  | } | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | TEST(NoiseModelGetGrainParameters, TestNoiseStrengthShiftBounds) { | 
|  | struct TestCase { | 
|  | double max_input_value; | 
|  | int expected_scaling_shift; | 
|  | int expected_value; | 
|  | }; | 
|  | const int kNumTestCases = 10; | 
|  | const TestCase test_cases[] = { | 
|  | { 0, 11, 0 },      { 1, 11, 64 },     { 2, 11, 128 }, { 3.99, 11, 255 }, | 
|  | { 4, 10, 128 },    { 7.99, 10, 255 }, { 8, 9, 128 },  { 16, 8, 128 }, | 
|  | { 31.99, 8, 255 }, { 64, 8, 255 },  // clipped | 
|  | }; | 
|  | const int lag = 1; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  |  | 
|  | for (int i = 0; i < kNumTestCases; ++i) { | 
|  | const TestCase &test_case = test_cases[i]; | 
|  | aom_equation_system_t &eqns = model.combined_state[0].strength_solver.eqns; | 
|  | // Set the fitted scale parameters to be a constant value. | 
|  | for (int j = 0; j < eqns.n; ++j) { | 
|  | eqns.x[j] = test_case.max_input_value; | 
|  | } | 
|  | aom_film_grain_t film_grain; | 
|  | EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); | 
|  | // We expect a single constant segemnt | 
|  | EXPECT_EQ(test_case.expected_scaling_shift, film_grain.scaling_shift); | 
|  | EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[0][1]); | 
|  | EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[1][1]); | 
|  | } | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | // The AR coefficients are the same inputs used to generate "Test 2" in the test | 
|  | // vectors | 
|  | TEST(NoiseModelGetGrainParameters, GetGrainParametersReal) { | 
|  | const double kInputCoeffsY[] = { 0.0315,  0.0073,  0.0218,  0.00235, 0.00511, | 
|  | -0.0222, 0.0627,  -0.022,  0.05575, -0.1816, | 
|  | 0.0107,  -0.1966, 0.00065, -0.0809, 0.04934, | 
|  | -0.1349, -0.0352, 0.41772, 0.27973, 0.04207, | 
|  | -0.0429, -0.1372, 0.06193, 0.52032 }; | 
|  | const double kInputCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, | 
|  | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5 }; | 
|  | const double kInputCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, | 
|  | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.5 }; | 
|  | const int kExpectedARCoeffsY[] = { 4,  1,   3,  0,   1,  -3,  8, -3, | 
|  | 7,  -23, 1,  -25, 0,  -10, 6, -17, | 
|  | -5, 53,  36, 5,   -5, -18, 8, 67 }; | 
|  | const int kExpectedARCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | 
|  | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 }; | 
|  | const int kExpectedARCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, | 
|  | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -126 }; | 
|  | // Scaling function is initialized analytically with a sqrt function. | 
|  | const int kNumScalingPointsY = 12; | 
|  | const int kExpectedScalingPointsY[][2] = { | 
|  | { 0, 0 },     { 13, 44 },   { 27, 62 },   { 40, 76 }, | 
|  | { 54, 88 },   { 67, 98 },   { 94, 117 },  { 121, 132 }, | 
|  | { 148, 146 }, { 174, 159 }, { 201, 171 }, { 255, 192 }, | 
|  | }; | 
|  |  | 
|  | const int lag = 3; | 
|  | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; | 
|  | aom_noise_model_t model; | 
|  | EXPECT_TRUE(aom_noise_model_init(&model, params)); | 
|  |  | 
|  | // Setup the AR coeffs | 
|  | memcpy(model.combined_state[0].eqns.x, kInputCoeffsY, sizeof(kInputCoeffsY)); | 
|  | memcpy(model.combined_state[1].eqns.x, kInputCoeffsCB, | 
|  | sizeof(kInputCoeffsCB)); | 
|  | memcpy(model.combined_state[2].eqns.x, kInputCoeffsCR, | 
|  | sizeof(kInputCoeffsCR)); | 
|  | for (int i = 0; i < model.combined_state[0].strength_solver.num_bins; ++i) { | 
|  | const double x = | 
|  | ((double)i) / (model.combined_state[0].strength_solver.num_bins - 1.0); | 
|  | model.combined_state[0].strength_solver.eqns.x[i] = 6 * sqrt(x); | 
|  | model.combined_state[1].strength_solver.eqns.x[i] = 3; | 
|  | model.combined_state[2].strength_solver.eqns.x[i] = 2; | 
|  |  | 
|  | // Inject some observations into the strength solver, as during film grain | 
|  | // parameter extraction an estimate of the average strength will be used to | 
|  | // adjust correlation. | 
|  | const int n = model.combined_state[0].strength_solver.num_bins; | 
|  | for (int j = 0; j < model.combined_state[0].strength_solver.num_bins; ++j) { | 
|  | model.combined_state[0].strength_solver.eqns.A[i * n + j] = 1; | 
|  | model.combined_state[1].strength_solver.eqns.A[i * n + j] = 1; | 
|  | model.combined_state[2].strength_solver.eqns.A[i * n + j] = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | aom_film_grain_t film_grain; | 
|  | EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); | 
|  | EXPECT_EQ(lag, film_grain.ar_coeff_lag); | 
|  | EXPECT_EQ(3, film_grain.ar_coeff_lag); | 
|  | EXPECT_EQ(7, film_grain.ar_coeff_shift); | 
|  | EXPECT_EQ(10, film_grain.scaling_shift); | 
|  | EXPECT_EQ(kNumScalingPointsY, film_grain.num_y_points); | 
|  | EXPECT_EQ(1, film_grain.update_parameters); | 
|  | EXPECT_EQ(1, film_grain.apply_grain); | 
|  |  | 
|  | const int kNumARCoeffs = 24; | 
|  | for (int i = 0; i < kNumARCoeffs; ++i) { | 
|  | EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]); | 
|  | } | 
|  | for (int i = 0; i < kNumARCoeffs + 1; ++i) { | 
|  | EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]); | 
|  | } | 
|  | for (int i = 0; i < kNumARCoeffs + 1; ++i) { | 
|  | EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]); | 
|  | } | 
|  | for (int i = 0; i < kNumScalingPointsY; ++i) { | 
|  | EXPECT_EQ(kExpectedScalingPointsY[i][0], film_grain.scaling_points_y[i][0]); | 
|  | EXPECT_EQ(kExpectedScalingPointsY[i][1], film_grain.scaling_points_y[i][1]); | 
|  | } | 
|  |  | 
|  | // CB strength should just be a piecewise segment | 
|  | EXPECT_EQ(2, film_grain.num_cb_points); | 
|  | EXPECT_EQ(0, film_grain.scaling_points_cb[0][0]); | 
|  | EXPECT_EQ(255, film_grain.scaling_points_cb[1][0]); | 
|  | EXPECT_EQ(96, film_grain.scaling_points_cb[0][1]); | 
|  | EXPECT_EQ(96, film_grain.scaling_points_cb[1][1]); | 
|  |  | 
|  | // CR strength should just be a piecewise segment | 
|  | EXPECT_EQ(2, film_grain.num_cr_points); | 
|  | EXPECT_EQ(0, film_grain.scaling_points_cr[0][0]); | 
|  | EXPECT_EQ(255, film_grain.scaling_points_cr[1][0]); | 
|  | EXPECT_EQ(64, film_grain.scaling_points_cr[0][1]); | 
|  | EXPECT_EQ(64, film_grain.scaling_points_cr[1][1]); | 
|  |  | 
|  | EXPECT_EQ(128, film_grain.cb_mult); | 
|  | EXPECT_EQ(192, film_grain.cb_luma_mult); | 
|  | EXPECT_EQ(256, film_grain.cb_offset); | 
|  | EXPECT_EQ(128, film_grain.cr_mult); | 
|  | EXPECT_EQ(192, film_grain.cr_luma_mult); | 
|  | EXPECT_EQ(256, film_grain.cr_offset); | 
|  | EXPECT_EQ(0, film_grain.chroma_scaling_from_luma); | 
|  | EXPECT_EQ(0, film_grain.grain_scale_shift); | 
|  |  | 
|  | aom_noise_model_free(&model); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | class WienerDenoiseTest : public ::testing::Test, public T { | 
|  | public: | 
|  | static void SetUpTestSuite() { aom_dsp_rtcd(); } | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | static const float kNoiseLevel = 5.f; | 
|  | static const float kStd = 4.0; | 
|  | static const double kMaxValue = (1 << T::kBitDepth) - 1; | 
|  |  | 
|  | chroma_sub_[0] = 1; | 
|  | chroma_sub_[1] = 1; | 
|  | stride_[0] = kWidth; | 
|  | stride_[1] = kWidth / 2; | 
|  | stride_[2] = kWidth / 2; | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | data_[k].resize(kWidth * kHeight); | 
|  | denoised_[k].resize(kWidth * kHeight); | 
|  | noise_psd_[k].resize(kBlockSize * kBlockSize); | 
|  | } | 
|  |  | 
|  | const double kCoeffsY[] = { 0.0406, -0.116, -0.078, -0.152, 0.0033, -0.093, | 
|  | 0.048,  0.404,  0.2353, -0.035, -0.093, 0.441 }; | 
|  | const int kCoords[12][2] = { | 
|  | { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 }, | 
|  | { -1, -1 }, { 0, -1 },  { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } | 
|  | }; | 
|  | const int kLag = 2; | 
|  | const int kLength = 12; | 
|  | libaom_test::ACMRandom random; | 
|  | std::vector<double> noise(kWidth * kHeight); | 
|  | noise_synth(&random, kLag, kLength, kCoords, kCoeffsY, &noise[0], kWidth, | 
|  | kHeight); | 
|  | noise_psd_[0] = get_noise_psd(&noise[0], kWidth, kHeight, kBlockSize); | 
|  | for (int i = 0; i < kBlockSize * kBlockSize; ++i) { | 
|  | noise_psd_[0][i] = (float)(noise_psd_[0][i] * kStd * kStd * kScaleNoise * | 
|  | kScaleNoise / (kMaxValue * kMaxValue)); | 
|  | } | 
|  |  | 
|  | float psd_value = | 
|  | aom_noise_psd_get_default_value(kBlockSizeChroma, kNoiseLevel); | 
|  | for (int i = 0; i < kBlockSizeChroma * kBlockSizeChroma; ++i) { | 
|  | noise_psd_[1][i] = psd_value; | 
|  | noise_psd_[2][i] = psd_value; | 
|  | } | 
|  | for (int y = 0; y < kHeight; ++y) { | 
|  | for (int x = 0; x < kWidth; ++x) { | 
|  | data_[0][y * stride_[0] + x] = (typename T::data_type_t)fclamp( | 
|  | (x + noise[y * stride_[0] + x] * kStd) * kScaleNoise, 0, kMaxValue); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (int c = 1; c < 3; ++c) { | 
|  | for (int y = 0; y < (kHeight >> 1); ++y) { | 
|  | for (int x = 0; x < (kWidth >> 1); ++x) { | 
|  | data_[c][y * stride_[c] + x] = (typename T::data_type_t)fclamp( | 
|  | (x + randn(&random, kStd)) * kScaleNoise, 0, kMaxValue); | 
|  | } | 
|  | } | 
|  | } | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | noise_psd_ptrs_[k] = &noise_psd_[k][0]; | 
|  | } | 
|  | } | 
|  | static const int kBlockSize = 32; | 
|  | static const int kBlockSizeChroma = 16; | 
|  | static const int kWidth = 256; | 
|  | static const int kHeight = 256; | 
|  | static const int kScaleNoise = 1 << (T::kBitDepth - 8); | 
|  |  | 
|  | std::vector<typename T::data_type_t> data_[3]; | 
|  | std::vector<typename T::data_type_t> denoised_[3]; | 
|  | std::vector<float> noise_psd_[3]; | 
|  | int chroma_sub_[2]; | 
|  | float *noise_psd_ptrs_[3]; | 
|  | int stride_[3]; | 
|  | }; | 
|  |  | 
|  | TYPED_TEST_SUITE_P(WienerDenoiseTest); | 
|  |  | 
|  | TYPED_TEST_P(WienerDenoiseTest, InvalidBlockSize) { | 
|  | const uint8_t *const data_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->data_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[2][0]), | 
|  | }; | 
|  | uint8_t *denoised_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), | 
|  | }; | 
|  | EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, | 
|  | this->kHeight, this->stride_, | 
|  | this->chroma_sub_, this->noise_psd_ptrs_, | 
|  | 18, this->kBitDepth, this->kUseHighBD)); | 
|  | EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, | 
|  | this->kHeight, this->stride_, | 
|  | this->chroma_sub_, this->noise_psd_ptrs_, | 
|  | 48, this->kBitDepth, this->kUseHighBD)); | 
|  | EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, | 
|  | this->kHeight, this->stride_, | 
|  | this->chroma_sub_, this->noise_psd_ptrs_, | 
|  | 64, this->kBitDepth, this->kUseHighBD)); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(WienerDenoiseTest, InvalidChromaSubsampling) { | 
|  | const uint8_t *const data_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->data_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[2][0]), | 
|  | }; | 
|  | uint8_t *denoised_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), | 
|  | }; | 
|  | int chroma_sub[2] = { 1, 0 }; | 
|  | EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, | 
|  | this->kHeight, this->stride_, chroma_sub, | 
|  | this->noise_psd_ptrs_, 32, this->kBitDepth, | 
|  | this->kUseHighBD)); | 
|  |  | 
|  | chroma_sub[0] = 0; | 
|  | chroma_sub[1] = 1; | 
|  | EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, | 
|  | this->kHeight, this->stride_, chroma_sub, | 
|  | this->noise_psd_ptrs_, 32, this->kBitDepth, | 
|  | this->kUseHighBD)); | 
|  | } | 
|  |  | 
|  | TYPED_TEST_P(WienerDenoiseTest, GradientTest) { | 
|  | const int width = this->kWidth; | 
|  | const int height = this->kHeight; | 
|  | const int block_size = this->kBlockSize; | 
|  | const uint8_t *const data_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->data_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->data_[2][0]), | 
|  | }; | 
|  | uint8_t *denoised_ptrs[3] = { | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), | 
|  | reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), | 
|  | }; | 
|  | const int ret = aom_wiener_denoise_2d( | 
|  | data_ptrs, denoised_ptrs, width, height, this->stride_, this->chroma_sub_, | 
|  | this->noise_psd_ptrs_, block_size, this->kBitDepth, this->kUseHighBD); | 
|  | EXPECT_EQ(1, ret); | 
|  |  | 
|  | // Check the noise on the denoised image (from the analytical gradient) | 
|  | // and make sure that it is less than what we added. | 
|  | for (int c = 0; c < 3; ++c) { | 
|  | std::vector<double> measured_noise(width * height); | 
|  |  | 
|  | double var = 0; | 
|  | const int shift = (c > 0); | 
|  | for (int x = 0; x < (width >> shift); ++x) { | 
|  | for (int y = 0; y < (height >> shift); ++y) { | 
|  | const double diff = this->denoised_[c][y * this->stride_[c] + x] - | 
|  | x * this->kScaleNoise; | 
|  | var += diff * diff; | 
|  | measured_noise[y * width + x] = diff; | 
|  | } | 
|  | } | 
|  | var /= (width * height); | 
|  | const double std = sqrt(std::max(0.0, var)); | 
|  | EXPECT_LE(std, 1.25f * this->kScaleNoise); | 
|  | if (c == 0) { | 
|  | std::vector<float> measured_psd = | 
|  | get_noise_psd(&measured_noise[0], width, height, block_size); | 
|  | std::vector<double> measured_psd_d(block_size * block_size); | 
|  | std::vector<double> noise_psd_d(block_size * block_size); | 
|  | std::copy(measured_psd.begin(), measured_psd.end(), | 
|  | measured_psd_d.begin()); | 
|  | std::copy(this->noise_psd_[0].begin(), this->noise_psd_[0].end(), | 
|  | noise_psd_d.begin()); | 
|  | EXPECT_LT( | 
|  | aom_normalized_cross_correlation(&measured_psd_d[0], &noise_psd_d[0], | 
|  | (int)(noise_psd_d.size())), | 
|  | 0.35); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | REGISTER_TYPED_TEST_SUITE_P(WienerDenoiseTest, InvalidBlockSize, | 
|  | InvalidChromaSubsampling, GradientTest); | 
|  |  | 
|  | // Note the empty final argument can be removed if C++20 is made the minimum | 
|  | // requirement. | 
|  | INSTANTIATE_TYPED_TEST_SUITE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest, | 
|  | AllBitDepthParams, ); |