• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018, Alliance for Open Media. All rights reserved
3  *
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.
10  */
11 
12 #include <math.h>
13 #include <algorithm>
14 #include <vector>
15 
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"
21 
22 namespace {
23 
24 // Return normally distrbuted values with standard deviation of sigma.
randn(libaom_test::ACMRandom * random,double 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));
36     }
37   }
38   return 0;
39 }
40 
41 // Synthesizes noise using the auto-regressive filter of the given lag,
42 // with the provided n coefficients sampled at the given coords.
noise_synth(libaom_test::ACMRandom * random,int lag,int n,const int (* coords)[2],const double * coeffs,double * data,int w,int h)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);
51 
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);
55     }
56   }
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];
65       }
66       padded[y * padded_w + x] += sum;
67     }
68   }
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);
72   }
73 }
74 
get_noise_psd(double * noise,int width,int height,int block_size)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];
87         }
88       }
89       aom_noise_tx_forward(tx, &block[0]);
90       aom_noise_tx_add_energy(tx, &psd[0]);
91       num_blocks++;
92     }
93   }
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;
97     }
98   }
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];
102   }
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];
107     }
108   }
109   aom_noise_tx_free(tx);
110   aom_free(block);
111   return psd;
112 }
113 
114 }  // namespace
115 
TEST(NoiseStrengthSolver,GetCentersTwoBins)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);
122 }
123 
TEST(NoiseStrengthSolver,GetCentersTwoBins10bit)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);
130 }
131 
TEST(NoiseStrengthSolver,GetCenters256Bins)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);
136 
137   for (int i = 0; i < 256; ++i) {
138     EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5);
139   }
140   aom_noise_strength_solver_free(&solver);
141 }
142 
143 // Tests that the noise strength solver returns the identity transform when
144 // given identity-like constraints.
TEST(NoiseStrengthSolver,ObserveIdentity)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));
149 
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);
155   }
156   for (int i = 0; i < 256; ++i) {
157     aom_noise_strength_solver_add_measurement(&solver, i, i);
158   }
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);
162   }
163 
164   aom_noise_strength_lut_t lut;
165   EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, 2, &lut));
166 
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);
172 
173   aom_noise_strength_lut_free(&lut);
174   aom_noise_strength_solver_free(&solver);
175 }
176 
TEST(NoiseStrengthSolver,SimplifiesCurve)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));
181 
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);
186   }
187   EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver));
188 
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);
194 
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);
200 
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);
207   }
208   EXPECT_NEAR(255.0, lut.points[kMaxPoints - 1][0], 1e-5);
209 
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);
213 }
214 
TEST(NoiseStrengthLut,LutEvalSinglePoint)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);
225 }
226 
TEST(NoiseStrengthLut,LutEvalMultiPointInterp)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);
232 
233   lut.points[0][0] = 0;
234   lut.points[0][1] = 0;
235 
236   lut.points[1][0] = 1;
237   lut.points[1][1] = 1;
238 
239   lut.points[2][0] = 2;
240   lut.points[2][1] = 1;
241 
242   lut.points[3][0] = 100;
243   lut.points[3][1] = 1001;
244 
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));
248 
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);
252 
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);
256 
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);
261 
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));
265 
266   aom_noise_strength_lut_free(&lut);
267 }
268 
TEST(NoiseModel,InitSuccessWithValidSquareShape)269 TEST(NoiseModel, InitSuccessWithValidSquareShape) {
270   aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 };
271   aom_noise_model_t model;
272 
273   EXPECT_TRUE(aom_noise_model_init(&model, params));
274 
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]);
284   }
285   aom_noise_model_free(&model);
286 }
287 
TEST(NoiseModel,InitSuccessWithValidDiamondShape)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]);
301   }
302   aom_noise_model_free(&model);
303 }
304 
TEST(NoiseModel,InitFailsWithTooLargeLag)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);
310 }
311 
TEST(NoiseModel,InitFailsWithTooSmallLag)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);
317 }
318 
TEST(NoiseModel,InitFailsWithInvalidShape)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);
324 }
325 
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;
335 };
336 
337 template <typename T>
338 class FlatBlockEstimatorTest : public ::testing::Test, public T {
339  public:
SetUp()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_;
344 };
345 
346 TYPED_TEST_SUITE_P(FlatBlockEstimatorTest);
347 
TYPED_TEST_P(FlatBlockEstimatorTest,ExtractBlock)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;
354 
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);
360 
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;
369     }
370   }
371   std::vector<double> block(kBlockSize * kBlockSize, 1);
372   std::vector<double> plane(kBlockSize * kBlockSize, 1);
373 
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);
384     }
385   }
386 
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);
398     }
399   }
400   aom_flat_block_finder_free(&flat_block_finder);
401 }
402 
TYPED_TEST_P(FlatBlockEstimatorTest,FindFlatBlocks)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));
408 
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);
415 
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;
421 
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;
425 
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;
429 
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;
433 
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;
437 
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;
441 
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;
446 
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;
451     }
452   }
453 
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]));
457 
458   // First two blocks are not flat
459   EXPECT_EQ(0, flat_blocks[0]);
460   EXPECT_EQ(0, flat_blocks[1]);
461 
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]);
467 
468   // Last 2 are not flat by threshold
469   EXPECT_EQ(0, flat_blocks[6]);
470   EXPECT_EQ(0, flat_blocks[7]);
471 
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));
478     }
479   }
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]);
493 
494   aom_flat_block_finder_free(&flat_block_finder);
495 }
496 
497 REGISTER_TYPED_TEST_SUITE_P(FlatBlockEstimatorTest, ExtractBlock,
498                             FindFlatBlocks);
499 
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_SUITE_P(FlatBlockInstatiation, FlatBlockEstimatorTest,
506                                AllBitDepthParams);
507 
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;
516 
SetUp()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));
521 
522     random_.Reset(100171);
523 
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);
529 
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;
535 
536       data_ptr_raw_[c] = (uint8_t *)&data_[offset];
537       denoised_ptr_raw_[c] = (uint8_t *)&denoised_[offset];
538     }
539     chroma_sub_[0] = 0;
540     chroma_sub_[1] = 0;
541   }
542 
NoiseModelUpdate(int block_size=kBlockSize)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);
547   }
548 
TearDown()549   void TearDown() { aom_noise_model_free(&model_); }
550 
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_;
555 
556   std::vector<double> noise_;
557   std::vector<double> renoise_;
558   std::vector<uint8_t> flat_blocks_;
559 
560   typename T::data_type_t *data_ptr_[3];
561   typename T::data_type_t *denoised_ptr_[3];
562 
563   double *noise_ptr_[3];
564   int strides_[3];
565   int chroma_sub_[2];
566   libaom_test::ACMRandom random_;
567 
568  private:
569   uint8_t *data_ptr_raw_[3];
570   uint8_t *denoised_ptr_raw_[3];
571 };
572 
573 TYPED_TEST_SUITE_P(NoiseModelUpdateTest);
574 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateFailsNoFlatBlocks)575 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) {
576   EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS,
577             this->NoiseModelUpdate());
578 }
579 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForZeroNoiseAllFlat)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());
585 }
586 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateFailsBlockSizeTooSmall)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*/));
593 }
594 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForWhiteRandomNoise)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;
599 
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];
611       }
612     }
613   }
614   this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
615   EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
616 
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);
623     }
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);
628 
629       EXPECT_NEAR(1, model.latest_state[c].eqns.x[n], kCoeffEps);
630       EXPECT_NEAR(1, model.combined_state[c].eqns.x[n], kCoeffEps);
631     }
632   }
633 
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;
638 
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);
646   }
647 
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);
657 }
658 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForScaledWhiteNoise)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;
663 
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;
681       }
682     }
683   }
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());
687 
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);
695     }
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);
699 
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);
703     }
704   }
705 
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);
710 
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);
721   }
722 
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);
734 }
735 
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForCorrelatedNoise)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 },
758   };
759 
760   ASSERT_EQ(model.n, kNumCoeffs);
761   this->chroma_sub_[0] = this->chroma_sub_[1] = 1;
762 
763   this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
764 
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;
779       }
780     }
781   }
782   EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
783 
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);
790     }
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);
795     }
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));
800 
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);
804 
805     EXPECT_TRUE(aom_noise_data_validate(&this->renoise_[0], kWidth, kHeight));
806   }
807 
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);
815     }
816   }
817 }
818 
TYPED_TEST_P(NoiseModelUpdateTest,NoiseStrengthChangeSignalsDifferentNoiseType)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;
828 
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;
836     }
837   }
838   this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
839   EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
840 
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);
848 
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;
854   }
855   EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
856 
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);
862 
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);
867   }
868 
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;
876     }
877   }
878   EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
879 
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);
885 
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);
893 
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);
898   }
899 }
900 
TYPED_TEST_P(NoiseModelUpdateTest,NoiseCoeffsSignalsDifferentNoiseType)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 }
914   };
915 
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]);
920   }
921   this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
922   EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
923 
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]);
929   }
930   EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
931 }
932 REGISTER_TYPED_TEST_SUITE_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks,
933                             UpdateSuccessForZeroNoiseAllFlat,
934                             UpdateFailsBlockSizeTooSmall,
935                             UpdateSuccessForWhiteRandomNoise,
936                             UpdateSuccessForScaledWhiteNoise,
937                             UpdateSuccessForCorrelatedNoise,
938                             NoiseStrengthChangeSignalsDifferentNoiseType,
939                             NoiseCoeffsSignalsDifferentNoiseType);
940 
941 INSTANTIATE_TYPED_TEST_SUITE_P(NoiseModelUpdateTestInstatiation,
942                                NoiseModelUpdateTest, AllBitDepthParams);
943 
TEST(NoiseModelGetGrainParameters,TestLagSize)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);
953   }
954 
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);
960 }
961 
TEST(NoiseModelGetGrainParameters,TestARCoeffShiftBounds)962 TEST(NoiseModelGetGrainParameters, TestARCoeffShiftBounds) {
963   struct TestCase {
964     double max_input_value;
965     int expected_ar_coeff_shift;
966     int expected_value;
967   };
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 },
994   };
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));
998 
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;
1002 
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]);
1008   }
1009   aom_noise_model_free(&model);
1010 }
1011 
TEST(NoiseModelGetGrainParameters,TestNoiseStrengthShiftBounds)1012 TEST(NoiseModelGetGrainParameters, TestNoiseStrengthShiftBounds) {
1013   struct TestCase {
1014     double max_input_value;
1015     int expected_scaling_shift;
1016     int expected_value;
1017   };
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
1023   };
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));
1028 
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;
1035     }
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]);
1042   }
1043   aom_noise_model_free(&model);
1044 }
1045 
1046 // The AR coefficients are the same inputs used to generate "Test 2" in the test
1047 // vectors
TEST(NoiseModelGetGrainParameters,GetGrainParametersReal)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 },
1071   };
1072 
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));
1077 
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;
1090 
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;
1099     }
1100   }
1101 
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);
1111 
1112   const int kNumARCoeffs = 24;
1113   for (int i = 0; i < kNumARCoeffs; ++i) {
1114     EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]);
1115   }
1116   for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1117     EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]);
1118   }
1119   for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1120     EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]);
1121   }
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]);
1125   }
1126 
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]);
1133 
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]);
1140 
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);
1149 
1150   aom_noise_model_free(&model);
1151 }
1152 
1153 template <typename T>
1154 class WienerDenoiseTest : public ::testing::Test, public T {
1155  public:
SetUpTestCase()1156   static void SetUpTestCase() { aom_dsp_rtcd(); }
1157 
1158  protected:
SetUp()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;
1163 
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);
1173     }
1174 
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 }
1180     };
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));
1191     }
1192 
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;
1198     }
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);
1203       }
1204     }
1205 
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);
1211         }
1212       }
1213     }
1214     for (int k = 0; k < 3; ++k) {
1215       noise_psd_ptrs_[k] = &noise_psd_[k][0];
1216     }
1217   }
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);
1223 
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];
1230 };
1231 
1232 TYPED_TEST_SUITE_P(WienerDenoiseTest);
1233 
TYPED_TEST_P(WienerDenoiseTest,InvalidBlockSize)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]),
1239   };
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]),
1244   };
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));
1257 }
1258 
TYPED_TEST_P(WienerDenoiseTest,InvalidChromaSubsampling)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]),
1264   };
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]),
1269   };
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));
1275 
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));
1282 }
1283 
TYPED_TEST_P(WienerDenoiseTest,GradientTest)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]),
1292   };
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]),
1297   };
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);
1303 
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);
1308 
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;
1317       }
1318     }
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);
1335     }
1336   }
1337 }
1338 
1339 REGISTER_TYPED_TEST_SUITE_P(WienerDenoiseTest, InvalidBlockSize,
1340                             InvalidChromaSubsampling, GradientTest);
1341 
1342 INSTANTIATE_TYPED_TEST_SUITE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest,
1343                                AllBitDepthParams);
1344