Bug 1797755 - Part 5: Use a single initial mark stack size regardless of whether...
[gecko.git] / third_party / aom / test / noise_model_test.cc
blobb5b387e313b2278a53ac5d76312d641246c7ab57
1 /*
2 * Copyright (c) 2018, Alliance for Open Media. All rights reserved
4 * This source code is subject to the terms of the BSD 2 Clause License and
5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6 * was not distributed with this source code in the LICENSE file, you can
7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8 * Media Patent License 1.0 was not distributed with this source code in the
9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
12 #include <math.h>
13 #include <algorithm>
14 #include <vector>
16 #include "aom_dsp/noise_model.h"
17 #include "aom_dsp/noise_util.h"
18 #include "config/aom_dsp_rtcd.h"
19 #include "test/acm_random.h"
20 #include "third_party/googletest/src/googletest/include/gtest/gtest.h"
22 namespace {
24 // Return normally distrbuted values with standard deviation of sigma.
25 double randn(libaom_test::ACMRandom *random, double sigma) {
26 while (1) {
27 const double u = 2.0 * ((double)random->Rand31() /
28 testing::internal::Random::kMaxRange) -
29 1.0;
30 const double v = 2.0 * ((double)random->Rand31() /
31 testing::internal::Random::kMaxRange) -
32 1.0;
33 const double s = u * u + v * v;
34 if (s > 0 && s < 1) {
35 return sigma * (u * sqrt(-2.0 * log(s) / s));
38 return 0;
41 // Synthesizes noise using the auto-regressive filter of the given lag,
42 // with the provided n coefficients sampled at the given coords.
43 void noise_synth(libaom_test::ACMRandom *random, int lag, int n,
44 const int (*coords)[2], const double *coeffs, double *data,
45 int w, int h) {
46 const int pad_size = 3 * lag;
47 const int padded_w = w + pad_size;
48 const int padded_h = h + pad_size;
49 int x = 0, y = 0;
50 std::vector<double> padded(padded_w * padded_h);
52 for (y = 0; y < padded_h; ++y) {
53 for (x = 0; x < padded_w; ++x) {
54 padded[y * padded_w + x] = randn(random, 1.0);
57 for (y = lag; y < padded_h; ++y) {
58 for (x = lag; x < padded_w; ++x) {
59 double sum = 0;
60 int i = 0;
61 for (i = 0; i < n; ++i) {
62 const int dx = coords[i][0];
63 const int dy = coords[i][1];
64 sum += padded[(y + dy) * padded_w + (x + dx)] * coeffs[i];
66 padded[y * padded_w + x] += sum;
69 // Copy over the padded rows to the output
70 for (y = 0; y < h; ++y) {
71 memcpy(data + y * w, &padded[0] + y * padded_w, sizeof(*data) * w);
75 std::vector<float> get_noise_psd(double *noise, int width, int height,
76 int block_size) {
77 float *block =
78 (float *)aom_memalign(32, block_size * block_size * sizeof(block));
79 std::vector<float> psd(block_size * block_size);
80 int num_blocks = 0;
81 struct aom_noise_tx_t *tx = aom_noise_tx_malloc(block_size);
82 for (int y = 0; y <= height - block_size; y += block_size / 2) {
83 for (int x = 0; x <= width - block_size; x += block_size / 2) {
84 for (int yy = 0; yy < block_size; ++yy) {
85 for (int xx = 0; xx < block_size; ++xx) {
86 block[yy * block_size + xx] = (float)noise[(y + yy) * width + x + xx];
89 aom_noise_tx_forward(tx, &block[0]);
90 aom_noise_tx_add_energy(tx, &psd[0]);
91 num_blocks++;
94 for (int yy = 0; yy < block_size; ++yy) {
95 for (int xx = 0; xx <= block_size / 2; ++xx) {
96 psd[yy * block_size + xx] /= num_blocks;
99 // Fill in the data that is missing due to symmetries
100 for (int xx = 1; xx < block_size / 2; ++xx) {
101 psd[(block_size - xx)] = psd[xx];
103 for (int yy = 1; yy < block_size; ++yy) {
104 for (int xx = 1; xx < block_size / 2; ++xx) {
105 psd[(block_size - yy) * block_size + (block_size - xx)] =
106 psd[yy * block_size + xx];
109 aom_noise_tx_free(tx);
110 aom_free(block);
111 return psd;
114 } // namespace
116 TEST(NoiseStrengthSolver, GetCentersTwoBins) {
117 aom_noise_strength_solver_t solver;
118 aom_noise_strength_solver_init(&solver, 2, 8);
119 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5);
120 EXPECT_NEAR(255, aom_noise_strength_solver_get_center(&solver, 1), 1e-5);
121 aom_noise_strength_solver_free(&solver);
124 TEST(NoiseStrengthSolver, GetCentersTwoBins10bit) {
125 aom_noise_strength_solver_t solver;
126 aom_noise_strength_solver_init(&solver, 2, 10);
127 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5);
128 EXPECT_NEAR(1023, aom_noise_strength_solver_get_center(&solver, 1), 1e-5);
129 aom_noise_strength_solver_free(&solver);
132 TEST(NoiseStrengthSolver, GetCenters256Bins) {
133 const int num_bins = 256;
134 aom_noise_strength_solver_t solver;
135 aom_noise_strength_solver_init(&solver, num_bins, 8);
137 for (int i = 0; i < 256; ++i) {
138 EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5);
140 aom_noise_strength_solver_free(&solver);
143 // Tests that the noise strength solver returns the identity transform when
144 // given identity-like constraints.
145 TEST(NoiseStrengthSolver, ObserveIdentity) {
146 const int num_bins = 256;
147 aom_noise_strength_solver_t solver;
148 EXPECT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8));
150 // We have to add a big more strength to constraints at the boundary to
151 // overcome any regularization.
152 for (int j = 0; j < 5; ++j) {
153 aom_noise_strength_solver_add_measurement(&solver, 0, 0);
154 aom_noise_strength_solver_add_measurement(&solver, 255, 255);
156 for (int i = 0; i < 256; ++i) {
157 aom_noise_strength_solver_add_measurement(&solver, i, i);
159 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver));
160 for (int i = 2; i < num_bins - 2; ++i) {
161 EXPECT_NEAR(i, solver.eqns.x[i], 0.1);
164 aom_noise_strength_lut_t lut;
165 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, 2, &lut));
167 ASSERT_EQ(2, lut.num_points);
168 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5);
169 EXPECT_NEAR(0.0, lut.points[0][1], 0.5);
170 EXPECT_NEAR(255.0, lut.points[1][0], 1e-5);
171 EXPECT_NEAR(255.0, lut.points[1][1], 0.5);
173 aom_noise_strength_lut_free(&lut);
174 aom_noise_strength_solver_free(&solver);
177 TEST(NoiseStrengthSolver, SimplifiesCurve) {
178 const int num_bins = 256;
179 aom_noise_strength_solver_t solver;
180 EXPECT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8));
182 // Create a parabolic input
183 for (int i = 0; i < 256; ++i) {
184 const double x = (i - 127.5) / 63.5;
185 aom_noise_strength_solver_add_measurement(&solver, i, x * x);
187 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver));
189 // First try to fit an unconstrained lut
190 aom_noise_strength_lut_t lut;
191 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, -1, &lut));
192 ASSERT_LE(20, lut.num_points);
193 aom_noise_strength_lut_free(&lut);
195 // Now constrain the maximum number of points
196 const int kMaxPoints = 9;
197 EXPECT_EQ(1,
198 aom_noise_strength_solver_fit_piecewise(&solver, kMaxPoints, &lut));
199 ASSERT_EQ(kMaxPoints, lut.num_points);
201 // Check that the input parabola is still well represented
202 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5);
203 EXPECT_NEAR(4.0, lut.points[0][1], 0.1);
204 for (int i = 1; i < lut.num_points - 1; ++i) {
205 const double x = (lut.points[i][0] - 128.) / 64.;
206 EXPECT_NEAR(x * x, lut.points[i][1], 0.1);
208 EXPECT_NEAR(255.0, lut.points[kMaxPoints - 1][0], 1e-5);
210 EXPECT_NEAR(4.0, lut.points[kMaxPoints - 1][1], 0.1);
211 aom_noise_strength_lut_free(&lut);
212 aom_noise_strength_solver_free(&solver);
215 TEST(NoiseStrengthLut, LutEvalSinglePoint) {
216 aom_noise_strength_lut_t lut;
217 ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 1));
218 ASSERT_EQ(1, lut.num_points);
219 lut.points[0][0] = 0;
220 lut.points[0][1] = 1;
221 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, -1));
222 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 0));
223 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 1));
224 aom_noise_strength_lut_free(&lut);
227 TEST(NoiseStrengthLut, LutEvalMultiPointInterp) {
228 const double kEps = 1e-5;
229 aom_noise_strength_lut_t lut;
230 ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 4));
231 ASSERT_EQ(4, lut.num_points);
233 lut.points[0][0] = 0;
234 lut.points[0][1] = 0;
236 lut.points[1][0] = 1;
237 lut.points[1][1] = 1;
239 lut.points[2][0] = 2;
240 lut.points[2][1] = 1;
242 lut.points[3][0] = 100;
243 lut.points[3][1] = 1001;
245 // Test lower boundary
246 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, -1));
247 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, 0));
249 // Test first part that should be identity
250 EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut, 0.25), kEps);
251 EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut, 0.75), kEps);
253 // This is a constant section (should evaluate to 1)
254 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.25), kEps);
255 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.75), kEps);
257 // Test interpolation between to non-zero y coords.
258 EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut, 2), kEps);
259 EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut, 26.5), kEps);
260 EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut, 75.5), kEps);
262 // Test upper boundary
263 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 100));
264 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 101));
266 aom_noise_strength_lut_free(&lut);
269 TEST(NoiseModel, InitSuccessWithValidSquareShape) {
270 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 };
271 aom_noise_model_t model;
273 EXPECT_TRUE(aom_noise_model_init(&model, params));
275 const int kNumCoords = 12;
276 const int kCoords[][2] = { { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 },
277 { 2, -2 }, { -2, -1 }, { -1, -1 }, { 0, -1 },
278 { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } };
279 EXPECT_EQ(kNumCoords, model.n);
280 for (int i = 0; i < kNumCoords; ++i) {
281 const int *coord = kCoords[i];
282 EXPECT_EQ(coord[0], model.coords[i][0]);
283 EXPECT_EQ(coord[1], model.coords[i][1]);
285 aom_noise_model_free(&model);
288 TEST(NoiseModel, InitSuccessWithValidDiamondShape) {
289 aom_noise_model_t model;
290 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_DIAMOND, 2, 8, 0 };
291 EXPECT_TRUE(aom_noise_model_init(&model, params));
292 EXPECT_EQ(6, model.n);
293 const int kNumCoords = 6;
294 const int kCoords[][2] = { { 0, -2 }, { -1, -1 }, { 0, -1 },
295 { 1, -1 }, { -2, 0 }, { -1, 0 } };
296 EXPECT_EQ(kNumCoords, model.n);
297 for (int i = 0; i < kNumCoords; ++i) {
298 const int *coord = kCoords[i];
299 EXPECT_EQ(coord[0], model.coords[i][0]);
300 EXPECT_EQ(coord[1], model.coords[i][1]);
302 aom_noise_model_free(&model);
305 TEST(NoiseModel, InitFailsWithTooLargeLag) {
306 aom_noise_model_t model;
307 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 10, 8, 0 };
308 EXPECT_FALSE(aom_noise_model_init(&model, params));
309 aom_noise_model_free(&model);
312 TEST(NoiseModel, InitFailsWithTooSmallLag) {
313 aom_noise_model_t model;
314 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 0, 8, 0 };
315 EXPECT_FALSE(aom_noise_model_init(&model, params));
316 aom_noise_model_free(&model);
319 TEST(NoiseModel, InitFailsWithInvalidShape) {
320 aom_noise_model_t model;
321 aom_noise_model_params_t params = { aom_noise_shape(100), 3, 8, 0 };
322 EXPECT_FALSE(aom_noise_model_init(&model, params));
323 aom_noise_model_free(&model);
326 // A container template class to hold a data type and extra arguments.
327 // All of these args are bundled into one struct so that we can use
328 // parameterized tests on combinations of supported data types
329 // (uint8_t and uint16_t) and bit depths (8, 10, 12).
330 template <typename T, int bit_depth, bool use_highbd>
331 struct BitDepthParams {
332 typedef T data_type_t;
333 static const int kBitDepth = bit_depth;
334 static const bool kUseHighBD = use_highbd;
337 template <typename T>
338 class FlatBlockEstimatorTest : public ::testing::Test, public T {
339 public:
340 virtual void SetUp() { random_.Reset(171); }
341 typedef std::vector<typename T::data_type_t> VecType;
342 VecType data_;
343 libaom_test::ACMRandom random_;
346 TYPED_TEST_CASE_P(FlatBlockEstimatorTest);
348 TYPED_TEST_P(FlatBlockEstimatorTest, ExtractBlock) {
349 const int kBlockSize = 16;
350 aom_flat_block_finder_t flat_block_finder;
351 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize,
352 this->kBitDepth, this->kUseHighBD));
353 const double normalization = flat_block_finder.normalization;
355 // Test with an image of more than one block.
356 const int h = 2 * kBlockSize;
357 const int w = 2 * kBlockSize;
358 const int stride = 2 * kBlockSize;
359 this->data_.resize(h * stride, 128);
361 // Set up the (0,0) block to be a plane and the (0,1) block to be a
362 // checkerboard
363 const int shift = this->kBitDepth - 8;
364 for (int y = 0; y < kBlockSize; ++y) {
365 for (int x = 0; x < kBlockSize; ++x) {
366 this->data_[y * stride + x] = (-y + x + 128) << shift;
367 this->data_[y * stride + x + kBlockSize] =
368 ((x % 2 + y % 2) % 2 ? 128 - 20 : 128 + 20) << shift;
371 std::vector<double> block(kBlockSize * kBlockSize, 1);
372 std::vector<double> plane(kBlockSize * kBlockSize, 1);
374 // The block data should be a constant (zero) and the rest of the plane
375 // trend is covered in the plane data.
376 aom_flat_block_finder_extract_block(&flat_block_finder,
377 (uint8_t *)&this->data_[0], w, h, stride,
378 0, 0, &plane[0], &block[0]);
379 for (int y = 0; y < kBlockSize; ++y) {
380 for (int x = 0; x < kBlockSize; ++x) {
381 EXPECT_NEAR(0, block[y * kBlockSize + x], 1e-5);
382 EXPECT_NEAR((double)(this->data_[y * stride + x]) / normalization,
383 plane[y * kBlockSize + x], 1e-5);
387 // The plane trend is a constant, and the block is a zero mean checkerboard.
388 aom_flat_block_finder_extract_block(&flat_block_finder,
389 (uint8_t *)&this->data_[0], w, h, stride,
390 kBlockSize, 0, &plane[0], &block[0]);
391 const int mid = 128 << shift;
392 for (int y = 0; y < kBlockSize; ++y) {
393 for (int x = 0; x < kBlockSize; ++x) {
394 EXPECT_NEAR(((double)this->data_[y * stride + x + kBlockSize] - mid) /
395 normalization,
396 block[y * kBlockSize + x], 1e-5);
397 EXPECT_NEAR(mid / normalization, plane[y * kBlockSize + x], 1e-5);
400 aom_flat_block_finder_free(&flat_block_finder);
403 TYPED_TEST_P(FlatBlockEstimatorTest, FindFlatBlocks) {
404 const int kBlockSize = 32;
405 aom_flat_block_finder_t flat_block_finder;
406 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize,
407 this->kBitDepth, this->kUseHighBD));
409 const int num_blocks_w = 8;
410 const int h = kBlockSize;
411 const int w = kBlockSize * num_blocks_w;
412 const int stride = w;
413 this->data_.resize(h * stride, 128);
414 std::vector<uint8_t> flat_blocks(num_blocks_w, 0);
416 const int shift = this->kBitDepth - 8;
417 for (int y = 0; y < kBlockSize; ++y) {
418 for (int x = 0; x < kBlockSize; ++x) {
419 // Block 0 (not flat): constant doesn't have enough variance to qualify
420 this->data_[y * stride + x + 0 * kBlockSize] = 128 << shift;
422 // Block 1 (not flat): too high of variance is hard to validate as flat
423 this->data_[y * stride + x + 1 * kBlockSize] =
424 ((uint8_t)(128 + randn(&this->random_, 5))) << shift;
426 // Block 2 (flat): slight checkerboard added to constant
427 const int check = (x % 2 + y % 2) % 2 ? -2 : 2;
428 this->data_[y * stride + x + 2 * kBlockSize] = (128 + check) << shift;
430 // Block 3 (flat): planar block with checkerboard pattern is also flat
431 this->data_[y * stride + x + 3 * kBlockSize] =
432 (y * 2 - x / 2 + 128 + check) << shift;
434 // Block 4 (flat): gaussian random with standard deviation 1.
435 this->data_[y * stride + x + 4 * kBlockSize] =
436 ((uint8_t)(randn(&this->random_, 1) + x + 128.0)) << shift;
438 // Block 5 (flat): gaussian random with standard deviation 2.
439 this->data_[y * stride + x + 5 * kBlockSize] =
440 ((uint8_t)(randn(&this->random_, 2) + y + 128.0)) << shift;
442 // Block 6 (not flat): too high of directional gradient.
443 const int strong_edge = x > kBlockSize / 2 ? 64 : 0;
444 this->data_[y * stride + x + 6 * kBlockSize] =
445 ((uint8_t)(randn(&this->random_, 1) + strong_edge + 128.0)) << shift;
447 // Block 7 (not flat): too high gradient.
448 const int big_check = ((x >> 2) % 2 + (y >> 2) % 2) % 2 ? -16 : 16;
449 this->data_[y * stride + x + 7 * kBlockSize] =
450 ((uint8_t)(randn(&this->random_, 1) + big_check + 128.0)) << shift;
454 EXPECT_EQ(4, aom_flat_block_finder_run(&flat_block_finder,
455 (uint8_t *)&this->data_[0], w, h,
456 stride, &flat_blocks[0]));
458 // First two blocks are not flat
459 EXPECT_EQ(0, flat_blocks[0]);
460 EXPECT_EQ(0, flat_blocks[1]);
462 // Next 4 blocks are flat.
463 EXPECT_EQ(255, flat_blocks[2]);
464 EXPECT_EQ(255, flat_blocks[3]);
465 EXPECT_EQ(255, flat_blocks[4]);
466 EXPECT_EQ(255, flat_blocks[5]);
468 // Last 2 are not flat by threshold
469 EXPECT_EQ(0, flat_blocks[6]);
470 EXPECT_EQ(0, flat_blocks[7]);
472 // Add the noise from non-flat block 1 to every block.
473 for (int y = 0; y < kBlockSize; ++y) {
474 for (int x = 0; x < kBlockSize * num_blocks_w; ++x) {
475 this->data_[y * stride + x] +=
476 (this->data_[y * stride + x % kBlockSize + kBlockSize] -
477 (128 << shift));
480 // Now the scored selection will pick the one that is most likely flat (block
481 // 0)
482 EXPECT_EQ(1, aom_flat_block_finder_run(&flat_block_finder,
483 (uint8_t *)&this->data_[0], w, h,
484 stride, &flat_blocks[0]));
485 EXPECT_EQ(1, flat_blocks[0]);
486 EXPECT_EQ(0, flat_blocks[1]);
487 EXPECT_EQ(0, flat_blocks[2]);
488 EXPECT_EQ(0, flat_blocks[3]);
489 EXPECT_EQ(0, flat_blocks[4]);
490 EXPECT_EQ(0, flat_blocks[5]);
491 EXPECT_EQ(0, flat_blocks[6]);
492 EXPECT_EQ(0, flat_blocks[7]);
494 aom_flat_block_finder_free(&flat_block_finder);
497 REGISTER_TYPED_TEST_CASE_P(FlatBlockEstimatorTest, ExtractBlock,
498 FindFlatBlocks);
500 typedef ::testing::Types<BitDepthParams<uint8_t, 8, false>, // lowbd
501 BitDepthParams<uint16_t, 8, true>, // lowbd in 16-bit
502 BitDepthParams<uint16_t, 10, true>, // highbd data
503 BitDepthParams<uint16_t, 12, true> >
504 AllBitDepthParams;
505 INSTANTIATE_TYPED_TEST_CASE_P(FlatBlockInstatiation, FlatBlockEstimatorTest,
506 AllBitDepthParams);
508 template <typename T>
509 class NoiseModelUpdateTest : public ::testing::Test, public T {
510 public:
511 static const int kWidth = 128;
512 static const int kHeight = 128;
513 static const int kBlockSize = 16;
514 static const int kNumBlocksX = kWidth / kBlockSize;
515 static const int kNumBlocksY = kHeight / kBlockSize;
517 virtual void SetUp() {
518 const aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3,
519 T::kBitDepth, T::kUseHighBD };
520 ASSERT_TRUE(aom_noise_model_init(&model_, params));
522 random_.Reset(100171);
524 data_.resize(kWidth * kHeight * 3);
525 denoised_.resize(kWidth * kHeight * 3);
526 noise_.resize(kWidth * kHeight * 3);
527 renoise_.resize(kWidth * kHeight);
528 flat_blocks_.resize(kNumBlocksX * kNumBlocksY);
530 for (int c = 0, offset = 0; c < 3; ++c, offset += kWidth * kHeight) {
531 data_ptr_[c] = &data_[offset];
532 noise_ptr_[c] = &noise_[offset];
533 denoised_ptr_[c] = &denoised_[offset];
534 strides_[c] = kWidth;
536 data_ptr_raw_[c] = (uint8_t *)&data_[offset];
537 denoised_ptr_raw_[c] = (uint8_t *)&denoised_[offset];
539 chroma_sub_[0] = 0;
540 chroma_sub_[1] = 0;
543 int NoiseModelUpdate(int block_size = kBlockSize) {
544 return aom_noise_model_update(&model_, data_ptr_raw_, denoised_ptr_raw_,
545 kWidth, kHeight, strides_, chroma_sub_,
546 &flat_blocks_[0], block_size);
549 void TearDown() { aom_noise_model_free(&model_); }
551 protected:
552 aom_noise_model_t model_;
553 std::vector<typename T::data_type_t> data_;
554 std::vector<typename T::data_type_t> denoised_;
556 std::vector<double> noise_;
557 std::vector<double> renoise_;
558 std::vector<uint8_t> flat_blocks_;
560 typename T::data_type_t *data_ptr_[3];
561 typename T::data_type_t *denoised_ptr_[3];
563 double *noise_ptr_[3];
564 int strides_[3];
565 int chroma_sub_[2];
566 libaom_test::ACMRandom random_;
568 private:
569 uint8_t *data_ptr_raw_[3];
570 uint8_t *denoised_ptr_raw_[3];
573 TYPED_TEST_CASE_P(NoiseModelUpdateTest);
575 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) {
576 EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS,
577 this->NoiseModelUpdate());
580 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForZeroNoiseAllFlat) {
581 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
582 this->denoised_.assign(this->denoised_.size(), 128);
583 this->data_.assign(this->denoised_.size(), 128);
584 EXPECT_EQ(AOM_NOISE_STATUS_INTERNAL_ERROR, this->NoiseModelUpdate());
587 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsBlockSizeTooSmall) {
588 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
589 this->denoised_.assign(this->denoised_.size(), 128);
590 this->data_.assign(this->denoised_.size(), 128);
591 EXPECT_EQ(AOM_NOISE_STATUS_INVALID_ARGUMENT,
592 this->NoiseModelUpdate(6 /* block_size=6 is too small*/));
595 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForWhiteRandomNoise) {
596 aom_noise_model_t &model = this->model_;
597 const int kWidth = this->kWidth;
598 const int kHeight = this->kHeight;
600 const int shift = this->kBitDepth - 8;
601 for (int y = 0; y < kHeight; ++y) {
602 for (int x = 0; x < kWidth; ++x) {
603 this->data_ptr_[0][y * kWidth + x] =
604 int(64 + y + randn(&this->random_, 1)) << shift;
605 this->denoised_ptr_[0][y * kWidth + x] = (64 + y) << shift;
606 // Make the chroma planes completely correlated with the Y plane
607 for (int c = 1; c < 3; ++c) {
608 this->data_ptr_[c][y * kWidth + x] = this->data_ptr_[0][y * kWidth + x];
609 this->denoised_ptr_[c][y * kWidth + x] =
610 this->denoised_ptr_[0][y * kWidth + x];
614 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
615 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
617 const double kCoeffEps = 0.075;
618 const int n = model.n;
619 for (int c = 0; c < 3; ++c) {
620 for (int i = 0; i < n; ++i) {
621 EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps);
622 EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps);
624 // The second and third channels are highly correlated with the first.
625 if (c > 0) {
626 ASSERT_EQ(n + 1, model.latest_state[c].eqns.n);
627 ASSERT_EQ(n + 1, model.combined_state[c].eqns.n);
629 EXPECT_NEAR(1, model.latest_state[c].eqns.x[n], kCoeffEps);
630 EXPECT_NEAR(1, model.combined_state[c].eqns.x[n], kCoeffEps);
634 // The fitted noise strength should be close to the standard deviation
635 // for all intensity bins.
636 const double kStdEps = 0.1;
637 const double normalize = 1 << shift;
639 for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) {
640 EXPECT_NEAR(1.0,
641 model.latest_state[0].strength_solver.eqns.x[i] / normalize,
642 kStdEps);
643 EXPECT_NEAR(1.0,
644 model.combined_state[0].strength_solver.eqns.x[i] / normalize,
645 kStdEps);
648 aom_noise_strength_lut_t lut;
649 aom_noise_strength_solver_fit_piecewise(
650 &model.latest_state[0].strength_solver, -1, &lut);
651 ASSERT_EQ(2, lut.num_points);
652 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5);
653 EXPECT_NEAR(1.0, lut.points[0][1] / normalize, kStdEps);
654 EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5);
655 EXPECT_NEAR(1.0, lut.points[1][1] / normalize, kStdEps);
656 aom_noise_strength_lut_free(&lut);
659 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForScaledWhiteNoise) {
660 aom_noise_model_t &model = this->model_;
661 const int kWidth = this->kWidth;
662 const int kHeight = this->kHeight;
664 const double kCoeffEps = 0.055;
665 const double kLowStd = 1;
666 const double kHighStd = 4;
667 const int shift = this->kBitDepth - 8;
668 for (int y = 0; y < kHeight; ++y) {
669 for (int x = 0; x < kWidth; ++x) {
670 for (int c = 0; c < 3; ++c) {
671 // The image data is bimodal:
672 // Bottom half has low intensity and low noise strength
673 // Top half has high intensity and high noise strength
674 const int avg = (y < kHeight / 2) ? 4 : 245;
675 const double std = (y < kHeight / 2) ? kLowStd : kHighStd;
676 this->data_ptr_[c][y * kWidth + x] =
677 ((uint8_t)std::min((int)255,
678 (int)(2 + avg + randn(&this->random_, std))))
679 << shift;
680 this->denoised_ptr_[c][y * kWidth + x] = (2 + avg) << shift;
684 // Label all blocks as flat for the update
685 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
686 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
688 const int n = model.n;
689 // The noise is uncorrelated spatially and with the y channel.
690 // All coefficients should be reasonably close to zero.
691 for (int c = 0; c < 3; ++c) {
692 for (int i = 0; i < n; ++i) {
693 EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps);
694 EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps);
696 if (c > 0) {
697 ASSERT_EQ(n + 1, model.latest_state[c].eqns.n);
698 ASSERT_EQ(n + 1, model.combined_state[c].eqns.n);
700 // The correlation to the y channel should be low (near zero)
701 EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps);
702 EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps);
706 // Noise strength should vary between kLowStd and kHighStd.
707 const double kStdEps = 0.15;
708 // We have to normalize fitted standard deviation based on bit depth.
709 const double normalize = (1 << shift);
711 ASSERT_EQ(20, model.latest_state[0].strength_solver.eqns.n);
712 for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) {
713 const double a = i / 19.0;
714 const double expected = (kLowStd * (1.0 - a) + kHighStd * a);
715 EXPECT_NEAR(expected,
716 model.latest_state[0].strength_solver.eqns.x[i] / normalize,
717 kStdEps);
718 EXPECT_NEAR(expected,
719 model.combined_state[0].strength_solver.eqns.x[i] / normalize,
720 kStdEps);
723 // If we fit a piecewise linear model, there should be two points:
724 // one near kLowStd at 0, and the other near kHighStd and 255.
725 aom_noise_strength_lut_t lut;
726 aom_noise_strength_solver_fit_piecewise(
727 &model.latest_state[0].strength_solver, 2, &lut);
728 ASSERT_EQ(2, lut.num_points);
729 EXPECT_NEAR(0, lut.points[0][0], 1e-4);
730 EXPECT_NEAR(kLowStd, lut.points[0][1] / normalize, kStdEps);
731 EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5);
732 EXPECT_NEAR(kHighStd, lut.points[1][1] / normalize, kStdEps);
733 aom_noise_strength_lut_free(&lut);
736 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForCorrelatedNoise) {
737 aom_noise_model_t &model = this->model_;
738 const int kWidth = this->kWidth;
739 const int kHeight = this->kHeight;
740 const int kNumCoeffs = 24;
741 const double kStd = 4;
742 const double kStdEps = 0.3;
743 const double kCoeffEps = 0.065;
744 // Use different coefficients for each channel
745 const double kCoeffs[3][24] = {
746 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620,
747 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571,
748 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968,
749 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 },
750 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477,
751 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336,
752 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903,
753 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 },
754 { -0.00643, -0.01080, -0.01466, 0.06951, 0.03707, -0.00482,
755 0.00817, -0.00909, 0.02949, 0.12181, -0.25210, -0.07886,
756 0.06083, -0.01210, -0.03108, 0.08944, -0.35875, 0.49150,
757 0.00415, -0.12905, 0.02870, 0.09740, -0.34610, 0.58824 },
760 ASSERT_EQ(model.n, kNumCoeffs);
761 this->chroma_sub_[0] = this->chroma_sub_[1] = 1;
763 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
765 // Add different noise onto each plane
766 const int shift = this->kBitDepth - 8;
767 for (int c = 0; c < 3; ++c) {
768 noise_synth(&this->random_, model.params.lag, model.n, model.coords,
769 kCoeffs[c], this->noise_ptr_[c], kWidth, kHeight);
770 const int x_shift = c > 0 ? this->chroma_sub_[0] : 0;
771 const int y_shift = c > 0 ? this->chroma_sub_[1] : 0;
772 for (int y = 0; y < (kHeight >> y_shift); ++y) {
773 for (int x = 0; x < (kWidth >> x_shift); ++x) {
774 const uint8_t value = 64 + x / 2 + y / 4;
775 this->data_ptr_[c][y * kWidth + x] =
776 (uint8_t(value + this->noise_ptr_[c][y * kWidth + x] * kStd))
777 << shift;
778 this->denoised_ptr_[c][y * kWidth + x] = value << shift;
782 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
784 // For the Y plane, the solved coefficients should be close to the original
785 const int n = model.n;
786 for (int c = 0; c < 3; ++c) {
787 for (int i = 0; i < n; ++i) {
788 EXPECT_NEAR(kCoeffs[c][i], model.latest_state[c].eqns.x[i], kCoeffEps);
789 EXPECT_NEAR(kCoeffs[c][i], model.combined_state[c].eqns.x[i], kCoeffEps);
791 // The chroma planes should be uncorrelated with the luma plane
792 if (c > 0) {
793 EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps);
794 EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps);
796 // Correlation between the coefficient vector and the fitted coefficients
797 // should be close to 1.
798 EXPECT_LT(0.98, aom_normalized_cross_correlation(
799 model.latest_state[c].eqns.x, kCoeffs[c], kNumCoeffs));
801 noise_synth(&this->random_, model.params.lag, model.n, model.coords,
802 model.latest_state[c].eqns.x, &this->renoise_[0], kWidth,
803 kHeight);
805 EXPECT_TRUE(aom_noise_data_validate(&this->renoise_[0], kWidth, kHeight));
808 // Check fitted noise strength
809 const double normalize = 1 << shift;
810 for (int c = 0; c < 3; ++c) {
811 for (int i = 0; i < model.latest_state[c].strength_solver.eqns.n; ++i) {
812 EXPECT_NEAR(kStd,
813 model.latest_state[c].strength_solver.eqns.x[i] / normalize,
814 kStdEps);
819 TYPED_TEST_P(NoiseModelUpdateTest,
820 NoiseStrengthChangeSignalsDifferentNoiseType) {
821 aom_noise_model_t &model = this->model_;
822 const int kWidth = this->kWidth;
823 const int kHeight = this->kHeight;
824 const int kBlockSize = this->kBlockSize;
825 // Create a gradient image with std = 2 uncorrelated noise
826 const double kStd = 2;
827 const int shift = this->kBitDepth - 8;
829 for (int i = 0; i < kWidth * kHeight; ++i) {
830 const uint8_t val = (i % kWidth) < kWidth / 2 ? 64 : 192;
831 for (int c = 0; c < 3; ++c) {
832 this->noise_ptr_[c][i] = randn(&this->random_, 1);
833 this->data_ptr_[c][i] = ((uint8_t)(this->noise_ptr_[c][i] * kStd + val))
834 << shift;
835 this->denoised_ptr_[c][i] = val << shift;
838 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
839 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
841 const int kNumBlocks = kWidth * kHeight / kBlockSize / kBlockSize;
842 EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations);
843 EXPECT_EQ(kNumBlocks, model.latest_state[1].strength_solver.num_equations);
844 EXPECT_EQ(kNumBlocks, model.latest_state[2].strength_solver.num_equations);
845 EXPECT_EQ(kNumBlocks, model.combined_state[0].strength_solver.num_equations);
846 EXPECT_EQ(kNumBlocks, model.combined_state[1].strength_solver.num_equations);
847 EXPECT_EQ(kNumBlocks, model.combined_state[2].strength_solver.num_equations);
849 // Bump up noise by an insignificant amount
850 for (int i = 0; i < kWidth * kHeight; ++i) {
851 const uint8_t val = (i % kWidth) < kWidth / 2 ? 64 : 192;
852 this->data_ptr_[0][i] =
853 ((uint8_t)(this->noise_ptr_[0][i] * (kStd + 0.085) + val)) << shift;
855 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
857 const double kARGainTolerance = 0.02;
858 for (int c = 0; c < 3; ++c) {
859 EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations);
860 EXPECT_EQ(15250, model.latest_state[c].num_observations);
861 EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance);
863 EXPECT_EQ(2 * kNumBlocks,
864 model.combined_state[c].strength_solver.num_equations);
865 EXPECT_EQ(2 * 15250, model.combined_state[c].num_observations);
866 EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance);
869 // Bump up the noise strength on half the image for one channel by a
870 // significant amount.
871 for (int i = 0; i < kWidth * kHeight; ++i) {
872 const uint8_t val = (i % kWidth) < kWidth / 2 ? 64 : 128;
873 if (i % kWidth < kWidth / 2) {
874 this->data_ptr_[0][i] =
875 ((uint8_t)(randn(&this->random_, kStd + 0.5) + val)) << shift;
878 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
880 // Since we didn't update the combined state, it should still be at 2 *
881 // num_blocks
882 EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations);
883 EXPECT_EQ(2 * kNumBlocks,
884 model.combined_state[0].strength_solver.num_equations);
886 // In normal operation, the "latest" estimate can be saved to the "combined"
887 // state for continued updates.
888 aom_noise_model_save_latest(&model);
889 for (int c = 0; c < 3; ++c) {
890 EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations);
891 EXPECT_EQ(15250, model.latest_state[c].num_observations);
892 EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance);
894 EXPECT_EQ(kNumBlocks,
895 model.combined_state[c].strength_solver.num_equations);
896 EXPECT_EQ(15250, model.combined_state[c].num_observations);
897 EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance);
901 TYPED_TEST_P(NoiseModelUpdateTest, NoiseCoeffsSignalsDifferentNoiseType) {
902 aom_noise_model_t &model = this->model_;
903 const int kWidth = this->kWidth;
904 const int kHeight = this->kHeight;
905 const double kCoeffs[2][24] = {
906 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620,
907 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571,
908 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968,
909 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 },
910 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477,
911 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336,
912 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903,
913 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 }
916 noise_synth(&this->random_, model.params.lag, model.n, model.coords,
917 kCoeffs[0], this->noise_ptr_[0], kWidth, kHeight);
918 for (int i = 0; i < kWidth * kHeight; ++i) {
919 this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]);
921 this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
922 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
924 // Now try with the second set of AR coefficients
925 noise_synth(&this->random_, model.params.lag, model.n, model.coords,
926 kCoeffs[1], this->noise_ptr_[0], kWidth, kHeight);
927 for (int i = 0; i < kWidth * kHeight; ++i) {
928 this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]);
930 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
932 REGISTER_TYPED_TEST_CASE_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks,
933 UpdateSuccessForZeroNoiseAllFlat,
934 UpdateFailsBlockSizeTooSmall,
935 UpdateSuccessForWhiteRandomNoise,
936 UpdateSuccessForScaledWhiteNoise,
937 UpdateSuccessForCorrelatedNoise,
938 NoiseStrengthChangeSignalsDifferentNoiseType,
939 NoiseCoeffsSignalsDifferentNoiseType);
941 INSTANTIATE_TYPED_TEST_CASE_P(NoiseModelUpdateTestInstatiation,
942 NoiseModelUpdateTest, AllBitDepthParams);
944 TEST(NoiseModelGetGrainParameters, TestLagSize) {
945 aom_film_grain_t film_grain;
946 for (int lag = 1; lag <= 3; ++lag) {
947 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
948 aom_noise_model_t model;
949 EXPECT_TRUE(aom_noise_model_init(&model, params));
950 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
951 EXPECT_EQ(lag, film_grain.ar_coeff_lag);
952 aom_noise_model_free(&model);
955 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 4, 8, 0 };
956 aom_noise_model_t model;
957 EXPECT_TRUE(aom_noise_model_init(&model, params));
958 EXPECT_FALSE(aom_noise_model_get_grain_parameters(&model, &film_grain));
959 aom_noise_model_free(&model);
962 TEST(NoiseModelGetGrainParameters, TestARCoeffShiftBounds) {
963 struct TestCase {
964 double max_input_value;
965 int expected_ar_coeff_shift;
966 int expected_value;
968 const int lag = 1;
969 const int kNumTestCases = 19;
970 const TestCase test_cases[] = {
971 // Test cases for ar_coeff_shift = 9
972 { 0, 9, 0 },
973 { 0.125, 9, 64 },
974 { -0.125, 9, -64 },
975 { 0.2499, 9, 127 },
976 { -0.25, 9, -128 },
977 // Test cases for ar_coeff_shift = 8
978 { 0.25, 8, 64 },
979 { -0.2501, 8, -64 },
980 { 0.499, 8, 127 },
981 { -0.5, 8, -128 },
982 // Test cases for ar_coeff_shift = 7
983 { 0.5, 7, 64 },
984 { -0.5001, 7, -64 },
985 { 0.999, 7, 127 },
986 { -1, 7, -128 },
987 // Test cases for ar_coeff_shift = 6
988 { 1.0, 6, 64 },
989 { -1.0001, 6, -64 },
990 { 2.0, 6, 127 },
991 { -2.0, 6, -128 },
992 { 4, 6, 127 },
993 { -4, 6, -128 },
995 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
996 aom_noise_model_t model;
997 EXPECT_TRUE(aom_noise_model_init(&model, params));
999 for (int i = 0; i < kNumTestCases; ++i) {
1000 const TestCase &test_case = test_cases[i];
1001 model.combined_state[0].eqns.x[0] = test_case.max_input_value;
1003 aom_film_grain_t film_grain;
1004 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1005 EXPECT_EQ(1, film_grain.ar_coeff_lag);
1006 EXPECT_EQ(test_case.expected_ar_coeff_shift, film_grain.ar_coeff_shift);
1007 EXPECT_EQ(test_case.expected_value, film_grain.ar_coeffs_y[0]);
1009 aom_noise_model_free(&model);
1012 TEST(NoiseModelGetGrainParameters, TestNoiseStrengthShiftBounds) {
1013 struct TestCase {
1014 double max_input_value;
1015 int expected_scaling_shift;
1016 int expected_value;
1018 const int kNumTestCases = 10;
1019 const TestCase test_cases[] = {
1020 { 0, 11, 0 }, { 1, 11, 64 }, { 2, 11, 128 }, { 3.99, 11, 255 },
1021 { 4, 10, 128 }, { 7.99, 10, 255 }, { 8, 9, 128 }, { 16, 8, 128 },
1022 { 31.99, 8, 255 }, { 64, 8, 255 }, // clipped
1024 const int lag = 1;
1025 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
1026 aom_noise_model_t model;
1027 EXPECT_TRUE(aom_noise_model_init(&model, params));
1029 for (int i = 0; i < kNumTestCases; ++i) {
1030 const TestCase &test_case = test_cases[i];
1031 aom_equation_system_t &eqns = model.combined_state[0].strength_solver.eqns;
1032 // Set the fitted scale parameters to be a constant value.
1033 for (int j = 0; j < eqns.n; ++j) {
1034 eqns.x[j] = test_case.max_input_value;
1036 aom_film_grain_t film_grain;
1037 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1038 // We expect a single constant segemnt
1039 EXPECT_EQ(test_case.expected_scaling_shift, film_grain.scaling_shift);
1040 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[0][1]);
1041 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[1][1]);
1043 aom_noise_model_free(&model);
1046 // The AR coefficients are the same inputs used to generate "Test 2" in the test
1047 // vectors
1048 TEST(NoiseModelGetGrainParameters, GetGrainParametersReal) {
1049 const double kInputCoeffsY[] = { 0.0315, 0.0073, 0.0218, 0.00235, 0.00511,
1050 -0.0222, 0.0627, -0.022, 0.05575, -0.1816,
1051 0.0107, -0.1966, 0.00065, -0.0809, 0.04934,
1052 -0.1349, -0.0352, 0.41772, 0.27973, 0.04207,
1053 -0.0429, -0.1372, 0.06193, 0.52032 };
1054 const double kInputCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1055 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5 };
1056 const double kInputCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1057 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.5 };
1058 const int kExpectedARCoeffsY[] = { 4, 1, 3, 0, 1, -3, 8, -3,
1059 7, -23, 1, -25, 0, -10, 6, -17,
1060 -5, 53, 36, 5, -5, -18, 8, 67 };
1061 const int kExpectedARCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1062 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 };
1063 const int kExpectedARCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1064 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -126 };
1065 // Scaling function is initialized analytically with a sqrt function.
1066 const int kNumScalingPointsY = 12;
1067 const int kExpectedScalingPointsY[][2] = {
1068 { 0, 0 }, { 13, 44 }, { 27, 62 }, { 40, 76 },
1069 { 54, 88 }, { 67, 98 }, { 94, 117 }, { 121, 132 },
1070 { 148, 146 }, { 174, 159 }, { 201, 171 }, { 255, 192 },
1073 const int lag = 3;
1074 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
1075 aom_noise_model_t model;
1076 EXPECT_TRUE(aom_noise_model_init(&model, params));
1078 // Setup the AR coeffs
1079 memcpy(model.combined_state[0].eqns.x, kInputCoeffsY, sizeof(kInputCoeffsY));
1080 memcpy(model.combined_state[1].eqns.x, kInputCoeffsCB,
1081 sizeof(kInputCoeffsCB));
1082 memcpy(model.combined_state[2].eqns.x, kInputCoeffsCR,
1083 sizeof(kInputCoeffsCR));
1084 for (int i = 0; i < model.combined_state[0].strength_solver.num_bins; ++i) {
1085 const double x =
1086 ((double)i) / (model.combined_state[0].strength_solver.num_bins - 1.0);
1087 model.combined_state[0].strength_solver.eqns.x[i] = 6 * sqrt(x);
1088 model.combined_state[1].strength_solver.eqns.x[i] = 3;
1089 model.combined_state[2].strength_solver.eqns.x[i] = 2;
1091 // Inject some observations into the strength solver, as during film grain
1092 // parameter extraction an estimate of the average strength will be used to
1093 // adjust correlation.
1094 const int n = model.combined_state[0].strength_solver.num_bins;
1095 for (int j = 0; j < model.combined_state[0].strength_solver.num_bins; ++j) {
1096 model.combined_state[0].strength_solver.eqns.A[i * n + j] = 1;
1097 model.combined_state[1].strength_solver.eqns.A[i * n + j] = 1;
1098 model.combined_state[2].strength_solver.eqns.A[i * n + j] = 1;
1102 aom_film_grain_t film_grain;
1103 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1104 EXPECT_EQ(lag, film_grain.ar_coeff_lag);
1105 EXPECT_EQ(3, film_grain.ar_coeff_lag);
1106 EXPECT_EQ(7, film_grain.ar_coeff_shift);
1107 EXPECT_EQ(10, film_grain.scaling_shift);
1108 EXPECT_EQ(kNumScalingPointsY, film_grain.num_y_points);
1109 EXPECT_EQ(1, film_grain.update_parameters);
1110 EXPECT_EQ(1, film_grain.apply_grain);
1112 const int kNumARCoeffs = 24;
1113 for (int i = 0; i < kNumARCoeffs; ++i) {
1114 EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]);
1116 for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1117 EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]);
1119 for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1120 EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]);
1122 for (int i = 0; i < kNumScalingPointsY; ++i) {
1123 EXPECT_EQ(kExpectedScalingPointsY[i][0], film_grain.scaling_points_y[i][0]);
1124 EXPECT_EQ(kExpectedScalingPointsY[i][1], film_grain.scaling_points_y[i][1]);
1127 // CB strength should just be a piecewise segment
1128 EXPECT_EQ(2, film_grain.num_cb_points);
1129 EXPECT_EQ(0, film_grain.scaling_points_cb[0][0]);
1130 EXPECT_EQ(255, film_grain.scaling_points_cb[1][0]);
1131 EXPECT_EQ(96, film_grain.scaling_points_cb[0][1]);
1132 EXPECT_EQ(96, film_grain.scaling_points_cb[1][1]);
1134 // CR strength should just be a piecewise segment
1135 EXPECT_EQ(2, film_grain.num_cr_points);
1136 EXPECT_EQ(0, film_grain.scaling_points_cr[0][0]);
1137 EXPECT_EQ(255, film_grain.scaling_points_cr[1][0]);
1138 EXPECT_EQ(64, film_grain.scaling_points_cr[0][1]);
1139 EXPECT_EQ(64, film_grain.scaling_points_cr[1][1]);
1141 EXPECT_EQ(128, film_grain.cb_mult);
1142 EXPECT_EQ(192, film_grain.cb_luma_mult);
1143 EXPECT_EQ(256, film_grain.cb_offset);
1144 EXPECT_EQ(128, film_grain.cr_mult);
1145 EXPECT_EQ(192, film_grain.cr_luma_mult);
1146 EXPECT_EQ(256, film_grain.cr_offset);
1147 EXPECT_EQ(0, film_grain.chroma_scaling_from_luma);
1148 EXPECT_EQ(0, film_grain.grain_scale_shift);
1150 aom_noise_model_free(&model);
1153 template <typename T>
1154 class WienerDenoiseTest : public ::testing::Test, public T {
1155 public:
1156 static void SetUpTestCase() { aom_dsp_rtcd(); }
1158 protected:
1159 void SetUp() {
1160 static const float kNoiseLevel = 5.f;
1161 static const float kStd = 4.0;
1162 static const double kMaxValue = (1 << T::kBitDepth) - 1;
1164 chroma_sub_[0] = 1;
1165 chroma_sub_[1] = 1;
1166 stride_[0] = kWidth;
1167 stride_[1] = kWidth / 2;
1168 stride_[2] = kWidth / 2;
1169 for (int k = 0; k < 3; ++k) {
1170 data_[k].resize(kWidth * kHeight);
1171 denoised_[k].resize(kWidth * kHeight);
1172 noise_psd_[k].resize(kBlockSize * kBlockSize);
1175 const double kCoeffsY[] = { 0.0406, -0.116, -0.078, -0.152, 0.0033, -0.093,
1176 0.048, 0.404, 0.2353, -0.035, -0.093, 0.441 };
1177 const int kCoords[12][2] = {
1178 { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 },
1179 { -1, -1 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 }
1181 const int kLag = 2;
1182 const int kLength = 12;
1183 libaom_test::ACMRandom random;
1184 std::vector<double> noise(kWidth * kHeight);
1185 noise_synth(&random, kLag, kLength, kCoords, kCoeffsY, &noise[0], kWidth,
1186 kHeight);
1187 noise_psd_[0] = get_noise_psd(&noise[0], kWidth, kHeight, kBlockSize);
1188 for (int i = 0; i < kBlockSize * kBlockSize; ++i) {
1189 noise_psd_[0][i] = (float)(noise_psd_[0][i] * kStd * kStd * kScaleNoise *
1190 kScaleNoise / (kMaxValue * kMaxValue));
1193 float psd_value =
1194 aom_noise_psd_get_default_value(kBlockSizeChroma, kNoiseLevel);
1195 for (int i = 0; i < kBlockSizeChroma * kBlockSizeChroma; ++i) {
1196 noise_psd_[1][i] = psd_value;
1197 noise_psd_[2][i] = psd_value;
1199 for (int y = 0; y < kHeight; ++y) {
1200 for (int x = 0; x < kWidth; ++x) {
1201 data_[0][y * stride_[0] + x] = (typename T::data_type_t)fclamp(
1202 (x + noise[y * stride_[0] + x] * kStd) * kScaleNoise, 0, kMaxValue);
1206 for (int c = 1; c < 3; ++c) {
1207 for (int y = 0; y < (kHeight >> 1); ++y) {
1208 for (int x = 0; x < (kWidth >> 1); ++x) {
1209 data_[c][y * stride_[c] + x] = (typename T::data_type_t)fclamp(
1210 (x + randn(&random, kStd)) * kScaleNoise, 0, kMaxValue);
1214 for (int k = 0; k < 3; ++k) {
1215 noise_psd_ptrs_[k] = &noise_psd_[k][0];
1218 static const int kBlockSize = 32;
1219 static const int kBlockSizeChroma = 16;
1220 static const int kWidth = 256;
1221 static const int kHeight = 256;
1222 static const int kScaleNoise = 1 << (T::kBitDepth - 8);
1224 std::vector<typename T::data_type_t> data_[3];
1225 std::vector<typename T::data_type_t> denoised_[3];
1226 std::vector<float> noise_psd_[3];
1227 int chroma_sub_[2];
1228 float *noise_psd_ptrs_[3];
1229 int stride_[3];
1232 TYPED_TEST_CASE_P(WienerDenoiseTest);
1234 TYPED_TEST_P(WienerDenoiseTest, InvalidBlockSize) {
1235 const uint8_t *const data_ptrs[3] = {
1236 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1237 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1238 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1240 uint8_t *denoised_ptrs[3] = {
1241 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1242 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1243 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1245 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1246 this->kHeight, this->stride_,
1247 this->chroma_sub_, this->noise_psd_ptrs_,
1248 18, this->kBitDepth, this->kUseHighBD));
1249 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1250 this->kHeight, this->stride_,
1251 this->chroma_sub_, this->noise_psd_ptrs_,
1252 48, this->kBitDepth, this->kUseHighBD));
1253 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1254 this->kHeight, this->stride_,
1255 this->chroma_sub_, this->noise_psd_ptrs_,
1256 64, this->kBitDepth, this->kUseHighBD));
1259 TYPED_TEST_P(WienerDenoiseTest, InvalidChromaSubsampling) {
1260 const uint8_t *const data_ptrs[3] = {
1261 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1262 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1263 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1265 uint8_t *denoised_ptrs[3] = {
1266 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1267 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1268 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1270 int chroma_sub[2] = { 1, 0 };
1271 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1272 this->kHeight, this->stride_, chroma_sub,
1273 this->noise_psd_ptrs_, 32, this->kBitDepth,
1274 this->kUseHighBD));
1276 chroma_sub[0] = 0;
1277 chroma_sub[1] = 1;
1278 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1279 this->kHeight, this->stride_, chroma_sub,
1280 this->noise_psd_ptrs_, 32, this->kBitDepth,
1281 this->kUseHighBD));
1284 TYPED_TEST_P(WienerDenoiseTest, GradientTest) {
1285 const int kWidth = this->kWidth;
1286 const int kHeight = this->kHeight;
1287 const int kBlockSize = this->kBlockSize;
1288 const uint8_t *const data_ptrs[3] = {
1289 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1290 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1291 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1293 uint8_t *denoised_ptrs[3] = {
1294 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1295 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1296 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1298 const int ret = aom_wiener_denoise_2d(
1299 data_ptrs, denoised_ptrs, kWidth, kHeight, this->stride_,
1300 this->chroma_sub_, this->noise_psd_ptrs_, this->kBlockSize,
1301 this->kBitDepth, this->kUseHighBD);
1302 EXPECT_EQ(1, ret);
1304 // Check the noise on the denoised image (from the analytical gradient)
1305 // and make sure that it is less than what we added.
1306 for (int c = 0; c < 3; ++c) {
1307 std::vector<double> measured_noise(kWidth * kHeight);
1309 double var = 0;
1310 const int shift = (c > 0);
1311 for (int x = 0; x < (kWidth >> shift); ++x) {
1312 for (int y = 0; y < (kHeight >> shift); ++y) {
1313 const double diff = this->denoised_[c][y * this->stride_[c] + x] -
1314 x * this->kScaleNoise;
1315 var += diff * diff;
1316 measured_noise[y * kWidth + x] = diff;
1319 var /= (kWidth * kHeight);
1320 const double std = sqrt(std::max(0.0, var));
1321 EXPECT_LE(std, 1.25f * this->kScaleNoise);
1322 if (c == 0) {
1323 std::vector<float> measured_psd =
1324 get_noise_psd(&measured_noise[0], kWidth, kHeight, kBlockSize);
1325 std::vector<double> measured_psd_d(kBlockSize * kBlockSize);
1326 std::vector<double> noise_psd_d(kBlockSize * kBlockSize);
1327 std::copy(measured_psd.begin(), measured_psd.end(),
1328 measured_psd_d.begin());
1329 std::copy(this->noise_psd_[0].begin(), this->noise_psd_[0].end(),
1330 noise_psd_d.begin());
1331 EXPECT_LT(
1332 aom_normalized_cross_correlation(&measured_psd_d[0], &noise_psd_d[0],
1333 (int)(noise_psd_d.size())),
1334 0.35);
1339 REGISTER_TYPED_TEST_CASE_P(WienerDenoiseTest, InvalidBlockSize,
1340 InvalidChromaSubsampling, GradientTest);
1342 INSTANTIATE_TYPED_TEST_CASE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest,
1343 AllBitDepthParams);