Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  noise_model_test.cc   Sprache: C

 
/*
 * 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, );

Messung V0.5
C=92 H=82 G=86

¤ Dauer der Verarbeitung: 0.6 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge